Commits

Anonymous committed 242ba74

Added replace method with method object refactoring
Hiding UI parts

Comments (0)

Files changed (24)

docs/dev/done.txt

 ===========
 
 
+- Replace method with method object : February 12, 2007
+
+
 - Enhancing searching : February 10, 2007
 
 

docs/dev/stories.txt

 
 
 > Public Release 0.5m1 : February 18, 2007
-
-
-* Replace method with method object refactoring

docs/dev/workingon.txt

+Refactoring
+===========
+
+- Replace Method With Method Object:
+
+  - Multi-line function header
+  - Simple parameters
+  - ``self`` parameter
+  - Using parameters in ``__call__`` body
+  - Args_arg, kwds_arg
+  - Check performing on things other than functions
+  - Adding to UI
+  - Changing the caller
+  - Nested and class methods
+
+- Using `functionutils.DefinitionInfo` in show doc
+- Get pyname on keywords
+- Proposing `functionutils.DefinitionInfo.arg` while none exists
+- Extract method and  ``a = 1 + a\n`` and ``a += 1\n``
+- ``core.set('show_menu_bar', False)``
+- ``core.set('show_status_bar', False)``
+- ``core.set('show_buffer_list_bar', False)``
+- Only showing active actions in current context in execute command
+- ``C-g`` and code assist dialog
+- Problems for inside list comprehension assists
+
+* Showing pysvn urls in docs
+* Changing ``C-a C-a`` to move to the first character in the line
+* Changing open project dialog
+
+
 Remaining Small Stories
 =======================
 
-High Priorities:
+Main:
 
 * Separating UI and functional tests from unit tests
 * Should we stop when transforming local variable to fields when there
 * Importing star and removing self imports; stack overflow
 * Extract constant
 * What to do if a file is removed while editing
-* Problems for inside list comprehension assists
 * `PyClass.superclasses` should be concluded data
 * Better `ropetest` package structure
 * Handling `AssList` for inline variable and encapsulate field
   * Encapsulate field
   * Transform module to package
   * Convert local variable to field
+  * Replace method with method object
   * Inline argument default value
   * Previewing refactorings
   * Undo/redo refactorings
   * Python and reST highlighting
   * Multiple editers
   * Project file history
-  * Basic SVN support using pysvn
+  * Basic SVN support using pysvn library
+  * Configurable keybinding
+  * Basic UI plug-in support
   * Correcting indentation
   * Project tree view
-  * Basic UI plug-in support
 
 
 Download

rope/base/change.py

 
     def get_description(self):
         differ = difflib.Differ()
-        result = list(differ.compare(self.resource.read().splitlines(True),
-                                     self.old_content.splitlines(True)))
+        result = list(differ.compare(self.old_content.splitlines(True),
+                                     self.new_content.splitlines(True)))
         return ''.join(result)
 
     def get_changed_resources(self):

rope/base/codeanalyze.py

     def get_pyname_in_scope(self, holding_scope, name):
         #ast = compiler.parse(name)
         # parenthesizing for handling cases like 'a_var.\nattr'
-        ast = compiler.parse('(%s)' % name)
+        try:
+            ast = compiler.parse('(%s)' % name)
+        except SyntaxError:
+            raise BadIdentifierError('Not a python identifier selected.')
         result = evaluate.get_statement_result(holding_scope, ast)
         return result
 
 
