Anonymous avatar Anonymous committed d3b5093 Draft

ropecommon: renamed ropemacs package to ropecommon

Comments (0)

Files changed (16)

ropecommon/__init__.py

+"""ropecommon, an emacs mode for using rope refactoring library"""
+
+COPYRIGHT = """\
+Copyright (C) 2007-2008 Ali Gholami Rudi
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of GNU General Public License as published by the
+Free Software Foundation; either version 2 of the license, or (at your
+opinion) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details."""

ropecommon/decorators.py

+import traceback
+
+from rope.base import exceptions
+
+from ropecommon import lisputils
+
+
+def lisphook(func):
+    def newfunc(*args, **kwds):
+        try:
+            func(*args, **kwds)
+        except Exception, e:
+            trace = str(traceback.format_exc())
+            lisputils.message(
+                '%s\nIgnored an exception in ropemacs hook: %s' %
+                (trace, _exception_message(e)))
+    newfunc.lisp = None
+    newfunc.__name__ = func.__name__
+    newfunc.__doc__ = func.__doc__
+    return newfunc
+
+
+def interactive(func):
+    func.interaction = ''
+    return func
+
+
+def lispfunction(func):
+    func.lisp = None
+    return func
+
+
+input_exceptions = (exceptions.RefactoringError,
+                    exceptions.ModuleSyntaxError,
+                    exceptions.BadIdentifierError)
+
+def _lisp_name(func):
+    return 'rope-' + func.__name__.replace('_', '-')
+
+def _exception_handler(func):
+    def newfunc(*args, **kwds):
+        try:
+            func(*args, **kwds)
+        except exceptions.RopeError, e:
+            lisputils.message(str(traceback.format_exc()))
+            if isinstance(e, input_exceptions):
+                lisputils.message(_exception_message(e))
+    newfunc.__name__ = func.__name__
+    newfunc.__doc__ = func.__doc__
+    return newfunc
+
+def _exception_message(e):
+    return '%s: %s' % (e.__class__.__name__, str(e))
+
+def rope_hook(hook):
+    def decorator(func):
+        func = lisphook(func)
+        func.lisp_name = _lisp_name(func)
+        func.kind = 'hook'
+        func.hook = hook
+        return func
+    return decorator
+
+
+def local_command(key=None, interaction='', shortcut=None, name=None):
+    def decorator(func, name=name):
+        func = _exception_handler(func)
+        func.kind = 'local'
+        if interaction is not None:
+            func.interaction = interaction
+        func.local_key = key
+        func.shortcut_key = shortcut
+        if name is None:
+            name = _lisp_name(func)
+        func.lisp_name = name
+        return func
+    return decorator
+
+
+def global_command(key=None, interaction=''):
+    def decorator(func):
+        func = _exception_handler(func)
+        func.kind = 'global'
+        if interaction is not None:
+            func.interaction = interaction
+        func.global_key = key
+        func.lisp_name = _lisp_name(func)
+        return func
+    return decorator

ropecommon/dialog.py

+class Data(object):
+
+    def __init__(self, prompt=None, default=None, values=None,
+                 kind=None, decode=None):
+        self.prompt = prompt
+        self.default = default
+        self.values = values
+        self.kind = kind
+        self._decode = decode
+
+    def decode(self, value):
+        if self._decode:
+            return self._decode(value)
+        return value
+
+
+class Boolean(Data):
+
+    def __init__(self, prompt=None, default=False):
+        Data.__init__(self, prompt, self._encode(default),
+                      [self._encode(True), self._encode(False)])
+
+    def _encode(self, value):
+        if value:
+            return 'yes'
+        return 'no'
+
+    def decode(self, value):
+        if value.lower() in ('yes', '1', 'true'):
+            return True
+        return False
+
+
+def show_dialog(askdata, actions, confs={}, optionals={}, initial_asking=True):
+    result = {}
+    if initial_asking:
+        for name, conf in confs.items():
+            result[name] = askdata(conf)
+    actions.append('batchset')
+    names = list(confs.keys())
+    names.extend(optionals.keys())
+    names.extend(actions)
+    base_question = Data('Choose what to do: ',
+                         default=actions[0], values=names)
+    batchset_question = Data('Batch sets: ')
+    while True:
+        response = askdata(base_question)
+        if response == '':
+            response = base_question.default
+        elif response == 'batchset':
+            sets = askdata(batchset_question)
+            for key, value in _parse_batchset(sets).items():
+                if key.endswith(':'):
+                    key = key[:-1]
+                if key in names:
+                    conf = confs.get(key, optionals.get(key))
+                    result[key] = value
+        elif response in actions:
+            break
+        else:
+            if response in confs:
+                conf = confs[response]
+            else:
+                conf = optionals[response]
+            oldvalue = result.get(response, None)
+            result[response] = askdata(conf, starting=oldvalue)
+    decoded = {}
+    all_confs = dict(confs)
+    all_confs.update(optionals)
+    for key in all_confs:
+        conf = all_confs.get(key)
+        if key in result:
+            decoded[key] = conf.decode(result[key])
+        else:
+            decoded[key] = conf.decode(conf.default)
+    return response, decoded
+
+
+def _parse_batchset(sets):
+    result = []
+    multiline = False
+    for line in sets.splitlines(True):
+        if line[0].isspace():
+            if multiline:
+                result[-1][1] += line[1:]
+        else:
+            if not line.strip():
+                continue
+            multiline= False
+            tokens = line.split(None, 1)
+            value = ''
+            if len(tokens) > 1:
+                result.append([tokens[0], tokens[1].rstrip('\r\n')])
+            else:
+                multiline = True
+                result.append([tokens[0], ''])
+    return dict(result)

ropecommon/filter.py

+from rope.base import exceptions
+
+
+def resources(project, rules):
+    """Find python files in the `project` matching `rules`
+
+    `rules` is a multi-line `str`; each line starts with either a '+'
+    or '-'.  Each '+' means include the file (or its children if it's
+    a folder) that comes after it.  '-' has the same meaning for
+    exclusion.
+
+    """
+    all = set(project.pycore.get_python_files())
+    files = None
+    for line in rules.splitlines():
+        if not line.strip():
+            continue
+        first, path = (line[0], line[1:])
+        if first not in '+-':
+            continue
+        try:
+            resource = project.get_resource(path.strip())
+        except exceptions.ResourceNotFoundError:
+            continue
+        if resource.is_folder():
+            matches = set(filter(lambda item: resource.contains(item), all))
+        else:
+            matches = set([resource])
+        if first == '+':
+            if files is None:
+                files = set()
+            files.update(matches)
+        if first == '-':
+            if files is None:
+                files = set(all)
+            files -= matches
+    if files is None:
+        return all
+    return files

ropecommon/interface.py

