module imports are working now

This commit is contained in:
David Halter
2012-04-09 00:27:10 +02:00
parent baf8c60e9a
commit 429a8eedb3
7 changed files with 164 additions and 84 deletions

View File

@@ -12,6 +12,14 @@ import debug
import builtin import builtin
memoize_caches = []
def clear_caches():
for m in memoize_caches:
m.clear()
def memoize(default=None): def memoize(default=None):
""" """
This is a typical memoization decorator, BUT there is one difference: This is a typical memoization decorator, BUT there is one difference:
@@ -23,6 +31,7 @@ def memoize(default=None):
""" """
def func(function): def func(function):
memo = {} memo = {}
memoize_caches.append(memo)
def wrapper(*args): def wrapper(*args):
if args in memo: if args in memo:
@@ -201,12 +210,13 @@ def strip_imports(scopes):
if isinstance(s, parsing.Import): if isinstance(s, parsing.Import):
print 'dini mueter, steile griech!' print 'dini mueter, steile griech!'
try: try:
new = follow_import(s) new_scopes = follow_import(s)
except modules.ModuleNotFound: except modules.ModuleNotFound:
debug.dbg('Module not found: ' + str(s)) debug.dbg('Module not found: ' + str(s))
else: else:
result.append(new) result += new_scopes
result += strip_imports(i for i in new.get_imports() if i.star) for n in new_scopes:
result += strip_imports(i for i in n.get_imports() if i.star)
else: else:
result.append(s) result.append(s)
return result return result
@@ -319,9 +329,13 @@ def follow_import(_import):
if _import.namespace: if _import.namespace:
ns_list += _import.namespace.names ns_list += _import.namespace.names
scope, rest = modules.find_module(ns_list) loaded_in = _import.get_parent_until()
if rest:
scope = follow_path(rest.__iter__(), scope)
debug.dbg('after import', scope, rest) scope, rest = modules.find_module(loaded_in, ns_list)
return scope if rest:
scopes = follow_path(rest.__iter__(), scope)
else:
scopes = [scope]
debug.dbg('after import', scopes, rest)
return scopes

View File

@@ -3,26 +3,16 @@
import functions import functions
#functions.debug.debug_function = functions.debug.print_to_stdout #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, '.') 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() code = f.read()
for i in range(1): 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) 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)

View File

