1
0
forked from VimPlug/jedi

Get closer to fixing a lot of issues with the completion for repl.

This commit is contained in:
Dave Halter
2016-05-15 14:26:10 +02:00
parent beeffd2dcd
commit cc331d62e0
6 changed files with 97 additions and 59 deletions

View File

@@ -626,46 +626,6 @@ class Interpreter(Script):
interpreter.add_namespaces_to_parser(self._evaluator, namespaces, interpreter.add_namespaces_to_parser(self._evaluator, namespaces,
self._parser.module()) self._parser.module())
def _simple_complete(self, path, dot, like):
user_stmt = self._parser.user_stmt_with_whitespace()
is_simple_path = not path or re.search('^[\w][\w\d.]*$', path)
if isinstance(user_stmt, tree.Import) or not is_simple_path:
return super(Interpreter, self)._simple_complete(path, dot, like)
else:
# TODO Remove this branch? The above branch should be fast enough IMO.
class NamespaceModule(object):
def __getattr__(_, name):
for n in self.namespaces:
try:
return n[name]
except KeyError:
pass
raise AttributeError()
def __dir__(_):
gen = (n.keys() for n in self.namespaces)
return list(set(chain.from_iterable(gen)))
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 Exception:
pass
completion_names = []
for namespace in namespaces:
for name in dir(namespace):
if name.lower().startswith(like.lower()):
scope = self._parser.module()
n = FakeName(name, scope)
completion_names.append(n)
return completion_names
def defined_names(source, path=None, encoding='utf-8'): def defined_names(source, path=None, encoding='utf-8'):
""" """

View File

