move completion to interpreter, which improves shell completion

This commit is contained in:
David Halter
2013-08-17 10:49:23 +04:30
parent 2612963f58
commit e46d33be90
3 changed files with 54 additions and 30 deletions

View File

@@ -11,6 +11,7 @@ from __future__ import with_statement
import re import re
import os import os
import warnings import warnings
from itertools import chain
from jedi import parsing from jedi import parsing
from jedi import parsing_representation as pr from jedi import parsing_representation as pr
@@ -21,7 +22,7 @@ from jedi import common
from jedi import cache from jedi import cache
from jedi import modules from jedi import modules
from jedi import interpret from jedi import interpret
from jedi._compatibility import next, unicode from jedi._compatibility import next, unicode, builtins
import keywords import keywords
import evaluate import evaluate
import api_classes import api_classes
@@ -586,12 +587,55 @@ class Interpreter(Script):
`source`. `source`.
""" """
super(Interpreter, self).__init__(source, **kwds) super(Interpreter, self).__init__(source, **kwds)
self.namespaces = namespaces
# Here we add the namespaces to the current parser. # Here we add the namespaces to the current parser.
importer = interpret.ObjectImporter(self._parser.user_scope) importer = interpret.ObjectImporter(self._parser.user_scope)
for ns in namespaces: for ns in namespaces:
importer.import_raw_namespace(ns) 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'): def defined_names(source, path=None, source_encoding='utf-8'):
""" """

View File

@@ -4,9 +4,7 @@ Utilities for end-users.
from __future__ import absolute_import from __future__ import absolute_import
import __main__ import __main__
import re
from jedi._compatibility import builtins
from jedi import Interpreter from jedi import Interpreter
@@ -65,36 +63,11 @@ def setup_readline(namespace_module=__main__):
if state == 0: if state == 0:
interpreter = Interpreter(text, [namespace_module.__dict__]) 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() path, dot, like = interpreter._get_completion_parts()
before = text[:len(text) - len(like)] before = text[:len(text) - len(like)]
# Shouldn't be an import statement and just a simple path completions = interpreter.completions()
# 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
self.matches = [] self.matches = [before + c.name_with_symbols for c in completions]
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]
try: try:
return self.matches[state] return self.matches[state]
except IndexError: except IndexError:

View File

@@ -60,6 +60,13 @@ class TestSetupReadline(TestCase):
assert set(self.completions(s)) == set([s + 'ltsep', s + 'bspath']) assert set(self.completions(s)) == set([s + 'ltsep', s + 'bspath'])
assert self.completions('import keyword') == ['import keyword'] 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): def test_preexisting_values(self):
self.namespace.a = range(10) self.namespace.a = range(10)
assert set(self.completions('a.')) == set(['a.' + n for n in dir(range(1))]) assert set(self.completions('a.')) == set(['a.' + n for n in dir(range(1))])