diff --git a/functions.py b/functions.py index 0eedb12e..41a746ac 100644 --- a/functions.py +++ b/functions.py @@ -2,7 +2,7 @@ import parsing import re -__all__ = ['complete'] +__all__ = ['complete', 'set_debug_function'] class ParserError(LookupError): @@ -73,10 +73,12 @@ class File(object): :type is_first: str :return: The list of names and an is_finished param. :rtype: (list, bool) + + :raises: ParserError """ def get_char(): - char = self._relevant_temp[-1] - self._relevant_temp = self._relevant_temp[:-1] + self._relevant_temp, char = self._relevant_temp[:-1], \ + self._relevant_temp[-1] return char whitespace = [' ', '\n', '\r', '\\'] @@ -103,8 +105,9 @@ class File(object): if char == '.': if not is_first and not name: - raise ParserError('No name after point: %s' - % self._relevant_temp) + raise ParserError('No name after point (@%s): %s' + % (self._row_temp, + self._relevant_temp + char)) break elif char in whitespace: if is_word(name[0]): @@ -112,6 +115,7 @@ class File(object): elif char in close_brackets: # TODO strings are not looked at here, they are dangerous! # handle them! + # TODO handle comments if force_no_brackets: is_finished = True break @@ -134,6 +138,7 @@ class File(object): if level == 0: break elif is_word(char): + # TODO handle strings -> "asdf".join([1,2]) name = char + name force_no_brackets = True else: @@ -144,7 +149,6 @@ class File(object): self._row_temp = self.row self._relevant_temp = '' fetch_line(True) - print self._relevant_temp names = [] is_finished = False @@ -171,13 +175,93 @@ def complete(source, row, column, file_callback=None): """ row = 84 column = 17 + + row = 140 + column = 2 f = File(source=source, row=row) + scope = f.parser.user_scope print print - print f.parser.user_scope + print scope print f.parser.user_scope.get_simple_for_line(row) - print f.get_row_path(column) + try: + path = f.get_row_path(column) + except ParserError as e: + path = [] + dbg(e) - return f.parser.user_scope.get_set_vars() + result = [] + if path: + + name = path.pop() + if path: + scopes = follow_path(scope, tuple(path)) + + dbg('possible scopes', scopes) + compl = [] + for s in scopes: + compl += s.get_defined_names() + + dbg('possible-compl', compl) + + # make a partial comparison, because the other options have to + # be returned as well. + result = [c for c in compl if name in c.names[-1]] + + return result + + +def get_names_for_scope(scope): + """ Get all completions possible for the current scope. """ + comp = [] + start_scope = scope + while scope: + # class variables/functions are only availabe + if not isinstance(scope, parsing.Class) or scope == start_scope: + comp += scope.get_set_vars() + scope = scope.parent + return comp + +def follow_path(scope, path): + """ + Follow a path of python names. + recursive! + :returns: list(Scope) + """ + comp = get_names_for_scope(scope) + print path, comp + + path = list(path) + name = path.pop() + scopes = [] + + # make the full comparison, because the names have to match exactly + comp = [c for c in comp if [name] == list(c.names)] + # TODO differentiate between the different comp input (some are overridden) + for c in comp: + p_class = c.parent.__class__ + if p_class == parsing.Class or p_class == parsing.Scope: + scopes.append(c.parent) + #elif p_class == parsing.Function: + else: + print 'error follow_path:', p_class + if path: + for s in tuple(scopes): + scopes += follow_path(s, tuple(path)) + return set(scopes) + + +def dbg(*args): + if debug_function: + debug_function(*args) + + +def set_debug_function(func): + global debug_function + debug_function = func + parsing.debug_function = func + + +debug_function = None diff --git a/parsing.py b/parsing.py index 5287a09e..0328e549 100644 --- a/parsing.py +++ b/parsing.py @@ -110,7 +110,7 @@ class Scope(Simple): index = index1 if index1 < index2 and index1 > -1 else index2 prefix = string[:index] d = string[index:] - print 'docstr', d, prefix + dbg('add_docstr', d, prefix) # now clean docstr d = d.replace('\n', ' ') @@ -187,6 +187,9 @@ class Scope(Simple): return n + def get_defined_names(self): + return [n for n in self.get_set_vars() if len(n) == 1] + def is_empty(self): """ :return: True if there are no subscopes, imports and statements. @@ -233,7 +236,10 @@ class Class(Scope): def __init__(self, name, supers, indent, line_nr, docstr=''): super(Class, self).__init__(indent, line_nr, docstr) self.name = name + name.parent = self self.supers = supers + for s in self.supers: + s.parent = self self.decorators = [] def get_code(self, first_indent=False, indention=" "): @@ -248,7 +254,7 @@ class Class(Scope): str += "pass\n" return str - def get_set_vars(self): + def get_instance_set_vars(self): n = [] for s in self.subscopes: try: @@ -424,6 +430,10 @@ class Import(Simple): return [self] return [self.alias] if self.alias else [self.namespace] + def __repr__(self): + return "<%s: %s@%s>" % \ + (self.__class__.__name__, self.get_code(), self.line_nr) + class Statement(Simple): """ @@ -497,6 +507,9 @@ class Name(Simple): return "<%s: %s@%s>" % \ (self.__class__.__name__, self.get_code(), self.line_nr) + def __len__(self): + return len(self.names) + class PyFuzzyParser(object): """ @@ -667,8 +680,8 @@ class PyFuzzyParser(object): start_line = self.line_nr token_type, cname, ind = self.next() if token_type != tokenize.NAME: - print "class: syntax error - token is not a name@%s (%s: %s)" \ - % (self.line_nr, tokenize.tok_name[token_type], cname) + dbg("class: syntax error - token is not a name@%s (%s: %s)" \ + % (self.line_nr, tokenize.tok_name[token_type], cname)) return None cname = Name([cname], ind, self.line_nr, self.line_nr) @@ -820,8 +833,8 @@ class PyFuzzyParser(object): type, tok, position, dummy, self.parserline = self.gen.next() (self.line_nr, indent) = position if self.line_nr == self.user_line: - print 'user scope found [%s] =%s' % \ - (self.parserline.replace('\n', ''), repr(self.scope)) + dbg('user scope found [%s] =%s' % \ + (self.parserline.replace('\n', ''), repr(self.scope))) self.user_scope = self.scope self.last_token = self.current self.current = (type, tok, indent) @@ -854,7 +867,7 @@ class PyFuzzyParser(object): % (tok, token_type, indent)) while token_type == tokenize.DEDENT and self.scope != self.top: - print 'dedent', self.scope + dbg('dedent', self.scope) token_type, tok, indent = self.next() if indent <= self.scope.indent: self.scope.line_end = self.line_nr @@ -866,8 +879,8 @@ class PyFuzzyParser(object): while indent <= self.scope.indent \ and token_type in [tokenize.NAME] \ and self.scope != self.top: - print 'syntax_err, dedent @%s - %s<=%s', \ - (self.line_nr, indent, self.scope.indent) + dbg( 'syntax_err, dedent @%s - %s<=%s', \ + (self.line_nr, indent, self.scope.indent)) self.scope.line_end = self.line_nr self.scope = self.scope.parent @@ -952,7 +965,7 @@ class PyFuzzyParser(object): stmt, tok = self._parse_statement(self.current) if stmt: self.scope.add_statement(stmt) - print 'global_vars', stmt.used_vars + dbg('global_vars', stmt.used_vars) for name in stmt.used_vars: # add the global to the top, because there it is # important. @@ -986,7 +999,6 @@ class PyFuzzyParser(object): def dbg(*args): - global debug_function if debug_function: debug_function(*args) diff --git a/test.py b/test.py index 0f2019a2..619cf65f 100644 --- a/test.py +++ b/test.py @@ -3,7 +3,7 @@ # test comment import datetime -from token import * +#from token import * from time import sleep from token import OP as OP_TEST, INDENT as INDENT_TEST @@ -124,3 +124,17 @@ def flow_test(a): if True or a: print a + +# completion +import time +class c1(): + c2 = 1 + def c3(self): + import time as c4 + c5 = 3 + + + + +c1.\ +c