diff --git a/README.md b/README.md index 4011720acdc..2e110bdde86 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ a Python version greater than 2.7 (Python 3.2 is not supported anymore). * **Python** 2.7 or 3.3+ * **PyQt5** 5.2+ or **PyQt4** 4.6+: PyQt5 is recommended. * **qtconsole** 4.2.0+: Enhanced Python interpreter. -* **Rope** and **Jedi** 0.9.0: Editor code completion, calltips +* **Rope** and **Jedi**: Editor code completion, calltips and go-to-definition. * **Pyflakes**: Real-time code analysis. * **Sphinx**: Rich text mode for the Help pane. diff --git a/continuous_integration/circle/install.sh b/continuous_integration/circle/install.sh index f3564d5579c..c42b12e9d0f 100755 --- a/continuous_integration/circle/install.sh +++ b/continuous_integration/circle/install.sh @@ -7,10 +7,10 @@ if [ "$CIRCLE_NODE_INDEX" = "3" ]; then export CONDA_DEPENDENCIES="" else export CONDA_DEPENDENCIES_FLAGS="--quiet" - export CONDA_DEPENDENCIES="rope jedi pyflakes sphinx pygments pylint pep8 psutil nbconvert \ + export CONDA_DEPENDENCIES="rope pyflakes sphinx pygments pylint pep8 psutil nbconvert \ qtawesome pickleshare qtpy pyzmq chardet mock nomkl pandas \ pytest pytest-cov numpydoc scipy" - export PIP_DEPENDENCIES="coveralls pytest-qt pytest-xvfb flaky" + export PIP_DEPENDENCIES="coveralls pytest-qt pytest-xvfb flaky jedi" fi diff --git a/continuous_integration/conda-recipes/spyder/meta.yaml b/continuous_integration/conda-recipes/spyder/meta.yaml index 41f90cbaa8e..e0e971dbab8 100644 --- a/continuous_integration/conda-recipes/spyder/meta.yaml +++ b/continuous_integration/conda-recipes/spyder/meta.yaml @@ -23,7 +23,7 @@ requirements: - rope 0.9.* # [py34 or py35] - rope # [py27] - pyflakes - - jedi 0.9.* + - jedi - qtconsole - nbconvert - pygments diff --git a/doc/installation.rst b/doc/installation.rst index 678ba8f381a..9b31fd78659 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -161,7 +161,7 @@ The requirements to run Spyder are: enhanced Python interpreter. * `Rope `_ >=0.9.4 and - `Jedi ` 0.9.0 -- for code completion, + `Jedi `_ >=0.9.0 -- for code completion, go-to-definition and calltips on the Editor. * `Pyflakes `_ -- for real-time diff --git a/setup.py b/setup.py index 9fe1c5d29d0..60d6d4a4679 100644 --- a/setup.py +++ b/setup.py @@ -272,7 +272,7 @@ def run(self): install_requires = [ 'rope_py3k' if PY3 else 'rope>=0.9.4', - 'jedi==0.9.0', + 'jedi>=0.9.0', 'pyflakes', 'pygments>=2.0', 'qtconsole>=4.2.0', diff --git a/spyder/utils/introspection/jedi_patch.py b/spyder/utils/introspection/jedi_patch.py index 1790be10d7c..d6cf23cfdb6 100644 --- a/spyder/utils/introspection/jedi_patch.py +++ b/spyder/utils/introspection/jedi_patch.py @@ -22,10 +22,11 @@ def apply(): See [1] and [2] module docstring.""" from spyder.utils.programs import is_module_installed - if is_module_installed('jedi', '=0.9.0'): + if (is_module_installed('jedi', '=0.9.0') or + is_module_installed('jedi', '=0.10.0')): import jedi else: - raise ImportError("jedi %s can't be patched" % jedi.__version__) + raise ImportError("jedi not =0.9.0 or 0.10.0, can't be patched") # [1] Adding numpydoc type returns to docstrings from spyder.utils.introspection import numpy_docstr @@ -36,11 +37,10 @@ def apply(): # [2] Adding type returns for compiled objects in jedi # Patching jedi.evaluate.compiled.CompiledObject... - from jedi.evaluate.compiled import ( - CompiledObject, builtin, _create_from_name, debug) + if is_module_installed('jedi', '=0.9.0'): + from jedi.evaluate.compiled import (builtin, _create_from_name, + debug, CompiledObject) - class CompiledObject(CompiledObject): - # ...adding docstrings int _execute_function... def _execute_function(self, evaluator, params): if self.type != 'funcdef': return @@ -58,87 +58,110 @@ def _execute_function(self, evaluator, params): except AttributeError: continue else: - if isinstance(bltn_obj, CompiledObject) and bltn_obj.obj is None: + if (isinstance(bltn_obj, CompiledObject) and + bltn_obj.obj is None): # We want everything except None. continue for result in evaluator.execute(bltn_obj, params): yield result - # ...docstrings needs a raw_doc property - @property - def raw_doc(self): - try: - doc = unicode(self.doc) - except NameError: # python 3 - doc = self.doc - return doc - - jedi.evaluate.compiled.CompiledObject = CompiledObject - - # [3] Fixing introspection for matplotlib Axes objects - # Patching jedi.evaluate.precedence... - from jedi.evaluate.precedence import tree, calculate - - def calculate_children(evaluator, children): - """ - Calculate a list of children with operators. - """ - iterator = iter(children) - types = evaluator.eval_element(next(iterator)) - for operator in iterator: - try:# PATCH: Catches StopIteration error - right = next(iterator) - if tree.is_node(operator, 'comp_op'): # not in / is not - operator = ' '.join(str(c.value) for c in operator.children) - - # handle lazy evaluation of and/or here. - if operator in ('and', 'or'): - left_bools = set([left.py__bool__() for left in types]) - if left_bools == set([True]): - if operator == 'and': - types = evaluator.eval_element(right) - elif left_bools == set([False]): - if operator != 'and': - types = evaluator.eval_element(right) - # Otherwise continue, because of uncertainty. + else: # Code for Jedi 0.10.0 + from jedi.evaluate.compiled import debug, create + from jedi._compatibility import builtins as _builtins + + def _execute_function(self, params): + from spyder.utils.introspection import numpy_docstr + if self.type != 'funcdef': + return + types = set([]) + types |= set(numpy_docstr.find_return_types(self.parent_context, + self)) + debug.dbg('docstrings type return: %s in %s', types, self) + for name in self._parse_function_doc()[1].split(): + try: + bltn_obj = getattr(_builtins, name) + except AttributeError: + continue else: - types = calculate(evaluator, types, operator, - evaluator.eval_element(right)) - except StopIteration: - debug.warning('calculate_children StopIteration %s', types) - debug.dbg('calculate_children types %s', types) - return types - - jedi.evaluate.precedence.calculate_children = calculate_children - - # [4] Fixing introspection for matplotlib Axes objects - # Patching jedi.evaluate.precedence... - from jedi.evaluate.representation import ( - InstanceName, Instance, compiled, FunctionExecution, InstanceElement) - - def get_instance_el(evaluator, instance, var, is_class_var=False): - """ - Returns an InstanceElement if it makes sense, otherwise leaves the object - untouched. - - Basically having an InstanceElement is context information. That is needed - in quite a lot of cases, which includes Nodes like ``power``, that need to - know where a self name comes from for example. - """ - if isinstance(var, tree.Name): - parent = get_instance_el(evaluator, instance, var.parent, is_class_var) - return InstanceName(var, parent) - # PATCH: compiled objects can be None - elif var is None: - return var - elif var.type != 'funcdef' \ - and isinstance(var, (Instance, compiled.CompiledObject, tree.Leaf, - tree.Module, FunctionExecution)): - return var - - var = evaluator.wrap(var) - return InstanceElement(evaluator, instance, var, is_class_var) - - jedi.evaluate.representation.get_instance_el = get_instance_el + if bltn_obj is None: + # We want to evaluate everything except None. + continue + bltn_obj = create(self.evaluator, bltn_obj) + types |= set(self.evaluator.execute(bltn_obj, params)) + for result in types: + yield result + + jedi.evaluate.compiled.CompiledObject._execute_function = _execute_function + + if is_module_installed('jedi', '=0.9.0'): + # [3] Fixing introspection for matplotlib Axes objects + # Patching jedi.evaluate.precedence... + from jedi.evaluate.precedence import tree, calculate + + def calculate_children(evaluator, children): + """ + Calculate a list of children with operators. + """ + iterator = iter(children) + types = evaluator.eval_element(next(iterator)) + for operator in iterator: + try: # PATCH: Catches StopIteration error + right = next(iterator) + if tree.is_node(operator, 'comp_op'): # not in / is not + operator = ' '.join(str(c.value) for c in + operator.children) + + # handle lazy evaluation of and/or here. + if operator in ('and', 'or'): + left_bools = set([left.py__bool__() for left in types]) + if left_bools == set([True]): + if operator == 'and': + types = evaluator.eval_element(right) + elif left_bools == set([False]): + if operator != 'and': + types = evaluator.eval_element(right) + # Otherwise continue, because of uncertainty. + else: + types = calculate(evaluator, types, operator, + evaluator.eval_element(right)) + except StopIteration: + debug.warning('calculate_children StopIteration %s', types) + debug.dbg('calculate_children types %s', types) + return types + + jedi.evaluate.precedence.calculate_children = calculate_children + + # [4] Fixing introspection for matplotlib Axes objects + # Patching jedi.evaluate.precedence... + from jedi.evaluate.representation import (InstanceName, Instance, + compiled, FunctionExecution, + InstanceElement) + + def get_instance_el(evaluator, instance, var, is_class_var=False): + """ + Returns an InstanceElement if it makes sense, otherwise leaves the + object untouched. + + Basically having an InstanceElement is context information. That is + needed in quite a lot of cases, which includes Nodes like + ``power``, that need to know where a self name comes from for + example. + """ + if isinstance(var, tree.Name): + parent = get_instance_el(evaluator, instance, var.parent, + is_class_var) + return InstanceName(var, parent) + # PATCH: compiled objects can be None + elif var is None: + return var + elif var.type != 'funcdef' \ + and isinstance(var, (Instance, compiled.CompiledObject, + tree.Leaf, tree.Module, FunctionExecution)): + return var + + var = evaluator.wrap(var) + return InstanceElement(evaluator, instance, var, is_class_var) + + jedi.evaluate.representation.get_instance_el = get_instance_el return jedi diff --git a/spyder/utils/introspection/jedi_plugin.py b/spyder/utils/introspection/jedi_plugin.py index c66d5e8afbb..1ff5a7da66e 100644 --- a/spyder/utils/introspection/jedi_plugin.py +++ b/spyder/utils/introspection/jedi_plugin.py @@ -54,7 +54,6 @@ def get_completions(self, info): completions = self.get_jedi_object('completions', info) if DEBUG_EDITOR: log_last_error(LOG_FILENAME, str("comp: " + str(completions)[:100])) - debug_print("comp: " + str(completions)[:100]) completions = [(c.name, c.type) for c in completions] debug_print(str(completions)[:100]) return completions diff --git a/spyder/utils/introspection/manager.py b/spyder/utils/introspection/manager.py index eb5df245754..6eb0e1a9c79 100644 --- a/spyder/utils/introspection/manager.py +++ b/spyder/utils/introspection/manager.py @@ -33,7 +33,7 @@ _("Editor's code completion, go-to-definition and help"), required_version=ROPE_REQVER) -JEDI_REQVER = '=0.9.0' +JEDI_REQVER = '>=0.9.0' dependencies.add('jedi', _("Editor's code completion, go-to-definition and help"), required_version=JEDI_REQVER) diff --git a/spyder/utils/introspection/numpy_docstr.py b/spyder/utils/introspection/numpy_docstr.py index c3ff0e83c1d..08432459c00 100644 --- a/spyder/utils/introspection/numpy_docstr.py +++ b/spyder/utils/introspection/numpy_docstr.py @@ -15,7 +15,9 @@ from ast import literal_eval import re -from jedi._compatibility import is_py3 +from spyder.utils.programs import is_module_installed + +from jedi._compatibility import u, is_py3 from jedi.evaluate.cache import memoize_default from jedi.evaluate.docstrings import (_evaluate_for_statement_string, _strip_rst_role, @@ -105,9 +107,9 @@ def _search_return_in_numpydocstr(docstr): found.extend(_expand_typestr(p_type)) return found - -@memoize_default(None, evaluator_is_first_arg=True) -def find_return_types(evaluator, func): +# Caching disabled because jedi_patch breaks it +# @memoize_default(None, evaluator_is_first_arg=True) +def find_return_types(module_context, func): """ Determines a set of potential return types for `func` using docstring hints :type evaluator: jedi.evaluate.Evaluator @@ -140,11 +142,17 @@ def search_return_in_docstr(docstr): # Check for numpy style return hint found = _search_return_in_numpydocstr(docstr) return found - - docstr = func.raw_doc - module = func.get_parent_until() + try: + docstr = u(func.raw_doc) + except AttributeError: + docstr = u(func.doc) types = [] for type_str in search_return_in_docstr(docstr): - type_ = _evaluate_for_statement_string(evaluator, type_str, module) + if is_module_installed('jedi', '=0.10.0'): + type_ = _evaluate_for_statement_string(module_context, type_str) + else: + module = func.get_parent_until() + type_ = _evaluate_for_statement_string(module_context, + type_str, module) types.extend(type_) return types diff --git a/spyder/utils/introspection/test/test_jedi_plugin.py b/spyder/utils/introspection/test/test_jedi_plugin.py index eb8e870939d..5c5395961d8 100644 --- a/spyder/utils/introspection/test/test_jedi_plugin.py +++ b/spyder/utils/introspection/test/test_jedi_plugin.py @@ -32,7 +32,7 @@ def test_get_info(): - source_code = "import os; os.walk(" + source_code = "import os; os.walk" docs = p.get_info(CodeInfo('info', source_code, len(source_code))) assert docs['calltip'].startswith('walk(') and docs['name'] == 'walk' @@ -63,7 +63,7 @@ def test_get_docstring(): def test(a, b): """Test docstring""" pass - test(1,''') + test''') path, line = p.get_definition(CodeInfo('definition', source_code, len(source_code), 'dummy.txt', is_python_like=True))