diff --git a/AUTHORS.txt b/AUTHORS.txt index bc258d6b..0777b930 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -34,5 +34,6 @@ Phillip Berndt (@phillipberndt) Ian Lee (@IanLee1521) Farkhad Khatamov (@hatamov) Kevin Kelley (@kelleyk) +Sid Shanker (@squidarth) Note: (@user) means a github user name. diff --git a/README.rst b/README.rst index 17f1aba8..7bab1904 100644 --- a/README.rst +++ b/README.rst @@ -33,12 +33,14 @@ It's really easy. Jedi can currently be used with the following editors: - Vim (jedi-vim_, YouCompleteMe_) -- Emacs (Jedi.el_, elpy_, anaconda-mode_, ycmd_) +- Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_) - Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3]) - SynWrite_ - TextMate_ (Not sure if it's actually working) - Kate_ version 4.13+ supports it natively, you have to enable it, though. [`proof `_] +- Atom_ (autocomplete-python_) +- SourceLair_ And it powers the following projects: @@ -95,7 +97,7 @@ You can run Jedi on cPython 2.6, 2.7, 3.2, 3.3 or 3.4, but it should also understand/parse code older than those versions. Tips on how to use Jedi efficiently can be found `here -`_. +`_. API --- @@ -176,6 +178,7 @@ For more detailed information visit the `testing documentation .. _jedi-vim: https://github.com/davidhalter/jedi-vim .. _youcompleteme: http://valloric.github.io/YouCompleteMe/ .. _Jedi.el: https://github.com/tkf/emacs-jedi +.. _company-mode: https://github.com/syohex/emacs-company-jedi .. _elpy: https://github.com/jorgenschaefer/elpy .. _anaconda-mode: https://github.com/proofit404/anaconda-mode .. _ycmd: https://github.com/abingham/emacs-ycmd @@ -185,3 +188,6 @@ For more detailed information visit the `testing documentation .. _wdb: https://github.com/Kozea/wdb .. _TextMate: https://github.com/lawrenceakka/python-jedi.tmbundle .. _Kate: http://kate-editor.org +.. _Atom: https://atom.io/ +.. _autocomplete-python: https://atom.io/packages/autocomplete-python +.. _SourceLair: https://www.sourcelair.com diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst index 960ca2ab..55d9aca6 100644 --- a/docs/docs/usage.rst +++ b/docs/docs/usage.rst @@ -47,6 +47,14 @@ Kate: `__, you have to enable it, though. +Atom: + +- autocomplete-python_ + +SourceLair: + +- SourceLair_ + .. _other-software: @@ -86,3 +94,5 @@ Using a custom ``$HOME/.pythonrc.py`` .. _wdb: https://github.com/Kozea/wdb .. _TextMate: https://github.com/lawrenceakka/python-jedi.tmbundle .. _kate: http://kate-editor.org/ +.. _autocomplete-python: https://atom.io/packages/autocomplete-python +.. _SourceLair: https://www.sourcelair.com diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 4423755d..ed6b7d88 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -76,8 +76,8 @@ class Script(object): :type source: str :param line: The line to perform actions on (starting with 1). :type line: int - :param col: The column of the cursor (starting with 0). - :type col: int + :param column: The column of the cursor (starting with 0). + :type column: int :param path: The path of the file in the file system, or ``''`` if it hasn't been saved yet. :type path: str or None @@ -179,7 +179,7 @@ class Script(object): if unfinished_dotted: return completion_names else: - return keywords.keyword_names(self._evaluator, 'import') + return set([keywords.keyword(self._evaluator, 'import').name]) if isinstance(user_stmt, tree.Import): module = self._parser.module() @@ -190,7 +190,11 @@ class Script(object): if names is None and not isinstance(user_stmt, tree.Import): if not path and not dot: # add keywords - completion_names += keywords.keyword_names(self._evaluator, all=True) + completion_names += keywords.completion_names( + self._evaluator, + user_stmt, + self._pos, + module) # TODO delete? We should search for valid parser # transformations. completion_names += self._simple_complete(path, dot, like) diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index 97839f9f..5493f4b1 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -4,7 +4,7 @@ import keyword from jedi._compatibility import is_py3 from jedi import common from jedi.evaluate.helpers import FakeName - +from jedi.parser.tree import Leaf try: from pydoc_data import topics as pydoc_topics except ImportError: @@ -17,22 +17,50 @@ else: keys = keyword.kwlist + ['None', 'False', 'True'] -def keywords(evaluator, string='', pos=(0, 0), all=False): - if all: - return set([Keyword(evaluator, k, pos) for k in keys]) +def has_inappropriate_leaf_keyword(pos, module): + relevant_errors = filter( + lambda error: error.first_pos[0] == pos[0], + module.error_statement_stacks) + + for error in relevant_errors: + if error.next_token in keys: + return True + + return False + + +def completion_names(evaluator, stmt, pos, module): + keyword_list = all_keywords(evaluator) + + if not isinstance(stmt, Leaf) or has_inappropriate_leaf_keyword(pos, module): + keyword_list = filter( + lambda keyword: not keyword.only_valid_as_leaf, + keyword_list + ) + return [keyword.name for keyword in keyword_list] + + +def all_keywords(evaluator, pos=(0, 0)): + return set([Keyword(evaluator, k, pos) for k in keys]) + + +def keyword(evaluator, string, pos=(0, 0)): if string in keys: - return set([Keyword(evaluator, string, pos)]) - return set() - - -def keyword_names(evaluator, *args, **kwargs): - return [k.name for k in keywords(evaluator, *args, **kwargs)] + return Keyword(evaluator, string, pos) + else: + return None def get_operator(evaluator, string, pos): return Keyword(evaluator, string, pos) +keywords_only_valid_as_leaf = ( + 'continue', + 'break', +) + + class Keyword(object): def __init__(self, evaluator, name, pos): self.name = FakeName(name, self, pos) @@ -42,6 +70,10 @@ class Keyword(object): def get_parent_until(self): return self.parent + @property + def only_valid_as_leaf(self): + return self.name.value in keywords_only_valid_as_leaf + @property def names(self): """ For a `parsing.Name` like comparision """ diff --git a/jedi/evaluate/compiled/fake/builtins.pym b/jedi/evaluate/compiled/fake/builtins.pym index 1283de00..1ed9b0b2 100644 --- a/jedi/evaluate/compiled/fake/builtins.pym +++ b/jedi/evaluate/compiled/fake/builtins.pym @@ -207,6 +207,21 @@ class dict(): return d +class enumerate(): + def __init__(self, sequence, start=0): + self.__sequence = sequence + + def __iter__(self): + for i in self.__sequence: + yield 1, i + + def __next__(self): + return next(self.__iter__()) + + def next(self): + return next(self.__iter__()) + + class reversed(): def __init__(self, sequence): self.__sequence = sequence diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 27fd89f0..45b83256 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -305,6 +305,10 @@ class LeafWithNewLines(Leaf): return end_pos_line, end_pos_col + @utf8_repr + def __repr__(self): + return "<%s: %r>" % (type(self).__name__, self.value) + class Whitespace(LeafWithNewLines): """Contains NEWLINE and ENDMARKER tokens.""" __slots__ = () diff --git a/test/completion/comprehensions.py b/test/completion/comprehensions.py index 878ca1d1..9e65946f 100644 --- a/test/completion/comprehensions.py +++ b/test/completion/comprehensions.py @@ -51,6 +51,26 @@ left #? int() [a for a in {1:'x'}][0] +##? str() +{a-1:b for a,b in {1:'a', 3:1.0}.items()}[0] + +# with a set literal +#? int() +[a for a in {1, 2, 3}][0] + +##? set() +{a for a in range(10)} + +##? int() +[x for x in {a for a in range(10)}][0] + +##? int() +{a for a in range(10)}.pop() + +##? int() +iter({a for a in range(10)}).next() + + # list comprehensions should also work in combination with functions def listen(arg): for x in arg: diff --git a/test/completion/keywords.py b/test/completion/keywords.py index 851140b1..ba56c210 100644 --- a/test/completion/keywords.py +++ b/test/completion/keywords.py @@ -4,3 +4,24 @@ raise #? ['except', 'Exception'] except + +#? [] +b + continu + +#? [] +b + continue + +#? ['continue'] +b; continue + +#? ['continue'] +b; continu + +#? [] +c + brea + +#? [] +a + break + +#? ['break'] +b; break diff --git a/test/completion/stdlib.py b/test/completion/stdlib.py index 280bee43..68f8f10d 100644 --- a/test/completion/stdlib.py +++ b/test/completion/stdlib.py @@ -65,6 +65,15 @@ class X(): pass #? type type(X) +# ----------------- +# enumerate +# ----------------- +for i, j in enumerate(["as", "ad"]): + #? int() + i + #? str() + j + # ----------------- # re # ----------------- diff --git a/test/run.py b/test/run.py index 6be9b5f2..b1725917 100755 --- a/test/run.py +++ b/test/run.py @@ -295,9 +295,12 @@ def collect_dir_tests(base_dir, test_files, check_thirdparty=False): skip = 'Thirdparty-Library %s not found.' % lib path = os.path.join(base_dir, f_name) - source = open(path).read() - if not is_py3: - source = unicode(source, 'UTF-8') + + if is_py3: + source = open(path, encoding='utf-8').read() + else: + source = unicode(open(path).read(), 'UTF-8') + for case in collect_file_tests(StringIO(source), lines_to_execute): case.path = path