Commits

Anonymous committed f1e9f00

Fixing LogicalLinesFinder and inner indentations

Comments (0)

Files changed (6)

docs/dev/workingon.txt

 Next/Prev Scope
 ===============
 
-* `Scopes.prev()` not efficient
+- `Scopes.prev()` not efficient
+- Fixing `LogicalLineFinder.get_logical_line()`
 
 * Not scanning whole scopes in automatic SOI; only changed lines
 * Sorting methods; Move method up/down

rope/base/codeanalyze.py

         self.lines = lines
 
     def get_logical_line_in(self, line_number):
-        block_start = get_block_start(
-            self.lines, line_number,
-            count_line_indents(self.lines.get_line(line_number)))
+        indents = count_line_indents(self.lines.get_line(line_number))
+        tries = 0
+        while True:
+            block_start = get_block_start(
+                self.lines, line_number, indents)
+            try:
+                return self._logical_line_in(block_start, line_number)
+            except IndentationError, e:
+                tries += 1
+                if tries == 5:
+                    raise e
+                lineno = e.lineno + block_start - 1
+                indents = count_line_indents(self.lines.get_line(lineno))
+
+    def _logical_line_in(self, block_start, line_number):
         readline = LinesToReadline(self.lines, block_start)
-        last_line_start = block_start
+        shifted = line_number - block_start + 1
+        region = self._calculate_logical(readline, shifted)
+        start = self._get_first_non_blank(region[0] + block_start - 1)
+        if region[1] is None:
+            end = self.lines.end()
+        else:
+            end = region[1] + block_start - 1
+        return start, end
+
+    def _calculate_logical(self, readline, line_number):
+        last_line_start = 1
         for current in tokenize.generate_tokens(readline):
-            current_lineno = current[2][0] + block_start - 1
+            current_lineno = current[2][0]
             if current[0] == token.NEWLINE:
                 if current_lineno >= line_number:
-                    return (self._get_first_non_empty_line(last_line_start),
-                            current_lineno)
+                    return (last_line_start, current_lineno)
                 last_line_start = current_lineno + 1
-        return (last_line_start, self.lines.length())
+        return (last_line_start, None)
 
-    def _get_first_non_empty_line(self, line_number):
+    def _get_first_non_blank(self, line_number):
         current = line_number
-        while current <= self.lines.length():
-            line = self.lines.get_line(current)
-            if line.strip() != '' and not line.startswith('#'):
+        while current < self.lines.length():
+            line = self.lines.get_line(current).strip()
+            if line != '' and not line.startswith('#'):
                 return current
             current += 1
         return current

rope/base/evaluate.py

                 pyobject = self._get_object_for_node(stmt)
                 objects.append(pyobject)
         else:
-            objects.append(self._get_object_for_node(node.nodes[0]))
+            objects.append(self._get_object_for_node(node.elts[0]))
         self.result = rope.base.pynames.UnboundName(
             pyobject=rope.base.builtins.get_tuple(*objects))
 

rope/ide/movements.py

 
     def __init__(self, source):
         self.source = source
-        self.pattern = re.compile(r'^\s*(def|class)\s', re.M)
+        self.pattern = re.compile(r'^[ \t]*(def|class)\s', re.M)
+        self.matches = None
 
     def next(self, offset):
         match = self.pattern.search(self.source, offset)
         return offset + 1
 
     def prev(self, offset):
-        matches = list(self.pattern.finditer(self.source, 0, offset))
-        if matches:
-            start = matches[-1].start()
+        if self.matches is None:
+            self.matches = list(self.pattern.finditer(self.source))
+        prev_match = None
+        for match in self.matches:
+            if match.start() <= offset:
+                prev_match = match
+            else:
+                break
+        if prev_match is not None:
+            start = prev_match.start()
             if self.source[start] == '\n':
                 start += 1
+            if self.source[start:offset].strip() == '':
+                return self.prev(prev_match.start() - 1)
             return _next_char(self.source, start)
         return 0
 