@@ -12,23 +12,23 @@ __all__ = ['complete', 'get_completion_parts', 'complete_test', 'set_debug_funct
class FileWithCursor(modules.File): class FileWithCursor(modules.File):
""" """
Manages all files, that are parsed and caches them. 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. be there.
:param source: The source code of the file. :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 \ :param row: The row, the user is currently in. Only important for the \
main file. main file.
""" """
def __init__(self, module_name, source, row): def __init__(self, module_path, source, row):
super(FileWithCursor, self).__init__(module_name, source) super(FileWithCursor, self).__init__(module_path, source)
self.row = row self.row = row
# this two are only used, because there is no nonlocal in Python 2 # this two are only used, because there is no nonlocal in Python 2
self._row_temp = None self._row_temp = None
self._relevant_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): def get_row_path(self, column):
""" Get the path under the cursor. """ """ Get the path under the cursor. """
@@ -122,9 +122,29 @@ class Completion(object):
except: except:
return '' return ''
@property def get_type(self):
def type(self): return type(self.name.parent)
return '' # type(self.name)
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): def __str__(self):
return self.name.names[-1] 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) match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S)
return match.groups() return match.groups()
def complete(source, row, column, file_callback=None): def complete(source, row, column, source_path):
""" """
An auto completer for python files. An auto completer for python files.
@@ -148,17 +168,20 @@ def complete(source, row, column, file_callback=None):
:type row: int :type row: int
:param col: The column to complete in. :param col: The column to complete in.
:type col: int :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 :return: list of completion objects
:rtype: list :rtype: list
""" """
f = FileWithCursor('__main__', source=source, row=row) f = FileWithCursor(source_path, source=source, row=row)
scope = f.parser.user_scope scope = f.parser.user_scope
path = f.get_row_path(column) path = f.get_row_path(column)
debug.dbg('completion_start: %s in %s' % (path, scope)) debug.dbg('completion_start: %s in %s' % (path, scope))
# just parse one statement, take it and evaluate it # just parse one statement, take it and evaluate it
path, dot, like = get_completion_parts(path) path, dot, like = get_completion_parts(path)
r = parsing.PyFuzzyParser(path) r = parsing.PyFuzzyParser(path, source_path)
try: try:
stmt = r.top.statements[0] stmt = r.top.statements[0]
except IndexError: except IndexError:
@@ -171,13 +194,11 @@ def complete(source, row, column, file_callback=None):
for s in scopes: for s in scopes:
completions += s.get_defined_names() completions += s.get_defined_names()
print repr(path), repr(dot), repr(like), row, column
print len(completions)
needs_dot = not dot and path needs_dot = not dot and path
completions = [Completion(c, needs_dot, len(like)) for c in completions completions = [Completion(c, needs_dot, len(like)) for c in completions
if c.names[-1].lower().startswith(like.lower())] if c.names[-1].lower().startswith(like.lower())]
print 'nr2', len(completions)
_clear_caches()
return completions return completions
@@ -245,3 +266,6 @@ def set_debug_function(func_cb):
:param func_cb: The callback function for debug messages, with n params. :param func_cb: The callback function for debug messages, with n params.
""" """
debug.debug_function = func_cb debug.debug_function = func_cb
def _clear_caches():
evaluate.clear_caches()

View File

@@ -1,5 +1,6 @@
import imp import imp
import sys import sys
import os
import debug import debug
import parsing import parsing
@@ -20,11 +21,11 @@ class File(object):
Manages all files, that are parsed and caches them. Manages all files, that are parsed and caches them.
:param source: The source code of the file. :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.source = source
self.module_name = module_name self.module_path = module_path
self._line_cache = None self._line_cache = None
self._parser = None self._parser = None
@@ -32,30 +33,34 @@ class File(object):
def parser(self): def parser(self):
if self._parser: if self._parser:
return 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") raise AttributeError("Submit a module name or the source code")
elif self.module_name: elif self.module_path:
return self._load_module() return self._load_module()
def _load_module(self): def _load_module(self):
self._parser = parsing.PyFuzzyParser(self.source) self._parser = parsing.PyFuzzyParser(self.source, self.module_path)
del self.source # efficiency del self.source # efficiency
return self._parser 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). 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. :param point_path: A name from the parser.
:return: The rest of the path, and the module top scope. :return: The rest of the path, and the module top scope.
""" """
def follow_str(ns, string): def follow_str(ns, string):
debug.dbg('follow_module', ns, string) debug.dbg('follow_module', ns, string)
if ns: if ns:
path = ns[1] path = [ns[1]]
else: else:
# TODO modules can be system modules, without '.' in path # TODO modules can be system modules, without '.' in path
path = module_find_path path = None
debug.dbg('search_module', string, path) debug.dbg('search_module', string, path)
try: try:
i = imp.find_module(string, path) i = imp.find_module(string, path)
@@ -67,8 +72,9 @@ def find_module(point_path):
raise raise
return i return i
# now execute those paths
current_namespace = None current_namespace = None
sys.path.insert(0, os.path.dirname(current_module.module_path))
# now execute those paths
rest = [] rest = []
for i, s in enumerate(point_path): for i, s in enumerate(point_path):
try: try:
@@ -80,8 +86,19 @@ def find_module(point_path):
raise ModuleNotFound( raise ModuleNotFound(
'The module you searched has not been found') 'The module you searched has not been found')
if current_namespace[0]: sys.path.pop(0)
f = File(current_namespace[2], current_namespace[0].read()) 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:
source = current_namespace[0].read()
f = File(path, source)
else: else:
f = builtin.Parser(current_namespace[1]) f = builtin.Parser(path)
return f.parser.top, rest return f.parser.top, rest

