diff --git a/jedi/api.py b/jedi/api.py index 1b41e730..99a306d5 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -11,6 +11,7 @@ from __future__ import with_statement import re import os import warnings +from itertools import chain from jedi import parsing from jedi import parsing_representation as pr @@ -21,7 +22,7 @@ from jedi import common from jedi import cache from jedi import modules from jedi import interpret -from jedi._compatibility import next, unicode +from jedi._compatibility import next, unicode, builtins import keywords import evaluate import api_classes @@ -586,12 +587,55 @@ class Interpreter(Script): `source`. """ super(Interpreter, self).__init__(source, **kwds) + self.namespaces = namespaces # Here we add the namespaces to the current parser. importer = interpret.ObjectImporter(self._parser.user_scope) for ns in namespaces: importer.import_raw_namespace(ns) + def _simple_complete(self, path, like): + user_stmt = self._user_stmt(True) + is_simple_path = not path or re.search('^[\w][\w\d.]*$', path) + if isinstance(user_stmt, pr.Import) or not is_simple_path: + return super(type(self), self)._simple_complete(path, like) + else: + class NamespaceModule: + def __getattr__(_, name): + for n in self.namespaces: + try: + return n[name] + except KeyError: + pass + raise AttributeError() + + def __dir__(_): + return list(set(chain.from_iterable(n.keys() + for n in self.namespaces))) + + paths = path.split('.') if path else [] + + namespaces = (NamespaceModule(), builtins) + for p in paths: + old, namespaces = namespaces, [] + for n in old: + try: + namespaces.append(getattr(n, p)) + except AttributeError: + pass + + completions = [] + for n in namespaces: + for name in dir(n): + if name.lower().startswith(like.lower()): + scope = self._parser.module + n = pr.Name(self._parser.module, [(name, (0, 0))], + (0, 0), (0, 0), scope) + completions.append((n, scope)) + return completions + + + def defined_names(source, path=None, source_encoding='utf-8'): """ diff --git a/jedi/utils.py b/jedi/utils.py index 4c8b7c8b..f6e78348 100644 --- a/jedi/utils.py +++ b/jedi/utils.py @@ -4,9 +4,7 @@ Utilities for end-users. from __future__ import absolute_import import __main__ -import re -from jedi._compatibility import builtins from jedi import Interpreter @@ -65,36 +63,11 @@ def setup_readline(namespace_module=__main__): if state == 0: interpreter = Interpreter(text, [namespace_module.__dict__]) - # The following part is a bit hackish, because it tries to - # directly access some jedi internals. The goal is to just - # use the "default" completion with ``getattr`` if - # possible. path, dot, like = interpreter._get_completion_parts() before = text[:len(text) - len(like)] - # Shouldn't be an import statement and just a simple path - # with dots. - is_import = re.match('^\s*(import|from) ', text) - if not is_import and (not path or re.match('^[\w][\w\d.]*$', path)): - paths = path.split('.') if path else [] - namespaces = (namespace_module, builtins) - for p in paths: - old, namespaces = namespaces, [] - for n in old: - try: - namespaces.append(getattr(n, p)) - except AttributeError: - pass + completions = interpreter.completions() - self.matches = [] - for n in namespaces: - for name in dir(n): - if name.lower().startswith(like.lower()): - self.matches.append(before + name) - else: - completions = interpreter.completions() - - self.matches = [before + c.name_with_symbols - for c in completions] + self.matches = [before + c.name_with_symbols for c in completions] try: return self.matches[state] except IndexError: diff --git a/test/test_utils.py b/test/test_utils.py index 15430a9c..7903ded1 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -60,6 +60,13 @@ class TestSetupReadline(TestCase): assert set(self.completions(s)) == set([s + 'ltsep', s + 'bspath']) assert self.completions('import keyword') == ['import keyword'] + import os + s = 'from os import ' + goal = set([s + el for el in dir(os)]) + # There are minor differences, e.g. the dir doesn't include deleted + # items as well as items that are not only available on linux. + assert len(set(self.completions(s)).symmetric_difference(goal)) < 20 + def test_preexisting_values(self): self.namespace.a = range(10) assert set(self.completions('a.')) == set(['a.' + n for n in dir(range(1))])