ropetest/codeanalyzetest.py

     def tearDown(self):
         super(LogicalLineFinderTest, self).tearDown()
 
-    def _get_logical_line_finder(self, code):
-        return LogicalLineFinder(SourceLinesAdapter(code))
-
     def test_normal_lines(self):
         code = 'a_var = 10'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((1, 1), line_finder.get_logical_line_in(1))
 
     def test_normal_lines2(self):
         code = 'another = 10\na_var = 20\n'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((1, 1), line_finder.get_logical_line_in(1))
         self.assertEquals((2, 2), line_finder.get_logical_line_in(2))
 
     def test_implicit_continuation(self):
         code = 'a_var = 3 + \\\n    4 + \\\n    5'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((1, 3), line_finder.get_logical_line_in(2))
 
     def test_explicit_continuation(self):
         code = 'print 2\na_var = (3 + \n    4, \n    5)\n'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((2, 4), line_finder.get_logical_line_in(2))
 
     def test_explicit_continuation_comments(self):
         code = '#\na_var = 3\n'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((2, 2), line_finder.get_logical_line_in(2))
 
     def test_multiple_indented_ifs(self):
         code = 'if True:\n    if True:\n        if True:\n            pass\n    a = 10\n'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((5, 5), line_finder.get_logical_line_in(5))
 
     def test_list_comprehensions_and_fors(self):
         code = 'a_list = [i\n    for i in range(10)]\n'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((1, 2), line_finder.get_logical_line_in(2))
 
     def test_generator_expressions_and_fors(self):
         code = 'a_list = (i\n    for i in range(10))\n'
-        line_finder = self._get_logical_line_finder(code)
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
         self.assertEquals((1, 2), line_finder.get_logical_line_in(2))
 
     def test_fors_and_block_start(self):
         code = 'l = range(10)\nfor i in l:\n    print i\n'
-        self.assertEquals(2, get_block_start(SourceLinesAdapter(code),2 ))
+        self.assertEquals(2, get_block_start(SourceLinesAdapter(code), 2))
+
+    def test_problems_with_inner_indentations(self):
+        code = 'if True:\n    if True:\n        if True:\n            pass\n' \
+               '    a = \\\n        1\n'
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
+        self.assertEquals((5, 6), line_finder.get_logical_line_in(6))
+
+    def test_problems_with_inner_indentations2(self):
+        code = 'if True:\n    if True:\n        pass\n' \
+               'a = 1\n'
+        line_finder = LogicalLineFinder(SourceLinesAdapter(code))
+        self.assertEquals((4, 4), line_finder.get_logical_line_in(4))
 
 
 def suite():

ropetest/ide/movementstest.py

         statements = movements.Statements(code)
         self.assertEquals(code.index('a'), statements.prev(len(code)))
 
+    def xxx_test_prev_statement_and_comments(self):
+        code = 'a = 1\n# ccc\nb = 1\n'
+        statements = movements.Statements(code)
+        self.assertEquals(0, statements.prev(code.index('b')))
+
 
 class ScopesTest(unittest.TestCase):
 
         self.assertEquals(0, scopes.prev(10))
         self.assertEquals(0, scopes.prev(len(code)))
 
-    def test_next_on_functions(self):
+    def test_prev_on_functions(self):
         code = 'def f():\n    pass\n\ndef g():\n    pass\n'
         scopes = movements.Scopes(code)
         self.assertEquals(0, scopes.prev(code.index('()')))
         self.assertEquals(code.index('def g'), scopes.prev(len(code)))
 
+    def test_prev_on_indented_functions(self):
+        code = 'class A(object):\n    def f():\n        pass\n\n\n' \
+               '    def g():\n        pass\n'
+        scopes = movements.Scopes(code)
+        self.assertEquals(code.index('def f'),
+                          scopes.prev(code.index('def g')))
+
 
 def suite():
     result = unittest.TestSuite()