@@ -3,13 +3,13 @@ TODO Some parts of this module are still not well documented.
""" """
import inspect import inspect
import re import re
import sys
from jedi._compatibility import builtins from jedi._compatibility import builtins
from jedi import debug from jedi import debug
from jedi.common import source_to_unicode from jedi.common import source_to_unicode
from jedi.cache import underscore_memoization from jedi.cache import underscore_memoization
from jedi.evaluate import compiled from jedi.evaluate import compiled
from jedi.evaluate.compiled.fake import get_module
from jedi.parser import tree as pt from jedi.parser import tree as pt
from jedi.parser import load_grammar from jedi.parser import load_grammar
from jedi.parser.fast import FastParser from jedi.parser.fast import FastParser
@@ -42,6 +42,9 @@ class LazyName(helpers.FakeName):
def parent(self): def parent(self):
""" """
Creating fake statements for the interpreter. Creating fake statements for the interpreter.
Here we are trying to link back to Python code, if possible. This means
we try to find the python module for a name (not the builtin).
""" """
obj = self._value obj = self._value
parser_path = [] parser_path = []
@@ -63,10 +66,10 @@ class LazyName(helpers.FakeName):
# Unfortunately in some cases like `int` there's no __module__ # Unfortunately in some cases like `int` there's no __module__
module = builtins module = builtins
else: else:
# TODO this import is wrong. Yields x for x.y.z instead of z # If we put anything into fromlist, it will just return the
module = __import__(module_name) # latest name.
module = __import__(module_name, fromlist=[''])
parser_path = names parser_path = names
raw_module = get_module(self._value)
found = [] found = []
try: try:
@@ -74,17 +77,36 @@ class LazyName(helpers.FakeName):
except AttributeError: except AttributeError:
pass pass
else: else:
# cut the `c` from `.pyc` # Find the corresponding Python file for an interpreted one.
path = re.sub('c$', '', path) path = re.sub(r'\.pyc$', '.py', path)
if path.endswith('.py'): if path.endswith('.py'):
with open(path) as f: with open(path) as f:
source = source_to_unicode(f.read()) source = source_to_unicode(f.read())
mod = FastParser(load_grammar(), source, path).module mod = FastParser(load_grammar(), source, path).module
mod = self._evaluator.wrap(mod)
# We have to make sure that the modules that we import are also
# part of evaluator.modules.
for module_name, module in sys.modules.items():
try:
iterated_path = module.__file__
except AttributeError:
pass
else:
if iterated_path == path:
self._evaluator.modules[module_name] = mod
break
else:
raise NotImplementedError('This should not happen, a module '
'should be part of sys.modules.')
if parser_path: if parser_path:
assert len(parser_path) == 1 assert len(parser_path) == 1
found = self._evaluator.find_types(mod, parser_path[0], search_global=True) found = list(self._evaluator.find_types(mod, parser_path[0],
search_global=True))
else: else:
found = [self._evaluator.wrap(mod)] found = [mod]
if not found: if not found:
debug.warning('Interpreter lookup failed in global scope for %s', debug.warning('Interpreter lookup failed in global scope for %s',
@@ -94,11 +116,14 @@ class LazyName(helpers.FakeName):
evaluated = compiled.create(self._evaluator, obj) evaluated = compiled.create(self._evaluator, obj)
found = [evaluated] found = [evaluated]
if len(found) > 1 or True:
content = iterable.AlreadyEvaluated(found) content = iterable.AlreadyEvaluated(found)
stmt = pt.ExprStmt([self, pt.Operator(pt.zero_position_modifier, stmt = pt.ExprStmt([self, pt.Operator(pt.zero_position_modifier,
'=', (0, 0), ''), content]) '=', (0, 0), ''), content])
stmt.parent = self._module stmt.parent = self._module
return stmt return stmt
else:
return found[0]
@parent.setter @parent.setter
def parent(self, value): def parent(self, value):

View File

@@ -113,7 +113,7 @@ class CompiledObject(Base):
elif inspect.ismodule(cls): elif inspect.ismodule(cls):
return 'module' return 'module'
elif inspect.isbuiltin(cls) or inspect.ismethod(cls) \ elif inspect.isbuiltin(cls) or inspect.ismethod(cls) \
or inspect.ismethoddescriptor(cls): or inspect.ismethoddescriptor(cls) or inspect.isfunction(cls):
return 'function' return 'function'
@property @property

View File

@@ -6,6 +6,7 @@ mixing in Python code, the autocompletion should work much better for builtins.
import os import os
import inspect import inspect
import types
from jedi._compatibility import is_py3, builtins, unicode from jedi._compatibility import is_py3, builtins, unicode
from jedi.parser import ParserWithRecovery, load_grammar from jedi.parser import ParserWithRecovery, load_grammar
@@ -15,6 +16,28 @@ from jedi.evaluate.helpers import FakeName
modules = {} modules = {}
MethodDescriptorType = type(str.replace)
# These are not considered classes and access is granted even though they have
# a __class__ attribute.
NOT_CLASS_TYPES = (
types.BuiltinFunctionType,
types.CodeType,
types.DynamicClassAttribute,
types.FrameType,
types.FunctionType,
types.GeneratorType,
types.GetSetDescriptorType,
types.LambdaType,
types.MappingProxyType,
types.MemberDescriptorType,
types.MethodType,
types.ModuleType,
types.SimpleNamespace,
types.TracebackType,
MethodDescriptorType
)
def _load_faked_module(module): def _load_faked_module(module):
module_name = module.__name__ module_name = module.__name__
if module_name == '__builtin__' and not is_py3: if module_name == '__builtin__' and not is_py3:
@@ -134,7 +157,9 @@ def get_faked(module, obj, name=None):
def is_class_instance(obj): def is_class_instance(obj):
"""Like inspect.* methods.""" """Like inspect.* methods."""
return not (inspect.isclass(obj) or inspect.ismodule(obj) try:
or inspect.isbuiltin(obj) or inspect.ismethod(obj) cls = obj.__class__
or inspect.ismethoddescriptor(obj) or inspect.iscode(obj) except AttributeError:
or inspect.isgenerator(obj)) return False
else:
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)

View File

@@ -88,7 +88,7 @@ def setup_readline(namespace_module=__main__):
try: try:
import readline import readline
except ImportError: except ImportError:
print("Module readline not available.") print("Jedi: Module readline not available.")
else: else:
readline.set_completer(JediRL().complete) readline.set_completer(JediRL().complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")

View File

@@ -7,6 +7,34 @@ import jedi
from jedi._compatibility import is_py33 from jedi._compatibility import is_py33
def get_completion(source, namespace):
i = jedi.Interpreter(source, [namespace])
completions = i.completions()
assert len(completions) == 1
return completions[0]
def test_builtin_details():
import keyword
class EmptyClass:
pass
variable = EmptyClass()
def func():
pass
cls = get_completion('EmptyClass', locals())
var = get_completion('variable', locals())
f = get_completion('func', locals())
m = get_completion('keyword', locals())
assert cls.type == 'class'
assert var.type == 'instance'
assert f.type == 'function'
assert m.type == 'module'
class TestInterpreterAPI(TestCase): class TestInterpreterAPI(TestCase):
def check_interpreter_complete(self, source, namespace, completions, def check_interpreter_complete(self, source, namespace, completions,
**kwds): **kwds):