+import rope.base.change
+from Pymacs import lisp
+from rope.base import libutils
+from rope.contrib import codeassist, generate, autoimport, findit
+
+from ropecommon import refactor, lisputils, decorators, dialog
+
+
+class Ropemacs(object):
+
+    def __init__(self):
+        self.project = None
+        self.old_content = None
+        lisp(DEFVARS)
+
+        self._prepare_refactorings()
+        self.autoimport = None
+        self._init_ropemacs()
+        lisp(MINOR_MODE)
+
+    def init(self):
+        """Initialize rope mode"""
+
+    def _init_ropemacs(self):
+        global_prefix = lisp.ropemacs_global_prefix.value()
+        local_prefix = lisp.ropemacs_local_prefix.value()
+        enable_shortcuts = lisp['ropemacs-enable-shortcuts'].value()
+        for attrname in dir(self):
+            attr = getattr(self, attrname)
+            if not callable(attr):
+                continue
+            kind = getattr(attr, 'kind', None)
+            name = decorators._lisp_name(attr)
+            if kind == 'local':
+                local_key = getattr(attr, 'local_key', None)
+                shortcut_key = getattr(attr, 'shortcut_key', None)
+                if local_prefix is not None and local_key:
+                    self._bind_local_key(attr.lisp_name,
+                                         local_prefix + ' ' + local_key)
+                if enable_shortcuts and shortcut_key:
+                    self._bind_local_key(attr.lisp_name, shortcut_key)
+            if kind == 'global':
+                global_key = getattr(attr, 'global_key', None)
+                if global_key:
+                    key = self._key_sequence(global_prefix + ' ' + global_key)
+                    lisp.global_set_key(key, lisp[attr.lisp_name])
+            if kind == 'hook':
+                hook = getattr(attr, 'hook', None)
+                lisp.add_hook(lisp[hook], lisp[attr.lisp_name])
+        lisp.add_hook(lisp['python-mode-hook'], lisp['ropemacs-mode'])
+
+    def _prepare_refactorings(self):
+        for name in dir(refactor):
+            if not name.startswith('_') and name != 'Refactoring':
+                attr = getattr(refactor, name)
+                if isinstance(attr, type) and \
+                   issubclass(attr, refactor.Refactoring):
+                    ref_name = self._refactoring_name(attr)
+                    lisp_name = 'rope-' + ref_name.replace('_', '-')
+                    @decorators.local_command(attr.key, 'P', None, lisp_name)
+                    def do_refactor(prefix, self=self, refactoring=attr):
+                        initial_asking = prefix is None
+                        refactoring(self).show(initial_asking=initial_asking)
+                    setattr(self, ref_name, do_refactor)
+
+    def _refactoring_name(self, refactoring):
+        return refactor.refactoring_name(refactoring)
+
+    def _key_sequence(self, sequence):
+        result = []
+        for key in sequence.split():
+            if key.startswith('C-'):
+                number = ord(key[-1].upper()) - ord('A') + 1
+                result.append(chr(number))
+            elif key.startswith('M-'):
+                number = ord(key[-1].upper()) + 0x80
+                result.append(chr(number))
+            else:
+                result.append(key)
+        return ''.join(result)
+
+    @decorators.rope_hook('before-save-hook')
+    def before_save_actions(self):
+        if self.project is not None:
+            if not self._is_python_file(lisp.buffer_file_name()):
+                return
+            resource = self._get_resource()
+            if resource.exists():
+                self.old_content = resource.read()
+            else:
+                self.old_content = ''
+
+    @decorators.rope_hook('after-save-hook')
+    def after_save_actions(self):
+        if self.project is not None and self.old_content is not None:
+            libutils.report_change(self.project, lisp.buffer_file_name(),
+                                   self.old_content)
+            self.old_content = None
+
+    def _bind_local_key(self, callback, key):
+        lisp('(define-key ropemacs-local-keymap "%s" \'%s)' %
+             (self._key_sequence(key), callback))
+
+    @decorators.rope_hook('kill-emacs-hook')
+    def exiting_actions(self):
+        if self.project is not None:
+            self.close_project()
+
+    @decorators.lispfunction
+    def unload_hook(self):
+        """Unload registered hooks"""
+        for hook, function in hooks:
+            lisp.remove_hook(lisp[hook], lisp[function])
+
+    @decorators.global_command('o')
+    def open_project(self, root=None):
+        if not root:
+            root = lisputils.ask_directory('Rope project root folder: ')
+        if self.project is not None:
+            self.close_project()
+        progress = lisputils.create_progress('Opening "%s" project' % root)
+        self.project = rope.base.project.Project(root)
+        if lisp['ropemacs-enable-autoimport'].value():
+            underlined = lisp['ropemacs-autoimport-underlineds'].value()
+            self.autoimport = autoimport.AutoImport(self.project,
+                                                    underlined=underlined)
+        progress.done()
+
+    @decorators.global_command('k')
+    def close_project(self):
+        if self.project is not None:
+            progress = lisputils.create_progress('Closing "%s" project' %
+                                                 self.project.address)
+            self.project.close()
+            self.project = None
+            progress.done()
+
+    @decorators.global_command()
+    def write_project(self):
+        if self.project is not None:
+            progress = lisputils.create_progress(
+                'Writing "%s" project data to disk' % self.project.address)
+            self.project.sync()
+            progress.done()
+
+    @decorators.global_command('u')
+    def undo(self):
+        self._check_project()
+        change = self.project.history.tobe_undone
+        if change is None:
+            lisputils.message('Nothing to undo!')
+            return
+        if lisp.y_or_n_p('Undo <%s>? ' % str(change)):
+            def undo(handle):
+                for changes in self.project.history.undo(task_handle=handle):
+                    self._reload_buffers(changes, undo=True)
+            lisputils.runtask(undo, 'Undo refactoring', interrupts=False)
+
+    @decorators.global_command('r')
+    def redo(self):
+        self._check_project()
+        change = self.project.history.tobe_redone
+        if change is None:
+            lisputils.message('Nothing to redo!')
+            return
+        if lisp.y_or_n_p('Redo <%s>? ' % str(change)):
+            def redo(handle):
+                for changes in self.project.history.redo(task_handle=handle):
+                    self._reload_buffers(changes)
+            lisputils.runtask(redo, 'Redo refactoring', interrupts=False)
+
+    def _get_region(self):
+        offset1 = self._get_offset()
+        lisp.exchange_point_and_mark()
+        offset2 = self._get_offset()
+        lisp.exchange_point_and_mark()
+        return min(offset1, offset2), max(offset1, offset2)
+
+    def _get_offset(self):
+        return lisp.point() - 1
+
+    def _get_text(self):
+        if not lisp.buffer_modified_p():
+            return self._get_resource().read()
+        end = lisp.buffer_size() + 1
+        old_min = lisp.point_min()
+        old_max = lisp.point_max()
+        narrowed = (old_min != 1 or old_max != end)
+        if narrowed:
+            lisp.narrow_to_region(1, lisp.buffer_size() + 1)
+        try:
+            return lisp.buffer_string()
+        finally:
+            if narrowed:
+                lisp.narrow_to_region(old_min, old_max)
+
+    @decorators.local_command('a g', shortcut='C-c g')
+    def goto_definition(self):
+        self._check_project()
+        resource, offset = self._get_location()
+        maxfixes = lisp['ropemacs-codeassist-maxfixes'].value()
+        definition = codeassist.get_definition_location(
+            self.project, self._get_text(), offset, resource, maxfixes)
+        if tuple(definition) != (None, None):
+            lisp.push_mark()
+            self._goto_location(definition[0], definition[1])
+        else:
+            lisputils.message('Cannot find the definition!')
+
+    @decorators.local_command('a d', 'P', 'C-c d')
+    def show_doc(self, prefix):
+        self._check_project()
+        self._base_show_doc(prefix, codeassist.get_doc)
+
+    @decorators.local_command('a c', 'P')
+    def show_calltip(self, prefix):
+        self._check_project()
+        def _get_doc(project, text, offset, *args, **kwds):
+            try:
+                offset = text.rindex('(', 0, offset) - 1
+            except ValueError:
+                return None
+            return codeassist.get_calltip(project, text, offset, *args, **kwds)
+        self._base_show_doc(prefix, _get_doc)
+
+    @decorators.local_command()
+    def show_call_doc(self, prefix):
+        self._check_project()
+        lisputils.message('ropemacs: use `rope-show-calltip\' instead!')
+
+    def _base_show_doc(self, prefix, get_doc):
+        maxfixes = lisp['ropemacs-codeassist-maxfixes'].value()
+        text = self._get_text()
+        offset = self._get_offset()
+        docs = get_doc(self.project, text, offset,
+                       self._get_resource(), maxfixes)
+
+        use_minibuffer = not prefix
+        if lisp['ropemacs-separate-doc-buffer'].value():
+            use_minibuffer = not use_minibuffer
+        if use_minibuffer and docs:
+            docs = '\n'.join(docs.split('\n')[:7])
+            lisputils.message(docs)
+        else:
+            fit_lines = lisp["ropemacs-max-doc-buffer-height"].value()
+            buffer = lisputils.make_buffer('*rope-pydoc*', docs,
+                                           empty_goto=False,
+                                           fit_lines=fit_lines)
+            lisp.local_set_key('q', lisp.bury_buffer)
+        if docs is None:
+            lisputils.message('No docs avilable!')
+
+    def _base_findit(self, do_find, optionals, get_kwds):
+        self._check_project()
+        self._save_buffers()
+        resource, offset = self._get_location()
+
+        action, values = dialog.show_dialog(
+            lisputils.askdata, ['search', 'cancel'], optionals=optionals)
+        if action == 'search':
+            kwds = get_kwds(values)
+            def calculate(handle):
+                resources = refactor._resources(self.project,
+                                                values.get('resources'))
+                return do_find(self.project, resource, offset,
+                               resources=resources, task_handle=handle, **kwds)
+            result = lisputils.runtask(calculate, 'Find Occurrences')
+            text = []
+            for occurrence in result:
+                line = '%s : %s' % (occurrence.resource.path, occurrence.offset)
+                if occurrence.unsure:
+                    line += ' ?'
+                text.append(line)
+            text = '\n'.join(text) + '\n'
+            buffer = lisputils.make_buffer('*rope-occurrences*',
+                                           text, switch=True)
+            lisp.set_buffer(buffer)
+            lisp.local_set_key('\r', lisp.rope_occurrences_goto_occurrence)
+            lisp.local_set_key('q', lisp.rope_occurrences_quit)
+
+    @decorators.local_command('a f', shortcut='C-c f')
+    def find_occurrences(self):
+        optionals = {
+            'unsure': dialog.Data('Find uncertain occurrences: ',
+                                  default='no', values=['yes', 'no']),
+            'resources': dialog.Data('Files to search: '),
+            'in_hierarchy': dialog.Data(
+                    'Rename methods in class hierarchy: ',
+                    default='no', values=['yes', 'no'])}
+        def get_kwds(values):
+            return {'unsure': values.get('unsure') == 'yes',
+                    'in_hierarchy': values.get('in_hierarchy') == 'yes'}
+        self._base_findit(findit.find_occurrences, optionals, get_kwds)
+
+    @decorators.local_command('a i')
+    def find_implementations(self):
+        optionals = {'resources': dialog.Data('Files to search: ')}
+        def get_kwds(values):
+            return {}
+        self._base_findit(findit.find_implementations, optionals, get_kwds)
+
+    @decorators.interactive
+    def occurrences_goto_occurrence(self):
+        self._check_project()
+        start = lisp.line_beginning_position()
+        end = lisp.line_end_position()
+        line = lisp.buffer_substring_no_properties(start, end)
+        tokens = line.split()
+        if tokens:
+            resource = self.project.get_resource(tokens[0])
+            offset = int(tokens[2])
+            lisp.find_file_other_window(resource.real_path)
+            lisp.goto_char(offset + 1)
+            lisp.switch_to_buffer_other_window('*rope-occurrences*')
+
+    @decorators.interactive
+    def occurrences_quit(self):
+        lisputils.hide_buffer('*rope-occurrences*')
+
+    @decorators.local_command('a /', 'P', 'M-/')
+    def code_assist(self, prefix):
+        _CodeAssist(self).code_assist(prefix)
+
+    @decorators.local_command('a ?', 'P', 'M-?')
+    def lucky_assist(self, prefix):
+        _CodeAssist(self).lucky_assist(prefix)
+
+    @decorators.local_command()
+    def auto_import(self):
+        _CodeAssist(self).auto_import()
+
+    def _check_autoimport(self):
+        self._check_project()
+        if self.autoimport is None:
+            lisputils.message('autoimport is disabled; '
+                              'see `ropemacs-enable-autoimport\' variable')
+            return False
+        return True
+
+    @decorators.global_command()
+    def generate_autoimport_cache(self):
+        if not self._check_autoimport():
+            return
+        modules = lisp['ropemacs-autoimport-modules'].value()
+        modnames = []
+        if modules:
+            for i in range(len(modules)):
+                modname = modules[i]
+                if not isinstance(modname, basestring):
+                    modname = modname.value()
+                modnames.append(modname)
+        def generate(handle):
+            self.autoimport.generate_cache(task_handle=handle)
+            self.autoimport.generate_modules_cache(modules, task_handle=handle)
+        lisputils.runtask(generate, 'Generate autoimport cache')
+
+    @decorators.global_command('f', 'P')
+    def find_file(self, prefix):
+        file = self._base_find_file(prefix)
+        if file is not None:
+            lisp.find_file(file.real_path)
+
+    @decorators.global_command('4 f', 'P')
+    def find_file_other_window(self, prefix):
+        file = self._base_find_file(prefix)
+        if file is not None:
+            lisp.find_file_other_window(file.real_path)
+
+    def _base_find_file(self, prefix):
+        self._check_project()
+        if prefix:
+            files = self.project.pycore.get_python_files()
+        else:
+            files = self.project.get_files()
+        return self._ask_file(files)
+
+    def _ask_file(self, files):
+        names = []
+        for file in files:
+            names.append('<'.join(reversed(file.path.split('/'))))
+        result = lisputils.ask_values('Rope Find File: ', names, exact=True)
+        if result is not None:
+            path = '/'.join(reversed(result.split('<')))
+            file = self.project.get_file(path)
+            return file
+        lisputils.message('No file selected')
+
+    @decorators.local_command('a j')
+    def jump_to_global(self):
+        if not self._check_autoimport():
+            return
+        all_names = list(self.autoimport.get_all_names())
+        name = lisputils.ask_values('Global name: ', all_names)
+        result = dict(self.autoimport.get_name_locations(name))
+        if len(result) == 1:
+            resource = list(result.keys())[0]
+        else:
+            resource = self._ask_file(result.keys())
+        if resource:
+            self._goto_location(resource, result[resource])
+
+    @decorators.global_command('c')
+    def project_config(self):
+        self._check_project()
+        if self.project.ropefolder is not None:
+            config = self.project.ropefolder.get_child('config.py')
+            lisp.find_file(config.real_path)
+        else:
+            lisputils.message('No rope project folder found')
+
+    @decorators.global_command('n m')
+    def create_module(self):
+        def callback(sourcefolder, name):
+            return generate.create_module(self.project, name, sourcefolder)
+        self._create('module', callback)
+
+    @decorators.global_command('n p')
+    def create_package(self):
+        def callback(sourcefolder, name):
+            folder = generate.create_package(self.project, name, sourcefolder)
+            return folder.get_child('__init__.py')
+        self._create('package', callback)
+
+    @decorators.global_command('n f')
+    def create_file(self):
+        def callback(parent, name):
+            return parent.create_file(name)
+        self._create('file', callback, 'parent')
+
+    @decorators.global_command('n d')
+    def create_directory(self):
+        def callback(parent, name):
+            parent.create_folder(name)
+        self._create('directory', callback, 'parent')
+
+    @decorators.local_command()
+    def analyze_module(self):
+        """Perform static object analysis on this module"""
+        self._check_project()
+        resource = self._get_resource()
+        self.project.pycore.analyze_module(resource)
+
+    @decorators.global_command()
+    def analyze_modules(self):
+        """Perform static object analysis on all project modules"""
+        self._check_project()
+        def _analyze_modules(handle):
+            libutils.analyze_modules(self.project, task_handle=handle)
+        lisputils.runtask(_analyze_modules, 'Analyze project modules')
+
+    def _create(self, name, callback, parentname='source'):
+        self._check_project()
+        confs = {'name': dialog.Data(name.title() + ' name: ')}
+        parentname = parentname + 'folder'
+        optionals = {parentname: dialog.Data(
+                parentname.title() + ' Folder: ',
+                default=self.project.address, kind='directory')}
+        action, values = dialog.show_dialog(
+            lisputils.askdata, ['perform', 'cancel'], confs, optionals)
+        if action == 'perform':
+            parent = libutils.path_to_resource(
+                self.project, values.get(parentname, self.project.address))
+            resource = callback(parent, values['name'])
+            if resource:
+                lisp.find_file(resource.real_path)
+
+    def _goto_location(self, resource, lineno):
+        if resource:
+            if resource.project == self.project:
+                lisp.find_file(str(resource.real_path))
+            else:
+                lisp.find_file_read_only(str(resource.real_path))
+        if lineno:
+            lisp.goto_line(lineno)
+
+    def _get_location(self):
+        resource = self._get_resource()
+        offset = self._get_offset()
+        return resource, offset
+
+    def _get_resource(self, filename=None):
+        if filename is None:
+            filename = lisp.buffer_file_name()
+        if filename is None:
+            return
+        resource = libutils.path_to_resource(self.project, filename, 'file')
+        return resource
+
+    def _check_project(self):
+        if self.project is None:
+            self.open_project()
+        else:
+            self.project.validate(self.project.root)
+
+    def _reload_buffers(self, changes, undo=False):
+        self._reload_buffers_for_changes(
+            changes.get_changed_resources(),
+            self._get_moved_resources(changes, undo))
+
+    def _reload_buffers_for_changes(self, changed_resources,
+                                    moved_resources={}):
+        if self._get_resource() in moved_resources:
+            initial = None
+        else:
+            initial = lisp.current_buffer()
+        for resource in changed_resources:
+            buffer = lisp.find_buffer_visiting(str(resource.real_path))
+            if buffer:
+                if resource.exists():
+                    lisp.set_buffer(buffer)
+                    lisp.revert_buffer(False, True)
+                elif resource in moved_resources:
+                    new_resource = moved_resources[resource]
+                    lisp.kill_buffer(buffer)
+                    lisp.find_file(new_resource.real_path)
+        if initial is not None:
+            lisp.set_buffer(initial)
+
+    def _get_moved_resources(self, changes, undo=False):
+        result = {}
+        if isinstance(changes, rope.base.change.ChangeSet):
+            for change in changes.changes:
+                result.update(self._get_moved_resources(change))
+        if isinstance(changes, rope.base.change.MoveResource):
+            result[changes.resource] = changes.new_resource
+        if undo:
+            return dict([(value, key) for key, value in result.items()])
+        return result
+
+    def _save_buffers(self, only_current=False):
+        ask = lisp['ropemacs-confirm-saving'].value()
+        initial = lisp.current_buffer()
+        current_buffer = lisp.current_buffer()
+        if only_current:
+            buffers = [current_buffer]
+        else:
+            buffers = lisp.buffer_list()
+        for buffer in buffers:
+            filename = lisp.buffer_file_name(buffer)
+            if filename:
+                if self._is_python_file(filename) and \
+                   lisp.buffer_modified_p(buffer):
+                    if not ask or lisp.y_or_n_p('Save %s buffer?' % filename):
+                        lisp.set_buffer(buffer)
+                        lisp.save_buffer()
+        lisp.set_buffer(initial)
+
+    def _is_python_file(self, path):
+        resource = self._get_resource(path)
+        return (resource is not None and
+                resource.project == self.project and
+                self.project.pycore.is_python_file(resource))
+
+
+class _CodeAssist(object):
+
+    def __init__(self, interface):
+        self.interface = interface
+        self.autoimport = interface.autoimport
+        self._source = None
+        self._offset = None
+        self._starting_offset = None
+        self._starting = None
+        self._expression = None
+
+    def code_assist(self, prefix):
+        names = self._calculate_proposals()
+        if prefix is not None:
+            arg = lisp.prefix_numeric_value(prefix)
+            if arg == 0:
+                arg = len(names)
+            common_start = self._calculate_prefix(names[:arg])
+            lisp.insert(common_start[self.offset - self.starting_offset:])
+            self._starting = common_start
+            self._offset = self.starting_offset + len(common_start)
+        prompt = 'Completion for %s: ' % self.expression
+        result = lisputils.ask_values(prompt, names,
+                                      starting=self.starting, exact=None)
+        self._apply_assist(result)
+
+    def lucky_assist(self, prefix):
+        names = self._calculate_proposals()
+        selected = 0
+        if prefix is not None:
+            selected = lisp.prefix_numeric_value(prefix)
+        if 0 <= selected < len(names):
+            result = names[selected]
+        else:
+            lisputils.message('Not enough proposals!')
+            return
+        self._apply_assist(result)
+
+    def auto_import(self):
+        if not self.interface._check_autoimport():
+            return
+        name = lisp.current_word()
+        modules = self.autoimport.get_modules(name)
+        if modules:
+            if len(modules) == 1:
+                module = modules[0]
+            else:
+                module = lisputils.ask_values(
+                    'Which module to import: ', modules)
+            self._insert_import(name, module)
+        else:
+            lisputils.message('Global name %s not found!' % name)
+
+    def _apply_assist(self, assist):
+        if ' : ' in assist:
+            name, module = assist.rsplit(' : ', 1)
+            lisp.delete_region(self.starting_offset + 1, self.offset + 1)
+            lisp.insert(name)
+            self._insert_import(name, module)
+        else:
+            lisp.delete_region(self.starting_offset + 1, self.offset + 1)
+            lisp.insert(assist)
+
+    def _calculate_proposals(self):
+        self.interface._check_project()
+        resource = self.interface._get_resource()
+        maxfixes = lisp['ropemacs-codeassist-maxfixes'].value()
+        proposals = codeassist.code_assist(
+            self.interface.project, self.source, self.offset,
+            resource, maxfixes=maxfixes)
+        proposals = codeassist.sorted_proposals(proposals)
+        names = [proposal.name for proposal in proposals]
+        if self.autoimport is not None:
+            if self.starting.strip() and '.' not in self.expression:
+                import_assists = self.autoimport.import_assist(self.starting)
+                names.extend(x[0] + ' : ' + x[1] for x in import_assists)
+        return names
+
+    def _insert_import(self, name, module):
+        lineno = self.autoimport.find_insertion_line(self.source)
+        current = lisp.point()
+        lisp.goto_line(lineno)
+        newimport = 'from %s import %s\n' % (module, name)
+        lisp.insert(newimport)
+        lisp.goto_char(current + len(newimport))
+
+    def _calculate_prefix(self, names):
+        if not names:
+            return ''
+        prefix = names[0]
+        for name in names:
+            common = 0
+            for c1, c2 in zip(prefix, name):
+                if c1 != c2 or ' ' in (c1, c2):
+                    break
+                common += 1
+            prefix = prefix[:common]
+        return prefix
+
+    @property
+    def offset(self):
+        if self._offset is None:
+            self._offset = self.interface._get_offset()
+        return self._offset
+
+    @property
+    def source(self):
+        if self._source is None:
+            self._source = self.interface._get_text()
+        return self._source
+
+    @property
+    def starting_offset(self):
+        if self._starting_offset is None:
+            self._starting_offset = codeassist.starting_offset(self.source,
+                                                               self.offset)
+        return self._starting_offset
+
+    @property
+    def starting(self):
+        if self._starting is None:
+            self._starting = self.source[self.starting_offset:self.offset]
+        return self._starting
+
+    @property
+    def expression(self):
+        if self._expression is None:
+            self._expression = codeassist.starting_expression(self.source,
+                                                              self.offset)
+        return self._expression
+
+
+DEFVARS = """\
+(defgroup ropemacs nil
+  "ropemacs, an emacs plugin for rope."
+  :link '(url-link "http://rope.sourceforge.net/ropemacs.html")
+  :prefix "rope-")
+
+(defcustom ropemacs-confirm-saving t
+  "Shows whether to confirm saving modified buffers before refactorings.
+
+If non-nil, you have to confirm saving all modified
+python files before refactorings; otherwise they are
+saved automatically.")
+
+(defcustom ropemacs-codeassist-maxfixes 1
+  "The number of errors to fix before code-assist.
+
+How many errors to fix, at most, when proposing code completions.")
+
+(defcustom ropemacs-separate-doc-buffer t
+  "Should `rope-show-doc' use a separate buffer or the minibuffer.")
+(defcustom ropemacs-max-doc-buffer-height 22
+  "The maximum buffer height for `rope-show-doc'.")
+
+(defcustom ropemacs-enable-autoimport 'nil
+  "Specifies whether autoimport should be enabled.")
+(defcustom ropemacs-autoimport-modules nil
+  "The name of modules whose global names should be cached.
+
+The `rope-generate-autoimport-cache' reads this list and fills its
+cache.")
+(defcustom ropemacs-autoimport-underlineds 'nil
+  "If set, autoimport will cache names starting with underlines, too.")
+
+(defcustom ropemacs-completing-read-function (if (and (boundp 'ido-mode)
+                                                      ido-mode)
+                                                 'ido-completing-read
+                                               'completing-read)
+  "Function to call when prompting user to choose between a list of options.
+This should take the same arguments as `completing-read'.
+Possible values are `completing-read' and `ido-completing-read'.
+Note that you must set `ido-mode' if using`ido-completing-read'."
+  :type 'function)
+
+(make-obsolete-variable
+  'rope-confirm-saving 'ropemacs-confirm-saving)
+(make-obsolete-variable
+  'rope-code-assist-max-fixes 'ropemacs-codeassist-maxfixes)
+
+(defcustom ropemacs-local-prefix "C-c r"
+  "The prefix for ropemacs refactorings.
+
+Use nil to prevent binding keys.")
+
+(defcustom ropemacs-global-prefix "C-x p"
+  "The prefix for ropemacs project commands.
+
+Use nil to prevent binding keys.")
+
+(defcustom ropemacs-enable-shortcuts 't
+  "Shows whether to bind ropemacs shortcuts keys.
+
+If non-nil it binds:
+
+================  ============================
+Key               Command
+================  ============================
+M-/               rope-code-assist
+C-c g             rope-goto-definition
+C-c d             rope-show-doc
+C-c f             rope-find-occurrences
+M-?               rope-lucky-assist
+================  ============================
+")
+
+(defvar ropemacs-local-keymap (make-sparse-keymap))
+
+(easy-menu-define ropemacs-mode-menu ropemacs-local-keymap
+"`ropemacs' menu"
+                  '("Rope"
+                    ["Code assist" rope-code-assist t]
+                    ["Lucky assist" rope-lucky-assist t]
+                    ["Goto definition" rope-goto-definition t]
+                    ["Jump to global" rope-jump-to-global t]
+                    ["Show documentation" rope-show-doc t]
+                    ["Find Occurrences" rope-find-occurrences t]
+                    ["Analyze module" rope-analyze-module t]
+                    ("Refactor"
+                      ["Inline" rope-inline t]
+                      ["Extract Variable" rope-extract-variable t]
+                      ["Extract Method" rope-extract-method t]
+                      ["Organize Imports" rope-organize-imports t]
+                      ["Rename" rope-rename t]
+                      ["Move" rope-move t]
+                      ["Restructure" rope-restructure t]
+                      ["Use Function" rope-use-function t]
+                      ["Introduce Factory" rope-introduce-factory t]
+                      ("Generate"
+                        ["Class" rope-generate-class t]
+                        ["Function" rope-generate-function t]
+                        ["Module" rope-generate-module t]
+                        ["Package" rope-generate-package t]
+                        ["Variable" rope-generate-variable t]
+                      )
+                      ("Module"
+                        ["Module to Package" rope-module-to-package t]
+                        ["Rename Module" rope-rename-current-module t]
+                        ["Move Module" rope-move-current-module t]
+                      )
+                      "--"
+                      ["Undo" rope-undo t]
+                      ["Redo" rope-redo t]
+                    )
+                    ("Project"
+                      ["Open project" rope-open-project t]
+                      ["Close project" rope-close-project t]
+                      ["Find file" rope-find-file t]
+                      ["Open project config" rope-project-config t]
+                    )
+                    ("Create"
+                      ["Module" rope-create-module t]
+                      ["Package" rope-create-package t]
+                      ["File" rope-create-file t]
+                      ["Directory" rope-create-directory t]
+                    )
+                    ))
+
+(provide 'ropemacs)
+"""
+
+MINOR_MODE = """\
+(define-minor-mode ropemacs-mode
+ "ropemacs, rope in emacs!" nil " Rope" ropemacs-local-keymap
+  :global nil)
+)
+"""