+class BadIdentifierError(rope.base.exceptions.RopeError):
+    pass
+
+
 def get_pyname_at(pycore, resource, offset):
     """Finds the pyname at the offset
 
         self.lines = lines
 
     def get_logical_line_in(self, line_number):
-        block_start = StatementRangeFinder.get_block_start(
+        block_start = get_block_start(
             self.lines, line_number,
             count_line_indents(self.lines.get_line(line_number)))
         readline = LinesToReadline(self.lines, block_start)
 
     def analyze(self):
         last_statement = 1
-        for current_line_number in range(self.get_block_start(self.lines, self.lineno),
+        for current_line_number in range(get_block_start(self.lines,
+                                                         self.lineno),
                                          self.lineno + 1):
             if not self.explicit_continuation and self.open_count == 0 and self.in_string == '':
                 last_statement = current_line_number
     def get_line_indents(self, line_number):
         return count_line_indents(self.lines.get_line(line_number))
 
-    @staticmethod
-    def get_block_start(lines, lineno, maximum_indents=80):
-        """Aproximating block start"""
-        pattern = StatementRangeFinder.get_block_start_patterns()
-        for i in reversed(range(1, lineno + 1)):
-            match = pattern.search(lines.get_line(i))
-            if match is not None and \
-               count_line_indents(lines.get_line(i)) <= maximum_indents:
-                return i
-        return 1
+    _block_start_pattern = None
 
-    @classmethod
-    def get_block_start_patterns(cls):
-        if not hasattr(cls, '__block_start_pattern'):
-            pattern = '^\\s*(((def|class|if|elif|except|for|while|with)\\s)|((try|else|finally|except)\\s*:))'
-            cls.__block_start_pattern = re.compile(pattern, re.M)
-        return cls.__block_start_pattern
+
+def get_block_start(lines, lineno, maximum_indents=80):
+    """Aproximate block start"""
+    pattern = get_block_start_patterns()
+    for i in range(lineno, 0, -1):
+        match = pattern.search(lines.get_line(i))
+        if match is not None and \
+           count_line_indents(lines.get_line(i)) <= maximum_indents:
+            striped = match.string.lstrip()
+            # Maybe we're in a list comprehension
+            if i > 1 and striped.startswith('if') or striped.startswith('for'):
+                bracs = 0
+                for j in range(i, min(i + 5, lines.length() + 1)):
+                    for c in lines.get_line(j):
+                        if c == '#':
+                            break
+                        if c == '[':
+                            bracs += 1
+                        if c == ']':
+                            bracs -= 1
+                            if bracs < 0:
+                                break
+                    if bracs < 0:
+                        break
+                if bracs < 0:
+                    continue
+            return i
+    return 1
+
+def get_block_start_patterns():
+    if not StatementRangeFinder._block_start_pattern:
+        pattern = '^\\s*(((def|class|if|elif|except|for|while|with)\\s)|'\
+                  '((try|else|finally|except)\\s*:))'
+        StatementRangeFinder._block_start_pattern = re.compile(pattern, re.M)
+    return StatementRangeFinder._block_start_pattern
 
 
 # XXX: Should we use it

rope/base/pyobjects.py

             self.scope_visitor.names[node.attrname].assignments.append(
                 pynames._Assignment(self.assigned_ast))
 
+    def visitAssTuple(self, node):
+        for child in node.getChildNodes():
+            compiler.walk(child, self)
+
     def visitAssName(self, node):
         pass
 

rope/ide/codeassist.py

 from rope.base.codeanalyze import (StatementRangeFinder, ArrayLinesAdapter,
                                    WordRangeFinder, ScopeNameFinder,
                                    SourceLinesAdapter)
-from rope.refactor import occurrences
+from rope.refactor import occurrences, functionutils
 
 
 class RopeSyntaxError(RopeError):
         block_start = self.lineno
         last_indents = self.current_indents
         while block_start > 0:
-            block_start = rope.base.codeanalyze.StatementRangeFinder.get_block_start(
+            block_start = rope.base.codeanalyze.get_block_start(
                 ArrayLinesAdapter(self.lines), block_start) - 1
             if self.lines[block_start].strip().startswith('try:'):
                 indents = self._get_line_indents(self.lines[block_start])
         pyobject = element.get_object()
         if isinstance(pyobject, rope.base.pyobjects.PyDefinedObject):
             if pyobject.get_type() == rope.base.pyobjects.get_base_type('Function'):
-                return _get_function_docstring(pyobject._get_ast())
+                return _get_function_docstring(pyobject)
             elif pyobject.get_type() == rope.base.pyobjects.get_base_type('Type'):
                 return _get_class_docstring(pyobject)
             else:
     if '__init__' in pyclass.get_attributes():
         init = pyclass.get_attribute('__init__').get_object()
         if isinstance(init, rope.base.pyobjects.PyDefinedObject):
-            doc += '\n\n' + _get_function_docstring(init._get_ast())
+            doc += '\n\n' + _get_function_docstring(init)
     return doc
 
-def _get_function_docstring(node):
-    signature = _get_function_signature(node)
+def _get_function_docstring(pyfunction):
+    signature = _get_function_signature(pyfunction)
 
-    return signature + _trim_docstring(node.doc)
+    return signature + '\n\n' + _trim_docstring(pyfunction._get_ast().doc)
 
-def _get_function_signature(node):
-    has_args = node.flags & 0x4
-    has_kwds = node.flags & 0x8
-    normal_arg_count = len(node.argnames)
-    if has_kwds:
-        normal_arg_count -= 1
-    if has_args:
-        normal_arg_count -= 1
-    signature = node.name + '(' + ', '.join(node.argnames[:normal_arg_count])
-    if has_args:
-        signature += ', *%s' % node.argnames[normal_arg_count]
-    if has_kwds:
-        signature += ', **%s' % node.argnames[-1]
-    signature += ')\n\n'
-    return signature
+def _get_function_signature(pyfunction):
+    info = functionutils.DefinitionInfo.read(pyfunction)
+    return info.to_string()
 
 def _trim_docstring(docstring):
     """The sample code from :PEP:`257`"""

rope/refactor/extract.py

 
 import rope.base.pyobjects
 from rope.base import codeanalyze
+from rope.base.change import ChangeSet, ChangeContents
 from rope.base.exceptions import RefactoringError
 from rope.refactor import sourceutils
-from rope.base.change import ChangeSet, ChangeContents
 
 
 class _ExtractRefactoring(object):
     def __init__(self, project, resource, start_offset, end_offset):
         self.pycore = project.pycore
         self.resource = resource
-        self.start_offset = start_offset
-        self.end_offset = end_offset
+        self.start_offset = self._fix_start(resource.read(), start_offset)
+        self.end_offset = self._fix_end(resource.read(), end_offset)
+
+    def _fix_start(self, source, offset):
+        while offset < len(source) and source[offset].isspace():
+            offset += 1
+        return offset
+
+    def _fix_end(self, source, offset):
+        while offset > 0 and source[offset - 1].isspace():
+            offset -= 1
+        return offset
 
 
 class ExtractMethodRefactoring(_ExtractRefactoring):
 
     def get_changes(self, extracted_name):
         info = _ExtractingPartOffsets(self.pycore, self.resource,
-                                   self.start_offset, self.end_offset)
+                                      self.start_offset, self.end_offset)
         if info.is_one_line_extract():
             new_contents = _OneLineExtractPerformer(self.pycore, self.resource, info,
                                                     extracted_name).extract()
     def visitAssName(self, node):
         self._written_variable(node.name, node.lineno)
 
+    def visitAssign(self, node):
+        compiler.walk(node.expr, self)
+        for child in node.nodes:
+            compiler.walk(child, self)
+
     def visitName(self, node):
         self._read_variable(node.name, node.lineno)
 

rope/refactor/functionutils.py

         if self.keywords_arg:
             params.append('**' + self.keywords_arg)
         return '%s(%s)' % (self.function_name, ', '.join(params))
+        
 
     @staticmethod
     def _read(pyfunction, code):
         args_with_defaults = [(name, None) for name in args]
         args_with_defaults.extend(keywords)
         return DefinitionInfo(info.get_function_name(), is_method,
-                               args_with_defaults, args_arg, keywords_arg)
+                              args_with_defaults, args_arg, keywords_arg)
 
     @staticmethod
     def read(pyfunction):

rope/refactor/method_object.py

+from rope.base import codeanalyze, pyobjects, exceptions, change
+from rope.refactor import sourceutils, functionutils, occurrences, rename
+
+
+class MethodObject(object):
+
+    def __init__(self, project, resource, offset):
+        self.pycore = project.pycore
+        pyname = codeanalyze.get_pyname_at(self.pycore, resource, offset)
+        if pyname is None or not isinstance(pyname.get_object(),
+                                            pyobjects.PyFunction):
+            raise exceptions.RefactoringError(
+                'Replace method with method object refactoring should be '
+                'performed on a function.')
+        self.pyfunction = pyname.get_object()
+        self.pymodule = self.pyfunction.get_module()
+        self.resource = self.pymodule.get_resource()
+
+    def get_new_class(self, name):
+        body = sourceutils.fix_indentation(self._get_body(), 8)
+        return 'class %s(object):\n\n%s    def __call__(self):\n%s' % \
+               (name, self._get_init(), body)
+
+    def get_changes(self, new_class_name):
+        collector = sourceutils.ChangeCollector(self.pymodule.source_code)
+        start, end = self._get_body_region()
+        indents = sourceutils.get_indents(
+            self.pymodule.lines, self.pyfunction.get_scope().get_start()) + 4
+        new_contents = ' ' * indents + 'return %s(%s)()\n' % \
+                       (new_class_name, ', '.join(self._get_parameter_names()))
+        collector.add_change(start, end, new_contents)
+        insertion = self._get_class_insertion_point()
+        collector.add_change(insertion, insertion,
+                             '\n\n' + self.get_new_class(new_class_name))
+        changes = change.ChangeSet('Replace method with method object refactoring')
+        changes.add_change(change.ChangeContents(self.resource,
+                                                 collector.get_changed()))
+        return changes
+
+    def _get_class_insertion_point(self):
+        current = self.pyfunction
+        while current.parent != self.pymodule:
+            current = current.parent
+        end = self.pymodule.lines.get_line_end(current.get_scope().get_end())
+        return min(end + 1, len(self.pymodule.source_code))
+
+    def _get_body(self):
+        body = self._get_unchanged_body()
+        for param in self._get_parameter_names():
+            body = param + ' = 1\n' + body
+            pymod = self.pycore.get_string_module(body, self.resource)
+            pyname = pymod.get_attribute(param)
+            finder = occurrences.FilteredOccurrenceFinder(
+                self.pycore, param, [pyname])
+            result = rename.rename_in_module(finder, 'self.' + param,
+                                             pymodule=pymod)
+            body = result[result.index('\n') + 1:]
+        return body
+
+    def _get_unchanged_body(self):
+        start, end = self._get_body_region()
+        return sourceutils.fix_indentation(
+            self.pymodule.source_code[start:end], 0)
+
+    def _get_body_region(self):
+        scope = self.pyfunction.get_scope()
+        lines = self.pymodule.lines
+        logical_lines = codeanalyze.LogicalLineFinder(lines)
+        start_line = logical_lines.get_logical_line_in(scope.get_start())[1] + 1
+        start = lines.get_line_start(start_line)
+        end = min(lines.get_line_end(scope.get_end()) + 1,
+                  len(self.pymodule.source_code))
+        return start, end
+
+    def _get_init(self):
+        params = self._get_parameter_names()
+        if not params:
+            return ''
+        header = '    def __init__(self'
+        body = ''
+        for arg in params:
+            new_name = arg
+            if arg == 'self':
+                new_name = 'host'
+            header += ', %s' % new_name
+            body += '        self.%s = %s\n' % (arg, new_name)
+        header += '):'
+        return '%s\n%s\n' % (header, body)
+
+    def _get_parameter_names(self):
+        info = functionutils.DefinitionInfo.read(self.pyfunction)
+        result = []
+        for arg, default in info.args_with_defaults:
+            result.append(arg)
+        if info.args_arg:
+            result.append(info.args_arg)
+        if info.keywords_arg:
+            result.append(info.keywords_arg)
+        return result

rope/ui/codeactions.py

         editor.get_text(), editor.get_current_offset(), context.resource)
     toplevel = Tkinter.Toplevel()
     toplevel.title('Code Assist Proposals')
+    handle = _CompletionListHandle(editor, toplevel, result)
     enhanced_list = EnhancedList(
-        toplevel, _CompletionListHandle(editor, toplevel, result),
-        title='Code Assist Proposals')
+        toplevel, handle, title='Code Assist Proposals')
     proposals = rope.ide.codeassist.ProposalSorter(result).get_sorted_proposal_list()
     for proposal in proposals:
         enhanced_list.add_entry(proposal)
                 enhanced_list.add_entry(proposal)
     enhanced_list.list.focus_set()
     enhanced_list.list.bind('<Any-KeyPress>', key_pressed)
+    enhanced_list.list.bind('<Control-g>', lambda event: handle.canceled())
     toplevel.grab_set()
 
 def _get_template_information(editor, result, proposal):
         for context in editingcontexts.contexts.values():
             context.menu = Menu(self.root, relief=RAISED, borderwidth=1)
             context.menu_manager = MenuBarManager(context.menu)
-        self.root['menu'] = editingcontexts.none.menu
 
         self.main = Frame(self.root, height='13c', width='26c', relief=RIDGE, bd=2)
         self.editor_panel = Frame(self.main, borderwidth=0)
                 if key:
                     menu.address[-1] = menu.address[-1].ljust(32) + key
                 self._add_menu_command(menu, callback, action.get_active_contexts())
+        self._editor_changed()
 
     def _bind_none_context_keys(self):
         context = editingcontexts.none
         context.key_binding.bind(self.root)
 
     def _get_matching_contexts(self, contexts):
-        contexts = list(contexts)
+        result = set()
+        for name in self._get_matching_context_names(contexts):
+            if name in editingcontexts.contexts:
+                result.add(editingcontexts.contexts[name])
+        return result
+
+    def _get_matching_context_names(self, contexts):
+        contexts = set(contexts)
         result = set()
         if 'all' in contexts:
             contexts.remove('all')
-            for name, context in editingcontexts.contexts.iteritems():
+            for name in editingcontexts.contexts.keys():
                 if name != 'none':
-                    result.add(context)
+                    result.add(name)
         for name in contexts:
-            if name in editingcontexts.contexts:
-                result.add(editingcontexts.contexts[name])
+                result.add(name)
         return result
 
     def _bind_key(self, key, function, active_contexts=['all']):
         self._init_key_binding()
         self._bind_none_context_keys()
         self._init_menus()
+        self.editor_manager.show(self.prefs.get('show_buffer_list', True))
         self.root.rowconfigure(0, weight=1)
         self.root.columnconfigure(0, weight=1)
         self.main.rowconfigure(0, weight=1)
         self.main.columnconfigure(0, weight=1)
         self.editor_panel.pack(fill=BOTH, expand=1)
-        self.status_bar.pack(fill=BOTH, side=BOTTOM)
+        if self.prefs.get('show_status_bar', True):
+            self.status_bar.pack(fill=BOTH, side=BOTTOM)
         self.main.pack(fill=BOTH, expand=1)
         self.main.pack_propagate(0)
         self.root.mainloop()
 
     def _editor_changed(self):
         active_editor = self.editor_manager.active_editor
+        if not self.prefs.get('show_menu_bar', True):
+            self.root['menu'] = None
+            return
         if active_editor:
             self.root['menu'] = active_editor.get_editor().get_editing_context().menu
         else:
             self.root['menu'] = editingcontexts.none.menu
 
+    def get_available_actions(self):
+        """Return the applicable actions in current context"""
+        context = 'none'
+        active_editor = self.editor_manager.active_editor
+        if active_editor:
+            context = active_editor.get_editor().get_editing_context().name
+        for action in self.actions:
+            action_contexts = self._get_matching_context_names(
+                action.get_active_contexts())
+            if context in action_contexts:
+                yield action
+
     _core = None
 
     @staticmethod

rope/ui/dot_rope.py

 # Changing editor font
 #core.set('font', ('Courier', 14))
 
+# Hiding menu bar
+#core.set('show_menu_bar', False)
+
+# Hiding status bar
+#core.set('show_status_bar', False)
+
+# Hiding buffer list
+#core.set('show_buffer_list', False)
+
+
 # If you don't like emacs keybinding, change this to False
 i_like_emacs = True
 if not i_like_emacs:
     core.rebind_action('introduce_factory', None)
     core.rebind_action('encapsulate_field', None)
     core.rebind_action('introduce_parameter', None)
+    core.rebind_action('method_object', None)
     core.rebind_action('rename_current_module', None)
     core.rebind_action('move_current_module', None)
     core.rebind_action('organize_imports', 'C-O')

rope/ui/editactions.py

         self.core = core
 
     def find_matches(self, starting):
-        return [action for action in self.core.actions
+        return [action for action in self.core.get_available_actions()
                 if action.get_name().startswith(starting)]
 
     def selected(self, action):

rope/ui/editorpile.py

         self.core = core
         self.editor_list = Frame(editor_panel, borderwidth=0)
         self.editor_frame = Frame(editor_panel, borderwidth=0, relief=RIDGE)
-        self.editor_list.pack(fill=BOTH, side=TOP)
-        self.editor_frame.pack(fill=BOTH, expand=1)
-        self.editor_frame.pack_propagate(0)
         self.editors = []
         self.buttons = {}
         self.active_file_path = StringVar('')
         self.active_editor = None
         self.last_edited_location = None
 
+    def show(self, show_list=True):
+        if show_list:
+            self.editor_list.pack(fill=BOTH, side=TOP)
+        self.editor_frame.pack(fill=BOTH, expand=1)
+        self.editor_frame.pack_propagate(0)
+
     def _editor_was_modified(self, editor):
         if editor not in self.buttons:
             return

rope/ui/highlighter.py

+import keyword
 import re
-import keyword
 
 import rope.base.codeanalyze
 
         return (start, end)
 
     def _find_block_start(self, text, index):
-        block_start_pattern = rope.base.codeanalyze.StatementRangeFinder.get_block_start_patterns()
+        block_start_pattern = rope.base.codeanalyze.get_block_start_patterns()
         index = self._get_line_start(text, index)
         while index > 0:
             new_index = self._get_line_start(text, index - 1)
         return 0
 
     def _find_block_end(self, text, index):
-        block_start_pattern = rope.base.codeanalyze.StatementRangeFinder.get_block_start_patterns()
+        block_start_pattern = rope.base.codeanalyze.get_block_start_patterns()
         index = self._get_line_end(text, index)
         while index < len(text) - 1:
             new_index = self._get_line_end(text, index + 1)

rope/ui/refactor.py

 import rope.refactor.localtofield
 import rope.refactor.move
 import rope.refactor.rename
+import rope.refactor.method_object
 import rope.ui.core
 from rope.refactor import ImportOrganizer
 from rope.ui.actionhelpers import ConfirmEditorsAreSaved
         label.grid(row=0, column=0)
         self.new_name_entry = Tkinter.Entry(frame)
         self.new_name_entry.grid(row=0, column=1)
+        self.new_name_entry.insert(0, 'extracted')
+        self.new_name_entry.select_range(0, Tkinter.END)
 
         self.new_name_entry.bind('<Return>', lambda event: self._ok())
         self.new_name_entry.focus_set()
         frame = Tkinter.Frame(self.toplevel)
         label = Tkinter.Label(frame, text='Factory Method Name :')
         self.new_name_entry = Tkinter.Entry(frame)
+        self.new_name_entry.insert(0, 'create')
+        self.new_name_entry.select_range(0, Tkinter.END)
 
         self.global_factory_val = Tkinter.BooleanVar(False)
-        static_factory_button = Tkinter.Radiobutton(frame, variable=self.global_factory_val,
-                                                    value=False, text='Use static method')
-        global_factory_button = Tkinter.Radiobutton(frame, variable=self.global_factory_val,
-                                                    value=True, text='Use global function')
+        static_factory_button = Tkinter.Radiobutton(
+            frame, variable=self.global_factory_val,
+            value=False, text='Use static method')
+        global_factory_button = Tkinter.Radiobutton(
+            frame, variable=self.global_factory_val,
+            value=True, text='Use global function')
         self.new_name_entry.bind('<Return>', lambda event: self._ok())
 
         label.grid(row=0, column=0)
         name_entry.focus_set()
         toplevel.grab_set()
 
+
 def change_signature(context):
     ChangeMethodSignatureDialog(context).show()
 
+
 class InlineArgumentDefaultDialog(RefactoringDialog):
 
     def __init__(self, context):
         label = Tkinter.Label(frame, text='New Parameter Name :')
         label.grid(row=0, column=0)
         self.new_name_entry = Tkinter.Entry(frame)
-        #self.new_name_entry.insert(0, '')
+        self.new_name_entry.insert(0, 'new_parameter')
         self.new_name_entry.select_range(0, Tkinter.END)
         self.new_name_entry.grid(row=0, column=1, columnspan=2)
         self.new_name_entry.bind('<Return>', lambda event: self._ok())
         context.project.do(changes)
 
 
+class MethodObjectDialog(RefactoringDialog):
+
+    def __init__(self, context):
+        editor = context.get_active_editor().get_editor()
+        super(MethodObjectDialog, self).__init__(
+            context.project, 'Replace Method With Method Object Refactoring')
+        self.renamer = rope.refactor.method_object.MethodObject(
+            context.project, context.resource, editor.get_current_offset())
+
+    def _get_changes(self):
+        new_name = self.new_name_entry.get()
+        return self.renamer.get_changes(new_name)
+
+    def _get_dialog_frame(self):
+        frame = Tkinter.Frame(self.toplevel)
+        label = Tkinter.Label(frame, text='New Class Name :')
+        label.grid(row=0, column=0)
+        self.new_name_entry = Tkinter.Entry(frame)
+        self.new_name_entry.insert(0, '_MethodObject')
+        self.new_name_entry.select_range(0, Tkinter.END)
+        self.new_name_entry.grid(row=0, column=1, columnspan=2)
+        self.new_name_entry.bind('<Return>', lambda event: self._ok())
+        self.new_name_entry.focus_set()
+        return frame
+
+
+def method_object(context):
+    MethodObjectDialog(context).show()
+
+
 actions = []
 actions.append(SimpleAction('rename', ConfirmEditorsAreSaved(rename), 'C-c r r',
                             MenuAddress(['Refactor', 'Rename'], 'n'), ['python']))
                             ConfirmEditorsAreSaved(introduce_parameter), 'C-c r p',
                             MenuAddress(['Refactor', 'Introduce Parameter'], None, 1),
                             ['python']))
+actions.append(SimpleAction('method_object',
+                            ConfirmEditorsAreSaved(method_object), 'C-c r j',
+                            MenuAddress(['Refactor', 'Method To Method Object'], 'j', 1),
+                            ['python']))
 actions.append(SimpleAction('local_to_field',
                             ConfirmEditorsAreSaved(convert_local_to_field), None,
                             MenuAddress(['Refactor', 'Convert Local Variable to Field'], 'b', 1),

rope/ui/uihelpers.py

 
 class EnhancedList(object):
 
-    def __init__(self, parent, handle, title="List", get_focus=True):
+    def __init__(self, parent, handle, title="List", get_focus=True,
+                 height=14, width=50):
         self.handle = handle
         self.entries = []
         self.frame = Frame(parent)
         label = Label(self.frame, text=title)
-        self.list = Listbox(self.frame, selectmode=SINGLE)
+        self.list = Listbox(self.frame, selectmode=SINGLE,
+                            height=height, width=width)
         scrollbar = Scrollbar(self.frame, orient=VERTICAL)
         scrollbar['command'] = self.list.yview
         self.list.config(yscrollcommand=scrollbar.set)

ropetest/codeanalyzetest.py

 import unittest
 
-from ropetest import testutils
+from rope.base import exceptions
 from rope.base.codeanalyze import (StatementRangeFinder, ArrayLinesAdapter,
                                    SourceLinesAdapter, WordRangeFinder,
-                                   ScopeNameFinder, LogicalLineFinder)
+                                   ScopeNameFinder, LogicalLineFinder,
+                                   get_block_start)
 from rope.base.project import Project
+from ropetest import testutils
 
 
 class StatementRangeFinderTest(unittest.TestCase):
         super(ScopeNameFinderTest, self).tearDown()
 
     # FIXME: in normal scopes the interpreter raises `UnboundLocalName`
-    # exception, but no in class bodies
+    # exception, but not in class bodies
     def xxx_test_global_name_in_class_body(self):
         code = 'a_var = 10\nclass Sample(object):\n    a_var = a_var\n'
         scope = self.pycore.get_string_scope(code)
         found_pyname = name_finder.get_pyname_at(mod1.read().index('pkg2') + 1)
         self.assertEquals(pkg2_pyobject, found_pyname.get_object())
 
+    @testutils.assert_raises(exceptions.RopeError)
+    def test_get_pyname_at_on_language_keywords(self):
+        code = 'def a_func(a_func):\n    pass\n'
+        pymod = self.pycore.get_string_module(code)
+        name_finder = ScopeNameFinder(pymod)
+        name_finder.get_pyname_at(code.index('pass'))
+
 
 class LogicalLineFinderTest(unittest.TestCase):
 
         line_finder = self._get_logical_line_finder(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)
+        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 ))
+
 
 def suite():
     result = unittest.TestSuite()

ropetest/pycoretest.py

         c_class = pymod.get_attribute('C').get_object()
         self.assertFalse('my_var' in c_class.get_attributes())
 
+    def test_not_leaking_tuple_assigned_names_inside_parent_scope(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        mod.write('class C(object):\n    def f(self):\n'
+                  '        var1, var2 = range(2)\n')
+        pymod = self.pycore.resource_to_pyobject(mod)
+        c_class = pymod.get_attribute('C').get_object()
+        self.assertFalse('var1' in c_class.get_attributes())
+
 
 class PyCoreInProjectsTest(unittest.TestCase):
 

ropetest/refactor/__init__.py

 from rope.refactor.encapsulate_field import EncapsulateFieldRefactoring
 from rope.refactor.introduce_factory import IntroduceFactoryRefactoring
 from rope.refactor.localtofield import ConvertLocalToFieldRefactoring
+from rope.refactor.method_object import MethodObject
 from ropetest import testutils
 
 
+class MethodObjectTest(unittest.TestCase):
+
+    def setUp(self):
+        super(MethodObjectTest, self).setUp()
+        self.project_root = 'sampleproject'
+        testutils.remove_recursively(self.project_root)
+        self.project = Project(self.project_root)
+        self.pycore = self.project.get_pycore()
+
+    def tearDown(self):
+        testutils.remove_recursively(self.project_root)
+        super(MethodObjectTest, self).tearDown()
+
+    def test_empty_method(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func():\n    pass\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.assertEquals(
+            'class _New(object):\n\n    def __call__(self):\n        pass\n',
+            replacer.get_new_class('_New'))
+
+    def test_trivial_return(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func():\n    return 1\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.assertEquals(
+            'class _New(object):\n\n    def __call__(self):\n        return 1\n',
+            replacer.get_new_class('_New'))
+
+    def test_multi_line_header(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func(\n    ):\n    return 1\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.assertEquals(
+            'class _New(object):\n\n    def __call__(self):\n        return 1\n',
+            replacer.get_new_class('_New'))
+
+    def test_a_single_parameter(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func(param):\n    return 1\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.assertEquals(
+            'class _New(object):\n\n'
+            '    def __init__(self, param):\n        self.param = param\n\n'
+            '    def __call__(self):\n        return 1\n',
+            replacer.get_new_class('_New'))
+
+    def test_self_parameter(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func(self):\n    return 1\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.assertEquals(
+            'class _New(object):\n\n'
+            '    def __init__(self, host):\n        self.self = host\n\n'
+            '    def __call__(self):\n        return 1\n',
+            replacer.get_new_class('_New'))
+
+    def test_self_parameter(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func(param):\n    return param\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.assertEquals(
+            'class _New(object):\n\n'
+            '    def __init__(self, param):\n        self.param = param\n\n'
+            '    def __call__(self):\n        return self.param\n',
+            replacer.get_new_class('_New'))
+
+    def test_self_keywords_and_args_parameters(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func(arg, *args, **kwds):\n' \
+               '    result = arg + args[0] + kwds[arg]\n    return result\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.assertEquals(
+            'class _New(object):\n\n'
+            '    def __init__(self, arg, args, kwds):\n'
+            '        self.arg = arg\n        self.args = args\n        self.kwds = kwds\n\n'
+            '    def __call__(self):\n'
+            '        result = self.arg + self.args[0] + self.kwds[self.arg]\n'
+            '        return result\n',
+            replacer.get_new_class('_New'))
+
+    @testutils.assert_raises(RefactoringError)
+    def test_performing_on_not_a_function(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'my_var = 10\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('my_var'))
+
+    def test_changing_the_module(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'def func():\n    return 1\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.project.do(replacer.get_changes('_New'))
+        self.assertEquals(
+            'def func():\n    return _New()()\n\n\n'
+            'class _New(object):\n\n    def __call__(self):\n        return 1\n',
+            mod.read())
+
+    def test_changing_the_module_and_class_methods(self):
+        mod = self.pycore.create_module(self.project.root, 'mod')
+        code = 'class C(object):\n\n    def a_func(self):\n        return 1\n\n' \
+               '    def another_func(self):\n        pass\n'
+        mod.write(code)
+        replacer = MethodObject(self.project, mod, code.index('func'))
+        self.project.do(replacer.get_changes('_New'))
+        self.assertEquals(
+            'class C(object):\n\n    def a_func(self):\n        return _New(self)()\n\n'
+            '    def another_func(self):\n        pass\n\n\n'
+            'class _New(object):\n\n'
+            '    def __init__(self, host):\n        self.self = host\n\n'
+            '    def __call__(self):\n        return 1\n',
+            mod.read())
+
+
 class IntroduceFactoryTest(unittest.TestCase):
 
     def setUp(self):

ropetest/refactor/extracttest.py

         end = code.rindex(')') + 1
         refactored = self.do_extract_method(code, start, end, 'new_func')
 
+    def test_extract_method_and_extra_blank_lines(self):
+        code = '\nprint 1\n'
+        refactored = self.do_extract_method(code, 0, len(code), 'new_f')
+        expected = '\n\ndef new_f():\n    print 1\n\nnew_f()\n'
+        self.assertEquals(expected, refactored)
+
+    def test_variable_writes_in_the_same_line_as_variable_read(self):
+        code = 'a = 1\na = 1 + a\n'
+        start = code.index('\n') + 1
+        end = len(code)
+        refactored = self.do_extract_method(code, start, end, 'new_f')
+        expected = 'a = 1\n\ndef new_f(a):\n    a = 1 + a\n\nnew_f(a)\n'
+        self.assertEquals(expected, refactored)
+
+    def test_variable_writes_in_the_same_line_as_variable_read2(self):
+        code = 'a = 1\na += 1\n'
+        start = code.index('\n') + 1
+        end = len(code)
+        refactored = self.do_extract_method(code, start, end, 'new_f')
+        expected = 'a = 1\n\ndef new_f(a):\n    a += 1\n\nnew_f(a)\n'
+        self.assertEquals(expected, refactored)
+
 
 if __name__ == '__main__':
     unittest.main()

ropetest/ui/highlightertest.py

 from rope.ui.highlighter import (PythonHighlighting, HighlightingStyle,
                                  ReSTHighlighting, NoHighlighting)
 
+
 class HighlightTest(unittest.TestCase):
 
     def setUp(self):