Commits

Anonymous committed af773ee

Completing function keyword arguments when calling
Registering templates in ~/.rope

  • Participants
  • Parent commits f9fcbdd

Comments (0)

Files changed (21)

File docs/dev/done.txt

 ===========
 
 
+- Registering templates in ``~/.rope`` : February 26, 2007
+
+
+- Auto-completing function keyword arguments when calling : February 26, 2007
+
+
 - Better status bar : February 23, 2007
 
 

File docs/dev/issues.txt

 * DOI
 * SOI
 * Implicit interfaces
-* Changing strucutre refactorings; for example: ``a = b -> a.set(b)``
+* Changing strucutre refactorings; like ``a = b`` -> ``a.set(b)``
 * Advanced refactorings
 
 
 * Indexing source files for faster occurrence finding
 * Faster module running
 * Saving hard to compute information like class hierarchies to files
-* Using a modification of `compiler` AST for simplifying refactorings
 * Finding available refactorings
 
 
-Change Occurrences
-==================
+Marking Errors And Warnings In GUI
+==================================
 
-* How specify a region in a file
-
-  * Always use the current scope
-  * Using physical scopes; The mark should be on a defined object
-  * Store the last place of mark for each editor;  the region
-    between the two last marks is the region
-  * ``C-u # C-r r o`` where # is the number of scopes outside
-    current scope
+* Syntax error
+* Multiple definition warning
+* Codetag
+* Adding `show_errors`, `show_warnings` and `show_tags` config
 
 
 Checking Variable And Function Usages In Their Defining Scopes

File docs/dev/stories.txt

 IDE And UI Stories
 ==================
 
-* Showing syntactical errors
 * Editor folding
 * Variable indentation and tab size
 * Having multiple clipboards
   * Inserting or overwriting
   * Inserting common prefixes
 
-* Showing function signature when calling
 * ReST
 
   * highlighting inside pydocs
   * outlines
   * codeassists
 
-* Auto-completing function parameter names when calling
-* Code contexts; context_finder.get_context(text, offset)
-  This can be used when proposing code assists.
-* Formating code
 * Show PyDoc reST highlighting
 * Enhancing module running
 
   * Customizing CWD and parameters
   * Running last run
 
+* Formating code
+* Showing function signature when calling
+
 
 Stories
 =======
 * Moving initialization to constructor in local variable to field
 
 
+* Inlining a single occurrence
+
+
+* Performing import actions only on one import statement
+
+
 * Adding an option to remove old parameter in inline argument default
 
 
 * Extract class
 
 
+* Showing the first syntax error
+
+
 > Public Release 0.5m2 : March 4, 2007
-
-
-* Inlining a single occurrence
-
-
-* Performing import actions only on one import statement

File docs/dev/workingon.txt

 Small Stories
 =============
 
-- Change occurrences
+- Enhancing `CodeAssist`
 