View File

@@ -99,7 +99,6 @@ class Scope(Simple):
self.subscopes = [] self.subscopes = []
self.imports = [] self.imports = []
self.statements = [] self.statements = []
self.global_vars = []
self.docstr = docstr self.docstr = docstr
def add_scope(self, sub, decorators): def add_scope(self, sub, decorators):
@@ -154,18 +153,6 @@ class Scope(Simple):
i += s.get_imports() i += s.get_imports()
return i 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=" "): def get_code(self, first_indent=False, indention=" "):
""" """
:return: Returns the code of the current scope. :return: Returns the code of the current scope.
@@ -203,11 +190,10 @@ class Scope(Simple):
# function and class names # function and class names
n += [s.name for s in self.subscopes] n += [s.name for s in self.subscopes]
n += self.global_vars
for i in self.imports: for i in self.imports:
if not i.star: if not i.star:
n += i.get_names() n += i.get_defined_names()
return n return n
@@ -229,12 +215,41 @@ class Scope(Simple):
try: try:
name = self.command name = self.command
except: except:
name = 'global' name = self.module_path
return "<%s: %s@%s-%s>" % \ return "<%s: %s@%s-%s>" % \
(self.__class__.__name__, name, self.line_nr, self.line_end) (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): class Class(Scope):
""" """
Used to store the parsed contents of a python class. Used to store the parsed contents of a python class.
@@ -420,7 +435,7 @@ class Import(Simple):
:type star: bool :type star: bool
""" """
def __init__(self, indent, line_nr, line_end, namespace, alias='', \ 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) super(Import, self).__init__(indent, line_nr, line_end)
self.namespace = namespace self.namespace = namespace
@@ -436,6 +451,7 @@ class Import(Simple):
from_ns.parent = self from_ns.parent = self
self.star = star self.star = star
self.relative_count = relative_count
def get_code(self): def get_code(self):
if self.alias: if self.alias:
@@ -449,7 +465,7 @@ class Import(Simple):
else: else:
return "import " + ns_str + '\n' return "import " + ns_str + '\n'
def get_names(self): def get_defined_names(self):
if self.star: if self.star:
return [self] return [self]
return [self.alias] if self.alias else [self.namespace] 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. :param user_line: The line, the user is currently on.
:type user_line: int :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.user_line = user_line
self.code = code + '\n' # end with \n, because the parser needs it self.code = code + '\n' # end with \n, because the parser needs it
# initialize global Scope # initialize global Scope
self.top = Scope(0, 0) self.top = GlobalScope(module_path)
self.scope = self.top self.scope = self.top
self.current = (None, None, None) self.current = (None, None, None)
@@ -1072,8 +1088,9 @@ class PyFuzzyParser(object):
continue continue
elif '=' in tok and not tok in ['>=', '<=', '==', '!=']: elif '=' in tok and not tok in ['>=', '<=', '==', '!=']:
# there has been an assignement -> change vars # there has been an assignement -> change vars
set_vars = used_vars if level == 0:
used_vars = [] set_vars = used_vars
used_vars = []
elif tok in ['{', '(', '[']: elif tok in ['{', '(', '[']:
level += 1 level += 1
elif tok in ['}', ')', ']']: elif tok in ['}', ')', ']']:
@@ -1139,8 +1156,8 @@ class PyFuzzyParser(object):
while True: while True:
try: try:
token_type, tok, indent = self.next() token_type, tok, indent = self.next()
debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\ #debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\
% (tok, token_type, indent)) # % (tok, token_type, indent))
while token_type == tokenize.DEDENT and self.scope != self.top: while token_type == tokenize.DEDENT and self.scope != self.top:
debug.dbg('dedent', self.scope) debug.dbg('dedent', self.scope)
@@ -1187,10 +1204,19 @@ class PyFuzzyParser(object):
for m, alias in imports: for m, alias in imports:
i = Import(indent, start_line, self.line_nr, m, alias) i = Import(indent, start_line, self.line_nr, m, alias)
self.scope.add_import(i) self.scope.add_import(i)
debug.dbg("new import: %s" % (i))
self.freshscope = False self.freshscope = False
elif tok == 'from': 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 = \ mod, token_type, tok, start_indent, start_line2 = \
self._parsedotname() self._parsedotname(self.current)
if not mod or tok != "import": if not mod or tok != "import":
debug.warning("from: syntax error@%s" % debug.warning("from: syntax error@%s" %
self.line_nr) self.line_nr)
@@ -1202,8 +1228,9 @@ class PyFuzzyParser(object):
if star: if star:
name = None name = None
i = Import(indent, start_line, self.line_nr, name, i = Import(indent, start_line, self.line_nr, name,
alias, mod, star) alias, mod, star, relative_count)
self.scope.add_import(i) self.scope.add_import(i)
debug.dbg("new from: %s" % (i))
self.freshscope = False self.freshscope = False
#loops #loops
elif tok == 'for': elif tok == 'for':

