mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-08 06:44:46 +08:00
module imports are working now
This commit is contained in:
30
evaluate.py
30
evaluate.py
@@ -12,6 +12,14 @@ import debug
|
||||
import builtin
|
||||
|
||||
|
||||
memoize_caches = []
|
||||
|
||||
|
||||
def clear_caches():
|
||||
for m in memoize_caches:
|
||||
m.clear()
|
||||
|
||||
|
||||
def memoize(default=None):
|
||||
"""
|
||||
This is a typical memoization decorator, BUT there is one difference:
|
||||
@@ -23,6 +31,7 @@ def memoize(default=None):
|
||||
"""
|
||||
def func(function):
|
||||
memo = {}
|
||||
memoize_caches.append(memo)
|
||||
|
||||
def wrapper(*args):
|
||||
if args in memo:
|
||||
@@ -201,12 +210,13 @@ def strip_imports(scopes):
|
||||
if isinstance(s, parsing.Import):
|
||||
print 'dini mueter, steile griech!'
|
||||
try:
|
||||
new = follow_import(s)
|
||||
new_scopes = follow_import(s)
|
||||
except modules.ModuleNotFound:
|
||||
debug.dbg('Module not found: ' + str(s))
|
||||
else:
|
||||
result.append(new)
|
||||
result += strip_imports(i for i in new.get_imports() if i.star)
|
||||
result += new_scopes
|
||||
for n in new_scopes:
|
||||
result += strip_imports(i for i in n.get_imports() if i.star)
|
||||
else:
|
||||
result.append(s)
|
||||
return result
|
||||
@@ -319,9 +329,13 @@ def follow_import(_import):
|
||||
if _import.namespace:
|
||||
ns_list += _import.namespace.names
|
||||
|
||||
scope, rest = modules.find_module(ns_list)
|
||||
if rest:
|
||||
scope = follow_path(rest.__iter__(), scope)
|
||||
loaded_in = _import.get_parent_until()
|
||||
|
||||
debug.dbg('after import', scope, rest)
|
||||
return scope
|
||||
scope, rest = modules.find_module(loaded_in, ns_list)
|
||||
if rest:
|
||||
scopes = follow_path(rest.__iter__(), scope)
|
||||
else:
|
||||
scopes = [scope]
|
||||
|
||||
debug.dbg('after import', scopes, rest)
|
||||
return scopes
|
||||
|
||||
24
ftest.py
24
ftest.py
@@ -3,26 +3,16 @@
|
||||
import functions
|
||||
|
||||
#functions.debug.debug_function = functions.debug.print_to_stdout
|
||||
functions.debug.ignored_modules += ['parsing', 'builtin']
|
||||
#functions.debug.ignored_modules += ['parsing', 'builtin']
|
||||
functions.modules.module_find_path.insert(0, '.')
|
||||
|
||||
with open('test.py') as f:
|
||||
f_name = 'test.py'
|
||||
import os
|
||||
path = os.getcwd() + '/' + f_name
|
||||
|
||||
with open(path) as f:
|
||||
code = f.read()
|
||||
for i in range(1):
|
||||
completions = functions.complete(code, 50, 200)
|
||||
completions = functions.complete(code, 150, 200, path)
|
||||
|
||||
print '\n', ', '.join(str(c) for c in completions)
|
||||
|
||||
out = []
|
||||
for c in completions:
|
||||
d = dict(word=str(c),
|
||||
abbr=c.complete,
|
||||
menu=c.description, # the stuff directly behind the completion
|
||||
info=c.help, # docstr and similar stuff
|
||||
kind=c.type, # completion type
|
||||
icase=1, # case insensitive
|
||||
dup=1, # allow duplicates (maybe later remove this)
|
||||
)
|
||||
out.append(d)
|
||||
|
||||
print str(out)
|
||||
|
||||
52
functions.py
52
functions.py
@@ -12,23 +12,23 @@ __all__ = ['complete', 'get_completion_parts', 'complete_test', 'set_debug_funct
|
||||
class FileWithCursor(modules.File):
|
||||
"""
|
||||
Manages all files, that are parsed and caches them.
|
||||
Important are the params source and module_name, one of them has to
|
||||
Important are the params source and module_path, one of them has to
|
||||
be there.
|
||||
|
||||
:param source: The source code of the file.
|
||||
:param module_name: The module name of the file.
|
||||
:param module_path: The module name of the file.
|
||||
:param row: The row, the user is currently in. Only important for the \
|
||||
main file.
|
||||
"""
|
||||
def __init__(self, module_name, source, row):
|
||||
super(FileWithCursor, self).__init__(module_name, source)
|
||||
def __init__(self, module_path, source, row):
|
||||
super(FileWithCursor, self).__init__(module_path, source)
|
||||
self.row = row
|
||||
|
||||
# this two are only used, because there is no nonlocal in Python 2
|
||||
self._row_temp = None
|
||||
self._relevant_temp = None
|
||||
|
||||
self._parser = parsing.PyFuzzyParser(source, row)
|
||||
self._parser = parsing.PyFuzzyParser(source, module_path, row)
|
||||
|
||||
def get_row_path(self, column):
|
||||
""" Get the path under the cursor. """
|
||||
@@ -122,9 +122,29 @@ class Completion(object):
|
||||
except:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return '' # type(self.name)
|
||||
def get_type(self):
|
||||
return type(self.name.parent)
|
||||
|
||||
def get_vim_type(self):
|
||||
"""
|
||||
This is the only function, which is vim specific, it returns the vim
|
||||
type, see help(complete-items)
|
||||
"""
|
||||
typ = self.get_type()
|
||||
if typ == parsing.Statement:
|
||||
return 'v' # variable
|
||||
elif typ == parsing.Function:
|
||||
return 'f' # function / method
|
||||
elif typ in [parsing.Class, evaluate.Instance]:
|
||||
return 't' # typedef -> abused as class
|
||||
elif typ == parsing.Import:
|
||||
return 'd' # define -> abused as import
|
||||
if typ == parsing.Param:
|
||||
return 'm' # member -> abused as param
|
||||
else:
|
||||
debug.dbg('other python type: ', typ)
|
||||
|
||||
return ''
|
||||
|
||||
def __str__(self):
|
||||
return self.name.names[-1]
|
||||
@@ -138,7 +158,7 @@ def get_completion_parts(path):
|
||||
match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S)
|
||||
return match.groups()
|
||||
|
||||
def complete(source, row, column, file_callback=None):
|
||||
def complete(source, row, column, source_path):
|
||||
"""
|
||||
An auto completer for python files.
|
||||
|
||||
@@ -148,17 +168,20 @@ def complete(source, row, column, file_callback=None):
|
||||
:type row: int
|
||||
:param col: The column to complete in.
|
||||
:type col: int
|
||||
:param source_path: The path in the os, the current module is in.
|
||||
:type source_path: int
|
||||
|
||||
:return: list of completion objects
|
||||
:rtype: list
|
||||
"""
|
||||
f = FileWithCursor('__main__', source=source, row=row)
|
||||
f = FileWithCursor(source_path, source=source, row=row)
|
||||
scope = f.parser.user_scope
|
||||
path = f.get_row_path(column)
|
||||
debug.dbg('completion_start: %s in %s' % (path, scope))
|
||||
|
||||
# just parse one statement, take it and evaluate it
|
||||
path, dot, like = get_completion_parts(path)
|
||||
r = parsing.PyFuzzyParser(path)
|
||||
r = parsing.PyFuzzyParser(path, source_path)
|
||||
try:
|
||||
stmt = r.top.statements[0]
|
||||
except IndexError:
|
||||
@@ -171,13 +194,11 @@ def complete(source, row, column, file_callback=None):
|
||||
for s in scopes:
|
||||
completions += s.get_defined_names()
|
||||
|
||||
print repr(path), repr(dot), repr(like), row, column
|
||||
print len(completions)
|
||||
needs_dot = not dot and path
|
||||
completions = [Completion(c, needs_dot, len(like)) for c in completions
|
||||
if c.names[-1].lower().startswith(like.lower())]
|
||||
print 'nr2', len(completions)
|
||||
|
||||
_clear_caches()
|
||||
return completions
|
||||
|
||||
|
||||
@@ -245,3 +266,6 @@ def set_debug_function(func_cb):
|
||||
:param func_cb: The callback function for debug messages, with n params.
|
||||
"""
|
||||
debug.debug_function = func_cb
|
||||
|
||||
def _clear_caches():
|
||||
evaluate.clear_caches()
|
||||
|
||||
43
modules.py
43
modules.py
@@ -1,5 +1,6 @@
|
||||
import imp
|
||||
import sys
|
||||
import os
|
||||
|
||||
import debug
|
||||
import parsing
|
||||
@@ -20,11 +21,11 @@ class File(object):
|
||||
Manages all files, that are parsed and caches them.
|
||||
|
||||
:param source: The source code of the file.
|
||||
:param module_name: The module name of the file.
|
||||
:param module_path: The module path of the file.
|
||||
"""
|
||||
def __init__(self, module_name, source):
|
||||
def __init__(self, module_path, source):
|
||||
self.source = source
|
||||
self.module_name = module_name
|
||||
self.module_path = module_path
|
||||
self._line_cache = None
|
||||
self._parser = None
|
||||
|
||||
@@ -32,30 +33,34 @@ class File(object):
|
||||
def parser(self):
|
||||
if self._parser:
|
||||
return self._parser
|
||||
if not self.module_name and not self.source:
|
||||
if not self.module_path and not self.source:
|
||||
raise AttributeError("Submit a module name or the source code")
|
||||
elif self.module_name:
|
||||
elif self.module_path:
|
||||
return self._load_module()
|
||||
|
||||
def _load_module(self):
|
||||
self._parser = parsing.PyFuzzyParser(self.source)
|
||||
self._parser = parsing.PyFuzzyParser(self.source, self.module_path)
|
||||
del self.source # efficiency
|
||||
return self._parser
|
||||
|
||||
def find_module(point_path):
|
||||
def find_module(current_module, point_path):
|
||||
"""
|
||||
Find a module with a path (of the module, like usb.backend.libusb10).
|
||||
|
||||
Relative imports: http://www.python.org/dev/peps/pep-0328
|
||||
are only used like this (py3000): from .module import name.
|
||||
|
||||
:param current_ns_path: A path to the current namespace.
|
||||
:param point_path: A name from the parser.
|
||||
:return: The rest of the path, and the module top scope.
|
||||
"""
|
||||
def follow_str(ns, string):
|
||||
debug.dbg('follow_module', ns, string)
|
||||
if ns:
|
||||
path = ns[1]
|
||||
path = [ns[1]]
|
||||
else:
|
||||
# TODO modules can be system modules, without '.' in path
|
||||
path = module_find_path
|
||||
path = None
|
||||
debug.dbg('search_module', string, path)
|
||||
try:
|
||||
i = imp.find_module(string, path)
|
||||
@@ -67,8 +72,9 @@ def find_module(point_path):
|
||||
raise
|
||||
return i
|
||||
|
||||
# now execute those paths
|
||||
current_namespace = None
|
||||
sys.path.insert(0, os.path.dirname(current_module.module_path))
|
||||
# now execute those paths
|
||||
rest = []
|
||||
for i, s in enumerate(point_path):
|
||||
try:
|
||||
@@ -80,8 +86,19 @@ def find_module(point_path):
|
||||
raise ModuleNotFound(
|
||||
'The module you searched has not been found')
|
||||
|
||||
if current_namespace[0]:
|
||||
f = File(current_namespace[2], current_namespace[0].read())
|
||||
sys.path.pop(0)
|
||||
path = current_namespace[1]
|
||||
is_package_directory = current_namespace[2][2] == imp.PKG_DIRECTORY
|
||||
|
||||
if is_package_directory or current_namespace[0]:
|
||||
# is a directory module
|
||||
if is_package_directory:
|
||||
path += '/__init__.py'
|
||||
with open(path) as f:
|
||||
source = f.read()
|
||||
else:
|
||||
f = builtin.Parser(current_namespace[1])
|
||||
source = current_namespace[0].read()
|
||||
f = File(path, source)
|
||||
else:
|
||||
f = builtin.Parser(path)
|
||||
return f.parser.top, rest
|
||||
|
||||
75
parsing.py
75
parsing.py
@@ -99,7 +99,6 @@ class Scope(Simple):
|
||||
self.subscopes = []
|
||||
self.imports = []
|
||||
self.statements = []
|
||||
self.global_vars = []
|
||||
self.docstr = docstr
|
||||
|
||||
def add_scope(self, sub, decorators):
|
||||
@@ -154,18 +153,6 @@ class Scope(Simple):
|
||||
i += s.get_imports()
|
||||
return i
|
||||
|
||||
def add_global(self, name):
|
||||
"""
|
||||
Global means in these context a function (subscope) which has a global
|
||||
statement.
|
||||
This is only relevant for the top scope.
|
||||
|
||||
:param name: The name of the global.
|
||||
:type name: Name
|
||||
"""
|
||||
self.global_vars.append(name)
|
||||
# set no parent here, because globals are not defined in this scope.
|
||||
|
||||
def get_code(self, first_indent=False, indention=" "):
|
||||
"""
|
||||
:return: Returns the code of the current scope.
|
||||
@@ -203,11 +190,10 @@ class Scope(Simple):
|
||||
|
||||
# function and class names
|
||||
n += [s.name for s in self.subscopes]
|
||||
n += self.global_vars
|
||||
|
||||
for i in self.imports:
|
||||
if not i.star:
|
||||
n += i.get_names()
|
||||
n += i.get_defined_names()
|
||||
|
||||
return n
|
||||
|
||||
@@ -229,12 +215,41 @@ class Scope(Simple):
|
||||
try:
|
||||
name = self.command
|
||||
except:
|
||||
name = 'global'
|
||||
name = self.module_path
|
||||
|
||||
return "<%s: %s@%s-%s>" % \
|
||||
(self.__class__.__name__, name, self.line_nr, self.line_end)
|
||||
|
||||
|
||||
class GlobalScope(Scope):
|
||||
"""
|
||||
The top scope, which is a module.
|
||||
I don't know why I didn't name it Module :-)
|
||||
"""
|
||||
def __init__(self, module_path, docstr=''):
|
||||
super(GlobalScope, self).__init__(module_path, docstr)
|
||||
self.module_path = module_path
|
||||
self.global_vars = []
|
||||
|
||||
def add_global(self, name):
|
||||
"""
|
||||
Global means in these context a function (subscope) which has a global
|
||||
statement.
|
||||
This is only relevant for the top scope.
|
||||
|
||||
:param name: The name of the global.
|
||||
:type name: Name
|
||||
"""
|
||||
self.global_vars.append(name)
|
||||
# set no parent here, because globals are not defined in this scope.
|
||||
|
||||
def get_set_vars(self):
|
||||
n = []
|
||||
n += super(GlobalScope, self).get_set_vars()
|
||||
n += self.global_vars
|
||||
return n
|
||||
|
||||
|
||||
class Class(Scope):
|
||||
"""
|
||||
Used to store the parsed contents of a python class.
|
||||
@@ -420,7 +435,7 @@ class Import(Simple):
|
||||
:type star: bool
|
||||
"""
|
||||
def __init__(self, indent, line_nr, line_end, namespace, alias='', \
|
||||
from_ns='', star=False):
|
||||
from_ns='', star=False, relative_count=None):
|
||||
super(Import, self).__init__(indent, line_nr, line_end)
|
||||
|
||||
self.namespace = namespace
|
||||
@@ -436,6 +451,7 @@ class Import(Simple):
|
||||
from_ns.parent = self
|
||||
|
||||
self.star = star
|
||||
self.relative_count = relative_count
|
||||
|
||||
def get_code(self):
|
||||
if self.alias:
|
||||
@@ -449,7 +465,7 @@ class Import(Simple):
|
||||
else:
|
||||
return "import " + ns_str + '\n'
|
||||
|
||||
def get_names(self):
|
||||
def get_defined_names(self):
|
||||
if self.star:
|
||||
return [self]
|
||||
return [self.alias] if self.alias else [self.namespace]
|
||||
@@ -805,12 +821,12 @@ class PyFuzzyParser(object):
|
||||
:param user_line: The line, the user is currently on.
|
||||
:type user_line: int
|
||||
"""
|
||||
def __init__(self, code, user_line=None):
|
||||
def __init__(self, code, module_path=None, user_line=None):
|
||||
self.user_line = user_line
|
||||
self.code = code + '\n' # end with \n, because the parser needs it
|
||||
|
||||
# initialize global Scope
|
||||
self.top = Scope(0, 0)
|
||||
self.top = GlobalScope(module_path)
|
||||
self.scope = self.top
|
||||
self.current = (None, None, None)
|
||||
|
||||
@@ -1072,6 +1088,7 @@ class PyFuzzyParser(object):
|
||||
continue
|
||||
elif '=' in tok and not tok in ['>=', '<=', '==', '!=']:
|
||||
# there has been an assignement -> change vars
|
||||
if level == 0:
|
||||
set_vars = used_vars
|
||||
used_vars = []
|
||||
elif tok in ['{', '(', '[']:
|
||||
@@ -1139,8 +1156,8 @@ class PyFuzzyParser(object):
|
||||
while True:
|
||||
try:
|
||||
token_type, tok, indent = self.next()
|
||||
debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\
|
||||
% (tok, token_type, indent))
|
||||
#debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\
|
||||
# % (tok, token_type, indent))
|
||||
|
||||
while token_type == tokenize.DEDENT and self.scope != self.top:
|
||||
debug.dbg('dedent', self.scope)
|
||||
@@ -1187,10 +1204,19 @@ class PyFuzzyParser(object):
|
||||
for m, alias in imports:
|
||||
i = Import(indent, start_line, self.line_nr, m, alias)
|
||||
self.scope.add_import(i)
|
||||
debug.dbg("new import: %s" % (i))
|
||||
self.freshscope = False
|
||||
elif tok == 'from':
|
||||
# take care for relative imports
|
||||
relative_count = 0
|
||||
while 1:
|
||||
token_type, tok, indent = self.next()
|
||||
if tok != '.':
|
||||
break;
|
||||
relative_count += 1
|
||||
# the from import
|
||||
mod, token_type, tok, start_indent, start_line2 = \
|
||||
self._parsedotname()
|
||||
self._parsedotname(self.current)
|
||||
if not mod or tok != "import":
|
||||
debug.warning("from: syntax error@%s" %
|
||||
self.line_nr)
|
||||
@@ -1202,8 +1228,9 @@ class PyFuzzyParser(object):
|
||||
if star:
|
||||
name = None
|
||||
i = Import(indent, start_line, self.line_nr, name,
|
||||
alias, mod, star)
|
||||
alias, mod, star, relative_count)
|
||||
self.scope.add_import(i)
|
||||
debug.dbg("new from: %s" % (i))
|
||||
self.freshscope = False
|
||||
#loops
|
||||
elif tok == 'for':
|
||||
|
||||
@@ -10,6 +10,9 @@ if !has('python')
|
||||
finish
|
||||
endif
|
||||
|
||||
" ------------------------------------------------------------------------
|
||||
" Completion
|
||||
" ------------------------------------------------------------------------
|
||||
|
||||
function! jedi#Complete(findstart, base)
|
||||
if a:findstart == 1
|
||||
@@ -17,17 +20,19 @@ function! jedi#Complete(findstart, base)
|
||||
else
|
||||
python << PYTHONEOF
|
||||
if 1:
|
||||
# TODO change the finstart column, to switch cases, if they are not right.
|
||||
row, column = vim.current.window.cursor
|
||||
buf_path = vim.current.buffer.name
|
||||
source = '\n'.join(vim.current.buffer)
|
||||
try:
|
||||
completions = functions.complete(source, row, column)
|
||||
completions = functions.complete(source, row, column, buf_path)
|
||||
out = []
|
||||
for c in completions:
|
||||
d = dict(word=c.complete,
|
||||
abbr=str(c),
|
||||
menu=c.description, # stuff directly behind the completion
|
||||
info=c.help, # docstr and similar stuff
|
||||
kind=c.type, # completion type
|
||||
kind=c.get_vim_type(), # completion type
|
||||
icase=1, # case insensitive
|
||||
dup=1, # allow duplicates (maybe later remove this)
|
||||
)
|
||||
@@ -35,10 +40,11 @@ if 1:
|
||||
|
||||
strout = str(out)
|
||||
except Exception as e:
|
||||
print 'error:', e
|
||||
# print to stdout, will be in :messages
|
||||
print(traceback.format_exc())
|
||||
strout = ''
|
||||
|
||||
print 'end', strout
|
||||
#print 'end', strout
|
||||
vim.command('return ' + strout)
|
||||
PYTHONEOF
|
||||
endif
|
||||
@@ -48,7 +54,7 @@ endfunction
|
||||
" ------------------------------------------------------------------------
|
||||
" Initialization of Jedi
|
||||
" ------------------------------------------------------------------------
|
||||
"
|
||||
|
||||
let s:current_file=expand("<sfile>")
|
||||
|
||||
python << PYTHONEOF
|
||||
@@ -60,6 +66,8 @@ import sys
|
||||
from os.path import dirname
|
||||
sys.path.insert(0, dirname(dirname(vim.eval('s:current_file'))))
|
||||
|
||||
import traceback # for exception output
|
||||
|
||||
import functions
|
||||
PYTHONEOF
|
||||
|
||||
|
||||
Reference in New Issue
Block a user