diff --git a/jedi/api/completion.py b/jedi/api/completion.py index 9006faf7..dbec8fa6 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -222,7 +222,7 @@ class Completion: return level, names def _get_importer_names(self, names, level=0, only_modules=True): - names = [str(n) for n in names] + names = [n.value for n in names] i = imports.Importer(self._evaluator, names, self._module_context, level) return i.completion_names(self._evaluator, only_modules=only_modules) diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 2c9bdc9a..2f8545c3 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -28,6 +28,7 @@ from jedi import settings from jedi.common import source_to_unicode, unite from jedi.evaluate import compiled from jedi.evaluate import analysis +from jedi.evaluate.filters import AbstractNameDefinition def completion_names(evaluator, imp, pos): @@ -163,6 +164,36 @@ def get_init_path(directory_path): return None +class ImportName(AbstractNameDefinition): + api_type = 'module' + start_pos = (1, 0) + + def __init__(self, parent_module, string_name): + self.parent_context = parent_module + self.string_name = string_name + + def infer(self): + return Importer( + self.parent_context.evaluator, + [self.string_name], + self.parent_context, + ).follow() + + def get_root_context(self): + # Not sure if this is correct. + return self.parent_context.get_root_context() + + +class SubModuleName(ImportName): + def infer(self): + return Importer( + self.parent_context.evaluator, + [self.string_name], + self.parent_context, + level=1 + ).follow() + + class Importer(object): def __init__(self, evaluator, import_path, module_context, level=0): """ @@ -350,14 +381,16 @@ class Importer(object): self._evaluator.modules[module_name] = module return set([module]) - def _generate_name(self, name): + def _generate_name(self, name, in_module=None): # Create a pseudo import to be able to follow them. - name = helpers.FakeName(name) - imp = helpers.FakeImport(name, parent=self.module_context) - name.parent = imp - return name + if in_module is None: + #imp = helpers.FakeImport(name, parent=self.module_context) + #name.parent = imp + #return name + return ImportName(self.module_context, name) + return SubModuleName(in_module, name) - def _get_module_names(self, search_path=None): + def _get_module_names(self, search_path=None, in_module=None): """ Get the names of all modules in the search_path. This means file names and not names defined in the files. @@ -365,13 +398,13 @@ class Importer(object): names = [] # add builtin module names - if search_path is None: + if search_path is None and in_module is None: names += [self._generate_name(name) for name in sys.builtin_module_names] if search_path is None: search_path = self.sys_path_with_modifications() for module_loader, name, is_pkg in pkgutil.iter_modules(search_path): - names.append(self._generate_name(name)) + names.append(self._generate_name(name, in_module=in_module)) return names def completion_names(self, evaluator, only_modules=False): @@ -380,6 +413,7 @@ class Importer(object): definition that is not defined in a module. """ from jedi.evaluate import finder + from jedi.evaluate.representation import ModuleContext names = [] if self.import_path: # flask @@ -396,15 +430,16 @@ class Importer(object): if os.path.isdir(flaskext): names += self._get_module_names([flaskext]) - for scope in self.follow(): + for context in self.follow(): # Non-modules are not completable. - if not scope.type == 'file_input': # not a module + if context.api_type != 'module': # not a module continue # namespace packages - if isinstance(scope, tree.Module) and scope.path.endswith('__init__.py'): - paths = scope.py__path__() - names += self._get_module_names(paths) + if isinstance(context, ModuleContext) and \ + context.py__file__().endswith('__init__.py'): + paths = context.py__path__() + names += self._get_module_names(paths, in_module=context) if only_modules: # In the case of an import like `from x.` we don't need to @@ -416,12 +451,8 @@ class Importer(object): continue - for names_dict in scope.names_dicts(search_global=False): - _names = list(chain.from_iterable(names_dict.values())) - if not _names: - continue - _names = finder.filter_definition_names(_names, scope) - names += _names + for filter in context.get_filters(search_global=False): + names += filter.values() else: # Empty import path=completion after import if not self.level: diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index 0425cead..9b8096f9 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -773,23 +773,6 @@ class ModuleAttributeName(AbstractNameDefinition): ) -class SubModuleName(AbstractNameDefinition): - api_type = 'module' - start_pos = (1, 0) - - def __init__(self, parent_module, string_name): - self.parent_context = parent_module - self.string_name = string_name - - def infer(self): - return imports.Importer( - self.parent_context.evaluator, - [self.string_name], - self.parent_context, - level=1 - ).follow() - - class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext, Wrapper)): api_type = 'module' parent_context = None @@ -941,7 +924,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext, Wrapper) mods = pkgutil.iter_modules([os.path.dirname(path)]) for module_loader, name, is_pkg in mods: # It's obviously a relative import to the current module. - names[name] = SubModuleName(self, name) + names[name] = imports.SubModuleName(self, name) # TODO add something like this in the future, its cleaner than the # import hacks.