View File

@@ -10,6 +10,9 @@ if !has('python')
finish finish
endif endif
" ------------------------------------------------------------------------
" Completion
" ------------------------------------------------------------------------
function! jedi#Complete(findstart, base) function! jedi#Complete(findstart, base)
if a:findstart == 1 if a:findstart == 1
@@ -17,17 +20,19 @@ function! jedi#Complete(findstart, base)
else else
python << PYTHONEOF python << PYTHONEOF
if 1: if 1:
# TODO change the finstart column, to switch cases, if they are not right.
row, column = vim.current.window.cursor row, column = vim.current.window.cursor
buf_path = vim.current.buffer.name
source = '\n'.join(vim.current.buffer) source = '\n'.join(vim.current.buffer)
try: try:
completions = functions.complete(source, row, column) completions = functions.complete(source, row, column, buf_path)
out = [] out = []
for c in completions: for c in completions:
d = dict(word=c.complete, d = dict(word=c.complete,
abbr=str(c), abbr=str(c),
menu=c.description, # stuff directly behind the completion menu=c.description, # stuff directly behind the completion
info=c.help, # docstr and similar stuff info=c.help, # docstr and similar stuff
kind=c.type, # completion type kind=c.get_vim_type(), # completion type
icase=1, # case insensitive icase=1, # case insensitive
dup=1, # allow duplicates (maybe later remove this) dup=1, # allow duplicates (maybe later remove this)
) )
@@ -35,10 +40,11 @@ if 1:
strout = str(out) strout = str(out)
except Exception as e: except Exception as e:
print 'error:', e # print to stdout, will be in :messages
print(traceback.format_exc())
strout = '' strout = ''
print 'end', strout #print 'end', strout
vim.command('return ' + strout) vim.command('return ' + strout)
PYTHONEOF PYTHONEOF
endif endif
@@ -48,7 +54,7 @@ endfunction
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" Initialization of Jedi " Initialization of Jedi
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
"
let s:current_file=expand("<sfile>") let s:current_file=expand("<sfile>")
python << PYTHONEOF python << PYTHONEOF
@@ -60,6 +66,8 @@ import sys
from os.path import dirname from os.path import dirname
sys.path.insert(0, dirname(dirname(vim.eval('s:current_file')))) sys.path.insert(0, dirname(dirname(vim.eval('s:current_file'))))
import traceback # for exception output
import functions import functions
PYTHONEOF PYTHONEOF

View File

@@ -147,4 +147,4 @@ b= asdf2
#import parsing as test #import parsing as test
c = b().c3() c = b().c3()
1.0.fromhex() 1.0.fromhex(); from flask import Flask; Flask.