diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 0528e601..a7b0fbfe 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -26,7 +26,7 @@ from jedi.api import classes from jedi.api import interpreter from jedi.api import helpers from jedi.api.helpers import validate_line_column -from jedi.api.completion import Completion, complete_trailer +from jedi.api.completion import Completion, search_in_module from jedi.api.keywords import KeywordName from jedi.api.environment import InterpreterEnvironment from jedi.api.project import get_default_project, Project @@ -354,27 +354,17 @@ class Script(object): @to_list def _search(self, string, complete=False, all_scopes=False, fuzzy=False): - wanted_type, wanted_names = helpers.split_search_string(string) - names = self._names(all_scopes=all_scopes) - for s in wanted_names[:-1]: - new_names = [] - for n in names: - if s == n.string_name: - new_names += complete_trailer( - self._get_module_context(), - n.infer() - ) - names = new_names - - last_name = wanted_names[-1].lower() - for n in names: - string = n.string_name.lower() - if complete and helpers.match(string, last_name, fuzzy=fuzzy) \ - or not complete and string == last_name: - def_ = classes.Definition(self._inference_state, n) - if not wanted_type or wanted_type == def_.type: - yield def_ + wanted_type, wanted_names = helpers.split_search_string(string) + return search_in_module( + self._inference_state, + self._get_module_context(), + names=names, + wanted_type=wanted_type, + wanted_names=wanted_names, + complete=complete, + fuzzy=fuzzy, + ) @validate_line_column def help(self, line=None, column=None): diff --git a/jedi/api/completion.py b/jedi/api/completion.py index b80c0e01..1f60de14 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -573,3 +573,25 @@ def _complete_getattr(user_context, instance): objects = context.infer_node(object_node) return complete_trailer(user_context, objects) return [] + + +def search_in_module(inference_state, module_context, names, wanted_names, + wanted_type, complete=False, fuzzy=False): + for s in wanted_names[:-1]: + new_names = [] + for n in names: + if s == n.string_name: + new_names += complete_trailer( + module_context, + n.infer() + ) + names = new_names + + last_name = wanted_names[-1].lower() + for n in names: + string = n.string_name.lower() + if complete and helpers.match(string, last_name, fuzzy=fuzzy) \ + or not complete and string == last_name: + def_ = classes.Definition(inference_state, n) + if not wanted_type or wanted_type == def_.type: + yield def_ diff --git a/jedi/api/project.py b/jedi/api/project.py index e9f6b921..7d4d841d 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -1,12 +1,14 @@ import os import errno import json +import sys from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError from jedi._compatibility import scandir from jedi.api.environment import get_cached_default_environment, create_environment from jedi.api.exceptions import WrongVersion -from jedi.api.classes import Definition +from jedi.api.completion import search_in_module +from jedi.api.helpers import split_search_string from jedi._compatibility import force_unicode from jedi.inference.sys_path import discover_buildout_paths from jedi.inference.cache import inference_state_as_method_param_cache @@ -176,10 +178,25 @@ class Project(object): Returns a generator of names """ inference_state = InferenceState(self) + if inference_state.grammar.version_info < (3, 6) or sys.version_info < (3, 6): + raise NotImplementedError( + "No support for refactorings/search on Python 2/3.5" + ) + wanted_type, wanted_names = split_search_string(string) + name = wanted_names[0] + file_io_iterator = recurse_find_python_files(FolderIO(self._path)) - for module_context in search_in_file_ios(inference_state, file_io_iterator, string): - for name in get_module_names(module_context.ree_node, all_scopes=all_scopes): - yield Definition(inference_state, module_context.create_name(name)) + for module_context in search_in_file_ios(inference_state, file_io_iterator, name): + names = get_module_names(module_context.tree_node, all_scopes=all_scopes) + for x in search_in_module( + inference_state, + module_context, + names=[module_context.create_name(n) for n in names], + wanted_type=wanted_type, + wanted_names=wanted_names, + complete=complete + ): + yield x # Python 2... def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self._path) diff --git a/test/test_api/test_project.py b/test/test_api/test_project.py index c3d016f3..b34fcacc 100644 --- a/test/test_api/test_project.py +++ b/test/test_api/test_project.py @@ -1,6 +1,9 @@ import os +import sys -from ..helpers import get_example_dir, set_cwd, root_dir +import pytest + +from ..helpers import get_example_dir, set_cwd, root_dir, test_dir from jedi import Interpreter from jedi.api import Project, get_default_project @@ -38,3 +41,20 @@ def test_load_save_project(tmpdir): loaded = Project.load(tmpdir.strpath) assert loaded.added_sys_path == ['/foo'] + + +@pytest.mark.parametrize( + 'string, full_names, kwargs', [ + ('test_load_save_project', ['test_api.test_project.test_load_save_project'], {}), + ('test_load_savep', [], {'complete': True}), + ('test_load_save_p', ['test_api.test_project.test_load_save_project'], + dict(complete=True)), + ('test_load_save_p', ['test_api.test_project.test_load_save_project'], + dict(complete=True, all_scopes=True)), + ] +) +@pytest.mark.skipif(sys.version_info < (3, 6), reason="Ignore Python 2, because EOL") +def test_search(string, full_names, kwargs, skip_pre_python36): + project = Project(test_dir) + defs = project.search(string, **kwargs) + assert [d.full_name for d in defs] == full_names