-  - Find file and entering when nothing is selected
-  - Renaming `FilteredOccurrenceFinder` to `FilteredFinder`
-  - Considering ``*`` to be block starts in reST mode
-  - `get_old_name()` should return a primary
-  - Only calls
-  - Reads and writes
+  - ``a_func(${cursor}``
+  - ``a_func(1, ${cursor}``
+  - What if it is not a function
+  - Not proposing ``**`` and ``*`` args
+  - New `parameter_keyword` proposal kind
+  - Better order for `parameter_keyword` when sorting
+  - Auto-completing function parameter names when calling
+  - Not mistaking over ``return (x, y)`` or ``print (x, y)``
+  - Code assist on ``function${cursor}(...)``
 
-- ``--  docs/dev/workingon.txt  (rest)``
+- Better file browsing in large projects
+- Not recompiling current buffer for each codeassist
+- Changing `uihelpers.TreeView` to use `uihelpers.EnhancedList`
+- Page up and page down in lists
+- Adding codeassist templates in ``~/.rope``
+- Adding `rope.ui.prefs` module
 
-* A completing text widget for dialogs; `Completing(name, list, handle)`
-* Version control commands support; commiting, updating, ...
+* Faster find file in large projects
+* Searching inside `EnhancedList`\s
+* Supporting templates in other modes, too
 * Moving static and class methods
 * Inlining methods with decorators
 * Decorators and method refactorings
 * Unifying ``x.get_type() == get_base_type('y')``, ``isinstance(x, PyY)``
-* Finding unused variables
 * Document how to move fields
-* Adding codeassist templates in ``~/.rope``? Should we access
-  codeassist directly or use core
 
 
 Remaining Small Stories
 
 UI and IDE:
 
+* A completing text widget for dialogs; `Completing(name, list, handle)`
 * Goto definition for ``"# comment.\na_var"``
 * Showing properties in quick outline
 * Changing ``C-a C-a`` to move to the first character in the line

File docs/user/overview.txt

 * T: ``template``
 * K: ``keyword``
 * B: ``built-in``
+* P: ``parameter_keyword``
 
 In the second column if present:
 
 * I: ``imported``
 
 You can use ``main``, ``testcase``, ``hash``, ``eq`` and ``super``
-templates.  If you want to add more or edit these you can edit
-`rope.ide.codeassist.PythonCodeAssist._get_default_templates` method.
+templates.  If you want to add more you can edit your ``~/.rope``
+file. (For more information see the default ``~/.rope`` file.)
 
 
 Show PyDoc

File rope/base/codeanalyze.py

 
 import rope.base.pyobjects
 import rope.base.pynames
+from rope.base import pyobjects, pynames
 import rope.base.exceptions
 from rope.base import builtins
 from rope.base import evaluate
         """
         if offset == 0:
             return ('', '', 0)
-        word_start = self._find_atom_start(offset - 1)
-        real_start = self._find_primary_start(offset - 1)
+        end = offset - 1
+        word_start = self._find_atom_start(end)
+        real_start = self._find_primary_start(end)
         if self.source_code[word_start:offset].strip() == '':
-            word_start = offset
-        if self.source_code[real_start:offset].strip() == '':
-            real_start = offset
+            word_start = end
+        if self.source_code[real_start:word_start].strip() == '':
+            real_start = word_start
+        if real_start == word_start == end and not self._is_id_char(end):
+            return ('', '', offset)
         if real_start == word_start:
             return ('', self.source_code[word_start:offset], word_start)
         else:
-            if self.source_code[offset - 1] == '.':
-                return (self.source_code[real_start:offset - 1], '', offset)
+            if self.source_code[end] == '.':
+                return (self.source_code[real_start:end], '', offset)
             last_dot_position = word_start
             if self.source_code[word_start] != '.':
                 last_dot_position = self._find_last_non_space_char(word_start - 1)
             return False
         return True
 
-    def find_parens_start_from_inside(self, offset):
+    def is_on_function_call_keyword(self, offset, stop_searching=0):
+        current_offset = offset
+        if self._is_id_char(current_offset):
+            current_offset = self._find_word_start(current_offset) - 1
+        current_offset = self._find_last_non_space_char(current_offset)
+        if current_offset <= stop_searching or \
+           self.source_code[current_offset] not in '(,':
+            return False
+        parens_start = self.find_parens_start_from_inside(offset, stop_searching)
+        if stop_searching < parens_start:
+            return True
+        return False
+
+    def find_parens_start_from_inside(self, offset, stop_searching=0):
         current_offset = offset
         opens = 1
-        while current_offset > 0:
+        while current_offset > stop_searching:
             if self.source_code[current_offset] == '(':
                 opens -= 1
             if opens == 0:
         # function keyword parameter
         if self.word_finder.is_function_keyword_parameter(offset):
             keyword_name = self.word_finder.get_word_at(offset)
-            function_parens = self.word_finder.find_parens_start_from_inside(offset)
-            function_pyname = self.get_pyname_at(function_parens - 1)
-            if function_pyname is not None:
-                function_pyobject = function_pyname.get_object()
-                if function_pyobject.get_type() == \
-                   rope.base.pyobjects.get_base_type('Type'):
-                    function_pyobject = function_pyobject.get_attribute('__init__').get_object()
-                return function_pyobject.get_parameters().get(keyword_name, None)
+            pyobject = self.get_enclosing_function(offset)
+            if isinstance(pyobject, pyobjects.PyFunction):
+                return pyobject.get_parameters().get(keyword_name, None)
 
         # class body
         if self._is_defined_in_class_body(holding_scope, offset, lineno):
         result = self.get_pyname_in_scope(holding_scope, name)
         return result
 
+    def get_enclosing_function(self, offset):
+        function_parens = self.word_finder.find_parens_start_from_inside(offset)
+        try:
+            function_pyname = self.get_pyname_at(function_parens - 1)
+        except BadIdentifierError:
+            function_pyname = None
+        if function_pyname is not None:
+            pyobject = function_pyname.get_object()
+            if isinstance(pyobject, pyobjects.PyFunction):
+                return pyobject
+            elif pyobject.get_type() == pyobjects.get_base_type('Type') and \
+                 '__init__' in pyobject.get_attributes():
+                return pyobject.get_attribute('__init__').get_object()
+            elif '__call__' in pyobject.get_attributes():
+                return pyobject.get_attribute('__call__').get_object()
+        return None
+
     def _find_module(self, module_name):
         current_folder = None
         if self.module_scope.pyobject.get_resource():
         return rope.base.pynames.ImportedModule(
             self.module_scope.pyobject, module_name[dot_count:], dot_count)
 
-    def get_pyname_in_scope(self, holding_scope, name):
+    @staticmethod
+    def get_pyname_in_scope(holding_scope, name):
         #ast = compiler.parse(name)
         # parenthesizing for handling cases like 'a_var.\nattr'
         try:
         self.open_count = 0
         self.explicit_continuation = False
         self.open_parens = []
+        self._analyze()
 
     def _analyze_line(self, current_line_number):
         current_line = self.lines.get_line(current_line_number)
         else:
             self.explicit_continuation = False
 
-    def analyze(self):
+    def _analyze(self):
         last_statement = 1
         for current_line_number in range(get_block_start(self.lines,
                                                          self.lineno),

File rope/ide/codeassist.py

 import sys
 
 import rope.base.codeanalyze
-import rope.base.pyobjects
+from rope.base import pyobjects, pynames
 from rope.base.codeanalyze import (StatementRangeFinder, ArrayLinesAdapter,
                                    WordRangeFinder, ScopeNameFinder,
-                                   SourceLinesAdapter)
+                                   SourceLinesAdapter, get_pyname_at,
+                                   BadIdentifierError)
 from rope.base.exceptions import RopeError
 from rope.refactor import occurrences, functionutils
 
 
     The `kind` instance variable shows the kind of the proposal and
     can be ``global``, ``local``, ``builtin``, ``attribute``,
-    ``keyword``, and ``template``.
+    ``keyword``, ``parameter_keyword``, and ``template``.
 
     The `type` instance variable shows the type of the proposal and
     can be ``variable``, ``class``, ``function``, ``imported`` ,
         self.kind = kind
         self.type = type
 
+    def __str__(self):
+        return '%s (%s, %s)' % (self.name, self.kind, self.type)
+
+    def __repr__(self):
+        return str(self)
+
 
 class TemplateProposal(CodeAssistProposal):
     """A template proposal
     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"""
+        """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')
         self.project = project
         self.expression = expression
         self.starting = starting
+        self.offset = offset
         self.pycore = self.project.get_pycore()
+        self.unchanged_source = source_code
         self.lines = source_code.split('\n')
         self.source_code = source_code
         self.resource = resource
     def _comment_current_statement(self):
         range_finder = StatementRangeFinder(ArrayLinesAdapter(self.lines),
                                             self.lineno)
-        range_finder.analyze()
         start = range_finder.get_statement_start() - 1
         end = range_finder.get_block_end() - 1
         last_indents = self._get_line_indents(self.lines[start])
                     name, kind, self._get_pyname_type(pyname))
 
     def _get_pyname_type(self, pyname):
-        if isinstance(pyname, rope.base.pynames.AssignedName):
+        if isinstance(pyname, pynames.AssignedName):
             return 'variable'
-        if isinstance(pyname, rope.base.pynames.DefinedName):
+        if isinstance(pyname, pynames.DefinedName):
             pyobject = pyname.get_object()
-            if isinstance(pyobject, rope.base.pyobjects.PyFunction):
+            if isinstance(pyobject, pyobjects.PyFunction):
                 return 'function'
             else:
                 return 'class'
-        if isinstance(pyname, rope.base.pynames.ImportedName) or \
-           isinstance(pyname, rope.base.pynames.ImportedModule):
+        if isinstance(pyname, pynames.ImportedName) or \
+           isinstance(pyname, pynames.ImportedModule):
             return 'imported'
-        if isinstance(pyname, rope.base.pynames.ParameterName):
+        if isinstance(pyname, pynames.ParameterName):
             return 'parameter'
 
     def get_code_completions(self):
         try:
-            module_scope = self.pycore.get_string_scope(self.source_code,
-                                                        self.resource)
+            module_scope = get_pymodule(self.pycore, self.source_code,
+                                        self.resource).get_scope()
         except SyntaxError, e:
             raise RopeSyntaxError(e)
         result = {}
         if self.expression.strip() != '':
             result.update(self._get_dotted_completions(module_scope, inner_scope))
         else:
+            result.update(self._get_keyword_parameters(module_scope.pyobject, inner_scope))
             self._get_undotted_completions(inner_scope, result)
         return result
 
+    def _get_keyword_parameters(self, pymodule, scope):
+        offset = self.offset
+        if offset == 0:
+            return {}
+        word_finder = WordRangeFinder(self.unchanged_source)
+        lines = SourceLinesAdapter(self.unchanged_source)
+        lineno = lines.get_line_number(offset)
+        stop_line = StatementRangeFinder(lines, lineno).get_statement_start()
+        stop = lines.get_line_start(stop_line)
+        if word_finder.is_on_function_call_keyword(offset - 1, stop):
+            name_finder = ScopeNameFinder(pymodule)
+            function_parens = word_finder.find_parens_start_from_inside(offset - 1, stop)
+            primary = word_finder.get_primary_at(function_parens - 1)
+            try:
+                function_pyname = ScopeNameFinder.get_pyname_in_scope(scope, primary)
+            except BadIdentifierError, e:
+                return {}
+            if function_pyname is not None:
+                pyobject = function_pyname.get_object()
+                if isinstance(pyobject, pyobjects.PyFunction):
+                    pass
+                elif pyobject.get_type() == pyobjects.get_base_type('Type') and \
+                     '__init__' in pyobject.get_attributes():
+                    pyobject = pyobject.get_attribute('__init__').get_object()
+                elif '__call__' in pyobject.get_attributes():
+                    pyobject = pyobject.get_attribute('__call__').get_object()
+                if isinstance(pyobject, pyobjects.PyFunction):
+                    function_info = functionutils.DefinitionInfo.read(pyobject)
+                    result = {}
+                    for name, default in function_info.args_with_defaults:
+                        if name.startswith(self.starting):
+                            result[name + '='] = CompletionProposal(
+                                name + '=', 'parameter_keyword')
+                    return result
+        return {}
+
 
 class PythonCodeAssist(object):
 
         import keyword
         self.keywords = keyword.kwlist
         self.templates = []
-        self.templates.extend(self._get_default_templates())
+        self.templates.extend(PythonCodeAssist._get_default_templates())
 
     builtins = [str(name) for name in dir(__builtin__)
                 if not name.startswith('_')]
 
-    def _get_default_templates(self):
+    @staticmethod
+    def _get_default_templates():
+        templates = {}
+        templates['main'] = Template("if __name__ == '__main__':\n    ${cursor}\n")
+        test_case_template = \
+            ('import unittest\n\n'
+             'class ${class}(unittest.TestCase):\n\n'
+             '    def setUp(self):\n        super(${class}, self).setUp()\n\n'
+             '    def tearDown(self):\n        super(${class}, self).tearDown()\n\n'
+             '    def test_${aspect1}(self):\n        pass${cursor}\n\n\n'
+             'if __name__ == \'__main__\':\n'
+             '    unittest.main()\n')
+        templates['testcase'] = Template(test_case_template)
+        templates['hash'] = Template('\n    def __hash__(self):\n' +
+                                     '        return 1${cursor}\n')
+        templates['eq'] = Template('\n    def __eq__(self, obj):\n' +
+                                   '        ${cursor}return obj is self\n')
+        templates['super'] = Template('super(${class}, self)')
+        templates.update(PythonCodeAssist.default_templates)
         result = []
-        result.append(TemplateProposal('main', Template("if __name__ == '__main__':\n    ${cursor}\n")))
-        test_case_template = \
-            ("import unittest\n\n"
-             "class ${class}(unittest.TestCase):\n\n"
-             "    def setUp(self):\n        super(${class}, self).setUp()\n\n"
-             "    def tearDown(self):\n        super(${class}, self).tearDown()\n\n"
-             "    def test_${aspect1}(self):\n        pass${cursor}\n\n\n"
-             "if __name__ == '__main__':\n"
-             "    unittest.main()\n")
-        result.append(TemplateProposal('testcase', Template(test_case_template)))
-        result.append(TemplateProposal('hash', Template('\n    def __hash__(self):\n' + \
-                                                        '        return 1${cursor}\n')))
-        result.append(TemplateProposal('eq', Template('\n    def __eq__(self, obj):\n' + \
-                                                      '        ${cursor}return obj is self\n')))
-        result.append(TemplateProposal('super', Template('super(${class}, self)')))
+        for name, template in templates.items():
+            result.append(TemplateProposal(name, template))
         return result
 
+    default_templates = {}
+
+    @staticmethod
+    def add_default_template(name, definition):
+        PythonCodeAssist.default_templates[name] = Template(definition)
+
     def _find_starting_offset(self, source_code, offset):
         current_offset = offset - 1
         while current_offset >= 0 and (source_code[current_offset].isalnum() or
         return result
 
     def _get_code_completions(self, source_code, offset, expression, starting, resource):
-        collector = _CodeCompletionCollector(self.project, source_code,
-                                             offset, expression, starting, resource)
+        collector = _CodeCompletionCollector(self.project, source_code, offset,
+                                             expression, starting, resource)
         return collector.get_code_completions()
 
     def assist(self, source_code, offset, resource=None):
                                       offset, resource).get_definition_location()
 
     def get_doc(self, source_code, offset, resource=None):
-        pymodule = self.project.pycore.get_string_module(source_code, resource)
+        pymodule = get_pymodule(self.project.pycore, source_code, resource)
         scope_finder = ScopeNameFinder(pymodule)
         element = scope_finder.get_pyname_at(offset)
         if element is None:
             return None
         pyobject = element.get_object()
-        if isinstance(pyobject, rope.base.pyobjects.PyDefinedObject):
-            if pyobject.get_type() == rope.base.pyobjects.get_base_type('Function'):
+        if isinstance(pyobject, pyobjects.PyDefinedObject):
+            if pyobject.get_type() == pyobjects.get_base_type('Function'):
                 return _get_function_docstring(pyobject)
-            elif pyobject.get_type() == rope.base.pyobjects.get_base_type('Type'):
+            elif pyobject.get_type() == pyobjects.get_base_type('Type'):
                 return _get_class_docstring(pyobject)
             else:
                 return _trim_docstring(pyobject._get_ast().doc)
         return result
 
 
+def get_pymodule(pycore, source_code, resource):
+    if resource and resource.exists() and source_code == resource.read():
+        return pycore.resource_to_pyobject(resource)
+    return pycore.get_string_module(source_code, resource=resource)
+
 def _get_class_docstring(pyclass):
     node = pyclass._get_ast()
     doc = 'class %s\n\n' % node.name + _trim_docstring(node.doc)
 
     if '__init__' in pyclass.get_attributes():
         init = pyclass.get_attribute('__init__').get_object()
-        if isinstance(init, rope.base.pyobjects.PyDefinedObject):
+        if isinstance(init, pyobjects.PyDefinedObject):
             doc += '\n\n' + _get_function_docstring(init)
     return doc
 
     """The sample code from :PEP:`257`"""
     if not docstring:
         return ''
-    # Convert tabs to spaces (following the normal Python rules)
+    # Convert tabs to spaces (following normal Python rules)
     # and split into a list of lines:
     lines = docstring.expandtabs().splitlines()
     # Determine minimum indentation (first line doesn't count):
 
     def get_sorted_proposal_list(self):
         local_proposals = []
+        parameter_keyword_proposals = []
         global_proposals = []
         attribute_proposals = []
         others = []
                 local_proposals.append(proposal)
             elif proposal.kind == 'attribute':
                 attribute_proposals.append(proposal)
+            elif proposal.kind == 'parameter_keyword':
+                parameter_keyword_proposals.append(proposal)
             else:
                 others.append(proposal)
         template_proposals = self.proposals.templates
         local_proposals.sort(self._pyname_proposal_cmp)
+        parameter_keyword_proposals.sort(self._pyname_proposal_cmp)
         global_proposals.sort(self._pyname_proposal_cmp)
         attribute_proposals.sort(self._pyname_proposal_cmp)
         result = []
         result.extend(local_proposals)
+        result.extend(parameter_keyword_proposals)
         result.extend(global_proposals)
         result.extend(attribute_proposals)
         result.extend(template_proposals)

File rope/refactor/inline.py

         if definition_info.args_arg is not None or \
            definition_info.keywords_arg is not None:
             raise rope.base.exceptions.RefactoringError(
-                'Cannot functions with list and keyword arguements.')
+                'Cannot inline functions with list and keyword arguements.')
         return paramdict
 
     def get_function_name(self):

