diff --git a/functions.py b/functions.py index a07f8f2c..0eedb12e 100644 --- a/functions.py +++ b/functions.py @@ -1,5 +1,14 @@ import parsing +import re + +__all__ = ['complete'] + + +class ParserError(LookupError): + """ The exception that is thrown if some handmade parser fails. """ + pass + class File(object): """ @@ -18,6 +27,10 @@ class File(object): self.row = row self.line_cache = None + # this two are only used, because there is no nonlocal in Python 2 + self._row_temp = None + self._relevant_temp = None + if not self.module_name and not self.source: raise AttributeError("Submit a module name or the source code") elif self.module_name: @@ -33,12 +46,117 @@ class File(object): self.line_cache = self.source.split('\n') if 1 <= line <= len(self.line_cache): - return self.line_cache[line-1] + return self.line_cache[line - 1] else: return None + def get_row_path(self, column): + """ Get the path under the cursor. """ + def fetch_line(with_column=False): + line = self.get_line(self._row_temp) + if with_column: + self._relevant_temp = line[:column - 1] + else: + self._relevant_temp += line + ' ' + self._relevant_temp + while self._row_temp > 1: + self._row_temp -= 1 + last_line = self.get_line(self._row_temp) + if last_line and last_line[-1] == '\\': + self._relevant_temp = last_line[:-1] + self._relevant_temp + else: + break -def complete(source, row, colum, file_callback=None): + def fetch_name(is_first): + """ + :param is_first: This means, that there can be a point \ + (which is a name separator) directly. There is no need for a name. + :type is_first: str + :return: The list of names and an is_finished param. + :rtype: (list, bool) + """ + def get_char(): + char = self._relevant_temp[-1] + self._relevant_temp = self._relevant_temp[:-1] + return char + + whitespace = [' ', '\n', '\r', '\\'] + open_brackets = ['(', '[', '{'] + close_brackets = [')', ']', '}'] + is_word = lambda char: re.search('\w', char) + name = '' + force_point = False + force_no_brackets = False + is_finished = False + while True: + try: + char = get_char() + except IndexError: + is_finished = True + break + + if force_point: + if char in whitespace: + continue + elif char != '.': + is_finished = True + break + + if char == '.': + if not is_first and not name: + raise ParserError('No name after point: %s' + % self._relevant_temp) + break + elif char in whitespace: + if is_word(name[0]): + force_point = True + elif char in close_brackets: + # TODO strings are not looked at here, they are dangerous! + # handle them! + if force_no_brackets: + is_finished = True + break + level = 1 + name = char + name + while True: + try: + char = get_char() + except IndexError: + while not self._relevant_temp: + # TODO can raise an exception, when there are + # no more lines + fetch_line() + char = get_char() + if char in close_brackets: + level += 1 + elif char in open_brackets: + level -= 1 + name = char + name + if level == 0: + break + elif is_word(char): + name = char + name + force_no_brackets = True + else: + is_finished = True + break + return name, is_finished + + self._row_temp = self.row + self._relevant_temp = '' + fetch_line(True) + print self._relevant_temp + + names = [] + is_finished = False + while not is_finished: + # do this not with tokenize, because it might fail + # due to single line processing + name, is_finished = fetch_name(not bool(names)) + names.insert(0, name) + return names + + +def complete(source, row, column, file_callback=None): """ An auto completer for python files. @@ -51,12 +169,15 @@ def complete(source, row, colum, file_callback=None): :return: list :rtype: list """ - row = 89 + row = 84 + column = 17 f = File(source=source, row=row) - print - print - print f.get_line(row) + print + print print f.parser.user_scope print f.parser.user_scope.get_simple_for_line(row) + + print f.get_row_path(column) + return f.parser.user_scope.get_set_vars() diff --git a/parsing.py b/parsing.py index 0c649bea..5287a09e 100644 --- a/parsing.py +++ b/parsing.py @@ -389,7 +389,7 @@ class Import(Simple): :param line_nr: Line number. :type line_nr: int - :param namespace: The import, as an array list of Name,\ + :param namespace: The import, as an array list of Name, \ e.g. ['datetime', 'time']. :type namespace: list :param alias: The alias of a namespace(valid in the current namespace). @@ -399,7 +399,8 @@ class Import(Simple): :param star: If a star is used -> from time import *. :type star: bool """ - def __init__(self, indent, line_nr, line_end, namespace, alias='', from_ns='', star=False): + def __init__(self, indent, line_nr, line_end, namespace, alias='', \ + from_ns='', star=False): super(Import, self).__init__(indent, line_nr, line_end) self.namespace = namespace self.alias = alias @@ -552,19 +553,25 @@ class PyFuzzyParser(object): """ A value list is a comma separated list. This is used for: >>> for a,b,self.c in enumerate(test) + + TODO there may be multiple "sub" value lists e.g. (a,(b,c)). """ value_list = [] if pre_used_token: token_type, tok, indent = pre_used_token - n, token_type, tok, start_indent, start_line = self._parsedotname(tok) + n, token_type, tok, start_indent, start_line = \ + self._parsedotname(tok) if n: - value_list.append(Name(n, start_indent, start_line, self.line_nr)) + temp = Name(n, start_indent, start_line, self.line_nr) + value_list.append() token_type, tok, indent = self.next() while tok != 'in' and token_type != tokenize.NEWLINE: - n, token_type, tok, start_indent, start_line = self._parsedotname(self.current) + n, token_type, tok, start_indent, start_line = \ + self._parsedotname(self.current) if n: - value_list.append(Name(n, start_indent, start_line, self.line_nr)) + temp = Name(n, start_indent, start_line, self.line_nr) + value_list.append(temp) if tok == 'in': break @@ -589,14 +596,17 @@ class PyFuzzyParser(object): """ imports = [] while True: - name, token_type, tok, start_indent, start_line = self._parsedotname() + name, token_type, tok, start_indent, start_line = \ + self._parsedotname() if not name: break name2 = None if tok == 'as': - name2, token_type, tok, start_indent2, start_line = self._parsedotname() + name2, token_type, tok, start_indent2, start_line = \ + self._parsedotname() name2 = Name(name2, start_indent2, start_line, self.line_nr) - imports.append((Name(name, start_indent, start_line, self.line_nr), name2)) + i = Name(name, start_indent, start_line, self.line_nr) + imports.append((i, name2)) while tok != "," and "\n" not in tok: token_type, tok, indent = self.next() if tok != ",": @@ -882,11 +892,13 @@ class PyFuzzyParser(object): # import stuff elif tok == 'import': imports = self._parseimportlist() - for mod, alias in imports: - self.scope.add_import(Import(indent, start_line, self.line_nr, mod, alias)) + for m, alias in imports: + i = Import(indent, start_line, self.line_nr, m, alias) + self.scope.add_import(i) freshscope = False elif tok == 'from': - mod, token_type, tok, start_indent, start_line2 = self._parsedotname() + mod, token_type, tok, start_indent, start_line2 = \ + self._parsedotname() if not mod or tok != "import": print "from: syntax error..." continue @@ -896,7 +908,8 @@ class PyFuzzyParser(object): star = name.names[0] == '*' if star: name = None - i = Import(indent, start_line, self.line_nr, name, alias, mod, star) + i = Import(indent, start_line, self.line_nr, name, + alias, mod, star) self.scope.add_import(i) freshscope = False #loops @@ -920,7 +933,7 @@ class PyFuzzyParser(object): if tok in added_breaks: # the except statement defines a var # this is only true for python 2 - path, token_type, tok, start_indent, start_line2= \ + path, token_type, tok, start_indent, start_line2 = \ self._parsedotname() n = Name(path, start_indent, start_line2, self.line_nr) statement.set_vars.append(n) diff --git a/test.py b/test.py index 3d8a9215..0f2019a2 100644 --- a/test.py +++ b/test.py @@ -79,6 +79,9 @@ def ass_test(a): # test strange statements [a,c] ; {1: a}; (1,); `a` result = int((a+b)*2) + result = s.a( + adsfa ) [].3.\ + a() global global_test return result