ropecommon/lisputils.py

+import traceback
+
+from Pymacs import lisp
+from rope.base import taskhandle, exceptions
+
+
+def yes_or_no(prompt):
+    return lisp.yes_or_no_p(prompt)
+
+
+def make_buffer(name, contents, empty_goto=True,
+                switch=False, window='other', modes=[],
+                fit_lines=None):
+    """Make an emacs buffer
+
+    `window` can be one of `None`, 'current' or 'other'.
+
+    """
+    new_buffer = lisp.get_buffer_create(name)
+    lisp.set_buffer(new_buffer)
+    lisp.toggle_read_only(-1)
+    lisp.erase_buffer()
+    if contents or empty_goto:
+        lisp.insert(contents)
+        for mode in modes:
+            lisp[mode + '-mode']()
+        lisp.buffer_disable_undo(new_buffer)
+        lisp.toggle_read_only(1)
+        if switch:
+            if window == 'current':
+                lisp.switch_to_buffer(new_buffer)
+            else:
+                lisp.switch_to_buffer_other_window(new_buffer)
+            lisp.goto_char(lisp.point_min())
+        elif window == 'other':
+            new_window = lisp.display_buffer(new_buffer)
+            lisp.set_window_point(new_window, lisp.point_min())
+            if fit_lines and lisp.fboundp(lisp['fit-window-to-buffer']):
+                lisp.fit_window_to_buffer(new_window, fit_lines)
+                lisp.bury_buffer(new_buffer)
+    return new_buffer
+
+
+def hide_buffer(name, delete=True):
+    buffer = lisp.get_buffer(name)
+    if buffer is not None:
+        window = lisp.get_buffer_window(buffer)
+        if window is not None:
+            lisp.bury_buffer(buffer)
+            if delete:
+                lisp.delete_window(window)
+            else:
+                if lisp.buffer_name(lisp.current_buffer()) == name:
+                    lisp.switch_to_buffer(None)
+
+
+class RunTask(object):
+
+    def __init__(self, task, name, interrupts=True):
+        self.task = task
+        self.name = name
+        self.interrupts = interrupts
+
+    def __call__(self):
+        handle = taskhandle.TaskHandle(name=self.name)
+        progress = create_progress(self.name)
+        def update_progress():
+            jobset = handle.current_jobset()
+            if jobset:
+                percent = jobset.get_percent_done()
+                if percent is not None:
+                    progress.update(percent)
+        handle.add_observer(update_progress)
+        result = self.task(handle)
+        progress.done()
+        return result
+
+
+def runtask(command, name, interrupts=True):
+    return RunTask(command, name, interrupts)()
+
+def create_progress(name):
+    if lisp.fboundp(lisp['make-progress-reporter']):
+        progress = _LispProgress(name)
+    else:
+        progress = _OldProgress(name)
+    return progress
+
+
+class _LispProgress(object):
+
+    def __init__(self, name):
+        self.progress = lisp.make_progress_reporter('%s ... ' % name, 0, 100)
+
+    def update(self, percent):
+        lisp.progress_reporter_update(self.progress, percent)
+
+    def done(self):
+        lisp.progress_reporter_done(self.progress)
+
+class _OldProgress(object):
+
+    def __init__(self, name):
+        self.name = name
+        self.update(0)
+
+    def update(self, percent):
+        if percent != 0:
+            message('%s ... %s%%%%' % (self.name, percent))
+        else:
+            message('%s ... ' % self.name)
+
+    def done(self):
+        message('%s ... done' % self.name)
+
+
+def message(message):
+    lisp.message(message)
+
+
+def askdata(data, starting=None):
+    """`data` is a `ropemacs.dialog.Data` object"""
+    ask_func = ask
+    ask_args = {'prompt': data.prompt, 'starting': starting,
+                'default': data.default}
+    if data.values:
+        ask_func = ask_values
+        ask_args['values'] = data.values
+    elif data.kind == 'directory':
+        ask_func = ask_directory
+    return ask_func(**ask_args)
+
+
+def ask_values(prompt, values, default=None, starting=None, exact=True):
+    if _emacs_version() < 22:
+        values = [[value, value] for value in values]
+    if exact and default is not None:
+        prompt = prompt + ('[%s] ' % default)
+    reader = lisp['ropemacs-completing-read-function'].value()
+    result = reader(prompt, values, None, exact, starting)
+    if result == '' and exact:
+        return default
+    return result
+
+
+def ask(prompt, default=None, starting=None):
+    if default is not None:
+        prompt = prompt + ('[%s] ' % default)
+    result = lisp.read_from_minibuffer(prompt, starting, None, None,
+                                        None, default, None)
+    if result == '' and default is not None:
+        return default
+    return result
+
+def ask_directory(prompt, default=None, starting=None):
+    if default is not None:
+        prompt = prompt + ('[%s] ' % default)
+    if lisp.fboundp(lisp['read-directory-name']):
+        result = lisp.read_directory_name(prompt, starting, default)
+    else:
+        result = lisp.read_file_name(prompt, starting, default)
+    if result == '' and default is not None:
+        return default
+    return result
+
+def _emacs_version():
+    return int(lisp['emacs-version'].value().split('.')[0])

