Commits

Anonymous committed 3d46bb8

HoldingScopeFinder gets a source code instead of lines

Comments (0)

Files changed (5)

-Refactoring
-===========
+Complete ``AClass(param).a_``
+=============================
+
+1. Find the statement
+2. Parse the statement
+3. Walk the statement
+
+Statements are: 
+1. Atoms
+2. Primaries and operators
+3. Simple statements
+4. Compound statements
+
+* WorkRangeFinder should return one string?
 
 * Names in strings or comments
 * Using ScopeNameFinder in _CodeCompletionCollector
 * Better editor changing dialog; use uihelpers module
 * Enhancing highlighting
 * Profiling to find the bottlenecks
-* Document features added since release 0.1
+* Document features added since 0.1 release
 * Add a simple tutorial
 * Better homepage
 
+import compiler
 
 class WordRangeFinder(object):
 
         while current_offset >= 0:
             current_offset = self._find_last_non_space_char(current_offset)
             if current_offset >= 0:
-                word_start = self._find_statement_start(current_offset)
                 word_start = self.find_word_start(current_offset)
                 result.append(self.source_code[word_start:current_offset + 1])
                 current_offset = word_start - 1
         result[-1] = self.get_word_at(offset)
         return result
 
+    def get_name_at(self, offset):
+        return self.source_code[self.find_word_start(offset):self.find_word_end(offset)]
+
+
 class HoldingScopeFinder(object):
 
-    def __init__(self, lines):
-        self.lines = lines
+    def __init__(self, source_code):
+        self.source_code = source_code
+        self.lines = SourceLinesAdapter(source_code)
     
     def get_indents(self, lineno):
         indents = 0
-        for char in self.lines[lineno - 1]:
+        for char in self.lines.get_line(lineno):
             if char == ' ':
                 indents += 1
             else:
     def get_location(self, offset):
         current_pos = 0
         lineno = 1
-        while current_pos + len(self.lines[lineno - 1]) < offset:
-            current_pos += len(self.lines[lineno - 1]) + 1
+        while current_pos + len(self.lines.get_line(lineno)) < offset:
+            current_pos += len(self.lines.get_line(lineno)) + 1
             lineno += 1
         return (lineno, offset - current_pos)
 
             current_scope = new_scope
         min_indents = line_indents
         for l in range(scopes[-1][0].get_lineno() + 1, lineno):
-            if self.lines[l - 1].strip() != '' and \
-               not self.lines[l - 1].strip().startswith('#'):
+            if self.lines.get_line(l).strip() != '' and \
+               not self.lines.get_line(l).strip().startswith('#'):
                 min_indents = min(min_indents, self.get_indents(l))
         while len(scopes) > 1 and min_indents <= scopes[-1][1]:
             scopes.pop()
         return scopes[-1][0]
 
 
+class _StatementEvaluator(object):
+
+    def __init__(self, scope):
+        self.scope = scope
+        self.result = None
+
+    def visitName(self, node):
+        self.result = self.scope.lookup(node.name)
+    
+    def visitGetattr(self, node):
+        pyname = _StatementEvaluator.get_statement_result(self.scope, node.expr)
+        if pyname is not None:
+            self.result = pyname.get_attributes().get(node.attrname, None)
+
+    @staticmethod
+    def get_statement_result(scope, node):
+        evaluator = _StatementEvaluator(scope)
+        compiler.walk(node, evaluator)
+        return evaluator.result
+
+
 class ScopeNameFinder(object):
     
     def __init__(self, source_code, module_scope):
         self.source_code = source_code
         self.module_scope = module_scope
         self.lines = source_code.split('\n')
-        self.scope_finder = HoldingScopeFinder(self.lines)
+        self.scope_finder = HoldingScopeFinder(source_code)
         self.word_finder = WordRangeFinder(source_code)
     
     def get_pyname_at(self, offset):
         return result
     
     def get_pyname_in_scope(self, holding_scope, name_list):
-        pyname = holding_scope.lookup(name_list[0])
-        if pyname is not None and len(name_list) > 1:
-            for name in name_list[1:]:
-                if name in pyname.get_attributes():
-                    pyname = pyname.get_attributes()[name]
-                else:
-                    pyname = None
-                    break
-        return pyname
+        ast = compiler.parse('.'.join(name_list))
+        result = _StatementEvaluator.get_statement_result(holding_scope, ast)
+        return result
 
 
 
         self.lines.append('\n')
 
     def _find_inner_holding_scope(self, base_scope):
-        scope_finder = HoldingScopeFinder(self.lines)
+        scope_finder = HoldingScopeFinder(self.source_code)
         return scope_finder.get_holding_scope(base_scope, self.lineno, self.current_indents)
 
     def _get_dotted_completions(self, module_scope, holding_scope):
         for match in pattern.finditer(source_code):
             if self._get_previous_char(source_code, match.start()) == '.':
                 continue
-            if pyname_finder.get_pyname_at(match.start() + 1) == old_pyname:
+            new_pyname = None
+            try:
+                new_pyname = pyname_finder.get_pyname_at(match.start() + 1)
+            except SyntaxError:
+                pass
+            if new_pyname == old_pyname:
                 result.append(source_code[last_modified_char:match.start()] + new_name)
                 last_modified_char = match.end()
         result.append(source_code[last_modified_char:])

ropetest/codeanalyzetest.py

         word_finder = WordRangeFinder('a_func(a_var)')
         self.assertEquals(['a_v'], word_finder.get_name_list_before(10))
 
+    def test_simple_names(self):
+        word_finder = WordRangeFinder('a_var = 10')
+        self.assertEquals('a_var', word_finder.get_name_at(3))
+
+    def test_function_calls(self):
+        word_finder = WordRangeFinder('sample_function()')
+        self.assertEquals('sample_function', word_finder.get_name_at(10))
+    
+    # TODO: make it pass
+    def xxx_test_attribute_accesses(self):
+        word_finder = WordRangeFinder('a_var.an_attr')
+        self.assertEquals('a_var.an_attr', word_finder.get_name_at(10))
+    
+    # TODO: make it pass
+    def xxx_test_strings(self):
+        word_finder = WordRangeFinder('"a string".split()')
+        self.assertEquals(['"a string"', 'split'], word_finder.get_name_list_at(14))
+
+    # TODO: make it pass
+    def xxx_test_function_calls(self):
+        word_finder = WordRangeFinder('file("test.txt").read()')
+        self.assertEquals(['file("afile.txt")', 'read'], word_finder.get_name_list_at(18))
+
 
 def suite():
     result = unittest.TestSuite()