cleaned up and fixed most cases of import related stuff, fixes #2

This commit is contained in:
David Halter
2012-09-12 21:36:16 +02:00
parent 139eb1d642
commit 119455c3b3
7 changed files with 105 additions and 54 deletions

View File

@@ -1,7 +1,7 @@
Jedi - a clever Python auto-completion
============================
** now in alpha testing phase **
**now in alpha testing phase**
*If you have any comments or feature request, please tell me! I really want to
know, what you think of Jedi.*
@@ -55,8 +55,8 @@ Jedi supports many of the widely used Python features:
However, it does not yet support (and probably will in future versions, because
they are on my todo list):
- getattr() / __getattr__ / __getattribute__
- sys.path modifications
- `getattr()` / `__getattr__` / `__getattribute__`
- `sys.path` modifications
- manipulations of instances outside the instance variables, without using
functions
- mro

View File

@@ -1448,39 +1448,17 @@ def goto(scopes, search_name=None, statement_path_offset=1):
except imports.ModuleNotFound:
continue
else:
try:
if not isinstance(s, parsing.Module):
s = statement_path[0]
except IndexError:
pass
definitions.append(s)
else:
def remove_unreal_imports(names):
"""
These imports are only virtual, because of multi-line imports.
"""
new_names = []
for n in names:
par = n.parent()
# This is a special case: If the Import is "virtual" (which
# means the position is not defined), follow those modules.
if isinstance(par, parsing.Import) and not par.start_pos[0]:
module_count = 0
for scope in imports.ImportPath(par).follow():
if isinstance(scope, parsing.Import):
temp = scope.get_defined_names()
new_names += remove_unreal_imports(temp)
elif isinstance(scope, parsing.Module) \
and not module_count:
# only first module (others are star imports)
module_count += 1
new_names.append(scope.get_module_name(n.names))
else:
new_names.append(n)
return new_names
names = []
for s in scopes:
names += s.get_defined_names()
names = remove_unreal_imports(names)
if isinstance(s, imports.ImportPath):
modules = s.follow()
if modules:
names.append(modules[0].get_module_name())
else:
names += s.get_defined_names()
definitions = [n for n in names if n.names[-1] == search_name]
return definitions

View File