ropecommon/refactor.py

+import re
+
+import rope.base.change
+import rope.contrib.generate
+import rope.refactor.change_signature
+import rope.refactor.extract
+import rope.refactor.inline
+import rope.refactor.introduce_factory
+import rope.refactor.method_object
+import rope.refactor.move
+import rope.refactor.rename
+import rope.refactor.restructure
+import rope.refactor.usefunction
+
+from ropecommon import dialog, lisputils, filter
+
+
+class Refactoring(object):
+    key = None
+    confs = {}
+    optionals = {}
+    saveall = True
+
+    def __init__(self, interface):
+        self.interface = interface
+
+    def show(self, initial_asking=True):
+        self.interface._check_project()
+        self.interface._save_buffers(only_current=not self.saveall)
+        self._create_refactoring()
+        action, result = dialog.show_dialog(
+            lisputils.askdata, ['perform', 'preview', 'cancel'],
+            self._get_confs(), self._get_optionals(),
+            initial_asking=initial_asking)
+        if action == 'cancel':
+            lisputils.message('Cancelled!')
+            return
+        def calculate(handle):
+            return self._calculate_changes(result, handle)
+        name = 'Calculating %s changes' % self.name
+        changes = lisputils.runtask(calculate, name=name)
+        if action == 'perform':
+            self._perform(changes)
+        if action == 'preview':
+            if changes is not None:
+                diffs = str(changes.get_description())
+                lisputils.make_buffer('*rope-preview*', diffs, switch=True,
+                                      modes=['diff'], window='current')
+                if lisputils.yes_or_no('Do the changes? '):
+                    self._perform(changes)
+                else:
+                    lisputils.message('Thrown away!')
+                lisputils.hide_buffer('*rope-preview*', delete=False)
+            else:
+                lisputils.message('No changes!')
+
+    @property
+    def project(self):
+        return self.interface.project
+
+    @property
+    def resource(self):
+        return self.interface._get_resource()
+
+    @property
+    def offset(self):
+        return self.interface._get_offset()
+
+    @property
+    def region(self):
+        return self.interface._get_region()
+
+    @property
+    def name(self):
+        return refactoring_name(self.__class__)
+
+    def _calculate_changes(self, option_values, task_handle):
+        pass
+
+    def _create_refactoring(self):
+        pass
+
+    def _done(self):
+        pass
+
+    def _perform(self, changes):
+        if changes is None:
+            lisputils.message('No changes!')
+            return
+        def perform(handle, self=self, changes=changes):
+            self.project.do(changes, task_handle=handle)
+            self.interface._reload_buffers(changes)
+            self._done()
+        lisputils.runtask(perform, 'Making %s changes' % self.name,
+                          interrupts=False)
+        lisputils.message(str(changes.description) + ' finished')
+
+    def _get_confs(self):
+        return self.confs
+
+    def _get_optionals(self):
+        return self.optionals
+
+    @property
+    def resources_option(self):
+        return dialog.Data('Files to apply this refactoring on: ',
+                           decode=self._decode_resources)
+
+    def _decode_resources(self, value):
+        return _resources(self.project, value)
+
+
+class Rename(Refactoring):
+    key = 'r'
+
+    saveall = True
+
+    def __init__(self, interface):
+        self.interface = interface
+
+    def _create_refactoring(self):
+        self.renamer = rope.refactor.rename.Rename(
+            self.project, self.resource, self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        return self.renamer.get_changes(task_handle=task_handle, **values)
+
+    def _get_optionals(self):
+        opts = {}
+        opts['docs'] = dialog.Boolean('Search comments and docs: ', True)
+        if self.renamer.is_method():
+            opts['in_hierarchy'] = dialog.Boolean('Rename methods in '
+                                                  'class hierarchy: ')
+        opts['resources'] = self.resources_option
+        opts['unsure'] = dialog.Data('Unsure occurrences: ',
+                                     decode=self._decode_unsure,
+                                     values=['ignore', 'match'],
+                                     default='ignore')
+        return opts
+
+    def _get_confs(self):
+        oldname = str(self.renamer.get_old_name())
+        return {'new_name': dialog.Data('New name: ', default=oldname)}
+
+    def _decode_unsure(self, value):
+        unsure = value == 'match'
+        return lambda occurrence: unsure
+
+
+class RenameCurrentModule(Rename):
+    key = '1 r'
+    offset = None
+
+
+class Restructure(Refactoring):
+    key = 'x'
+    confs = {'pattern': dialog.Data('Restructuring pattern: '),
+             'goal': dialog.Data('Restructuring goal: ')}
+
+    def _calculate_changes(self, values, task_handle):
+        restructuring = rope.refactor.restructure.Restructure(
+            self.project, values['pattern'], values['goal'],
+            args=values['args'], imports=values['imports'])
+        return restructuring.get_changes(resources=values['resources'],
+                                         task_handle=task_handle)
+
+    def _get_optionals(self):
+        return {
+            'args': dialog.Data('Arguments: ', decode=self._decode_args),
+            'imports': dialog.Data('Imports: ', decode=self._decode_imports),
+            'resources': self.resources_option}
+
+    def _decode_args(self, value):
+        if value:
+            args = {}
+            for raw_check in value.split('\n'):
+                if raw_check:
+                    key, value = raw_check.split(':', 1)
+                    args[key.strip()] = value.strip()
+            return args
+
+    def _decode_imports(self, value):
+        if value:
+            return [line.strip() for line in value.split('\n')]
+
+
+class UseFunction(Refactoring):
+    key = 'u'
+
+    def _create_refactoring(self):
+        self.user = rope.refactor.usefunction.UseFunction(
+            self.project, self.resource, self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        return self.user.get_changes(task_handle=task_handle, **values)
+
+    def _get_optionals(self):
+        return {'resources': self.resources_option}
+
+
+class Move(Refactoring):
+    key = 'v'
+
+    def _create_refactoring(self):
+        self.mover = rope.refactor.move.create_move(self.project,
+                                                    self.resource,
+                                                    self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        destination = values['destination']
+        resources = values.get('resources', None)
+        if isinstance(self.mover, rope.refactor.move.MoveGlobal):
+            return self._move_global(destination, resources, task_handle)
+        if isinstance(self.mover, rope.refactor.move.MoveModule):
+            return self._move_module(destination, resources, task_handle)
+        if isinstance(self.mover, rope.refactor.move.MoveMethod):
+            return self._move_method(destination, resources, task_handle)
+
+    def _move_global(self, dest, resources, handle):
+        destination = self.project.pycore.find_module(dest)
+        return self.mover.get_changes(
+            destination, resources=resources, task_handle=handle)
+
+    def _move_method(self, dest, resources, handle):
+        return self.mover.get_changes(
+            dest, self.mover.get_method_name(),
+            resources=resources, task_handle=handle)
+
+    def _move_module(self, dest, resources, handle):
+        destination = self.project.pycore.find_module(dest)
+        return self.mover.get_changes(
+            destination, resources=resources, task_handle=handle)
+
+    def _get_confs(self):
+        if isinstance(self.mover, rope.refactor.move.MoveGlobal):
+            prompt = 'Destination module: '
+        if isinstance(self.mover, rope.refactor.move.MoveModule):
+            prompt = 'Destination package: '
+        if isinstance(self.mover, rope.refactor.move.MoveMethod):
+            prompt = 'Destination attribute: '
+        return {'destination': dialog.Data(prompt)}
+
+    def _get_optionals(self):
+        return {'resources': self.resources_option}
+
+
+class MoveCurrentModule(Move):
+    key = '1 v'
+    offset = None
+
+
+class ModuleToPackage(Refactoring):
+    key = '1 p'
+    saveall = False
+
+    def _create_refactoring(self):
+        self.packager = rope.refactor.ModuleToPackage(
+            self.project, self.resource)
+
+    def _calculate_changes(self, values, task_handle):
+        return self.packager.get_changes()
+
+
+class Inline(Refactoring):
+    key = 'i'
+
+    def _create_refactoring(self):
+        self.inliner = rope.refactor.inline.create_inline(
+            self.project, self.resource, self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        return self.inliner.get_changes(task_handle=task_handle, **values)
+
+    def _get_optionals(self):
+        opts = {'resources': self.resources_option}
+        if self.inliner.get_kind() == 'parameter':
+            opts['in_hierarchy'] = dialog.Boolean(
+                'Apply on all matching methods in class hierarchy: ', False)
+        else:
+            opts['remove'] = dialog.Boolean('Remove the definition: ', True)
+            opts['only_current'] = dialog.Boolean('Inline this '
+                                                  'occurrence only: ')
+        return opts
+
+
+class _Extract(Refactoring):
+    saveall = False
+    optionals = {'similar': dialog.Boolean('Extract similar pieces: ', True),
+                 'global_': dialog.Boolean('Make global: ')}
+    kind = None
+    constructor = None
+
+    def _create_refactoring(self):
+        start, end = self.region
+        self.extractor = self.constructor(self.project,
+                                          self.resource, start, end)
+
+    def _calculate_changes(self, values, task_handle):
+        similar = values.get('similar')
+        global_ = values.get('global_')
+        return self.extractor.get_changes(values['name'], similar=similar,
+                                          global_=global_)
+
+    def _get_confs(self):
+        return {'name': dialog.Data('Extracted %s name: ' % self.kind)}
+
+
+class ExtractVariable(_Extract):
+    key = 'l'
+    kind = 'variable'
+    constructor = rope.refactor.extract.ExtractVariable
+
+
+class ExtractMethod(_Extract):
+    key = 'm'
+    kind = 'method'
+    constructor = rope.refactor.extract.ExtractMethod
+
+
+class OrganizeImports(Refactoring):
+    key = 'o'
+    saveall = False
+
+    def _create_refactoring(self):
+        self.organizer = rope.refactor.ImportOrganizer(self.project)
+
+    def _calculate_changes(self, values, task_handle):
+        return self.organizer.organize_imports(self.resource)
+
+
+class MethodObject(Refactoring):
+    saveall = False
+    confs = {'classname': dialog.Data('New class name: ',
+                                      default='_ExtractedClass')}
+
+    def _create_refactoring(self):
+        self.objecter = rope.refactor.method_object.MethodObject(
+            self.project, self.resource, self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        classname = values.get('classname')
+        return self.objecter.get_changes(classname)
+
+
+class IntroduceFactory(Refactoring):
+    saveall = True
+    key = 'f'
+
+    def _create_refactoring(self):
+        self.factory = rope.refactor.introduce_factory.IntroduceFactory(
+            self.project, self.resource, self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        return self.factory.get_changes(task_handle=task_handle, **values)
+
+    def _get_confs(self):
+        default = 'create_%s' % self.factory.old_name.lower()
+        return {'factory_name': dialog.Data('Factory name: ', default)}
+
+    def _get_optionals(self):
+        return {'global_factory': dialog.Boolean('Make global: ', True),
+                'resources': self.resources_option}
+
+
+class ChangeSignature(Refactoring):
+    saveall = True
+    key = 's'
+
+    def _create_refactoring(self):
+        self.changer = rope.refactor.change_signature.ChangeSignature(
+            self.project, self.resource, self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        signature = values.get('signature')
+        info = self.changer.get_definition_info()
+        args = re.sub(r'[\s\(\)]+', '', signature).split(',')
+        olds = [arg[0] for arg in info.args_with_defaults]
+
+        changers = []
+        for arg in list(olds):
+            if arg in args:
+                continue
+            changers.append(rope.refactor.change_signature.
+                            ArgumentRemover(olds.index(arg)))
+            olds.remove(arg)
+
+        order = []
+        for index, arg in enumerate(args):
+            if arg not in olds:
+                changers.append(rope.refactor.change_signature.
+                                ArgumentAdder(index, arg))
+                olds.insert(index, arg)
+            order.append(olds.index(arg))
+        changers.append(rope.refactor.change_signature.
+                        ArgumentReorderer(order, autodef='None'))
+
+        del values['signature']
+        return self.changer.get_changes(changers, task_handle=task_handle,
+                                        **values)
+
+    def _get_confs(self):
+        info = self.changer.get_definition_info()
+        args = []
+        for arg, default in info.args_with_defaults:
+            args.append(arg)
+        signature = '(' + ', '.join(args) + ')'
+        return {'signature': dialog.Data('Change the signature: ',
+                                         default=signature)}
+
+    def _get_optionals(self):
+        opts = {'resources': self.resources_option}
+        if self.changer.is_method():
+            opts['in_hierarchy'] = dialog.Boolean('Rename methods in '
+                                                  'class hierarchy: ')
+        return opts
+
+
+class _GenerateElement(Refactoring):
+
+    def _create_refactoring(self):
+        kind = self.name.split('_')[-1]
+        self.generator = rope.contrib.generate.create_generate(
+            kind, self.project, self.resource, self.offset)
+
+    def _calculate_changes(self, values, task_handle):
+        return self.generator.get_changes()
+
+    def _done(self):
+        resource, lineno = self.generator.get_location()
+        self.interface._goto_location(resource, lineno)
+
+
+class GenerateVariable(_GenerateElement):
+    key = 'n v'
+
+
+class GenerateFunction(_GenerateElement):
+    key = 'n f'
+
+
+class GenerateClass(_GenerateElement):
+    key = 'n c'
+
+
+class GenerateModule(_GenerateElement):
+    key = 'n m'
+
+
+class GeneratePackage(_GenerateElement):
+    key = 'n p'
+
+
+def refactoring_name(refactoring):
+    classname = refactoring.__name__
+    result = []
+    for c in classname:
+        if result and c.isupper():
+            result.append('_')
+        result.append(c.lower())
+    name = ''.join(result)
+    return name
+
+def _resources(project, text):
+    if text is None or text.strip() == '':
+        return None
+    return filter.resources(project, text)
+"""ropemacs, an emacs mode for using rope refactoring library"""
+
+import ropecommon.dialog
+import ropecommon.interface
+import ropecommon.lisputils
+import traceback
+
+from Pymacs import lisp
+from rope.base import taskhandle, exceptions
+
+
+def _register_functions(interface):
+    for attrname in dir(interface):
+        attr = getattr(interface, attrname)
+        if hasattr(attr, 'interaction') or hasattr(attr, 'lisp'):
+            globals()[attrname] = attr
+
+
+_interface = ropecommon.interface.Ropemacs()
+_register_functions(_interface)
+_interface.init()

ropemacs/__init__.py

-"""ropemacs, an emacs mode for using rope refactoring library"""
-
-import ropemacs.dialog
-import ropemacs.interface
-import ropemacs.lisputils
-
-
-COPYRIGHT = """\
-Copyright (C) 2007-2008 Ali Gholami Rudi
-
-This program is free software; you can redistribute it and/or modify it
-under the terms of GNU General Public License as published by the
-Free Software Foundation; either version 2 of the license, or (at your
-opinion) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details."""
-
-
-def _register_functions(interface):
-    for attrname in dir(interface):
-        attr = getattr(interface, attrname)
-        if hasattr(attr, 'interaction') or hasattr(attr, 'lisp'):
-            globals()[attrname] = attr
-
-
-def init():
-    ropemacs.lisputils.lisp.warn(
-        'Calling (rope-init) is no longer needed.')
-
-_interface = ropemacs.interface.Ropemacs()
-_register_functions(_interface)
-_interface.init()

ropemacs/decorators.py

-import traceback
-
-from rope.base import exceptions
-
-from ropemacs import lisputils
-
-
-def lisphook(func):
-    def newfunc(*args, **kwds):
-        try:
-            func(*args, **kwds)
-        except Exception, e:
-            trace = str(traceback.format_exc())
-            lisputils.message(
-                '%s\nIgnored an exception in ropemacs hook: %s' %
-                (trace, _exception_message(e)))
-    newfunc.lisp = None
-    newfunc.__name__ = func.__name__
-    newfunc.__doc__ = func.__doc__
-    return newfunc
-
-
-def interactive(func):
-    func.interaction = ''
-    return func
-
-
-def lispfunction(func):
-    func.lisp = None
-    return func
-
-
-input_exceptions = (exceptions.RefactoringError,
-                    exceptions.ModuleSyntaxError,
-                    exceptions.BadIdentifierError)
-
-def _lisp_name(func):
-    return 'rope-' + func.__name__.replace('_', '-')
-
-def _exception_handler(func):
-    def newfunc(*args, **kwds):
-        try:
-            func(*args, **kwds)
-        except exceptions.RopeError, e:
-            lisputils.message(str(traceback.format_exc()))
-            if isinstance(e, input_exceptions):
-                lisputils.message(_exception_message(e))
-    newfunc.__name__ = func.__name__
-    newfunc.__doc__ = func.__doc__
-    return newfunc
-
-def _exception_message(e):
-    return '%s: %s' % (e.__class__.__name__, str(e))
-
-def rope_hook(hook):
-    def decorator(func):
-        func = lisphook(func)
-        func.lisp_name = _lisp_name(func)
-        func.kind = 'hook'
-        func.hook = hook
-        return func
-    return decorator
-
-
-def local_command(key=None, interaction='', shortcut=None, name=None):
-    def decorator(func, name=name):
-        func = _exception_handler(func)
-        func.kind = 'local'
-        if interaction is not None:
-            func.interaction = interaction
-        func.local_key = key
-        func.shortcut_key = shortcut
-        if name is None:
-            name = _lisp_name(func)
-        func.lisp_name = name
-        return func
-    return decorator
-
-
-def global_command(key=None, interaction=''):
-    def decorator(func):
-        func = _exception_handler(func)
-        func.kind = 'global'
-        if interaction is not None:
-            func.interaction = interaction
-        func.global_key = key
-        func.lisp_name = _lisp_name(func)
-        return func
-    return decorator

ropemacs/dialog.py

-class Data(object):
-
-    def __init__(self, prompt=None, default=None, values=None,
-                 kind=None, decode=None):
-        self.prompt = prompt
-        self.default = default
-        self.values = values
-        self.kind = kind
-        self._decode = decode
-
-    def decode(self, value):
-        if self._decode:
-            return self._decode(value)
-        return value
-
-
-class Boolean(Data):
-
-    def __init__(self, prompt=None, default=False):
-        Data.__init__(self, prompt, self._encode(default),
-                      [self._encode(True), self._encode(False)])
-
-    def _encode(self, value):
-        if value:
-            return 'yes'
-        return 'no'
-
-    def decode(self, value):
-        if value.lower() in ('yes', '1', 'true'):
-            return True
-        return False
-
-
-def show_dialog(askdata, actions, confs={}, optionals={}, initial_asking=True):
-    result = {}
-    if initial_asking:
-        for name, conf in confs.items():
-            result[name] = askdata(conf)
-    actions.append('batchset')
-    names = list(confs.keys())
-    names.extend(optionals.keys())
-    names.extend(actions)
-    base_question = Data('Choose what to do: ',
-                         default=actions[0], values=names)
-    batchset_question = Data('Batch sets: ')
-    while True:
-        response = askdata(base_question)
-        if response == '':
-            response = base_question.default
-        elif response == 'batchset':
-            sets = askdata(batchset_question)
-            for key, value in _parse_batchset(sets).items():
-                if key.endswith(':'):
-                    key = key[:-1]
-                if key in names:
-                    conf = confs.get(key, optionals.get(key))
-                    result[key] = value
-        elif response in actions:
-            break
-        else:
-            if response in confs:
-                conf = confs[response]
-            else:
-                conf = optionals[response]
-            oldvalue = result.get(response, None)
-            result[response] = askdata(conf, starting=oldvalue)
-    decoded = {}
-    all_confs = dict(confs)
-    all_confs.update(optionals)
-    for key in all_confs:
-        conf = all_confs.get(key)
-        if key in result:
-            decoded[key] = conf.decode(result[key])
-        else:
-            decoded[key] = conf.decode(conf.default)
-    return response, decoded
-
-
-def _parse_batchset(sets):
-    result = []
-    multiline = False
-    for line in sets.splitlines(True):
-        if line[0].isspace():
-            if multiline:
-                result[-1][1] += line[1:]
-        else:
-            if not line.strip():
-                continue
-            multiline= False
-            tokens = line.split(None, 1)
-            value = ''
-            if len(tokens) > 1:
-                result.append([tokens[0], tokens[1].rstrip('\r\n')])
-            else:
-                multiline = True
-                result.append([tokens[0], ''])
-    return dict(result)

ropemacs/filter.py

-from rope.base import exceptions
-
-
-def resources(project, rules):
-    """Find python files in the `project` matching `rules`
-
-    `rules` is a multi-line `str`; each line starts with either a '+'
-    or '-'.  Each '+' means include the file (or its children if it's
-    a folder) that comes after it.  '-' has the same meaning for
-    exclusion.
-
-    """
-    all = set(project.pycore.get_python_files())
-    files = None
-    for line in rules.splitlines():
-        if not line.strip():
-            continue
-        first, path = (line[0], line[1:])
-        if first not in '+-':
-            continue
-        try:
-            resource = project.get_resource(path.strip())
-        except exceptions.ResourceNotFoundError:
-            continue
-        if resource.is_folder():
-            matches = set(filter(lambda item: resource.contains(item), all))
-        else:
-            matches = set([resource])
-        if first == '+':
-            if files is None:
-                files = set()
-            files.update(matches)
-        if first == '-':
-            if files is None:
-                files = set(all)
-            files -= matches
-    if files is None:
-        return all
-    return files

ropemacs/interface.py

-import rope.base.change
-from Pymacs import lisp
-from rope.base import libutils
-from rope.contrib import codeassist, generate, autoimport, findit
-
-from ropemacs import refactor, lisputils, decorators, dialog
-
-
-class Ropemacs(object):
-
-    def __init__(self):
-        self.project = None
-        self.old_content = None
-        lisp(DEFVARS)
-
-        self._prepare_refactorings()
-        self.autoimport = None
-        self._init_ropemacs()
-        lisp(MINOR_MODE)
-
-    def init(self):
-        """Initialize rope mode"""
-
-    def _init_ropemacs(self):
-        global_prefix = lisp.ropemacs_global_prefix.value()
-        local_prefix = lisp.ropemacs_local_prefix.value()
-        enable_shortcuts = lisp['ropemacs-enable-shortcuts'].value()
-        for attrname in dir(self):
-            attr = getattr(self, attrname)
-            if not callable(attr):
-                continue
-            kind = getattr(attr, 'kind', None)
-            name = decorators._lisp_name(attr)
-            if kind == 'local':
-                local_key = getattr(attr, 'local_key', None)
-                shortcut_key = getattr(attr, 'shortcut_key', None)
-                if local_prefix is not None and local_key:
-                    self._bind_local_key(attr.lisp_name,
-                                         local_prefix + ' ' + local_key)
-                if enable_shortcuts and shortcut_key:
-                    self._bind_local_key(attr.lisp_name, shortcut_key)
-            if kind == 'global':
-                global_key = getattr(attr, 'global_key', None)
-                if global_key:
-                    key = self._key_sequence(global_prefix + ' ' + global_key)
-                    lisp.global_set_key(key, lisp[attr.lisp_name])
-            if kind == 'hook':
-                hook = getattr(attr, 'hook', None)
-                lisp.add_hook(lisp[hook], lisp[attr.lisp_name])
-        lisp.add_hook(lisp['python-mode-hook'], lisp['ropemacs-mode'])
-
-    def _prepare_refactorings(self):
-        for name in dir(refactor):
-            if not name.startswith('_') and name != 'Refactoring':
-                attr = getattr(refactor, name)
-                if isinstance(attr, type) and \
-                   issubclass(attr, refactor.Refactoring):
-                    ref_name = self._refactoring_name(attr)
-                    lisp_name = 'rope-' + ref_name.replace('_', '-')
-                    @decorators.local_command(attr.key, 'P', None, lisp_name)
-                    def do_refactor(prefix, self=self, refactoring=attr):
-                        initial_asking = prefix is None
-                        refactoring(self).show(initial_asking=initial_asking)
-                    setattr(self, ref_name, do_refactor)
-
-    def _refactoring_name(self, refactoring):
-        return refactor.refactoring_name(refactoring)
-
-    def _key_sequence(self, sequence):
-        result = []
-        for key in sequence.split():
-            if key.startswith('C-'):
-                number = ord(key[-1].upper()) - ord('A') + 1
-                result.append(chr(number))
-            elif key.startswith('M-'):
-                number = ord(key[-1].upper()) + 0x80
-                result.append(chr(number))
-            else:
-                result.append(key)
-        return ''.join(result)
-
-    @decorators.rope_hook('before-save-hook')
-    def before_save_actions(self):
-        if self.project is not None:
-            if not self._is_python_file(lisp.buffer_file_name()):
-                return
-            resource = self._get_resource()
-            if resource.exists():
-                self.old_content = resource.read()
-            else:
-                self.old_content = ''
-
-    @decorators.rope_hook('after-save-hook')
-    def after_save_actions(self):
-        if self.project is not None and self.old_content is not None:
-            libutils.report_change(self.project, lisp.buffer_file_name(),
-                                   self.old_content)
-            self.old_content = None
-
-    def _bind_local_key(self, callback, key):
-        lisp('(define-key ropemacs-local-keymap "%s" \'%s)' %
-             (self._key_sequence(key), callback))
-
-    @decorators.rope_hook('kill-emacs-hook')
-    def exiting_actions(self):
-        if self.project is not None:
-            self.close_project()
-
-    @decorators.lispfunction
-    def unload_hook(self):
-        """Unload registered hooks"""
-        for hook, function in hooks:
-            lisp.remove_hook(lisp[hook], lisp[function])
-
-    @decorators.global_command('o')
-    def open_project(self, root=None):
-        if not root:
-            root = lisputils.ask_directory('Rope project root folder: ')
-        if self.project is not None:
-            self.close_project()
-        progress = lisputils.create_progress('Opening "%s" project' % root)
-        self.project = rope.base.project.Project(root)
-        if lisp['ropemacs-enable-autoimport'].value():
-            underlined = lisp['ropemacs-autoimport-underlineds'].value()
-            self.autoimport = autoimport.AutoImport(self.project,
-                                                    underlined=underlined)
-        progress.done()
-
-    @decorators.global_command('k')
-    def close_project(self):
-        if self.project is not None:
-            progress = lisputils.create_progress('Closing "%s" project' %
-                                                 self.project.address)
-            self.project.close()
-            self.project = None
-            progress.done()
-
-    @decorators.global_command()
-    def write_project(self):
-        if self.project is not None:
-            progress = lisputils.create_progress(
-                'Writing "%s" project data to disk' % self.project.address)
-            self.project.sync()
-            progress.done()
-
-    @decorators.global_command('u')
-    def undo(self):
-        self._check_project()
-        change = self.project.history.tobe_undone
-        if change is None:
-            lisputils.message('Nothing to undo!')
-            return
-        if lisp.y_or_n_p('Undo <%s>? ' % str(change)):
-            def undo(handle):
-                for changes in self.project.history.undo(task_handle=handle):
-                    self._reload_buffers(changes, undo=True)
-            lisputils.runtask(undo, 'Undo refactoring', interrupts=False)
-
-    @decorators.global_command('r')
-    def redo(self):
-        self._check_project()
-        change = self.project.history.tobe_redone
-        if change is None:
-            lisputils.message('Nothing to redo!')
-            return
-        if lisp.y_or_n_p('Redo <%s>? ' % str(change)):
-            def redo(handle):
-                for changes in self.project.history.redo(task_handle=handle):
-                    self._reload_buffers(changes)
-            lisputils.runtask(redo, 'Redo refactoring', interrupts=False)
-
-    def _get_region(self):
-        offset1 = self._get_offset()
-        lisp.exchange_point_and_mark()
-        offset2 = self._get_offset()
-        lisp.exchange_point_and_mark()
-        return min(offset1, offset2), max(offset1, offset2)
-
-    def _get_offset(self):
-        return lisp.point() - 1
-
-    def _get_text(self):
-        if not lisp.buffer_modified_p():
-            return self._get_resource().read()
-        end = lisp.buffer_size() + 1
-        old_min = lisp.point_min()
-        old_max = lisp.point_max()
-        narrowed = (old_min != 1 or old_max != end)
-        if narrowed:
-            lisp.narrow_to_region(1, lisp.buffer_size() + 1)
-        try:
-            return lisp.buffer_string()
-        finally:
-            if narrowed:
-                lisp.narrow_to_region(old_min, old_max)
-
-    @decorators.local_command('a g', shortcut='C-c g')
-    def goto_definition(self):
-        self._check_project()
-        resource, offset = self._get_location()
-        maxfixes = lisp['ropemacs-codeassist-maxfixes'].value()
-        definition = codeassist.get_definition_location(
-            self.project, self._get_text(), offset, resource, maxfixes)
-        if tuple(definition) != (None, None):
-            lisp.push_mark()
-            self._goto_location(definition[0], definition[1])
-        else:
-            lisputils.message('Cannot find the definition!')
-
-    @decorators.local_command('a d', 'P', 'C-c d')
-    def show_doc(self, prefix):
-        self._check_project()
-        self._base_show_doc(prefix, codeassist.get_doc)
-
-    @decorators.local_command('a c', 'P')
-    def show_calltip(self, prefix):
-        self._check_project()
-        def _get_doc(project, text, offset, *args, **kwds):
-            try:
-                offset = text.rindex('(', 0, offset) - 1
-            except ValueError:
-                return None
-            return codeassist.get_calltip(project, text, offset, *args, **kwds)
-        self._base_show_doc(prefix, _get_doc)
-
-    @decorators.local_command()
-    def show_call_doc(self, prefix):
-        self._check_project()
-        lisputils.message('ropemacs: use `rope-show-calltip\' instead!')
-
-    def _base_show_doc(self, prefix, get_doc):
-        maxfixes = lisp['ropemacs-codeassist-maxfixes'].value()
-        text = self._get_text()
-        offset = self._get_offset()
-        docs = get_doc(self.project, text, offset,
-                       self._get_resource(), maxfixes)
-
-        use_minibuffer = not prefix
-        if lisp['ropemacs-separate-doc-buffer'].value():
-            use_minibuffer = not use_minibuffer
-        if use_minibuffer and docs:
-            docs = '\n'.join(docs.split('\n')[:7])
-            lisputils.message(docs)
-        else:
-            fit_lines = lisp["ropemacs-max-doc-buffer-height"].value()
-            buffer = lisputils.make_buffer('*rope-pydoc*', docs,
-                                           empty_goto=False,
-                                           fit_lines=fit_lines)
-            lisp.local_set_key('q', lisp.bury_buffer)