Path: blob/master/elisp/emacs-for-python/rope-dist/ropemode/interface.py
990 views
import os12import rope.base.change3from rope.base import libutils, utils, exceptions4from rope.contrib import codeassist, generate, autoimport, findit56from ropemode import refactor, decorators, dialog789class RopeMode(object):1011def __init__(self, env):12self.project = None13self.old_content = None14self.env = env1516self._prepare_refactorings()17self.autoimport = None18self._init_mode()1920def init(self):21"""Initialize rope mode"""2223def _init_mode(self):24for attrname in dir(self):25attr = getattr(self, attrname)26if not callable(attr):27continue28kind = getattr(attr, 'kind', None)29if kind == 'local':30key = getattr(attr, 'local_key', None)31prefix = getattr(attr, 'prefix', None)32self.env.local_command(attrname, attr, key, prefix)33if kind == 'global':34key = getattr(attr, 'global_key', None)35prefix = getattr(attr, 'prefix', None)36self.env.global_command(attrname, attr, key, prefix)37if kind == 'hook':38hook = getattr(attr, 'hook', None)39self.env.add_hook(attrname, attr, hook)4041def _prepare_refactorings(self):42for name in dir(refactor):43if not name.startswith('_') and name != 'Refactoring':44attr = getattr(refactor, name)45if isinstance(attr, type) and \46issubclass(attr, refactor.Refactoring):47refname = self._refactoring_name(attr)48@decorators.local_command(attr.key, 'P', None, refname)49def do_refactor(prefix, self=self, refactoring=attr):50initial_asking = prefix is None51refactoring(self, self.env).show(initial_asking=initial_asking)52setattr(self, refname, do_refactor)5354def _refactoring_name(self, refactoring):55return refactor.refactoring_name(refactoring)5657@decorators.rope_hook('before_save')58def before_save_actions(self):59if self.project is not None:60if not self._is_python_file(self.env.filename()):61return62resource = self._get_resource()63if resource.exists():64self.old_content = resource.read()65else:66self.old_content = ''6768@decorators.rope_hook('after_save')69def after_save_actions(self):70if self.project is not None and self.old_content is not None:71libutils.report_change(self.project, self.env.filename(),72self.old_content)73self.old_content = None7475@decorators.rope_hook('exit')76def exiting_actions(self):77if self.project is not None:78self.close_project()7980@decorators.global_command('o')81def open_project(self, root=None):82if not root:83root = self.env.ask_directory('Rope project root folder: ')84if self.project is not None:85self.close_project()86address = rope.base.project._realpath(os.path.join(root,87'.ropeproject'))88if not os.path.exists(address):89if not self.env.y_or_n('Project not exists in %s, ' \90'create one?' % root):91self.env.message("Project creation aborted")92return93progress = self.env.create_progress('Opening [%s] project' % root)94self.project = rope.base.project.Project(root)95if self.env.get('enable_autoimport'):96underlined = self.env.get('autoimport_underlineds')97self.autoimport = autoimport.AutoImport(self.project,98underlined=underlined)99progress.done()100self.env.project_opened()101102@decorators.global_command('k')103def close_project(self):104if self.project is not None:105progress = self.env.create_progress('Closing [%s] project' %106self.project.address)107self.project.close()108self.project = None109progress.done()110111@decorators.global_command()112def write_project(self):113if self.project is not None:114progress = self.env.create_progress(115'Writing [%s] project data to disk' % self.project.address)116self.project.sync()117progress.done()118119@decorators.global_command('u')120def undo(self):121self._check_project()122change = self.project.history.tobe_undone123if change is None:124self.env.message('Nothing to undo!')125return126if self.env.y_or_n('Undo [%s]? ' % str(change)):127def undo(handle):128for changes in self.project.history.undo(task_handle=handle):129self._reload_buffers(changes, undo=True)130refactor.runtask(self.env, undo, 'Undo refactoring',131interrupts=False)132133@decorators.global_command('r')134def redo(self):135self._check_project()136change = self.project.history.tobe_redone137if change is None:138self.env.message('Nothing to redo!')139return140if self.env.y_or_n('Redo [%s]? ' % str(change)):141def redo(handle):142for changes in self.project.history.redo(task_handle=handle):143self._reload_buffers(changes)144refactor.runtask(self.env, redo, 'Redo refactoring',145interrupts=False)146147@decorators.local_command('a g', shortcut='C-c g')148def goto_definition(self):149definition = self._base_definition_location()150if definition:151self.env.push_mark()152self._goto_location(definition[0], definition[1])153else:154self.env.message('Cannot find the definition!')155156@decorators.local_command()157def definition_location(self):158definition = self._base_definition_location()159if definition:160return str(definition[0].real_path), definition[1]161return None162163def _base_definition_location(self):164self._check_project()165resource, offset = self._get_location()166maxfixes = self.env.get('codeassist_maxfixes')167try:168definition = codeassist.get_definition_location(169self.project, self._get_text(), offset, resource, maxfixes)170except exceptions.BadIdentifierError:171return None172if tuple(definition) != (None, None):173return definition174return None175176@decorators.local_command('a d', 'P', 'C-c d')177def show_doc(self, prefix):178self._check_project()179self._base_show_doc(prefix, codeassist.get_doc)180181@decorators.local_command('a c', 'P')182def show_calltip(self, prefix):183self._check_project()184def _get_doc(project, text, offset, *args, **kwds):185try:186offset = text.rindex('(', 0, offset) - 1187except ValueError:188return None189return codeassist.get_calltip(project, text, offset, *args, **kwds)190self._base_show_doc(prefix, _get_doc)191192def _base_show_doc(self, prefix, get_doc):193docs = self._base_get_doc(get_doc)194if docs:195self.env.show_doc(docs, prefix)196else:197self.env.message('No docs available!')198199@decorators.local_command()200def get_doc(self):201self._check_project()202return self._base_get_doc(codeassist.get_doc)203204def _base_get_doc(self, get_doc):205maxfixes = self.env.get('codeassist_maxfixes')206text = self._get_text()207offset = self.env.get_offset()208try:209return get_doc(self.project, text, offset,210self.resource, maxfixes)211except exceptions.BadIdentifierError:212return None213214def _get_text(self):215resource = self.resource216if not self.env.is_modified() and resource is not None:217return resource.read()218return self.env.get_text()219220def _base_findit(self, do_find, optionals, get_kwds):221self._check_project()222self._save_buffers()223resource, offset = self._get_location()224225action, values = dialog.show_dialog(226self._askdata, ['search', 'cancel'], optionals=optionals)227if action == 'search':228kwds = get_kwds(values)229def calculate(handle):230resources = refactor._resources(self.project,231values.get('resources'))232return do_find(self.project, resource, offset,233resources=resources, task_handle=handle, **kwds)234result = refactor.runtask(self.env, calculate, 'Find Occurrences')235locations = [Location(location) for location in result]236self.env.show_occurrences(locations)237238@decorators.local_command('a f', shortcut='C-c f')239def find_occurrences(self):240optionals = {241'unsure': dialog.Data('Find uncertain occurrences: ',242default='no', values=['yes', 'no']),243'resources': dialog.Data('Files to search: '),244'in_hierarchy': dialog.Data(245'Rename methods in class hierarchy: ',246default='no', values=['yes', 'no'])}247def get_kwds(values):248return {'unsure': values.get('unsure') == 'yes',249'in_hierarchy': values.get('in_hierarchy') == 'yes'}250self._base_findit(findit.find_occurrences, optionals, get_kwds)251252@decorators.local_command('a i')253def find_implementations(self):254optionals = {'resources': dialog.Data('Files to search: ')}255def get_kwds(values):256return {}257self._base_findit(findit.find_implementations, optionals, get_kwds)258259@decorators.local_command('a /', 'P', 'M-/')260def code_assist(self, prefix):261_CodeAssist(self, self.env).code_assist(prefix)262263@decorators.local_command('a ?', 'P', 'M-?')264def lucky_assist(self, prefix):265_CodeAssist(self, self.env).lucky_assist(prefix)266267@decorators.local_command()268def auto_import(self):269_CodeAssist(self, self.env).auto_import()270271@decorators.local_command()272def completions(self):273return _CodeAssist(self, self.env).completions()274275@decorators.local_command()276def extended_completions(self):277return _CodeAssist(self, self.env).extended_completions()278279def _check_autoimport(self):280self._check_project()281if self.autoimport is None:282self.env.message('autoimport is disabled; '283'see `enable_autoimport\' variable')284return False285return True286287@decorators.global_command()288def generate_autoimport_cache(self):289if not self._check_autoimport():290return291modules = self.env.get('autoimport_modules')292modnames = []293if modules:294for i in range(len(modules)):295modname = modules[i]296if not isinstance(modname, basestring):297modname = modname.value()298modnames.append(modname)299else:300modules = []301def generate(handle):302self.autoimport.generate_cache(task_handle=handle)303self.autoimport.generate_modules_cache(modules, task_handle=handle)304refactor.runtask(self.env, generate, 'Generate autoimport cache')305306@decorators.global_command('f', 'P')307def find_file(self, prefix):308file = self._base_find_file(prefix)309if file is not None:310self.env.find_file(file.real_path)311312@decorators.global_command('4 f', 'P')313def find_file_other_window(self, prefix):314file = self._base_find_file(prefix)315if file is not None:316self.env.find_file(file.real_path, other=True)317318def _base_find_file(self, prefix):319self._check_project()320if prefix:321files = self.project.pycore.get_python_files()322else:323files = self.project.get_files()324return self._ask_file(files)325326def _ask_file(self, files):327names = []328for file in files:329names.append('<'.join(reversed(file.path.split('/'))))330result = self.env.ask_values('Rope Find File: ', names)331if result is not None:332path = '/'.join(reversed(result.split('<')))333file = self.project.get_file(path)334return file335self.env.message('No file selected')336337@decorators.local_command('a j')338def jump_to_global(self):339if not self._check_autoimport():340return341all_names = list(self.autoimport.get_all_names())342name = self.env.ask_values('Global name: ', all_names)343result = dict(self.autoimport.get_name_locations(name))344if len(result) == 1:345resource = list(result.keys())[0]346else:347resource = self._ask_file(result.keys())348if resource:349self._goto_location(resource, result[resource])350351@decorators.global_command('c')352def project_config(self):353self._check_project()354if self.project.ropefolder is not None:355config = self.project.ropefolder.get_child('config.py')356self.env.find_file(config.real_path)357else:358self.env.message('No rope project folder found')359360@decorators.global_command('n m')361def create_module(self):362def callback(sourcefolder, name):363return generate.create_module(self.project, name, sourcefolder)364self._create('module', callback)365366@decorators.global_command('n p')367def create_package(self):368def callback(sourcefolder, name):369folder = generate.create_package(self.project, name, sourcefolder)370return folder.get_child('__init__.py')371self._create('package', callback)372373@decorators.global_command('n f')374def create_file(self):375def callback(parent, name):376return parent.create_file(name)377self._create('file', callback, 'parent')378379@decorators.global_command('n d')380def create_directory(self):381def callback(parent, name):382parent.create_folder(name)383self._create('directory', callback, 'parent')384385@decorators.local_command()386def analyze_module(self):387"""Perform static object analysis on this module"""388self._check_project()389self.project.pycore.analyze_module(self.resource)390391@decorators.global_command()392def analyze_modules(self):393"""Perform static object analysis on all project modules"""394self._check_project()395def _analyze_modules(handle):396libutils.analyze_modules(self.project, task_handle=handle)397refactor.runtask(self.env, _analyze_modules, 'Analyze project modules')398399@decorators.local_command()400def run_module(self):401"""Run and perform dynamic object analysis on this module"""402self._check_project()403process = self.project.pycore.run_module(self.resource)404try:405process.wait_process()406finally:407process.kill_process()408409def _create(self, name, callback, parentname='source'):410self._check_project()411confs = {'name': dialog.Data(name.title() + ' name: ')}412parentname = parentname + 'folder'413optionals = {parentname: dialog.Data(414parentname.title() + ' Folder: ',415default=self.project.address, kind='directory')}416action, values = dialog.show_dialog(417self._askdata, ['perform', 'cancel'], confs, optionals)418if action == 'perform':419parent = libutils.path_to_resource(420self.project, values.get(parentname, self.project.address))421resource = callback(parent, values['name'])422if resource:423self.env.find_file(resource.real_path)424425def _goto_location(self, resource, lineno):426if resource:427self.env.find_file(str(resource.real_path),428other=self.env.get('goto_def_newwin'))429if lineno:430self.env.goto_line(lineno)431432def _get_location(self):433offset = self.env.get_offset()434return self.resource, offset435436def _get_resource(self, filename=None):437if filename is None:438filename = self.env.filename()439if filename is None or self.project is None:440return441resource = libutils.path_to_resource(self.project, filename, 'file')442return resource443444@property445def resource(self):446"""the current resource447448Returns `None` when file does not exist.449"""450resource = self._get_resource()451if resource and resource.exists():452return resource453454def _check_project(self):455if self.project is None:456if self.env.get('guess_project'):457self.open_project(self._guess_project())458else:459self.open_project()460else:461self.project.validate(self.project.root)462463def _guess_project(self):464cwd = self.env.filename()465if cwd is not None:466while True:467ropefolder = os.path.join(cwd, '.ropeproject')468if os.path.exists(ropefolder) and os.path.isdir(ropefolder):469return cwd470newcwd = os.path.dirname(cwd)471if newcwd == cwd:472break473cwd = newcwd474475def _reload_buffers(self, changes, undo=False):476self._reload_buffers_for_changes(477changes.get_changed_resources(),478self._get_moved_resources(changes, undo))479480def _reload_buffers_for_changes(self, changed, moved={}):481filenames = [resource.real_path for resource in changed]482moved = dict([(resource.real_path, moved[resource].real_path)483for resource in moved])484self.env.reload_files(filenames, moved)485486def _get_moved_resources(self, changes, undo=False):487result = {}488if isinstance(changes, rope.base.change.ChangeSet):489for change in changes.changes:490result.update(self._get_moved_resources(change))491if isinstance(changes, rope.base.change.MoveResource):492result[changes.resource] = changes.new_resource493if undo:494return dict([(value, key) for key, value in result.items()])495return result496497def _save_buffers(self, only_current=False):498if only_current:499filenames = [self.env.filename()]500else:501filenames = self.env.filenames()502pythons = []503for filename in filenames:504if self._is_python_file(filename):505pythons.append(filename)506self.env.save_files(pythons)507508def _is_python_file(self, path):509resource = self._get_resource(path)510return (resource is not None and511resource.project == self.project and512self.project.pycore.is_python_file(resource))513514def _askdata(self, data, starting=None):515ask_func = self.env.ask516ask_args = {'prompt': data.prompt, 'starting': starting,517'default': data.default}518if data.values:519ask_func = self.env.ask_values520ask_args['values'] = data.values521elif data.kind == 'directory':522ask_func = self.env.ask_directory523return ask_func(**ask_args)524525526class Location(object):527def __init__(self, location):528self.location = location529self.filename = location.resource.real_path530self.offset = location.offset531self.note = ''532if location.unsure:533self.note = '?'534535@property536def lineno(self):537if hasattr(self.location, 'lineno'):538return self.location.lineno539return self.location.resource.read().count('\n', 0, self.offset) + 1540541542class _CodeAssist(object):543544def __init__(self, interface, env):545self.interface = interface546self.env = env547548def code_assist(self, prefix):549proposals = self._calculate_proposals()550if prefix is not None:551arg = self.env.prefix_value(prefix)552if arg == 0:553arg = len(names)554common_start = self._calculate_prefix(proposals[:arg])555self.env.insert(common_start[self.offset - self.starting_offset:])556self._starting = common_start557self._offset = self.starting_offset + len(common_start)558prompt = 'Completion for %s: ' % self.expression559proposals = map(self.env._completion_data, proposals)560result = self.env.ask_completion(prompt, proposals, self.starting)561if result is not None:562self._apply_assist(result)563564def lucky_assist(self, prefix):565proposals = self._calculate_proposals()566selected = 0567if prefix is not None:568selected = self.env.prefix_value(prefix)569if 0 <= selected < len(proposals):570result = self.env._completion_text(proposals[selected])571else:572self.env.message('Not enough proposals!')573return574self._apply_assist(result)575576def auto_import(self):577if not self.interface._check_autoimport():578return579name = self.env.current_word()580modules = self.autoimport.get_modules(name)581if modules:582if len(modules) == 1:583module = modules[0]584else:585module = self.env.ask_values(586'Which module to import: ', modules)587self._insert_import(name, module)588else:589self.env.message('Global name %s not found!' % name)590591def completions(self):592proposals = self._calculate_proposals()593prefix = self.offset - self.starting_offset594return [self.env._completion_text(proposal)[prefix:]595for proposal in proposals]596597def extended_completions(self):598proposals = self._calculate_proposals()599prefix = self.offset - self.starting_offset600return [[proposal.name[prefix:], proposal.get_doc(),601proposal.type] for proposal in proposals]602603def _apply_assist(self, assist):604if ' : ' in assist:605name, module = assist.rsplit(' : ', 1)606self.env.delete(self.starting_offset + 1, self.offset + 1)607self.env.insert(name)608self._insert_import(name, module)609else:610self.env.delete(self.starting_offset + 1, self.offset + 1)611self.env.insert(assist)612613def _calculate_proposals(self):614self.interface._check_project()615resource = self.interface.resource616maxfixes = self.env.get('codeassist_maxfixes')617proposals = codeassist.code_assist(618self.interface.project, self.source, self.offset,619resource, maxfixes=maxfixes)620proposals = codeassist.sorted_proposals(proposals)621if self.autoimport is not None:622if self.starting.strip() and '.' not in self.expression:623import_assists = self.autoimport.import_assist(self.starting)624for assist in import_assists:625p = codeassist.CompletionProposal(' : '.join(assist),626'autoimport')627proposals.append(p)628return proposals629630def _insert_import(self, name, module):631lineno = self.autoimport.find_insertion_line(self.source)632line = 'from %s import %s' % (module, name)633self.env.insert_line(line, lineno)634635def _calculate_prefix(self, proposals):636if not proposals:637return ''638prefix = self.env._completion_text(proposals[0])639for proposal in proposals:640common = 0641name = self.env._completion_text(proposal)642for c1, c2 in zip(prefix, name):643if c1 != c2 or ' ' in (c1, c2):644break645common += 1646prefix = prefix[:common]647return prefix648649@property650@utils.cacheit651def offset(self):652return self.env.get_offset()653654@property655@utils.cacheit656def source(self):657return self.interface._get_text()658659@property660@utils.cacheit661def starting_offset(self):662return codeassist.starting_offset(self.source, self.offset)663664@property665@utils.cacheit666def starting(self):667return self.source[self.starting_offset:self.offset]668669@property670@utils.cacheit671def expression(self):672return codeassist.starting_expression(self.source, self.offset)673674@property675def autoimport(self):676return self.interface.autoimport677678679