Sun, 30 Jan 2011 14:02:31 +0100
Added the 'move' refactoring functions.
# -*- coding: utf-8 -*- # Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the refactoring interface to rope. """ import os import sys sys.path.insert(0, os.path.dirname(__file__)) import rope import rope.base.libutils import rope.base.project import rope.base.exceptions import rope.refactor.rename import rope.refactor.extract ##import rope.refactor.usefunction import rope.refactor.inline import rope.refactor.move ##import rope.refactor.change_signature ##import rope.refactor.introduce_factory ##import rope.refactor.introduce_parameter ##import rope.refactor.method_object ##import rope.refactor.encapsulate_field ##import rope.refactor.localtofield ##import rope.refactor.topackage ##from rope.refactor.importutils import ImportOrganizer import rope.contrib.findit ##import rope.contrib.finderrors from PyQt4.QtCore import QObject, SIGNAL from PyQt4.QtGui import QMenu, QApplication, QMessageBox from E5Gui.E5Application import e5App from E5Gui import E5MessageBox from E5Gui.E5Action import E5Action from QScintilla.MiniEditor import MiniEditor from FileSystemCommands import e5FileSystemCommands from ProgressHandle import ProgressHandle from HelpDialog import HelpDialog from MatchesDialog import MatchesDialog from RenameDialog import RenameDialog from ChangeOccurrencesDialog import ChangeOccurrencesDialog from HistoryDialog import HistoryDialog from ExtractDialog import ExtractDialog from InlineDialog import InlineDialog from MoveMethodDialog import MoveMethodDialog from MoveModuleDialog import MoveModuleDialog import Utilities class Refactoring(QObject): """ Class implementing the refactoring interface to rope. """ def __init__(self, plugin, newStyle, parent=None): """ Constructor @param plugin reference to the plugin object @param newStyle flag indicating usage of new style signals (bool) @param parent parent (QObject) """ QObject.__init__(self, parent) self.__plugin = plugin self.__newStyle = newStyle self.__ui = parent self.__e5project = e5App().getObject("Project") self.__projectpath = '' self.__projectLanguage = "" self.__projectopen = False self.__mainMenu = None self.__helpDialog = None # Rope objects self.__project = None self.__fsCommands = e5FileSystemCommands(self.__e5project) def initActions(self): """ Public method to define the refactoring actions. """ self.actions = [] ##################################################### ## Rename refactoring actions ##################################################### self.refactoringRenameAct = E5Action(self.trUtf8('Rename'), self.trUtf8('&Rename'), 0, 0, self,'refactoring_rename') self.refactoringRenameAct.setStatusTip(self.trUtf8( 'Rename the highlighted object')) self.refactoringRenameAct.setWhatsThis(self.trUtf8( """<b>Rename</b>""" """<p>Rename the highlighted Python object.</p>""" )) if self.__newStyle: self.refactoringRenameAct.triggered[()].connect( self.__rename) else: self.connect(self.refactoringRenameAct, SIGNAL('triggered()'), self.__rename) self.actions.append(self.refactoringRenameAct) self.refactoringRenameLocalAct = E5Action(self.trUtf8('Local Rename'), self.trUtf8('&Local Rename'), 0, 0, self,'refactoring_rename_local') self.refactoringRenameLocalAct.setStatusTip(self.trUtf8( 'Rename the highlighted object in the current module only')) self.refactoringRenameLocalAct.setWhatsThis(self.trUtf8( """<b>Local Rename</b>""" """<p>Rename the highlighted Python object in the current""" """ module only.</p>""" )) if self.__newStyle: self.refactoringRenameLocalAct.triggered[()].connect( self.__renameLocal) else: self.connect(self.refactoringRenameLocalAct, SIGNAL('triggered()'), self.__renameLocal) self.actions.append(self.refactoringRenameLocalAct) self.refactoringRenameModuleAct = E5Action( self.trUtf8('Rename Current Module'), self.trUtf8('Rename Current Module'), 0, 0, self,'refactoring_rename_module') self.refactoringRenameModuleAct.setStatusTip(self.trUtf8( 'Rename the current module')) self.refactoringRenameModuleAct.setWhatsThis(self.trUtf8( """<b>Rename Current Module</b>""" """<p>Rename the current module.</p>""" )) if self.__newStyle: self.refactoringRenameModuleAct.triggered[()].connect( self.__renameModule) else: self.connect(self.refactoringRenameModuleAct, SIGNAL('triggered()'), self.__renameModule) self.actions.append(self.refactoringRenameModuleAct) self.refactoringChangeOccurrencesAct = E5Action( self.trUtf8('Change Occurrences'), self.trUtf8('Change &Occurrences'), 0, 0, self,'refactoring_change_occurrences') self.refactoringChangeOccurrencesAct.setStatusTip(self.trUtf8( 'Change all occurrences in the local scope')) self.refactoringChangeOccurrencesAct.setWhatsThis(self.trUtf8( """<b>Change Occurrences</b>""" """<p>Change all occurrences in the local scope.</p>""" )) if self.__newStyle: self.refactoringChangeOccurrencesAct.triggered[()].connect( self.__changeOccurrences) else: self.connect(self.refactoringChangeOccurrencesAct, SIGNAL('triggered()'), self.__changeOccurrences) self.actions.append(self.refactoringChangeOccurrencesAct) ##################################################### ## Extract refactoring actions ##################################################### self.refactoringExtractMethodAct = E5Action( self.trUtf8('Extract method'), self.trUtf8('Extract &Method'), 0, 0, self,'refactoring_extract_method') self.refactoringExtractMethodAct.setStatusTip(self.trUtf8( 'Extract the highlighted area as a method')) self.refactoringExtractMethodAct.setWhatsThis(self.trUtf8( """<b>Extract method</b>""" """<p>Extract the highlighted area as a method or function.</p>""" )) if self.__newStyle: self.refactoringExtractMethodAct.triggered[()].connect( self.__extractMethod) else: self.connect(self.refactoringExtractMethodAct, SIGNAL('triggered()'), self.__extractMethod) self.actions.append(self.refactoringExtractMethodAct) self.refactoringExtractLocalVariableAct = E5Action( self.trUtf8('Extract local variable'), self.trUtf8('&Extract Local Variable'), 0, 0, self,'refactoring_extract_variable') self.refactoringExtractLocalVariableAct.setStatusTip(self.trUtf8( 'Extract the highlighted area as a local variable')) self.refactoringExtractLocalVariableAct.setWhatsThis(self.trUtf8( """<b>Extract local variable</b>""" """<p>Extract the highlighted area as a local variable.</p>""" )) if self.__newStyle: self.refactoringExtractLocalVariableAct.triggered[()].connect( self.__extractLocalVariable) else: self.connect(self.refactoringExtractLocalVariableAct, SIGNAL('triggered()'), self.__extractLocalVariable) self.actions.append(self.refactoringExtractLocalVariableAct) ##################################################### ## Inline refactoring actions ##################################################### self.refactoringInlineAct = E5Action( self.trUtf8('Inline'), self.trUtf8('&Inline'), 0, 0, self,'refactoring_inline') self.refactoringInlineAct.setStatusTip(self.trUtf8( 'Inlines the selected local variable or method')) self.refactoringInlineAct.setWhatsThis(self.trUtf8( """<b>Inline</b>""" """<p>Inlines the selected local variable or method.</p>""" )) if self.__newStyle: self.refactoringInlineAct.triggered[()].connect( self.__inline) else: self.connect(self.refactoringInlineAct, SIGNAL('triggered()'), self.__inline) self.actions.append(self.refactoringInlineAct) ##################################################### ## Move refactoring actions ##################################################### self.refactoringMoveMethodAct = E5Action( self.trUtf8('Move method'), self.trUtf8('Mo&ve Method'), 0, 0, self,'refactoring_move_method') self.refactoringMoveMethodAct.setStatusTip(self.trUtf8( 'Move the highlighted method to another class')) self.refactoringMoveMethodAct.setWhatsThis(self.trUtf8( """<b>Move method</b>""" """<p>Move the highlighted method to another class.</p>""" )) if self.__newStyle: self.refactoringMoveMethodAct.triggered[()].connect( self.__moveMethod) else: self.connect(self.refactoringMoveMethodAct, SIGNAL('triggered()'), self.__moveMethod) self.actions.append(self.refactoringMoveMethodAct) self.refactoringMoveModuleAct = E5Action( self.trUtf8('Move current module'), self.trUtf8('Move Current Module'), 0, 0, self,'refactoring_move_module') self.refactoringMoveModuleAct.setStatusTip(self.trUtf8( 'Move the current module to another package')) self.refactoringMoveModuleAct.setWhatsThis(self.trUtf8( """<b>Move current module</b>""" """<p>Move the current module to another package.</p>""" )) if self.__newStyle: self.refactoringMoveModuleAct.triggered[()].connect( self.__moveModule) else: self.connect(self.refactoringMoveModuleAct, SIGNAL('triggered()'), self.__moveModule) self.actions.append(self.refactoringMoveModuleAct) ##################################################### ## Undo/Redo actions ##################################################### self.refactoringUndoAct = E5Action(self.trUtf8('Undo'), self.trUtf8('&Undo'), 0, 0, self,'refactoring_undo') self.refactoringUndoAct.setStatusTip(self.trUtf8( 'Undo the last refactoring')) self.refactoringUndoAct.setWhatsThis(self.trUtf8( """<b>Undo</b>""" """<p>Undo the last refactoring.</p>""" )) if self.__newStyle: self.refactoringUndoAct.triggered[()].connect( self.__undo) else: self.connect(self.refactoringUndoAct, SIGNAL('triggered()'), self.__undo) self.actions.append(self.refactoringUndoAct) self.refactoringRedoAct = E5Action(self.trUtf8('Redo'), self.trUtf8('Re&do'), 0, 0, self,'refactoring_redo') self.refactoringRedoAct.setStatusTip(self.trUtf8( 'Redo the last refactoring')) self.refactoringRedoAct.setWhatsThis(self.trUtf8( """<b>Redo</b>""" """<p>Redo the last refactoring.</p>""" )) if self.__newStyle: self.refactoringRedoAct.triggered[()].connect( self.__redo) else: self.connect(self.refactoringRedoAct, SIGNAL('triggered()'), self.__redo) self.actions.append(self.refactoringRedoAct) self.refactoringUndoHistoryAct = \ E5Action(self.trUtf8('Show Project Undo History'), self.trUtf8('Show Project Undo History'), 0, 0, self,'refactoring_show_project_undo_history') self.refactoringUndoHistoryAct.setStatusTip(self.trUtf8( 'Show the undo history of the project')) self.refactoringUndoHistoryAct.setWhatsThis(self.trUtf8( """<b>Show Project Undo History</b>""" """<p>Opens a dialog to show the undo history list of""" """ the project.</p>""" )) if self.__newStyle: self.refactoringUndoHistoryAct.triggered[()].connect( self.__showProjectUndoHistory) else: self.connect(self.refactoringUndoHistoryAct, SIGNAL('triggered()'), self.__showProjectUndoHistory) self.actions.append(self.refactoringUndoHistoryAct) self.refactoringRedoHistoryAct = \ E5Action(self.trUtf8('Show Project Redo History'), self.trUtf8('Show Project Redo History'), 0, 0, self,'refactoring_show_project_redo_history') self.refactoringRedoHistoryAct.setStatusTip(self.trUtf8( 'Show the redo history of the project')) self.refactoringRedoHistoryAct.setWhatsThis(self.trUtf8( """<b>Show Project Redo History</b>""" """<p>Opens a dialog to show the redo history list of""" """ the project.</p>""" )) if self.__newStyle: self.refactoringRedoHistoryAct.triggered[()].connect( self.__showProjectRedoHistory) else: self.connect(self.refactoringRedoHistoryAct, SIGNAL('triggered()'), self.__showProjectRedoHistory) self.actions.append(self.refactoringRedoHistoryAct) self.refactoringUndoFileHistoryAct = \ E5Action(self.trUtf8('Show Current File Undo History'), self.trUtf8('Show Current File Undo History'), 0, 0, self,'refactoring_show_file_undo_history') self.refactoringUndoFileHistoryAct.setStatusTip(self.trUtf8( 'Show the undo history of the current file')) self.refactoringUndoFileHistoryAct.setWhatsThis(self.trUtf8( """<b>Show Current File Undo History</b>""" """<p>Opens a dialog to show the undo history list of""" """ the current file.</p>""" )) if self.__newStyle: self.refactoringUndoFileHistoryAct.triggered[()].connect( self.__showFileUndoHistory) else: self.connect(self.refactoringUndoFileHistoryAct, SIGNAL('triggered()'), self.__showFileUndoHistory) self.actions.append(self.refactoringUndoFileHistoryAct) self.refactoringRedoFileHistoryAct = \ E5Action(self.trUtf8('Show Current File Redo History'), self.trUtf8('Show Current File Redo History'), 0, 0, self,'refactoring_show_file_redo_history') self.refactoringRedoFileHistoryAct.setStatusTip(self.trUtf8( 'Show the redo history of the current file')) self.refactoringRedoFileHistoryAct.setWhatsThis(self.trUtf8( """<b>Show Current File Redo History</b>""" """<p>Opens a dialog to show the redo history list of""" """ the current file.</p>""" )) if self.__newStyle: self.refactoringRedoFileHistoryAct.triggered[()].connect( self.__showFileRedoHistory) else: self.connect(self.refactoringRedoFileHistoryAct, SIGNAL('triggered()'), self.__showFileRedoHistory) self.actions.append(self.refactoringRedoFileHistoryAct) self.refactoringClearHistoryAct = \ E5Action(self.trUtf8('Clear History'), self.trUtf8('Clear History'), 0, 0, self,'refactoring_clear_history') self.refactoringClearHistoryAct.setStatusTip(self.trUtf8( 'Clear the refactoring history')) self.refactoringClearHistoryAct.setWhatsThis(self.trUtf8( """<b>Clear History</b>""" """<p>Clears the refactoring history.</p>""" )) if self.__newStyle: self.refactoringClearHistoryAct.triggered[()].connect( self.__clearHistory) else: self.connect(self.refactoringClearHistoryAct, SIGNAL('triggered()'), self.__clearHistory) self.actions.append(self.refactoringClearHistoryAct) ##################################################### ## Query actions ##################################################### self.queryReferencesAct = E5Action(self.trUtf8('Find occurrences'), self.trUtf8('Find &Occurrences'), 0, 0, self,'refactoring_find_occurrences') self.queryReferencesAct.setStatusTip(self.trUtf8( 'Find occurrences of the highlighted object')) self.queryReferencesAct.setWhatsThis(self.trUtf8( """<b>Find occurrences</b>""" """<p>Find occurrences of the highlighted class, method,""" """ function or variable.</p>""" )) if self.__newStyle: self.queryReferencesAct.triggered[()].connect( self.__queryReferences) else: self.connect(self.queryReferencesAct, SIGNAL('triggered()'), self.__queryReferences) self.actions.append(self.queryReferencesAct) self.queryDefinitionAct = E5Action(self.trUtf8('Find definition'), self.trUtf8('Find &Definition'), 0, 0, self,'refactoring_find_definition') self.queryDefinitionAct.setStatusTip(self.trUtf8( 'Find definition of the highlighted item')) self.queryDefinitionAct.setWhatsThis(self.trUtf8( """<b>Find definition</b>""" """<p>Find the definition of the highlighted class, method,""" """ function or variable.</p>""" )) if self.__newStyle: self.queryDefinitionAct.triggered[()].connect( self.__queryDefinition) else: self.connect(self.queryDefinitionAct, SIGNAL('triggered()'), self.__queryDefinition) self.actions.append(self.queryDefinitionAct) self.queryImplementationsAct = E5Action( self.trUtf8('Find implementations'), self.trUtf8('Find &Implementations'), 0, 0, self,'refactoring_find_implementations') self.queryImplementationsAct.setStatusTip(self.trUtf8( 'Find places where the selected method is overridden')) self.queryImplementationsAct.setWhatsThis(self.trUtf8( """<b>Find implementations</b>""" """<p>Find places where the selected method is overridden.</p>""" )) if self.__newStyle: self.queryImplementationsAct.triggered[()].connect( self.__queryImplementations) else: self.connect(self.queryImplementationsAct, SIGNAL('triggered()'), self.__queryImplementations) self.actions.append(self.queryImplementationsAct) ##################################################### ## Various actions ##################################################### self.refactoringEditConfigAct = E5Action(self.trUtf8('Configure Rope'), self.trUtf8('&Configure Rope'), 0, 0, self,'refactoring_edit_config') self.refactoringEditConfigAct.setStatusTip(self.trUtf8( 'Open the rope configuration file')) self.refactoringEditConfigAct.setWhatsThis(self.trUtf8( """<b>Configure Rope</b>""" """<p>Opens the rope configuration file in an editor.</p>""" )) if self.__newStyle: self.refactoringEditConfigAct.triggered[()].connect( self.__editConfig) else: self.connect(self.refactoringEditConfigAct, SIGNAL('triggered()'), self.__editConfig) self.actions.append(self.refactoringEditConfigAct) self.refactoringHelpAct = E5Action(self.trUtf8('Rope help'), self.trUtf8('Rope &Help'), 0, 0, self,'refactoring_help') self.refactoringHelpAct.setStatusTip(self.trUtf8( 'Show help about the rope refactorings')) self.refactoringHelpAct.setWhatsThis(self.trUtf8( """<b>Rope help</b>""" """<p>Show some help text about the rope refactorings.</p>""" )) if self.__newStyle: self.refactoringHelpAct.triggered[()].connect( self.__showRopeHelp) else: self.connect(self.refactoringHelpAct, SIGNAL('triggered()'), self.__showRopeHelp) self.actions.append(self.refactoringHelpAct) for act in self.actions: act.setEnabled(False) def initMenu(self): """ Public slot to initialize the refactoring menu. @return the menu generated (QMenu) """ menu = QMenu(self.trUtf8('&Refactoring'), self.__ui) menu.setTearOffEnabled(True) act = menu.addAction('rope', self.__ropeInfo) font = act.font() font.setBold(True) act.setFont(font) menu.addSeparator() smenu = menu.addMenu(self.trUtf8("&Query")) smenu.addAction(self.queryReferencesAct) smenu.addAction(self.queryDefinitionAct) smenu.addAction(self.queryImplementationsAct) smenu = menu.addMenu(self.trUtf8("&Refactoring")) if self.__newStyle: smenu.aboutToShow.connect(self.__showRefactoringMenu) else: self.connect(smenu, SIGNAL("aboutToShow()"), self.__showRefactoringMenu) smenu.addAction(self.refactoringRenameAct) smenu.addAction(self.refactoringRenameLocalAct) smenu.addAction(self.refactoringChangeOccurrencesAct) smenu.addSeparator() smenu.addAction(self.refactoringExtractMethodAct) smenu.addAction(self.refactoringMoveMethodAct) smenu.addSeparator() smenu.addAction(self.refactoringInlineAct) smenu.addSeparator() smenu.addAction(self.refactoringExtractLocalVariableAct) smenu.addSeparator() smenu.addAction(self.refactoringRenameModuleAct) smenu.addAction(self.refactoringMoveModuleAct) smenu.addSeparator() smenu.addSeparator() smenu.addAction(self.refactoringUndoAct) smenu.addAction(self.refactoringRedoAct) smenu.addSeparator() hmenu = smenu.addMenu(self.trUtf8("History")) self.connect(hmenu, SIGNAL("aboutToShow()"), self.__showRefactoringHistoryMenu) hmenu.addAction(self.refactoringUndoHistoryAct) hmenu.addAction(self.refactoringUndoFileHistoryAct) hmenu.addSeparator() hmenu.addAction(self.refactoringRedoHistoryAct) hmenu.addAction(self.refactoringRedoFileHistoryAct) hmenu.addSeparator() hmenu.addAction(self.refactoringClearHistoryAct) menu.addSeparator() menu.addAction(self.refactoringEditConfigAct) menu.addAction(self.refactoringHelpAct) self.__mainMenu = menu return menu ################################################################## ## slots below implement general functionality ################################################################## def __ropeInfo(self): """ Private slot to show some info about rope. """ E5MessageBox.about(self.__ui, self.trUtf8("About rope"), self.trUtf8("{0}\nVersion {1}\n\n{2}".format( rope.INFO, rope.VERSION, rope.COPYRIGHT))) def __canUndo(self): """ Private slot to check, if there are changes to be undone. @return flag indicating, that undoable changes are available (boolean) """ return self.__project is not None and \ len(self.__project.history.undo_list) > 0 def __canRedo(self): """ Private slot to check, if there are changes to be redone. @return flag indicating, that redoable changes are available (boolean) """ return self.__project is not None and \ len(self.__project.history.redo_list) > 0 def __getFileUndoList(self, resource): """ Private slot to get a list of undoable changes. @param resource file resource to filter against (rope.base.resources.File) @return list of change objects (list of rope.base.change.Change) """ undoList = [] for change in self.__project.history.undo_list: if resource in change.get_changed_resources(): undoList.append(change) return undoList def __getFileRedoList(self, resource): """ Private slot to get a list of redoable changes. @param resource file resource to filter against (rope.base.resources.File) @return list of change objects (list of rope.base.change.Change) """ redoList = [] for change in self.__project.history.redo_list: if resource in change.get_changed_resources(): redoList.append(change) return redoList def __canUndoFile(self, resource): """ Private slot to check, if there are undoable changes for a resource. @param resource file resource to check against (rope.base.resources.File) @return flag indicating, that undoable changes are available (boolean) """ return self.__canUndo() and len(self.__getFileUndoList(resource)) > 0 def __canRedoFile(self, resource): """ Private slot to check, if there are redoable changes for a resource. @param resource file resource to check against (rope.base.resources.File) @return flag indicating, that redoable changes are available (boolean) """ return self.__canRedo() and len(self.__getFileRedoList(resource)) > 0 def __showRefactoringMenu(self): """ Private slot called before the refactoring menu is shown. """ self.refactoringUndoAct.setEnabled(self.__canUndo()) self.refactoringRedoAct.setEnabled(self.__canRedo()) def __showRefactoringHistoryMenu(self): """ Private slot called before the refactoring history menu is shown. """ aw = e5App().getObject("ViewManager").activeWindow() resource = None if aw is not None: filename = aw.getFileName() resource = rope.base.libutils.path_to_resource( self.__project, filename) self.refactoringUndoHistoryAct.setEnabled(self.__canUndo()) self.refactoringUndoFileHistoryAct.setEnabled( resource is not None and self.__canUndoFile(resource)) self.refactoringRedoHistoryAct.setEnabled(self.__canRedo()) self.refactoringRedoFileHistoryAct.setEnabled( resource is not None and self.__canRedoFile(resource)) def handleRopeError(self, err, title, handle=None): """ Public slot to handle a rope error. @param err rope exception object (Exception) @param title title to be displayed (string) @param handle reference to a taskhandle (ProgressHandle) """ if handle is not None: handle.reset() if str(type(err)).split()[-1][1:-2].split('.')[-1] == \ 'ModuleSyntaxError': res = E5MessageBox.warning(self.__ui, title, self.trUtf8("Rope error: {0}").format(str(err)), QMessageBox.Ok | QMessageBox.Open) if res == QMessageBox.Open: e5App().getObject("ViewManager").openSourceFile( os.path.join(self.__e4project.getProjectPath(), err.filename), err.lineno) else: E5MessageBox.warning(self.__ui, title, self.trUtf8("Rope error: {0}").format(str(err))) ################################################################## ## slots below implement the various refactorings ################################################################## ##################################################### ## Rename refactorings ##################################################### def __rename(self): """ Private slot to handle the Rename action. """ self.__doRename(self.trUtf8('Rename')) def __renameLocal(self): """ Private slot to handle the Local Rename action. """ self.__doRename(self.trUtf8('Local Rename'), isLocal=True) def __renameModule(self): """ Private slot to handle the Rename Current Module action. """ self.__doRename(self.trUtf8('Rename Current Module'), renameModule=True) def __doRename(self, title, isLocal=False, renameModule=False): """ Private method to perform the various renaming refactorings. @param title title of the refactoring (string) @param isLocal flag indicating to restrict refactoring to the local file (boolean) @param renameModule flag indicating a module rename refactoring (boolean) """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return if not renameModule and not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight the declaration you want to rename" " and try again.")) return if isLocal: if not self.confirmBufferIsSaved(aw): return else: if not self.confirmAllBuffersSaved(): return filename = aw.getFileName() if renameModule: offset = None else: line, index, line1, index1 = aw.getSelection() if line != line1: # selection span more than one line E5MessageBox.warning(self.__ui, title, self.trUtf8("The selection must not extend beyond" " one line.")) return index = int(index + (index1 - index) / 2) # keep it inside the object offset = aw.positionFromLineIndex(line, index) resource = rope.base.libutils.path_to_resource( self.__project, filename) try: renamer = rope.refactor.rename.Rename( self.__project, resource, offset) except Exception as err: self.handleRopeError(err, title) return if isLocal: localResource = resource else: localResource = None self.dlg = RenameDialog(self, title, renamer, resource=localResource, parent=self.__ui) self.dlg.show() def __changeOccurrences(self): """ Private slot to perform the Change Occurrences refactoring. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return title = self.trUtf8("Change Occurrences") if not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight an occurrence to be changed" " and try again.")) return if not self.confirmBufferIsSaved(aw): return filename = aw.getFileName() line, index, line1, index1 = aw.getSelection() offset = aw.positionFromLineIndex(line, index) resource = rope.base.libutils.path_to_resource( self.__project, filename) try: renamer = rope.refactor.rename.ChangeOccurrences( self.__project, resource, offset) except Exception as err: self.handleRopeError(err, title) return self.dlg = ChangeOccurrencesDialog(self, title, renamer, parent=self.__ui) self.dlg.show() ##################################################### ## Extract refactorings ##################################################### def __extractMethod(self): """ Private slot to handle the Extract Method action. """ self.__doExtract(self.trUtf8("Extract Method"), "method") def __extractLocalVariable(self): """ Private slot to handle the Extract Local Variable action. """ self.__doExtract(self.trUtf8("Extract Local Variable"), "variable") def __doExtract(self, title, kind): """ Private method to perform the extract refactoring. @param title title of the refactoring (QString) @param kind kind of extraction to be done (string, "method" or "variable") """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return if not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight the region of code you want to extract" " and try again.")) return if not self.confirmBufferIsSaved(aw): return filename = aw.getFileName() startline, startcolumn, endline, endcolumn = aw.getSelection() startOffset = aw.positionFromLineIndex(startline, startcolumn) endOffset = aw.positionFromLineIndex(endline, endcolumn) resource = rope.base.libutils.path_to_resource( self.__project, filename) try: if kind == "variable": extractor = rope.refactor.extract.ExtractVariable( self.__project, resource, startOffset, endOffset) elif kind == "method": extractor = rope.refactor.extract.ExtractMethod( self.__project, resource, startOffset, endOffset) else: raise Exception("Invalid extraction kind <{0}>.".format(kind)) except Exception as err: self.handleRopeError(err, title) return self.dlg = ExtractDialog(self, title, extractor, parent = self.__ui) self.dlg.show() ##################################################### ## Inline refactorings ##################################################### def __inline(self): """ Private slot to handle the Inline Local Variable action. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return title = self.trUtf8("Inline") if not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight the local variable, method or parameter" " you want to inline and try again.")) return if not self.confirmAllBuffersSaved(): return filename = aw.getFileName() line, index, line1, index1 = aw.getSelection() offset = aw.positionFromLineIndex(line, index) resource = rope.base.libutils.path_to_resource( self.__project, filename) try: inliner = rope.refactor.inline.create_inline( self.__project, resource, offset) except Exception as err: self.handleRopeError(err, title) return self.dlg = InlineDialog(self, title, inliner, parent = self.__ui) self.dlg.show() ##################################################### ## Move refactorings ##################################################### def __moveMethod(self): """ Private slot to handle the Move Method action. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return title = self.trUtf8("Move Method") if not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight the method to move" " and try again.")) return if not self.confirmAllBuffersSaved(): return filename = aw.getFileName() line, index, line1, index1 = aw.getSelection() offset = aw.positionFromLineIndex(line, index) resource = rope.base.libutils.path_to_resource( self.__project, filename) try: mover = rope.refactor.move.create_move( self.__project, resource, offset) except Exception as err: self.handleRopeError(err, title) return self.dlg = MoveMethodDialog(self, title, mover, parent=self.__ui) self.dlg.show() def __moveModule(self): """ Private slot to handle the Move Current Module action. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return title = self.trUtf8("Move Current Module") if not self.confirmAllBuffersSaved(): return filename = aw.getFileName() offset = None resource = rope.base.libutils.path_to_resource( self.__project, filename) try: mover = rope.refactor.move.create_move( self.__project, resource, offset) except Exception as err: self.handleRopeError(err, title) return self.dlg = MoveModuleDialog(self, title, mover, parent=self.__ui) self.dlg.show() ##################################################### ## Undo/Redo refactorings ##################################################### def __undo(self): """ Private slot to undo the last refactoring. """ title = self.trUtf8("Undo refactoring") history = self.__project.history res = E5MessageBox.question(None, title, self.trUtf8("""Shall the refactoring <b>{0}</b> be undone?""")\ .format(Utilities.html_encode( history.undo_list[-1].description)), QMessageBox.StandardButtons( QMessageBox.No | \ QMessageBox.Yes), QMessageBox.No) if res == QMessageBox.Yes: if not self.confirmAllBuffersSaved(): return changes = history.undo_list[-1] handle = ProgressHandle(self.trUtf8("Undo"), False, self.__ui) handle.show() QApplication.processEvents() try: history.undo(task_handle=handle) except Exception as err: self.handleRopeError(err, title, handle) handle.reset() self.refreshEditors(changes) if self.__e5project.isDirty(): self.__e5project.saveProject() def __redo(self): """ Private slot to redo the last refactoring. """ title = self.trUtf8("Redo refactoring") history = self.__project.history res = E5MessageBox.question(None, title, self.trUtf8("""Shall the refactoring <b>{0}</b> be redone?""")\ .format(Utilities.html_encode( history.redo_list[-1].description)), QMessageBox.StandardButtons(\ QMessageBox.No | \ QMessageBox.Yes), QMessageBox.No) if res == QMessageBox.Yes: if not self.confirmAllBuffersSaved(): return changes = history.redo_list[-1] handle = ProgressHandle(self.trUtf8("Redo"), False, self.__ui) handle.show() QApplication.processEvents() try: history.redo(task_handle=handle) except Exception as err: self.handleRopeError(err, title, handle) handle.reset() self.refreshEditors(changes) if self.__e5project.isDirty(): self.__e5project.saveProject() def __showProjectUndoHistory(self): """ Private method to show list of changes available for an undo operation. """ undoList = list(reversed(self.__project.history.undo_list)) self.dlg = HistoryDialog(self, undoList, True, self.__ui) self.dlg.show() def __showProjectRedoHistory(self): """ Private method to show list of changes available for a redo operation. """ redoList = self.__project.history.redo_list self.dlg = HistoryDialog(self, redoList, False, self.__ui) self.dlg.show() def __showFileUndoHistory(self): """ Private method to show list of changes related to the current file available for an undo operation. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return filename = aw.getFileName() resource = rope.base.libutils.path_to_resource( self.__project, filename) undoList = list(reversed(self.__getFileUndoList(resource))) self.dlg = HistoryDialog(self, undoList, True, self.__ui) self.dlg.show() def __showFileRedoHistory(self): """ Private method to show list of changes related to the current file available for a redo operation. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return filename = aw.getFileName() resource = rope.base.libutils.path_to_resource( self.__project, filename) redoList = self.__getFileRedoList(resource) self.dlg = HistoryDialog(self, redoList, False, self.__ui) self.dlg.show() def __clearHistory(self): """ Private slot to clear the redo and undo lists. """ res = E5MessageBox.question(None, self.trUtf8("Clear History"), self.trUtf8("""Do you really want to clear the undo""" """ and redo history?"""), QMessageBox.StandardButtons(\ QMessageBox.No | \ QMessageBox.Yes), QMessageBox.No) if res == QMessageBox.Yes: self.__project.history.clear() ##################################################### ## Find actions ##################################################### def __queryReferences(self): """ Private slot to handle the Find References action. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return title = self.trUtf8("Find Occurrences") if not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight the class, method, function or variable" " to search for and try again.")) return if not self.confirmAllBuffersSaved(): return filename = aw.getFileName() line, index, line1, index1 = aw.getSelection() offset = aw.positionFromLineIndex(line, index) resource = rope.base.libutils.path_to_resource( self.__project, filename) handle = ProgressHandle(title, True, self.__ui) handle.show() QApplication.processEvents() try: occurrences = rope.contrib.findit.find_occurrences( self.__project, resource, offset, unsure = True, in_hierarchy = True, task_handle = handle) except Exception as err: self.handleRopeError(err, title, handle) return handle.reset() if occurrences: self.dlg = MatchesDialog(self.__ui, True) self.dlg.show() for occurrence in occurrences: self.dlg.addEntry(occurrence.resource, occurrence.lineno, occurrence.unsure) else: E5MessageBox.warning(self.__ui, title, self.trUtf8("No occurrences found.")) def __queryDefinition(self): """ Private slot to handle the Find Definition action """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return title = self.trUtf8("Find &Definition") if not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight the class, method, function or" " variable reference to search definition for and" " try again.")) return if not self.confirmAllBuffersSaved(): return filename = aw.getFileName() line, index, line1, index1 = aw.getSelection() offset = aw.positionFromLineIndex(line, index) resource = rope.base.libutils.path_to_resource( self.__project, filename) try: location = rope.contrib.findit.find_definition( self.__project, aw.text(), offset, resource) except Exception as err: self.handleRopeError(err, title) return if location is not None: self.dlg = MatchesDialog(self.__ui, False) self.dlg.show() self.dlg.addEntry(location.resource, location.lineno) else: E5MessageBox.warning(self.__ui, title, self.trUtf8("No matching definition found.")) def __queryImplementations(self): """ Private slot to handle the Find Implementations action. """ aw = e5App().getObject("ViewManager").activeWindow() if aw is None: return title = self.trUtf8("Find Implementations") if not aw.hasSelectedText(): # no selection available E5MessageBox.warning(self.__ui, title, self.trUtf8("Highlight the method to search for" " and try again.")) return if not self.confirmAllBuffersSaved(): return filename = aw.getFileName() line, index, line1, index1 = aw.getSelection() offset = aw.positionFromLineIndex(line, index) resource = rope.base.libutils.path_to_resource(self.__project, filename) handle = ProgressHandle(title, True, self.__ui) handle.show() QApplication.processEvents() try: occurrences = rope.contrib.findit.find_implementations( self.__project, resource, offset, task_handle = handle) except Exception as err: self.handleRopeError(err, title, handle) return handle.reset() if occurrences: self.dlg = MatchesDialog(self.__ui, True) self.dlg.show() for occurrence in occurrences: self.dlg.addEntry(occurrence.resource, occurrence.lineno, occurrence.unsure) else: E5MessageBox.warning(self.__ui, title, self.trUtf8("No occurrences found.")) ##################################################### ## Various actions ##################################################### def __editConfig(self): """ Private slot to open the rope configuration file in an editor. """ ropedir = self.__project.ropefolder configfile = "" if ropedir is not None: configfile = os.path.join(ropedir.real_path, "config.py") if os.path.exists(configfile): self.__editor = MiniEditor(configfile) self.__editor.show() if self.__newStyle: self.__editor.editorSaved.connect(self.__configChanged) else: self.connect(self.__editor, SIGNAL("editorSaved"), self.__configChanged) else: E5MessageBox.critical(self.__ui, self.trUtf8("Configure Rope"), self.trUtf8("""The Rope configuration file '{0}' does""" """ not exist.""").format(configfile)) else: E5MessageBox.critical(self.__ui, self.trUtf8("Configure Rope"), self.trUtf8("""The Rope admin directory does not exist.""")) def __showRopeHelp(self): """ Private slot to show help about the refactorings offered by Rope. """ if self.__helpDialog is None: helpfile = os.path.join(os.path.dirname(__file__), "rope", "docs", "overview.txt") self.__helpDialog = \ HelpDialog(self.trUtf8("Help about rope refactorings"), helpfile) self.__helpDialog.show() ################################################################## ## methods below are private utility methods ################################################################## def __ropeConfigFile(self): """ Private method to get the name of the rope configuration file. @return name of the rope configuration file (string) """ configfile = None if self.__project is not None: ropedir = self.__project.ropefolder if ropedir is not None: configfile = os.path.join(ropedir.real_path, "config.py") if not os.path.exists(configfile): configfile = None return configfile def __configChanged(self): """ Private slot called, when the rope config file has changed. """ self.__project.close() self.__project = rope.base.project.Project(self.__projectpath, fscommands = self.__fsCommands) def __defaultConfig(self): """ Private slot to return the contents of rope's default configuration. @return string containing the source of rope's default configuration (string) """ if self.__project is not None: return self.__project._default_config() else: return "" ################################################################## ## methods below are public utility methods ################################################################## def getActions(self): """ Public method to get a list of all actions. @return list of all actions (list of E5Action) """ return self.actions[:] def projectOpened(self): """ Public slot to handle the projectOpened signal. """ if self.__projectopen: self.projectClosed() self.__projectopen = True self.__projectpath = self.__e5project.getProjectPath() self.__projectLanguage = self.__e5project.getProjectLanguage() if self.__projectLanguage in ["Python3"]: self.__project = rope.base.project.Project(self.__projectpath, fscommands = self.__fsCommands) for act in self.actions: act.setEnabled(True) def projectClosed(self): """ Public slot to handle the projectClosed signal. """ for act in self.actions: act.setEnabled(False) if self.__project is not None: self.__project.close() self.__project = None self.__projectopen = False self.__projectpath = '' self.__projectLanguage = "" def getProject(self): """ Public method to get a reference to the rope project object. @return reference to the rope project object (RopeProject) """ return self.__project def confirmBufferIsSaved(self, editor): """ Public method to check, if an editor has unsaved changes. @param editor reference to the editor to be checked @return flag indicating, that the editor doesn't contain unsaved edits (boolean) """ res = editor.checkDirty() self.__project.validate(self.__project.root) return res def confirmAllBuffersSaved(self): """ Private method to check, if any editor has unsaved changes. @return flag indicating, that no editor contains unsaved edits (boolean) """ res = e5App().getObject("ViewManager").checkAllDirty() self.__project.validate(self.__project.root) return res def refreshEditors(self, changes): """ Public method to refresh modified editors. @param reference to the Changes object (rope.base.change.ChangeSet) """ vm = e5App().getObject("ViewManager") changedFiles = [] for resource in changes.get_changed_resources(): if not resource.is_folder(): changedFiles.append(resource.real_path) openFiles = [Utilities.normcasepath(f) for f in vm.getOpenFilenames()] for file in changedFiles: normfile = Utilities.normcasepath(file) if normfile in openFiles: editor = vm.getEditor(normfile)[1] editor.refresh() aw = vm.activeWindow() if aw is not None: filename = aw.getFileName() if filename is not None: vm.openSourceFile(filename, aw.getCursorPosition()[0] + 1)