Commits

Ali Gholami Rudi  committed dac5125

Adding rope.codeassist.Template

  • Participants
  • Parent commits ca05d54

Comments (0)

Files changed (4)

File docs/workingon.txt

 *** Proposing templates in auto-completion proposals @ 1 ***
 
-- Normal templates
+* Machanisms for getting and editing and setting template variables
 
-* Machanisms for getting and editing and setting template variables
+- Multiple variables
+- substitute
+- Underlined variables
+- $$
+- ${name}${age}
+- The same variable many times
+- ${cursor}
+- [^$]$[^{$] in template
+- get_cursor_location
+- What if templates does not have ${cursor}
+? Defaults for templates
+
 * Showing templates in completion dialog
-? Variabled templates; ${variable}
-? Use ${cursor} for setting the place of curser when necessary
 * Builtin templates
 
-* Showing active editor as the last entry in the change editor dialog?
+?? Ignoring string contents while indenting
+?? Next/prev word should stop consider underline and capitals as spaces
+?? Better change editor
+?? GUI testing redux
 ? Show the type of the completion in the codeassist dialog
 ? From-import might cache module global variables
 ? Directories should contain __init__.py to be packages in codeassist

File rope/codeassist.py

 import compiler
 import inspect
 import __builtin__
+import re
 
 from rope.exceptions import RopeException
 
         self.definition = definition
 
 
+class Template(object):
+
+    def __init__(self, template):
+        self.template = template
+        self.var_pattern = re.compile(r'((?<=[^\$])|^)\${(?P<variable>[a-zA-Z][\w]*)}')
+
+    def variables(self):
+        '''Returns the list of variables sorted by their order of occurence in the template'''
+        result = []
+        for match in self.var_pattern.finditer(self.template):
+            new_var = match.group('variable')
+            if new_var not in result and new_var != 'cursor':
+                result.append(new_var)
+        return result
+    
+    def _substitute(self, input_string, mapping):
+        import string
+        single_dollar = re.compile('((?<=[^\$])|^)\$((?=[^{\$])|$)')
+        template = single_dollar.sub('$$', input_string)
+        t = string.Template(template)
+        return t.substitute(mapping, cursor='')
+
+    def substitute(self, mapping):
+        return self._substitute(self.template, mapping)
+
+    def get_cursor_location(self, mapping):
+        cursor_index = len(self.template)
+        for match in self.var_pattern.finditer(self.template):
+            new_var = match.group('variable')
+            if new_var == 'cursor':
+                cursor_index = match.start('variable') - 2
+        new_template = self.template[0:cursor_index]
+        start = len(self._substitute(new_template, mapping))
+        return start
+
+
 class Proposals(object):
     """A CodeAssist result.
     
         return indents
 
 
-    def _get_all_completions(self, global_scope, lines, lineno):
+    def _get_code_completions(self, source_code, offset, starting):
+        lines = source_code.split('\n')
+        current_pos = 0
+        lineno = 0
+        while current_pos + len(lines[lineno]) < offset:
+            current_pos += len(lines[lineno]) + 1
+            lineno += 1
+        self._comment_current_statement(lines, lineno)
+        source_code = '\n'.join(lines)
+        try:
+            code_ast = compiler.parse(source_code)
+        except SyntaxError, e:
+            raise RopeSyntaxError(e)
+        visitor = _GlobalScopeVisitor(self.project, starting)
+        compiler.walk(code_ast, visitor)
         result = {}
-        current_scope = global_scope
+        current_scope = visitor.scope
         current_indents = self._get_line_indents(lines, lineno)
         while current_scope is not None and \
               self._get_line_indents(lines, current_scope.lineno) <= current_indents:
 
     def assist(self, source_code, offset):
         if offset > len(source_code):
-            return []
+            return Proposals([], [], 0, 0)
         starting_offset = self._find_starting_offset(source_code, offset)
         starting = source_code[starting_offset:offset]
-        lines = source_code.split('\n')
-        current_pos = 0
-        lineno = 0
-        while current_pos + len(lines[lineno]) < offset:
-            current_pos += len(lines[lineno]) + 1
-            lineno += 1
-        self._comment_current_statement(lines, lineno)
-        commented_source_code = '\n'.join(lines)
-        try:
-            code_ast = compiler.parse(commented_source_code)
-        except SyntaxError, e:
-            raise RopeSyntaxError(e)
-        visitor = _GlobalScopeVisitor(self.project, starting)
-        compiler.walk(code_ast, visitor)
-        result = self._get_all_completions(visitor.scope, lines, lineno)
+        completions = self._get_code_completions(source_code, offset, starting)
+        templates = []
         if len(starting) > 0:
-            result.update(self._get_matching_builtins(starting))
-            result.update(self._get_matching_keywords(starting))
-        template_proposals = self._get_template_proposals(starting)
-        return Proposals(result.values(), template_proposals, starting_offset, offset)
+            completions.update(self._get_matching_builtins(starting))
+            completions.update(self._get_matching_keywords(starting))
+            templates = self._get_template_proposals(starting)
+        return Proposals(completions.values(), templates, starting_offset, offset)
 

File ropetest/codeassisttest.py

 import os
 import unittest
 
-from rope.codeassist import CodeAssist, RopeSyntaxError, CompletionProposal
+from rope.codeassist import CodeAssist, RopeSyntaxError, CompletionProposal, Template
 from rope.project import Project
 
 def _remove_recursively(file):
         self.assert_completion_in_result('nestedmod', 'unknown', result)
 
 
+class TemplateHelperTest(unittest.TestCase):
+
+    def test_template_get_variables(self):
+        template = Template('Name = ${name}')
+        self.assertEquals(['name'], template.variables())
+
+    def test_template_get_variables_multiple_variables(self):
+        template = Template('Name = ${name}\nAge = ${age}\n')
+        self.assertEquals(['name', 'age'], template.variables())
+
+    def test_substitution(self):
+        template = Template('Name = ${name}\nAge = ${age}\n')
+        self.assertEquals('Name = Ali\nAge = 20\n', 
+                          template.substitute({'name': 'Ali', 'age': '20'}))
+
+    def test_underlined_variables(self):
+        template = Template('Name = ${name_var}')
+        self.assertEquals(['name_var'], template.variables())
+        self.assertEquals('Name = Ali', template.substitute({'name_var': 'Ali'}))
+
+    def test_unmapped_variable(self):
+        template = Template('Name = ${name}')
+        try:
+            template.substitute({})
+            self.fail('Expected keyError')
+        except KeyError:
+            pass
+
+    def test_double_dollar_sign(self):
+        template = Template('Name = $${name}')
+        self.assertEquals([], template.variables())
+        self.assertEquals('Name = ${name}', template.substitute({'name': 'Ali'}))
+
+    def test_untemplate_dollar_signs(self):
+        template = Template('$name = ${value}')
+        self.assertEquals(['value'], template.variables())
+        self.assertEquals('$name = Ali', template.substitute({'value': 'Ali'}))
+
+    def test_template_get_variables_multiple_variables2(self):
+        template = Template('Name = ${name}${age}\n')
+        self.assertEquals(['name', 'age'], template.variables())
+
+    def test_template_get_variables_start_of_the_string(self):
+        template = Template('${name}\n')
+        self.assertEquals(['name'], template.variables())
+
+    def test_the_same_variable_many_time(self):
+        template = Template("Today is ${today}, the day after ${today} is ${tomorrow}")
+        self.assertEquals(['today', 'tomorrow'], template.variables())
+        self.assertEquals("Today is 26th, the day after 26th is 27th",
+                         template.substitute({'today': '26th', 'tomorrow': '27th'}))
+
+    def test_cursor_in_templates(self):
+        template = Template('My name is ${name}${cursor}.')
+        self.assertEquals(['name'], template.variables())
+        self.assertEquals('My name is Ali.', template.substitute({'name': 'Ali'}))
+
+    def test_get_cursor_location(self):
+        template = Template('My name is ${name}${cursor}.')
+        self.assertEquals('My name is Ali.', template.substitute({'name': 'Ali'}))
+        self.assertEquals(14, template.get_cursor_location({'name': 'Ali'}))
+
+    def test_get_cursor_location_with_no_cursor(self):
+        template = Template('My name is ${name}.')
+        self.assertEquals('My name is Ali.', template.substitute({'name': 'Ali'}))
+        self.assertEquals(15, template.get_cursor_location({'name': 'Ali'}))
+
+
+def suite():
+    result = unittest.TestSuite()
+    result.addTests(unittest.makeSuite(CodeAssistTest))
+    result.addTests(unittest.makeSuite(CodeAssistInProjectsTest))
+    result.addTests(unittest.makeSuite(TemplateHelperTest))
+    return result
+
 if __name__ == '__main__':
     unittest.main()
     result.addTests(unittest.makeSuite(ropetest.projecttest.TestPythonFileRunner))
     result.addTests(unittest.makeSuite(ropetest.highlighttest.HighlightTest))
     result.addTests(unittest.makeSuite(ropetest.indentertest.PythonCodeIndenterTest))
-    result.addTests(unittest.makeSuite(ropetest.codeassisttest.CodeAssistTest))
-    result.addTests(unittest.makeSuite(ropetest.codeassisttest.CodeAssistInProjectsTest))
+    result.addTests(ropetest.codeassisttest.suite())
     result.addTests(unittest.makeSuite(ropetest.statusbartest.StatusBarTest))
     runner = unittest.TextTestRunner()
     runner.run(result)