@@ -144,7 +144,7 @@ def complete(source, line, column, source_path):
path, dot, like = _get_completion_parts(path)
try:
scopes = _prepare_goto(source, pos, source_path, f, path, True)
scopes = _prepare_goto(pos, source_path, f, path, True)
except NotFoundError:
scope_generator = evaluate.get_names_for_scope(f.parser.user_scope,
pos)
@@ -159,7 +159,11 @@ def complete(source, line, column, source_path):
# TODO is this really the right way? just ignore the functions? \
# do the magic functions first? and then recheck here?
if not isinstance(s, evaluate.Function):
for c in s.get_defined_names():
if isinstance(s, imports.ImportPath):
names = s.get_defined_names(on_import_stmt=True)
else:
names = s.get_defined_names()
for c in names:
completions.append((c, s))
completions = [(c, s) for c, s in completions
@@ -174,7 +178,7 @@ def complete(source, line, column, source_path):
return c
def _prepare_goto(source, position, source_path, module, goto_path,
def _prepare_goto(position, source_path, module, goto_path,
is_like_search=False):
scope = module.parser.user_scope
debug.dbg('start: %s in %s' % (goto_path, scope))
@@ -187,7 +191,23 @@ def _prepare_goto(source, position, source_path, module, goto_path,
return []
if isinstance(user_stmt, parsing.Import):
scopes = [imports.ImportPath(user_stmt, is_like_search)]
import_names = user_stmt.get_all_import_names()
count = 0
found_count = None
for i in import_names:
for name_part in i.names:
count += 1
if name_part.start_pos <= position <= name_part.end_pos:
found_count = count
if found_count is None:
found_count = count
if is_like_search:
# is_like_search will decrease also one, so change this here.
found_count += 1
else:
return []
scopes = [imports.ImportPath(user_stmt, is_like_search,
kill_count=(count - found_count), direct_resolve=True)]
else:
# just parse one statement, take it and evaluate it
r = parsing.PyFuzzyParser(goto_path, source_path, no_docstr=True)
@@ -231,7 +251,7 @@ def get_definition(source, line, column, source_path):
op = f.get_operator_under_cursor()
scopes = set([keywords.get_operator(op, pos)] if op else [])
else:
scopes = set(_prepare_goto(source, pos, source_path, f, goto_path))
scopes = set(_prepare_goto(pos, source_path, f, goto_path))
for s in scopes.copy():
if isinstance(s, imports.ImportPath):
@@ -265,7 +285,7 @@ def goto(source, line, column, source_path):
if next(context) in ('class', 'def'):
definitions = set([f.parser.user_scope])
else:
scopes = _prepare_goto(source, pos, source_path, f, goto_path)
scopes = _prepare_goto(pos, source_path, f, goto_path)
definitions = evaluate.goto(scopes, search_name_new)
d = [Definition(d) for d in set(definitions)]
@@ -303,7 +323,7 @@ def related_names(source, line, column, source_path):
elif isinstance(f.parser.user_stmt, (parsing.Param, parsing.Import)):
definitions = [f.parser.user_stmt]
else:
scopes = _prepare_goto(source, pos, source_path, f, goto_path)
scopes = _prepare_goto(pos, source_path, f, goto_path)
definitions = evaluate.goto(scopes, search_name_new)
module = set([d.get_parent_until() for d in definitions])

View File

@@ -24,23 +24,27 @@ class ImportPath(object):
class GlobalNamespace(object):
pass
def __init__(self, import_stmt, is_like_search=False):
def __init__(self, import_stmt, is_like_search=False, kill_count=0,
direct_resolve=False):
self.import_stmt = import_stmt
self.is_like_search = is_like_search
self.direct_resolve = direct_resolve
self.is_partial_import = bool(kill_count)
self.file_path = os.path.dirname(import_stmt.get_parent_until().path)
# rest is import_path resolution
self.import_path = []
if import_stmt.from_ns:
self.import_path += import_stmt.from_ns.names
if import_stmt.namespace:
if self.is_nested_import():
if self.is_nested_import() and not direct_resolve:
self.import_path.append(import_stmt.namespace.names[0])
else:
self.import_path += import_stmt.namespace.names
self.is_like_search = is_like_search
if is_like_search:
# drop one path part, because that is used by the like search
for i in range(kill_count + int(is_like_search)):
self.import_path.pop()
self.file_path = os.path.dirname(import_stmt.get_parent_until().path)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.import_stmt)
@@ -52,7 +56,8 @@ class ImportPath(object):
>>> import foo.bar
"""
return not self.import_stmt.alias and not self.import_stmt.from_ns \
and len(self.import_stmt.namespace.names) > 1
and len(self.import_stmt.namespace.names) > 1 \
and not self.direct_resolve
def get_nested_import(self, parent):
"""
@@ -70,16 +75,30 @@ class ImportPath(object):
debug.dbg('Generated a nested import: %s' % new)
return new
def get_defined_names(self):
def get_defined_names(self, on_import_stmt=False):
names = []
for scope in self.follow():
if scope is ImportPath.GlobalNamespace:
names += self.get_module_names()
names += self.get_module_names([self.file_path])
else:
for s, n in evaluate.get_names_for_scope(scope,
if on_import_stmt and isinstance(scope, parsing.Module) \
and scope.path.endswith('__init__.py'):
pkg_path = os.path.dirname(scope.path)
names += self.get_module_names([pkg_path])
for s, scope_names in evaluate.get_names_for_scope(scope,
include_builtin=False):
names += n
for n in scope_names:
if not isinstance(n.parent(), parsing.Import) \
and (self.import_stmt.from_ns is None \
or self.is_partial_import):
# from_ns must be defined to access module
# values plus a partial import means that there
# is something after the import, which
# automatically implies that there must not be
# any non-module scope.
continue
names.append(n)
return names
def get_module_names(self, search_path=None):
@@ -99,7 +118,7 @@ class ImportPath(object):
Returns the imported modules.
"""
if self.import_path:
scope, rest = self.follow_file_system()
scope, rest = self._follow_file_system()
if len(rest) > 1 or rest and self.is_like_search:
scopes = []
elif rest:
@@ -119,7 +138,7 @@ class ImportPath(object):
debug.dbg('after import', scopes)
return scopes
def follow_file_system(self):
def _follow_file_system(self):
"""
Find a module with a path (of the module, like usb.backend.libusb10).
"""

View File

@@ -35,6 +35,7 @@ import tokenize
import re
import keyword
import weakref
import os
import debug
@@ -287,8 +288,12 @@ class Module(Scope):
n += self.global_vars
return n
def get_module_name(self, names):
def get_module_name(self):
""" This is used for the goto function. """
sep = (os.path.sep,) * 2
r = re.search(r'([^%s]+?)(%s__init__)?(\.py)?$' % sep, self.path)
string = r.group(1)
names = [(string, (0, 0))]
if not self._name:
self._name = Name(names, self.start_pos, self.end_pos, self)
return self._name
@@ -557,6 +562,16 @@ class Import(Simple):
else:
return [self.namespace]
def get_all_import_names(self):
n = []
if self.from_ns:
n.append(self.from_ns)
if self.namespace:
n.append(self.namespace)
if self.alias:
n.append(self.alias)
return n
class Statement(Simple):
"""

View File

@@ -165,6 +165,25 @@ from . import import_tree as imp_tree
#? str()
imp_tree.a
from . import datetime
from . import datetime as mod1
#? []
mod1.
#? str()
imp_tree.a
import datetime
#? 6 datetime
from datetime.time import time
#? []
import datetime.
#? []
import datetime.date
#? 18 ['mod1', 'random', 'pkg']
from import_tree. import pkg
#? 18 ['pkg']
from import_tree.p import pkg

View File

@@ -5,7 +5,7 @@ pylab.
#! ['module numpy']
import numpy
#! ['import random', 'module random']
#! ['module random']
import numpy.random
#? ['array2string']