diff -r fc72a5b922a6 -r 3be1b4662b48 RefactoringRope/Refactoring.py --- a/RefactoringRope/Refactoring.py Sat Jan 29 13:38:30 2011 +0100 +++ b/RefactoringRope/Refactoring.py Sat Jan 29 15:10:40 2011 +0100 @@ -16,7 +16,22 @@ 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 @@ -29,9 +44,11 @@ from QScintilla.MiniEditor import MiniEditor from FileSystemCommands import e5FileSystemCommands +from ProgressHandle import ProgressHandle from HelpDialog import HelpDialog -from ProgressHandle import ProgressHandle from MatchesDialog import MatchesDialog +from RenameDialog import RenameDialog +from ChangeOccurrencesDialog import ChangeOccurrencesDialog import Utilities @@ -72,6 +89,85 @@ 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) + + ##################################################### ## Query actions ##################################################### @@ -195,6 +291,19 @@ 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.refactoringRenameModuleAct) + smenu.addSeparator() + menu.addSeparator() menu.addAction(self.refactoringEditConfigAct) menu.addAction(self.refactoringHelpAct) @@ -215,6 +324,80 @@ 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. + """ + # TODO: enable these once undo/redo has been implemented +## self.refactoringUndoAct.setEnabled(self.__canUndo()) +## self.refactoringRedoAct.setEnabled(self.__canRedo()) + def handleRopeError(self, err, title, handle=None): """ Public slot to handle a rope error. @@ -239,6 +422,131 @@ 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() + ##################################################### ## Find actions #####################################################