File rope/ui/codeactions.py

 from rope.ui.actionhelpers import ConfirmEditorsAreSaved
 from rope.ui.extension import SimpleAction
 from rope.ui.menubar import MenuAddress
-from rope.ui.uihelpers import (TreeView, TreeViewHandle, EnhancedList,
+from rope.ui.uihelpers import (TreeView, TreeViewHandle, VolatileList,
                                EnhancedListHandle)
 from rope.ide import formatter
 
                          title='Quick Outline')
     for node in context.editingtools.outline.get_root_nodes(editor.get_text()):
         tree_view.add_entry(node)
-    tree_view.list.focus_set()
     toplevel.grab_set()
 
 class _CompletionListHandle(EnhancedListHandle):
     toplevel = Tkinter.Toplevel()
     toplevel.title('Code Assist Proposals')
     handle = _CompletionListHandle(editor, toplevel, result)
-    enhanced_list = EnhancedList(
+    enhanced_list = VolatileList(
         toplevel, handle, title='Code Assist Proposals')
     proposals = rope.ide.codeassist.ProposalSorter(result).get_sorted_proposal_list()
     for proposal in proposals:
     enhanced_list = None
     def focus_set():
         enhanced_list.list.focus_set()
-        toplevel.grab_set()
-    enhanced_list = EnhancedList(
+    enhanced_list = VolatileList(
         toplevel, _OccurrenceListHandle(toplevel, context.get_core(), focus_set),
         title='Occurrences')
     for occurrence in result:

File rope/ui/core.py

 import rope.ui.statusbar
 from rope.base.exceptions import RopeError
 from rope.base.project import Project, get_no_project
-from rope.ui import editingcontexts
+from rope.ui import editingcontexts, prefs
 from rope.ui.extension import ActionContext
 from rope.ui.menubar import MenuBarManager
 
         self.project = get_no_project()
         self.rebound_keys = {}
         self.actions = []
-        self.prefs = {}
+        self.prefs = prefs.Prefs()
 
     def _load_actions(self):
         """Load extension modules.
 
         Set the preference for `key` to `value`.
         """
-        self.prefs[key] = value
+        self.prefs.set(key, value)
+
+    def add(self, key, value):
+        """Add an entry to a list preference
+
+        Add `value` to the list of entries for the `key` preference.
+
+        """
+        self.prefs.add(key, value)
+
+    def get_prefs(self):
+        """Return a `rope.ui.pref.Prefs` object"""
+        return self.prefs
 
     def _add_menu_cascade(self, menu_address, active_contexts):
         active_contexts = self._get_matching_contexts(active_contexts)

File rope/ui/dot_rope.py

     core.rebind_action('contributing', None)
     core.rebind_action('library', None)
     core.rebind_action('about', None)
+
+
+# Add your python templates
+core.add('templates', ('hello', "print 'My name is ${name}'\n"))
+core.add('templates', ('field', "self.${field}${cursor} = ${field}\n"))

File rope/ui/editingcontexts.py

 
     def _get_editing_tools(self):
         project = self.core.get_open_project()
-        return editingtools.get_editingtools_for_context(self, project)
+        return editingtools.get_editingtools_for_context(
+            self, project, self.core.get_prefs())
 
     editingtools = property(_get_editing_tools)
 

File rope/ui/editingtools.py

 import rope.refactor
 
 
-def get_editingtools_for_context(editing_context, project):
+def get_editingtools_for_context(editing_context, project, prefs):
     if editing_context.name == 'python':
-        return PythonEditingTools(project)
+        return PythonEditingTools(project, prefs)
     if editing_context.name == 'rest':
         return ReSTEditingTools()
     return NormalEditingTools()
 
 class PythonEditingTools(EditingTools):
 
-    def __init__(self, project):
+    def __init__(self, project, prefs):
         self.project = project
+        self.prefs = prefs
         self._code_assist = None
         self._outline = None
 
     def _get_code_assist(self):
         if self._code_assist is None:
             self._code_assist = rope.ide.codeassist.PythonCodeAssist(self.project)
+            for name, template in self.prefs.get('templates', []):
+                self._code_assist.add_template(name, template)
         return self._code_assist
 
     def _get_outline(self):

File rope/ui/fileactions.py

 
     def get_children(self, resource):
         if resource.is_folder():
-            return [child for child in resource.get_children()
-                    if not child.name.startswith('.') and
-                    not child.name.endswith('.pyc')]
+            result = [child for child in resource.get_children()
+                      if not child.name.startswith('.') and
+                      not child.name.endswith('.pyc')]
+            result.sort(self._compare_files)
+            return result
         else:
             return []
 
+    def _compare_files(self, file1, file2):
+        return cmp((not file1.is_folder(), file1.name),
+                   (not file2.is_folder(), file2.name))
+
     def selected(self, resource):
         if not resource.is_folder():
             self.core.editor_manager.get_resource_editor(resource)
-            self.toplevel.destroy()
+        else:
+            return True
 
     def canceled(self):
         self.toplevel.destroy()
     def focus_went_out(self):
         pass
 
+
 def _show_resource_view(core):
     if not _check_if_project_is_open(core):
         return
     toplevel = Tkinter.Toplevel()
     toplevel.title('Resources')
     tree_handle = _ResourceViewHandle(core, toplevel)
-    tree_view = TreeView(toplevel, tree_handle, title='Resources')
+    tree_view = TreeView(toplevel, tree_handle, title='Resources',
+                         height=25, width=45)
     for child in tree_handle.get_children(core.project.root):
         tree_view.add_entry(child)
-    tree_view.list.focus_set()
-    toplevel.grab_set()
 
 def project_tree(context):
     _show_resource_view(context.get_core())

File rope/ui/indenter.py

     def _get_base_indentation(self, lineno):
         range_finder = codeanalyze.StatementRangeFinder(
             self.line_editor, self._get_last_non_empty_line(lineno))
-        range_finder.analyze()
         start = range_finder.get_statement_start()
         if not range_finder.is_line_continued():
             changes = self._get_indentation_changes_caused_by_prev_stmt(

File rope/ui/prefs.py

+class Prefs(object):
+
+    def __init__(self):
+        self.prefs = {}
+
+    def set(self, key, value):
+        """Set a preference
+
+        Set the preference for `key` to `value`.
+        """
+        self.prefs[key] = value
+
+    def add(self, key, value):
+        """Add an entry to a list preference
+
+        Add `value` to the list of entries for the `key` preference.
+
+        """
+        if not key in self.prefs:
+            self.prefs[key] = []
+        self.prefs[key].append(value)
+
+    def get(self, key, default=None):
+        """Get the value of the key preference"""
+        return self.prefs.get(key, default)

File rope/ui/uihelpers.py

         if selection:
             active = int(selection[0])
             if active - 1 >= 0:
-                self._activate(active - 1)
+                self.activate(active - 1)
         return 'break'
 
     def _select_next(self, event):
         if selection:
             active = int(selection[0])
             if active + 1 < self.list.size():
-                self._activate(active + 1)
+                self.activate(active + 1)
         return 'break'
 
-    def _activate(self, index):
+    def activate(self, index):
         self.list.select_clear(0, END)
         self.list.selection_set(index)
         self.list.activate(index)
         super(VolatileList, self).__init__(*args, **kwds)
         self.list.bind('<Alt-p>', lambda event: self.move_up())
         self.list.bind('<Alt-n>', lambda event: self.move_down())
+        self.list.bind('<Control-v>', self._next_page)
+        self.list.bind('<Next>', self._next_page)
+        self.list.bind('<Alt-v>', self._prev_page)
+        self.list.bind('<Prior>', self._prev_page)
+
+    def _next_page(self, event):
+        height = self.list['height']
+        next = min(len(self.entries) - 1, self.get_active_index() + height)
+        self.activate(next)
+        return 'break'
+
+    def _prev_page(self, event):
+        height = self.list['height']
+        next = max(0, self.get_active_index() - height)
+        self.activate(next)
+        return 'break'
 
     def insert_entry(self, entry, index=None):
         if index is None:
-            index = self.get_active_index()
+            index = len(self.entries)
         self.entries.insert(index, entry)
         self.list.insert(index, self.handle.entry_to_string(entry))
         if len(self.entries) == 1:
         if index > 0:
             entry = self.remove_entry(index)
             self.insert_entry(entry, index - 1)
-            self._activate(index - 1)
+            self.activate(index - 1)
 
     def move_down(self):
         index = self.get_active_index()
         if index < len(self.entries) - 1:
             entry = self.remove_entry(index)
             self.insert_entry(entry, index + 1)
-            self._activate(index + 1)
+            self.activate(index + 1)
 
     def update(self):
         index = self.get_active_index()
         if index > 0:
             entry = self.remove_entry(index)
             self.insert_entry(entry, index)
-            self._activate(index)
+            self.activate(index)
 
 
 class _DescriptionListHandle(EnhancedListHandle):
 
         description_text = ScrolledText.ScrolledText(frame, height=12, width=80)
         self.handle = _DescriptionListHandle(description_text, description)
-        self.list = EnhancedList(frame, self.handle, title)
+        self.list = VolatileList(frame, self.handle, title)
         description_text.grid(row=0, column=1, sticky=N+E+W+S)
         frame.grid()
 
         return []
 
     def selected(self, obj):
-        pass
+        """Return True if you want the node to be expanded/collapsed"""
+        return True
 
     def canceled(self):
         pass
         self.level = level
 
 
-class TreeView(object):
+class _TreeViewListAdapter(object):
 
-    def __init__(self, parent, handle, title='Tree'):
+    def __init__(self, tree_view, handle):
+        self.tree_view = tree_view
         self.handle = handle
-        self.nodes = []
-        self.frame = Frame(parent)
-        label = Label(self.frame, text=title)
-        self.list = Listbox(self.frame, selectmode=SINGLE, height=12, width=32)
-        scrollbar = Scrollbar(self.frame, orient=VERTICAL)
-        scrollbar['command'] = self.list.yview
-        self.list.config(yscrollcommand=scrollbar.set)
-        self.list.bind('<Return>', self._open_selected)
-        self.list.bind('<space>', self._open_selected)
-        self.list.bind('<Escape>', self._cancel)
-        self.list.bind('<Control-g>', self._cancel)
-        self.list.bind('<FocusOut>', self._focus_out)
-        self.list.bind('<Control-p>', self._select_prev)
-        self.list.bind('<Up>', self._select_prev)
-        self.list.bind('<Control-n>', self._select_next)
-        self.list.bind('<Down>', self._select_next)
-        self.list.bind('<e>', self._expand_item)
-        self.list.bind('<plus>', self._expand_item)
-        self.list.bind('<c>', self._collapse_item)
-        self.list.bind('<minus>', self._collapse_item)
-        label.grid(row=0, column=0, columnspan=2)
-        self.list.grid(row=1, column=0, sticky=N+E+W+S)
-        scrollbar.grid(row=1, column=1, sticky=N+E+W+S)
-        self.frame.grid(sticky=N+E+W+S)
 
-    def _focus_out(self, event):
-        self.handle.focus_went_out()
-
-    def _open_selected(self, event):
-        selection = self.list.curselection()
-        if selection:
-            selected = int(selection[0])
-            self.handle.selected(self.nodes[selected].entry)
-
-    def _cancel(self, event):
-        self.handle.canceled()
-
-    def _select_entry(self, index):
-        self.list.select_clear(0, END)
-        self.list.selection_set(index)
-        self.list.see(index)
-        self.list.activate(index)
-        self.list.see(index)
-
-    def _select_prev(self, event):
-        selection = self.list.curselection()
-        if selection:
-            active = int(selection[0])
-            if active - 1 >= 0:
-                self._select_entry(active - 1)
-        return 'break'
-
-    def _select_next(self, event):
-        selection = self.list.curselection()
-        if selection:
-            active = int(selection[0])
-            if active + 1 < self.list.size():
-                self._select_entry(active + 1)
-        return 'break'
-
-    def _expand_item(self, event):
-        selection = self.list.curselection()
-        if selection:
-            active = int(selection[0])
-            self.expand(active)
-
-    def _collapse_item(self, event):
-        selection = self.list.curselection()
-        if selection:
-            active = int(selection[0])
-            self.collapse(active)
-
-    def _update_entry_text(self, index):
-        node = self.nodes[index]
+    def entry_to_string(self, node):
         entry = node.entry
         if len(self.handle.get_children(entry)) > 0:
             if node.expanded:
         level = node.level
         new_text = 4 * level * ' ' + expansion_sign + \
                    ' ' + self.handle.entry_to_string(entry)
-        old_text = self.list.get(index)
-        if old_text != new_text:
-            old_selection = 0
-            selection = self.list.curselection()
-            if selection:
-                old_selection = int(selection[0])
-            self.list.delete(index)
-            self.list.insert(index, new_text)
-            self._select_entry(old_selection)
+        return new_text
+
+    def selected(self, node):
+        if self.handle.selected(node.entry):
+            index = self.tree_view.list.get_entries().index(node)
+            self.tree_view.toggle(entry_number=index)
+
+    def canceled(self):
+        self.handle.canceled()
+
+    def focus_went_out(self):
+        self.handle.focus_went_out()
+
+
+class TreeView(object):
+
+    def __init__(self, parent, handle, title='Tree', height=12, width=32):
+        self.handle = handle
+        self.frame = Frame(parent)
+        label = Label(self.frame, text=title)
+        self.adapter = _TreeViewListAdapter(self, handle)
+        self.list = VolatileList(parent, self.adapter,
+                                 title, height=height, width=width)
+        self.list.list.bind('<Control-g>', lambda event: self.adapter.canceled())
+        self.list.list.bind('<e>', self._expand_item)
+        self.list.list.bind('<plus>', self._expand_item)
+        self.list.list.bind('<c>', self._collapse_item)
+        self.list.list.bind('<minus>', self._collapse_item)
+
+    def _expand_item(self, event):
+        self.expand(self.list.get_active_index())
+
+    def _collapse_item(self, event):
+        self.collapse(self.list.get_active_index())
+
+    def _update_entry_text(self, index):
+        node = self.list.entries[index]
+        self.list.remove_entry(index)
+        self.list.insert_entry(node, index)
+        self.list.activate(index)
 
     def add_entry(self, entry, index=None, level=0):
-        if index is None:
-            index = self.list.size()
-        self.nodes.insert(index, _TreeNodeInformation(entry, level=level))
-        self.list.insert(index, 4 * level * '  ' +
-                         self.handle.entry_to_string(entry))
-        if len(self.nodes) == 1:
-            self._select_entry(1)
-        self._update_entry_text(index)
+        self.list.insert_entry(_TreeNodeInformation(entry, level=level), index)
 
     def remove(self, entry_number):
         self.collapse(entry_number)
-        self.nodes.pop(entry_number)
-        self.list.delete(entry_number)
+        self.list.remove_entry(entry_number)
 
     def clear(self):
-        self.nodes = []
-        self.list.delete(0, END)
+        self.list.clear()
 
     def expand(self, entry_number):
-        node = self.nodes[entry_number]
+        node = self.list.get_entries()[entry_number]
         if node.expanded:
             return
         new_children = self.handle.get_children(node.entry)
         self._update_entry_text(entry_number)
 
     def collapse(self, entry_number):
-        node = self.nodes[entry_number]
+        node = self.list.get_entries()[entry_number]
         if not node.expanded:
             return
         node.expanded = False
             self.remove(entry_number + 1)
         self._update_entry_text(entry_number)
 
+    def toggle(self, entry_number):
+        node = self.list.get_entries()[entry_number]
+        if not node.expanded:
+            self.expand(entry_number)
+        else:
+            self.collapse(entry_number)
+
+    def size(self):
+        return len(self.list.get_entries())
+
+    def get(self, index):
+        return self.adapter.entry_to_string(self.list.get_entries()[index])
+
 
 class FindItemHandle(object):
 

File ropetest/codeanalyzetest.py

 
     def get_range_finder(self, code, line):
         result = StatementRangeFinder(ArrayLinesAdapter(code.split('\n')), line)
-        result.analyze()
         return result
 
     def test_simple_statement_finding(self):
         self.assertEquals(('', '', 8),
                           word_finder.get_splitted_primary_before(8))
 
+    def test_empty_splitted_statement5(self):
+        word_finder = WordRangeFinder('a.')
+        self.assertEquals(('a', '', 2),
+                          word_finder.get_splitted_primary_before(2))
+
     def test_operators_inside_parens(self):
         word_finder = WordRangeFinder('(a_var + another_var).reverse()')
         self.assertEquals('(a_var + another_var).reverse',

File ropetest/codeassisttest.py

 import os
 import unittest
 
+from rope.base.project import Project
 from rope.ide.codeassist import PythonCodeAssist, RopeSyntaxError, Template, ProposalSorter
-from rope.base.project import Project
 from ropetest import testutils
 
 
         result = self.assist.assist(code, len(code))
         self.assert_completion_in_result('a_var', 'global', result)
 
+    def test_proposing_function_keywords_when_calling(self):
+        code = 'def f(p):\n    pass\nf(p'
+        result = self.assist.assist(code, len(code))
+        self.assert_completion_in_result('p=', 'parameter_keyword', result)
+
+    def test_proposing_function_keywords_when_calling_for_non_functions(self):
+        code = 'f = 1\nf(p'
+        result = self.assist.assist(code, len(code))
+
+    def test_proposing_function_keywords_when_calling_extra_spaces(self):
+        code = 'def f(p):\n    pass\nf( p'
+        result = self.assist.assist(code, len(code))
+        self.assert_completion_in_result('p=', 'parameter_keyword', result)
+
+    def test_proposing_function_keywords_when_calling_on_second_argument(self):
+        code = 'def f(p1, p2):\n    pass\nf(1, p'
+        result = self.assist.assist(code, len(code))
+        self.assert_completion_in_result('p2=', 'parameter_keyword', result)
+
+    def test_proposing_function_keywords_when_calling_not_proposing_args(self):
+        code = 'def f(p1, *args):\n    pass\nf(1, a'
+        result = self.assist.assist(code, len(code))
+        self.assert_completion_not_in_result('args=', 'parameter_keyword', result)
+
+    def test_proposing_function_keywords_when_calling_with_no_nothing_after_parens(self):
+        code = 'def f(p):\n    pass\nf('
+        result = self.assist.assist(code, len(code))
+        self.assert_completion_in_result('p=', 'parameter_keyword', result)
+
+    def test_proposing_function_keywords_when_calling_with_no_nothing_after_parens(self):
+        code = 'def f(p):\n    pass\ndef g():\n    h = f\n    f('
+        result = self.assist.assist(code, len(code))
+        self.assert_completion_in_result('p=', 'parameter_keyword', result)
+
+    def test_codeassists_before_opening_of_parens(self):
+        code = 'def f(p):\n    pass\na_var = 1\nf(1)\n'
+        result = self.assist.assist(code, code.rindex('f') + 1)
+        self.assert_completion_not_in_result('a_var', 'global', result)
+
 
 class CodeAssistInProjectsTest(unittest.TestCase):
 

File ropetest/refactor/renametest.py

         self.assertEquals('def a_func(new_param):\n    print new_param\n'
                           'a_func  (new_param=hey)\n', refactored)
 
+    def test_renaming_parameter_like_objects_after_keywords(self):
+        code = 'def a_func(param):\n    print(param)\ndict(param=hey)\n'
+        refactored = self._local_rename(code, code.find('param') + 1, 'new_param')
+        self.assertEquals('def a_func(new_param):\n    print(new_param)\n'
+                          'dict(param=hey)\n', refactored)
+
     def test_renaming_variables_in_init_dot_pys(self):
         pkg = self.pycore.create_package(self.project.root, 'pkg')
         init_dot_py = pkg.get_child('__init__.py')

File ropetest/ui/uihelperstest.py

         handle = SampleListHandle()
         enhanced_list = EnhancedList(self.parent, handle)
         enhanced_list.add_entry(1)
-        self.assertEquals('element 1', enhanced_list.list.get(0, 1)[0])
+        self.assertEquals('element 1', enhanced_list.list.get(0))
 
     def test_tree_view(self):
         handle = SampleTreeHandle()
         tree_viewer = TreeView(self.parent, handle)
         tree_viewer.add_entry('a')
-        self.assertTrue(tree_viewer.list.get(0, 1)[0].endswith('element a'))
+        self.assertTrue(tree_viewer.get(0).endswith('element a'))
 
     def test_tree_view_expanding(self):
         handle = SampleTreeHandle()
         tree_viewer = TreeView(self.parent, handle)
         tree_viewer.add_entry('a')
         tree_viewer.expand(0)
-        self.assertEquals(4, tree_viewer.list.size())
-        self.assertTrue(tree_viewer.list.get(0, 1)[0].endswith('element a'))
-        self.assertTrue(tree_viewer.list.get(1, 4)[0].endswith('element a0'))
-        self.assertTrue(tree_viewer.list.get(1, 4)[1].endswith('element a1'))
-        self.assertTrue(tree_viewer.list.get(1, 4)[2].endswith('element a2'))
+        self.assertEquals(4, tree_viewer.size())
+        self.assertTrue(tree_viewer.get(0).endswith('element a'))
+        self.assertTrue(tree_viewer.get(1).endswith('element a0'))
+        self.assertTrue(tree_viewer.get(2).endswith('element a1'))
+        self.assertTrue(tree_viewer.get(3).endswith('element a2'))
 
     def test_tree_view_multi_expanding(self):
         handle = SampleTreeHandle()
         tree_viewer.add_entry('a')
         tree_viewer.expand(0)
         tree_viewer.expand(0)
-        self.assertEquals(4, tree_viewer.list.size())
+        self.assertEquals(4, tree_viewer.size())
 
     def test_tree_view_shrinking(self):
         handle = SampleTreeHandle()
         tree_viewer = TreeView(self.parent, handle)
         tree_viewer.add_entry('a')
         tree_viewer.expand(0)
-        self.assertEquals(4, tree_viewer.list.size())
+        self.assertEquals(4, tree_viewer.size())
         tree_viewer.collapse(0)
-        self.assertEquals(1, tree_viewer.list.size())
-        self.assertTrue(tree_viewer.list.get(0, 1)[0].endswith('element a'))
+        self.assertEquals(1, tree_viewer.size())
+        self.assertTrue(tree_viewer.get(0).endswith('element a'))
 
     def test_expansion_signs(self):
         handle = SampleTreeHandle()
         tree_viewer = TreeView(self.parent, handle)
         tree_viewer.add_entry('a')
-        self.assertEquals('+ element a', tree_viewer.list.get(0, 1)[0])
+        self.assertEquals('+ element a', tree_viewer.get(0))
         tree_viewer.expand(0)
-        self.assertEquals('- element a', tree_viewer.list.get(0, 1)[0])
+        self.assertEquals('- element a', tree_viewer.get(0))
         tree_viewer.collapse(0)
-        self.assertEquals('+ element a', tree_viewer.list.get(0, 1)[0])
+        self.assertEquals('+ element a', tree_viewer.get(0))
 
     def test_expansion_signs_for_leaves(self):
         handle = SampleTreeHandle()
         tree_viewer = TreeView(self.parent, handle)
         tree_viewer.add_entry('a00')
-        self.assertEquals('  element a00', tree_viewer.list.get(0, 1)[0])
+        self.assertEquals('  element a00', tree_viewer.get(0))
         tree_viewer.expand(0)
-        self.assertEquals('  element a00', tree_viewer.list.get(0, 1)[0])
+        self.assertEquals('  element a00', tree_viewer.get(0))
+
+    def test_expansion_signs(self):
+        handle = SampleTreeHandle()
+        tree_viewer = TreeView(self.parent, handle)
+        tree_viewer.add_entry('a')
+        self.assertEquals('+ element a', tree_viewer.get(0))
+        tree_viewer.toggle(0)
+        self.assertEquals('- element a', tree_viewer.get(0))
+        tree_viewer.toggle(0)
+        self.assertEquals('+ element a', tree_viewer.get(0))
 
 
 if __name__ == '__main__':