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
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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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':

View File

@@ -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

View File

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