--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/RefactoringRope/RefactoringServer.py Sun Sep 24 13:56:36 2017 +0200 @@ -0,0 +1,2181 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 - 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the refactoring interface to rope. +""" + +from __future__ import unicode_literals +try: + str = unicode # __IGNORE_WARNING__ +except NameError: + pass + +import os +import sys + +sys.path.insert(0, os.path.dirname(__file__)) + +from PyQt5.QtWidgets import QMenu, QApplication, QAction +from PyQt5.Qsci import QsciScintilla + +from E5Gui.E5Application import e5App +from E5Gui import E5MessageBox +from E5Gui.E5Action import E5Action + +from .JsonServer import JsonServer +from .RopeProgressDialog import RopeProgressDialog + +import Utilities +import Preferences + + +class RefactoringServer(JsonServer): + """ + Class implementing the refactoring interface to rope. + """ + def __init__(self, plugin, parent=None): + """ + Constructor + + @param plugin reference to the plugin object + @param parent parent (QObject) + """ + super(RefactoringServer, self).__init__(parent) + + self.__plugin = plugin + self.__ui = parent + self.__e5project = e5App().getObject("Project") + self.__projectpath = '' + self.__projectLanguage = "" + self.__projectopen = False + self.__ropeConfig = {} + + self.__mainMenu = None + self.__progressDialog = None + self.__helpDialog = None + self.__historyDialog = None + self.__refactoringDialogs = {} + + from FileSystemCommands import E5FileSystemCommands + self.__fsCommands = E5FileSystemCommands(self.__e5project) + + self.__methodMapping = { + "Config": self.__setConfig, + "Progress": self.__processProgress, + "QueryReferencesResult": self.__queryReferencesResult, + "QueryDefinitionResult": self.__queryDefinitionResult, + "QueryImplementationsResult": self.__queryImplementationsResult, + "SoaFinished": self.__soaFinished, + + "FileSystemCommand": self.__fsCommands.processFileSystemCommand, + + "ClientException": self.__processClientException, + + "HistoryResult": self.__processHistoryResult, + + "Changes": self.__processChanges, + } + + def getMainWindow(self): + """ + Public method to get a reference to the IDE main window. + + @return reference to the IDE main window + @rtype UserInterface + """ + return self.__ui + + def initActions(self): + """ + Public method to define the refactoring actions. + """ + self.actions = [] + + ##################################################### + ## Rename refactoring actions + ##################################################### + + self.refactoringRenameAct = E5Action( + self.tr('Rename'), + self.tr('&Rename'), + 0, 0, + self, 'refactoring_rename') + self.refactoringRenameAct.setStatusTip(self.tr( + 'Rename the highlighted object')) + self.refactoringRenameAct.setWhatsThis(self.tr( + """<b>Rename</b>""" + """<p>Rename the highlighted Python object.</p>""" + )) + self.refactoringRenameAct.triggered.connect( + self.__rename) + self.actions.append(self.refactoringRenameAct) + + self.refactoringRenameLocalAct = E5Action( + self.tr('Local Rename'), + self.tr('&Local Rename'), + 0, 0, + self, 'refactoring_rename_local') + self.refactoringRenameLocalAct.setStatusTip(self.tr( + 'Rename the highlighted object in the current module only')) + self.refactoringRenameLocalAct.setWhatsThis(self.tr( + """<b>Local Rename</b>""" + """<p>Rename the highlighted Python object in the current""" + """ module only.</p>""" + )) + self.refactoringRenameLocalAct.triggered.connect( + self.__renameLocal) + self.actions.append(self.refactoringRenameLocalAct) + + self.refactoringRenameModuleAct = E5Action( + self.tr('Rename Current Module'), + self.tr('Rename Current Module'), + 0, 0, + self, 'refactoring_rename_module') + self.refactoringRenameModuleAct.setStatusTip(self.tr( + 'Rename the current module')) + self.refactoringRenameModuleAct.setWhatsThis(self.tr( + """<b>Rename Current Module</b>""" + """<p>Rename the current module.</p>""" + )) + self.refactoringRenameModuleAct.triggered.connect( + self.__renameModule) + self.actions.append(self.refactoringRenameModuleAct) + + self.refactoringChangeOccurrencesAct = E5Action( + self.tr('Change Occurrences'), + self.tr('Change &Occurrences'), + 0, 0, + self, 'refactoring_change_occurrences') + self.refactoringChangeOccurrencesAct.setStatusTip(self.tr( + 'Change all occurrences in the local scope')) + self.refactoringChangeOccurrencesAct.setWhatsThis(self.tr( + """<b>Change Occurrences</b>""" + """<p>Change all occurrences in the local scope.</p>""" + )) + self.refactoringChangeOccurrencesAct.triggered.connect( + self.__changeOccurrences) + self.actions.append(self.refactoringChangeOccurrencesAct) + + ##################################################### + ## Extract refactoring actions + ##################################################### + + self.refactoringExtractMethodAct = E5Action( + self.tr('Extract method'), + self.tr('Extract &Method'), + 0, 0, + self, 'refactoring_extract_method') + self.refactoringExtractMethodAct.setStatusTip(self.tr( + 'Extract the highlighted area as a method')) + self.refactoringExtractMethodAct.setWhatsThis(self.tr( + """<b>Extract method</b>""" + """<p>Extract the highlighted area as a method or function.</p>""" + )) + self.refactoringExtractMethodAct.triggered.connect( + self.__extractMethod) + self.actions.append(self.refactoringExtractMethodAct) + + self.refactoringExtractLocalVariableAct = E5Action( + self.tr('Extract local variable'), + self.tr('&Extract Local Variable'), + 0, 0, + self, 'refactoring_extract_variable') + self.refactoringExtractLocalVariableAct.setStatusTip(self.tr( + 'Extract the highlighted area as a local variable')) + self.refactoringExtractLocalVariableAct.setWhatsThis(self.tr( + """<b>Extract local variable</b>""" + """<p>Extract the highlighted area as a local variable.</p>""" + )) + self.refactoringExtractLocalVariableAct.triggered.connect( + self.__extractLocalVariable) + self.actions.append(self.refactoringExtractLocalVariableAct) + + ##################################################### + ## Inline refactoring actions + ##################################################### + + self.refactoringInlineAct = E5Action( + self.tr('Inline'), + self.tr('&Inline'), + 0, 0, + self, 'refactoring_inline') + self.refactoringInlineAct.setStatusTip(self.tr( + 'Inlines the selected local variable or method')) + self.refactoringInlineAct.setWhatsThis(self.tr( + """<b>Inline</b>""" + """<p>Inlines the selected local variable or method.</p>""" + )) + self.refactoringInlineAct.triggered.connect( + self.__inline) + self.actions.append(self.refactoringInlineAct) + + ##################################################### + ## Move refactoring actions + ##################################################### + + self.refactoringMoveMethodAct = E5Action( + self.tr('Move method'), + self.tr('Mo&ve Method'), + 0, 0, + self, 'refactoring_move_method') + self.refactoringMoveMethodAct.setStatusTip(self.tr( + 'Move the highlighted method to another class')) + self.refactoringMoveMethodAct.setWhatsThis(self.tr( + """<b>Move method</b>""" + """<p>Move the highlighted method to another class.</p>""" + )) + self.refactoringMoveMethodAct.triggered.connect( + lambda: self.__move("move_method")) + self.actions.append(self.refactoringMoveMethodAct) + + self.refactoringMoveModuleAct = E5Action( + self.tr('Move current module'), + self.tr('Move Current Module'), + 0, 0, + self, 'refactoring_move_module') + self.refactoringMoveModuleAct.setStatusTip(self.tr( + 'Move the current module to another package')) + self.refactoringMoveModuleAct.setWhatsThis(self.tr( + """<b>Move current module</b>""" + """<p>Move the current module to another package.</p>""" + )) + self.refactoringMoveModuleAct.triggered.connect( + lambda: self.__move("move_module")) + self.actions.append(self.refactoringMoveModuleAct) + + ##################################################### + ## Use function refactoring action + ##################################################### + + self.refactoringUseFunctionAct = E5Action( + self.tr('Use Function'), + self.tr('Use Function'), + 0, 0, + self, 'refactoring_use_function') + self.refactoringUseFunctionAct.setStatusTip(self.tr( + 'Use a function wherever possible.')) + self.refactoringUseFunctionAct.setWhatsThis(self.tr( + """<b>Use function</b>""" + """<p>Tries to use a function wherever possible.</p>""" + )) + self.refactoringUseFunctionAct.triggered.connect( + self.__useFunction) + self.actions.append(self.refactoringUseFunctionAct) + + ##################################################### + ## Introduce refactorings actions + ##################################################### + + self.refactoringIntroduceFactoryAct = E5Action( + self.tr('Introduce Factory Method'), + self.tr('Introduce &Factory Method'), + 0, 0, + self, 'refactoring_introduce_factory_method') + self.refactoringIntroduceFactoryAct.setStatusTip(self.tr( + 'Introduce a factory method or function')) + self.refactoringIntroduceFactoryAct.setWhatsThis(self.tr( + """<b>Introduce Factory Method</b>""" + """<p>Introduce a factory method or function.</p>""" + )) + self.refactoringIntroduceFactoryAct.triggered.connect( + self.__introduceFactoryMethod) + self.actions.append(self.refactoringIntroduceFactoryAct) + + self.refactoringIntroduceParameterAct = E5Action( + self.tr('Introduce Parameter'), + self.tr('Introduce &Parameter'), + 0, 0, + self, 'refactoring_introduce_parameter_method') + self.refactoringIntroduceParameterAct.setStatusTip(self.tr( + 'Introduce a parameter in a function')) + self.refactoringIntroduceParameterAct.setWhatsThis(self.tr( + """<b>Introduce Parameter</b>""" + """<p>Introduce a parameter in a function.</p>""" + )) + self.refactoringIntroduceParameterAct.triggered.connect( + self.__introduceParameter) + self.actions.append(self.refactoringIntroduceParameterAct) + + ##################################################### + ## Import refactorings actions + ##################################################### + + self.refactoringImportsOrganizeAct = E5Action( + self.tr('Organize Imports'), + self.tr('&Organize Imports'), + 0, 0, + self, 'refactoring_organize_imports') + self.refactoringImportsOrganizeAct.setStatusTip(self.tr( + 'Sort imports according to PEP-8')) + self.refactoringImportsOrganizeAct.setWhatsThis(self.tr( + """<b>Organize Imports</b>""" + """<p>Sort imports according to PEP-8.</p>""" + )) + self.refactoringImportsOrganizeAct.triggered.connect( + self.__importsOrganize) + self.actions.append(self.refactoringImportsOrganizeAct) + + self.refactoringImportsStarExpandAct = E5Action( + self.tr('Expand Star Imports'), + self.tr('E&xpand Star Imports'), + 0, 0, + self, 'refactoring_expand_star_imports') + self.refactoringImportsStarExpandAct.setStatusTip(self.tr( + 'Expand imports like "from xxx import *"')) + self.refactoringImportsStarExpandAct.setWhatsThis(self.tr( + """<b>Expand Star Imports</b>""" + """<p>Expand imports like "from xxx import *".</p>""" + """<p>Select the import to act on or none to do all.""" + """ Unused imports are deleted.</p>""" + )) + self.refactoringImportsStarExpandAct.triggered.connect( + self.__importsExpandStar) + self.actions.append(self.refactoringImportsStarExpandAct) + + self.refactoringImportsRelativeToAbsoluteAct = E5Action( + self.tr('Relative to Absolute'), + self.tr('Relative to &Absolute'), + 0, 0, + self, 'refactoring_relative_to_absolute_imports') + self.refactoringImportsRelativeToAbsoluteAct.setStatusTip(self.tr( + 'Transform relative imports to absolute ones')) + self.refactoringImportsRelativeToAbsoluteAct.setWhatsThis(self.tr( + """<b>Relative to Absolute</b>""" + """<p>Transform relative imports to absolute ones.</p>""" + """<p>Select the import to act on or none to do all.""" + """ Unused imports are deleted.</p>""" + )) + self.refactoringImportsRelativeToAbsoluteAct.triggered.connect( + self.__importsRelativeToAbsolute) + self.actions.append(self.refactoringImportsRelativeToAbsoluteAct) + + self.refactoringImportsFromsToImportsAct = E5Action( + self.tr('Froms to Imports'), + self.tr('Froms to &Imports'), + 0, 0, + self, 'refactoring_froms_to_imports') + self.refactoringImportsFromsToImportsAct.setStatusTip(self.tr( + 'Transform From imports to plain imports')) + self.refactoringImportsFromsToImportsAct.setWhatsThis(self.tr( + """<b>Froms to Imports</b>""" + """<p>Transform From imports to plain imports.</p>""" + """<p>Select the import to act on or none to do all.""" + """ Unused imports are deleted.</p>""" + )) + self.refactoringImportsFromsToImportsAct.triggered.connect( + self.__importsFromToImport) + self.actions.append(self.refactoringImportsFromsToImportsAct) + + self.refactoringImportsHandleLongAct = E5Action( + self.tr('Handle Long Imports'), + self.tr('Handle &Long Imports'), + 0, 0, + self, 'refactoring_organize_imports') + self.refactoringImportsHandleLongAct.setStatusTip(self.tr( + 'Transform long import statements to look better')) + self.refactoringImportsHandleLongAct.setWhatsThis(self.tr( + """<b>Handle Long Imports</b>""" + """<p>Transform long import statements to look better.</p>""" + """<p>Select the import to act on or none to do all.""" + """ Unused imports are deleted.</p>""" + )) + self.refactoringImportsHandleLongAct.triggered.connect( + self.__importsHandleLong) + self.actions.append(self.refactoringImportsHandleLongAct) + + ##################################################### + ## Various refactorings actions + ##################################################### + + self.refactoringRestructureAct = E5Action( + self.tr('Restructure'), + self.tr('Res&tructure'), + 0, 0, + self, 'refactoring_restructure') + self.refactoringRestructureAct.setStatusTip(self.tr( + 'Restructure code')) + self.refactoringRestructureAct.setWhatsThis(self.tr( + """<b>Restructure</b>""" + """<p>Restructure code. See "Rope Help" for examples.</p>""" + )) + self.refactoringRestructureAct.triggered.connect( + self.__restructure) + self.actions.append(self.refactoringRestructureAct) + + self.refactoringChangeSignatureAct = E5Action( + self.tr('Change Method Signature'), + self.tr('&Change Method Signature'), + 0, 0, + self, 'refactoring_change_method_signature') + self.refactoringChangeSignatureAct.setStatusTip(self.tr( + 'Change the signature of the selected method or function')) + self.refactoringChangeSignatureAct.setWhatsThis(self.tr( + """<b>Change Method Signature</b>""" + """<p>Change the signature of the selected method""" + """ or function.</p>""" + )) + self.refactoringChangeSignatureAct.triggered.connect( + self.__changeSignature) + self.actions.append(self.refactoringChangeSignatureAct) + + self.refactoringInlineArgumentDefaultAct = E5Action( + self.tr('Inline Argument Default'), + self.tr('Inline &Argument Default'), + 0, 0, + self, 'refactoring_inline_argument_default') + self.refactoringInlineArgumentDefaultAct.setStatusTip(self.tr( + 'Inline a parameters default value')) + self.refactoringInlineArgumentDefaultAct.setWhatsThis(self.tr( + """<b>Inline Argument Default</b>""" + """<p>Inline a parameters default value.</p>""" + )) + self.refactoringInlineArgumentDefaultAct.triggered.connect( + self.__inlineArgumentDefault) + self.actions.append(self.refactoringInlineArgumentDefaultAct) + + self.refactoringTransformModuleAct = E5Action( + self.tr('Transform Module to Package'), + self.tr('Transform Module to Package'), + 0, 0, + self, 'refactoring_transform_module_to_package') + self.refactoringTransformModuleAct.setStatusTip(self.tr( + 'Transform the current module to a package')) + self.refactoringTransformModuleAct.setWhatsThis(self.tr( + """<b>Transform Module to Package</b>""" + """<p>Transform the current module to a package.</p>""" + )) + self.refactoringTransformModuleAct.triggered.connect( + self.__transformModuleToPackage) + self.actions.append(self.refactoringTransformModuleAct) + + self.refactoringEncapsulateAttributeAct = E5Action( + self.tr('Encapsulate Attribute'), + self.tr('Encap&sulate Attribute'), + 0, 0, + self, 'refactoring_encapsulate_attribute') + self.refactoringEncapsulateAttributeAct.setStatusTip(self.tr( + 'Generate a getter/setter for an attribute')) + self.refactoringEncapsulateAttributeAct.setWhatsThis(self.tr( + """<b>Encapsulate Attribute</b>""" + """<p>Generate a getter/setter for an attribute and changes""" + """ its occurrences to use them.</p>""" + )) + self.refactoringEncapsulateAttributeAct.triggered.connect( + self.__encapsulateAttribute) + self.actions.append(self.refactoringEncapsulateAttributeAct) + + self.refactoringLocalVariableToAttributeAct = E5Action( + self.tr('Local Variable to Attribute'), + self.tr('Local Varia&ble to Attribute'), + 0, 0, + self, 'refactoring_local_variable_to_attribute') + self.refactoringLocalVariableToAttributeAct.setStatusTip(self.tr( + 'Change a local variable to an attribute')) + self.refactoringLocalVariableToAttributeAct.setWhatsThis(self.tr( + """<b>Local Variable to Attribute</b>""" + """<p>Change a local variable to an attribute.</p>""" + )) + self.refactoringLocalVariableToAttributeAct.triggered.connect( + self.__convertLocalToAttribute) + self.actions.append(self.refactoringLocalVariableToAttributeAct) + + self.refactoringMethodToMethodObjectAct = E5Action( + self.tr('Method To Method Object'), + self.tr('Method To Method Ob&ject'), + 0, 0, + self, 'refactoring_method_to_methodobject') + self.refactoringMethodToMethodObjectAct.setStatusTip(self.tr( + 'Transform a function or a method to a method object')) + self.refactoringMethodToMethodObjectAct.setWhatsThis(self.tr( + """<b>Method To Method Object</b>""" + """<p>Transform a function or a method to a method object.</p>""" + )) + self.refactoringMethodToMethodObjectAct.triggered.connect( + self.__methodToMethodObject) + self.actions.append(self.refactoringMethodToMethodObjectAct) + + ##################################################### + ## History actions + ##################################################### + + self.refactoringProjectHistoryAct = E5Action( + self.tr('Show Project History'), + self.tr('Show Project History...'), + 0, 0, + self, 'refactoring_show_project_history') + self.refactoringProjectHistoryAct.setStatusTip(self.tr( + 'Show the refactoring history of the project')) + self.refactoringProjectHistoryAct.setWhatsThis(self.tr( + """<b>Show Project History</b>""" + """<p>This opens a dialog to show the refactoring history of""" + """ the project.</p>""" + )) + self.refactoringProjectHistoryAct.triggered.connect( + self.__showProjectHistory) + self.actions.append(self.refactoringProjectHistoryAct) + + self.refactoringFileHistoryAct = E5Action( + self.tr('Show Current File History'), + self.tr('Show Current File History...'), + 0, 0, + self, 'refactoring_show_file_history') + self.refactoringFileHistoryAct.setStatusTip(self.tr( + 'Show the refactoring history of the current file')) + self.refactoringFileHistoryAct.setWhatsThis(self.tr( + """<b>Show Current File History</b>""" + """<p>This opens a dialog to show the refactoring history of""" + """ the current file.</p>""" + )) + self.refactoringFileHistoryAct.triggered.connect( + self.__showFileHistory) + self.actions.append(self.refactoringFileHistoryAct) + + self.refactoringClearHistoryAct = E5Action( + self.tr('Clear History'), + self.tr('Clear History'), + 0, 0, + self, 'refactoring_clear_history') + self.refactoringClearHistoryAct.setStatusTip(self.tr( + 'Clear the refactoring history')) + self.refactoringClearHistoryAct.setWhatsThis(self.tr( + """<b>Clear History</b>""" + """<p>Clears the refactoring history.</p>""" + )) + self.refactoringClearHistoryAct.triggered.connect( + self.__clearHistory) + self.actions.append(self.refactoringClearHistoryAct) + + ##################################################### + ## Query actions + ##################################################### + + self.queryReferencesAct = E5Action( + self.tr('Find occurrences'), + self.tr('Find &Occurrences'), + 0, 0, + self, 'refactoring_find_occurrences') + self.queryReferencesAct.setStatusTip(self.tr( + 'Find occurrences of the highlighted object')) + self.queryReferencesAct.setWhatsThis(self.tr( + """<b>Find occurrences</b>""" + """<p>Find occurrences of the highlighted class, method,""" + """ function or variable.</p>""" + )) + self.queryReferencesAct.triggered.connect( + self.__queryReferences) + self.actions.append(self.queryReferencesAct) + + self.queryDefinitionAct = E5Action( + self.tr('Find definition'), + self.tr('Find &Definition'), + 0, 0, + self, 'refactoring_find_definition') + self.queryDefinitionAct.setStatusTip(self.tr( + 'Find definition of the highlighted item')) + self.queryDefinitionAct.setWhatsThis(self.tr( + """<b>Find definition</b>""" + """<p>Find the definition of the highlighted class, method,""" + """ function or variable.</p>""" + )) + self.queryDefinitionAct.triggered.connect( + self.__queryDefinition) + self.actions.append(self.queryDefinitionAct) + + self.queryImplementationsAct = E5Action( + self.tr('Find implementations'), + self.tr('Find &Implementations'), + 0, 0, + self, 'refactoring_find_implementations') + self.queryImplementationsAct.setStatusTip(self.tr( + 'Find places where the selected method is overridden')) + self.queryImplementationsAct.setWhatsThis(self.tr( + """<b>Find implementations</b>""" + """<p>Find places where the selected method is overridden.</p>""" + )) + self.queryImplementationsAct.triggered.connect( + self.__queryImplementations) + self.actions.append(self.queryImplementationsAct) + + ##################################################### + ## Various actions + ##################################################### + + self.refactoringEditConfigAct = E5Action( + self.tr('Configure Rope'), + self.tr('&Configure Rope'), + 0, 0, + self, 'refactoring_edit_config') + self.refactoringEditConfigAct.setStatusTip(self.tr( + 'Open the rope configuration file')) + self.refactoringEditConfigAct.setWhatsThis(self.tr( + """<b>Configure Rope</b>""" + """<p>Opens the rope configuration file in an editor.</p>""" + )) + self.refactoringEditConfigAct.triggered.connect( + self.__editConfig) + self.refactoringEditConfigAct.setMenuRole(QAction.NoRole) + self.actions.append(self.refactoringEditConfigAct) + + self.refactoringHelpAct = E5Action( + self.tr('Rope Help'), + self.tr('Rope &Help'), + 0, 0, + self, 'refactoring_help') + self.refactoringHelpAct.setStatusTip(self.tr( + 'Show help about the rope refactorings')) + self.refactoringHelpAct.setWhatsThis(self.tr( + """<b>Rope help</b>""" + """<p>Show some help text about the rope refactorings.</p>""" + )) + self.refactoringHelpAct.triggered.connect( + self.__showRopeHelp) + self.actions.append(self.refactoringHelpAct) + + self.refactoringAllSoaAct = E5Action( + self.tr('Analyse all modules'), + self.tr('&Analyse all modules'), + 0, 0, + self, 'refactoring_analyze_all') + self.refactoringAllSoaAct.setStatusTip(self.tr( + 'Perform static object analysis on all modules')) + self.refactoringAllSoaAct.setWhatsThis(self.tr( + """<b>Analyse all modules</b>""" + """<p>Perform static object analysis (SOA) on all modules. """ + """This might be time consuming. Analysis of all modules """ + """should only be neccessary, if the project was created """ + """with the rope plugin disabled or if files were added.</p>""" + )) + self.refactoringAllSoaAct.triggered.connect( + self.__performSOA) + self.actions.append(self.refactoringAllSoaAct) + + self.updateConfigAct = E5Action( + self.tr('Update Configuration'), + self.tr('&Update Configuration'), + 0, 0, + self, 'refactoring_update_configuration') + self.updateConfigAct.setStatusTip(self.tr( + 'Generates a new configuration file overwriting the current one.')) + self.updateConfigAct.setWhatsThis(self.tr( + """<b>Update Configuration</b>""" + """<p>Generates a new configuration file overwriting""" + """ the current one.</p>""" + )) + self.updateConfigAct.triggered.connect( + self.__updateConfig) + self.actions.append(self.updateConfigAct) + + 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.tr('&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.tr("&Query")) + smenu.addAction(self.queryReferencesAct) + smenu.addAction(self.queryDefinitionAct) + smenu.addAction(self.queryImplementationsAct) + + smenu = menu.addMenu(self.tr("&Refactoring")) + 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.refactoringUseFunctionAct) + smenu.addSeparator() + smenu.addAction(self.refactoringChangeSignatureAct) + smenu.addAction(self.refactoringInlineArgumentDefaultAct) + smenu.addSeparator() + smenu.addAction(self.refactoringRestructureAct) + smenu.addSeparator() + smenu.addAction(self.refactoringIntroduceFactoryAct) + smenu.addAction(self.refactoringIntroduceParameterAct) + smenu.addAction(self.refactoringMethodToMethodObjectAct) + smenu.addSeparator() + smenu.addAction(self.refactoringEncapsulateAttributeAct) + smenu.addAction(self.refactoringLocalVariableToAttributeAct) + smenu.addSeparator() + smenu.addAction(self.refactoringRenameModuleAct) + smenu.addAction(self.refactoringMoveModuleAct) + smenu.addAction(self.refactoringTransformModuleAct) + smenu.addSeparator() + + imenu = smenu.addMenu(self.tr("Im&ports")) + imenu.addAction(self.refactoringImportsOrganizeAct) + imenu.addAction(self.refactoringImportsStarExpandAct) + imenu.addAction(self.refactoringImportsRelativeToAbsoluteAct) + imenu.addAction(self.refactoringImportsFromsToImportsAct) + imenu.addAction(self.refactoringImportsHandleLongAct) + + smenu.addSeparator() + + hmenu = smenu.addMenu(self.tr("History")) + hmenu.aboutToShow.connect(self.__showRefactoringHistoryMenu) + hmenu.addAction(self.refactoringProjectHistoryAct) + hmenu.addAction(self.refactoringFileHistoryAct) + hmenu.addSeparator() + hmenu.addAction(self.refactoringClearHistoryAct) + + smenu = menu.addMenu(self.tr("&Utilities")) + smenu.addAction(self.refactoringAllSoaAct) + smenu.addSeparator() + smenu.addAction(self.updateConfigAct) + + 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. + """ + if self.__ropeConfig: + E5MessageBox.about( + self.__ui, + self.tr("About rope"), + self.tr("{0}\nVersion {1}\n\n{2}".format( + self.__ropeConfig["RopeInfo"], + self.__ropeConfig["RopeVersion"], + self.__ropeConfig["RopeCopyright"]))) + + def __showRefactoringHistoryMenu(self): + """ + Private slot called before the refactoring history menu is shown. + """ + aw = e5App().getObject("ViewManager").activeWindow() + enable = aw is not None and bool(aw.getFileName()) + + self.refactoringFileHistoryAct.setEnabled(enable) + + def handleRopeError(self, result): + """ + Public method to handle a rope error. + + @param result dictionary containing the error information + @type dict + @return flag indicating, that the error is to be ignored + @rtype bool + """ + if "Error" not in result: + return True + + if "Title" in result: + title = result["Title"] + else: + title = self.tr("Rope Error") + + if result["Error"] == 'ModuleSyntaxError': + res = E5MessageBox.warning( + self.__ui, title, + self.tr("Rope error: {0}").format( + result["ErrorString"]), + E5MessageBox.Ok | E5MessageBox.Open) + if res == E5MessageBox.Open: + e5App().getObject("ViewManager").openSourceFile( + os.path.join(self.__e5project.getProjectPath(), + result["ErrorFile"]), + result["ErrorLine"]) + elif result["Error"] == "InterruptedTaskError": + return True + else: + E5MessageBox.warning( + self.__ui, title, + self.tr("Rope error: {0}").format( + result["ErrorString"]) + ) + + return False + + def __getOffset(self, editor, line, index): + r""" + Private method to get the offset into the text treating CRLF as ONE + character. + + Note: rope seems to convert all EOL styles to just \n. + + @param editor reference to the editor (Editor) + @param line line for the offset (integer) + @param index index into line for the offset (integer) + @return rope compliant offset into the file (integer) + """ + source = editor.text() + offset = len("".join(source.splitlines(True)[:line])) + index + if editor.eolMode() == QsciScintilla.EolWindows: + offset -= line + return offset + + ################################################################## + ## slots below implement the various refactorings + ################################################################## + + def __processChanges(self, result): + """ + Private method to process the changes data sent by the refactoring + client. + + @param result dictionary containing the changes data + @type dict + """ + if self.handleRopeError(result): + changeGroup = result["ChangeGroup"] + try: + self.__refactoringDialogs[changeGroup]\ + .processChangeData(result) + except KeyError: + # ignore data for non-existing dialogs + pass + + def __refactoringDialogClosed(self, changeGroup): + """ + Private slot handling the closing of a refactoring dialog. + + @param changeGroup name of the refactoring change group the dialog + belonged to + @type str + """ + try: + del self.__refactoringDialogs[changeGroup] + except KeyError: + # it's gone already; ignore it + pass + + ##################################################### + ## Rename refactorings + ##################################################### + + def __rename(self): + """ + Private slot to handle the Rename action. + """ + self.__doRename(self.tr('Rename')) + + def __renameLocal(self): + """ + Private slot to handle the Local Rename action. + """ + self.__doRename(self.tr('Local Rename'), isLocal=True) + + def __renameModule(self): + """ + Private slot to handle the Rename Current Module action. + """ + self.__doRename(self.tr('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.tr("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 + selectedText, _ = os.path.splitext(os.path.basename(filename)) + else: + line, index, line1, index1 = aw.getSelection() + if line != line1: + # selection span more than one line + E5MessageBox.warning( + self.__ui, title, + self.tr("The selection must not extend beyond" + " one line.")) + return + index = int(index + (index1 - index) / 2) + # keep it inside the object + offset = self.__getOffset(aw, line, index) + selectedText = aw.selectedText() + + from RenameDialog import RenameDialog + dlg = RenameDialog(self, title, filename, offset, isLocal, + selectedText=selectedText, parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + 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.tr("Change Occurrences") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("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 = self.__getOffset(aw, line, index) + + from ChangeOccurrencesDialog import ChangeOccurrencesDialog + dlg = ChangeOccurrencesDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + ##################################################### + ## Extract refactorings + ##################################################### + + def __extractMethod(self): + """ + Private slot to handle the Extract Method action. + """ + self.__doExtract(self.tr("Extract Method"), "method") + + def __extractLocalVariable(self): + """ + Private slot to handle the Extract Local Variable action. + """ + self.__doExtract(self.tr("Extract Local Variable"), "variable") + + def __doExtract(self, title, kind): + """ + Private method to perform the extract refactoring. + + @param title title of the refactoring + @type str + @param kind kind of extraction to be done + @type str ("method" or "variable") + """ + assert kind in ["variable", "method"] + + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("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 = self.__getOffset(aw, startline, startcolumn) + endOffset = self.__getOffset(aw, endline, endcolumn) + + from ExtractDialog import ExtractDialog + dlg = ExtractDialog(self, title, filename, startOffset, endOffset, + kind, parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + 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.tr("Inline") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("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 = self.__getOffset(aw, line, index) + + from InlineDialog import InlineDialog + dlg = InlineDialog(self, title, filename, offset, parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + ##################################################### + ## Move refactorings + ##################################################### + + def __move(self, moveKind): + """ + Private slot to handle the Move Method action. + + @param moveKind kind of move to be performed + @type str (one of 'move_method' or 'move_module') + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + if moveKind == "move_method": + title = self.tr("Move Method") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the method to move" + " and try again.")) + return + else: + title = self.tr("Move Current Module") + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + if moveKind == "move_method": + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + else: + offset = None + + from MoveDialog import MoveDialog + dlg = MoveDialog(self, title, filename, offset, parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + ##################################################### + ## Use function refactoring + ##################################################### + + def __useFunction(self): + """ + Private slot to use a function wherever possible. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Use Function") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight a global function and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from UseFunctionDialog import UseFunctionDialog + dlg = UseFunctionDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + ##################################################### + ## Introduce refactorings + ##################################################### + + def __introduceFactoryMethod(self): + """ + Private slot to introduce a factory method or global function. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Introduce Factory Method") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the class to introduce a factory" + " method for and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from IntroduceFactoryDialog import IntroduceFactoryDialog + dlg = IntroduceFactoryDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + def __introduceParameter(self): + """ + Private slot to introduce a parameter in a function. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Introduce Parameter") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the code for the new parameter" + " and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from IntroduceParameterDialog import IntroduceParameterDialog + dlg = IntroduceParameterDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + ##################################################### + ## Import refactorings + ##################################################### + + def __importsOrganize(self): + """ + Private slot to organize imports. + """ + self.__doImports( + self.tr("Organize Imports"), + "organize_imports") + + def __importsExpandStar(self): + """ + Private slot to expand star imports. + """ + self.__doImports( + self.tr("Expand Star Imports"), + "expand_star_imports") + + def __importsRelativeToAbsolute(self): + """ + Private slot to transform relative to absolute imports. + """ + self.__doImports( + self.tr("Relative to Absolute"), + "relatives_to_absolutes") + + def __importsFromToImport(self): + """ + Private slot to transform from imports to plain imports. + """ + self.__doImports( + self.tr("Froms to Imports"), + "froms_to_imports") + + def __importsHandleLong(self): + """ + Private slot to handle long imports. + """ + self.__doImports( + self.tr("Handle Long Imports"), + "handle_long_imports") + + def __doImports(self, title, methodName): + """ + Private method to perform the various imports refactorings. + + @param title title to be used for the import refactoring + @type str + @param methodName name of the method performing the import refactoring + @type str + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + if not self.confirmBufferIsSaved(aw): + return + + filename = aw.getFileName() + if aw.hasSelectedText(): + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + else: + offset = None + + from ConfirmationDialog import ConfirmationDialog + dlg = ConfirmationDialog( + self, title, "Imports", "CalculateImportsChanges", { + "MethodName": methodName, + "FileName": filename, + "Offset": offset, + }, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + ##################################################### + ## Various refactorings + ##################################################### + + def __restructure(self): + """ + Private slot to restructure code. + """ + from RestructureDialog import RestructureDialog + title = self.tr("Restructure") + dlg = RestructureDialog(self, title, parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + def __changeSignature(self): + """ + Private slot to change the signature of a method or function. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Change Method Signature") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the method or function to change" + " and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from ChangeSignatureDialog import ChangeSignatureDialog + dlg = ChangeSignatureDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + def __inlineArgumentDefault(self): + """ + Private slot to inline the default value of a parameter of a + method or function. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Inline Argument Default") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the method or function to inline" + " a parameter's default and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from InlineArgumentDefaultDialog import InlineArgumentDefaultDialog + dlg = InlineArgumentDefaultDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + def __transformModuleToPackage(self): + """ + Private slot to transform a module to a package. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Transform Module to Package") + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + + from ConfirmationDialog import ConfirmationDialog + dlg = ConfirmationDialog( + self, title, "ModuleToPackage", "CalculateModuleToPackageChanges", + { + "FileName": filename, + }, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + def __encapsulateAttribute(self): + """ + Private slot to encapsulate an attribute. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Encapsulate Attribute") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the attribute to encapsulate" + " and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from GetterSetterDialog import GetterSetterDialog + dlg = GetterSetterDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + def __convertLocalToAttribute(self): + """ + Private slot to convert a local variable to an attribute. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Local Variable to Attribute") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the local variable to make an attribute" + " and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from ConfirmationDialog import ConfirmationDialog + dlg = ConfirmationDialog( + self, title, "LocalToAttribute", + "CalculateLocalToAttributeChanges", { + "FileName": filename, + "Offset": offset, + }, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + def __methodToMethodObject(self): + """ + Private slot to change the signature of a method or function. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Replace Method With Method Object") + if not aw.hasSelectedText(): + # no selection available + E5MessageBox.warning( + self.__ui, title, + self.tr("Highlight the method or function to convert" + " and try again.")) + return + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index, line1, index1 = aw.getSelection() + offset = self.__getOffset(aw, line, index) + + from MethodToMethodObjectDialog import MethodToMethodObjectDialog + dlg = MethodToMethodObjectDialog(self, title, filename, offset, + parent=self.__ui) + changeGroup = dlg.getChangeGroupName() + self.__refactoringDialogs[changeGroup] = dlg + dlg.finished.connect( + lambda: self.__refactoringDialogClosed(changeGroup)) + dlg.show() + + ##################################################### + ## Refactoring History + ##################################################### + + def __showProjectHistory(self): + """ + Private method to show the project refactoring history. + """ + if self.__historyDialog is not None: + self.__historyDialog.close() + + from HistoryDialog import HistoryDialog + self.__historyDialog = HistoryDialog(self, parent=self.__ui) + self.__historyDialog.finished.connect(self.__historyDialogClosed) + self.__historyDialog.show() + + def __showFileHistory(self): + """ + Private method to show the refactoring history of the current file. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + if self.__historyDialog is not None: + self.__historyDialog.close() + + from HistoryDialog import HistoryDialog + filename = aw.getFileName() + if filename: + self.__historyDialog = HistoryDialog( + self, filename=filename, parent=self.__ui) + self.__historyDialog.show() + + def __clearHistory(self): + """ + Private slot to clear the redo and undo lists. + """ + res = E5MessageBox.yesNo( + None, + self.tr("Clear History"), + self.tr("Do you really want to clear the refactoring history?")) + if res: + self.sendJson("History", { + "Subcommand": "Clear", + }) + + if self.__historyDialog is not None: + self.__historyDialog.historyCleared() + + def __processHistoryResult(self, result): + """ + Private method to process the history data sent by the refactoring + client. + + @param result dictionary containing the history data + @type dict + """ + if self.handleRopeError(result) and self.__historyDialog is not None: + self.__historyDialog.processHistoryCommand(result) + + def __historyDialogClosed(self): + """ + Private slot handling the closing of the history dialog. + """ + self.__historyDialog = None + + ##################################################### + ## Find actions including mouse click handler + ##################################################### + + def __queryReferences(self): + """ + Private slot to handle the Find References action. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Find Occurrences") + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index = aw.getCursorPosition() + offset = self.__getOffset(aw, line, index) + + self.sendJson("QueryReferences", { + "Title": title, + "FileName": filename, + "Offset": offset, + }) + + def __queryReferencesResult(self, result): + """ + Private method to handle the "Query References" result sent by + the client. + + @param result dictionary containing the result data + @type dict + """ + if self.handleRopeError(result): + title = result["Title"] + if result["EntriesCount"] > 0: + from MatchesDialog import MatchesDialog + self.dlg = MatchesDialog(self.__ui, True) + self.dlg.show() + for occurrence in result["Entries"]: + self.dlg.addEntry( + # file name, lineno, unsure + occurrence[0], occurrence[1], occurrence[2]) + else: + E5MessageBox.warning( + self.__ui, title, + self.tr("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.tr("Find Definition") + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index = aw.getCursorPosition() + offset = self.__getOffset(aw, line, index) + + self.sendJson("QueryDefinition", { + "Title": title, + "FileName": filename, + "Offset": offset, + "Source": aw.text(), + "Subcommand": "Query", + }) + + def gotoDefinition(self, editor): + """ + Public slot to find the definition for the word at the cursor position + and go to it. + + Note: This is executed upon a mouse click sequence. + + @param editor reference to the calling editor (Editor) + """ + filename = editor.getFileName() + line, index = editor.getCursorPosition() + offset = self.__getOffset(editor, line, index) + + self.sendJson("QueryDefinition", { + "Title": "", + "FileName": filename, + "Offset": offset, + "Source": editor.text(), + "Subcommand": "Goto", + }) + + def __queryDefinitionResult(self, result): + """ + Private method to handle the "Query Definition" result sent by + the client. + + @param result dictionary containing the result data + @type dict + """ + if result["Subcommand"] == "Query": + if self.handleRopeError(result): + title = result["Title"] + if "Location" in result: + location = result["Location"] + + from MatchesDialog import MatchesDialog + self.dlg = MatchesDialog(self.__ui, False) + self.dlg.show() + self.dlg.addEntry(location[0], location[1]) + # file name, lineno + else: + E5MessageBox.warning( + self.__ui, title, + self.tr("No matching definition found.")) + elif result["Subcommand"] == "Goto": + if "Error" not in result: + # ignore errors silently + if "Location" in result: + location = result["Location"] + try: + e5App().getObject("ViewManager").openSourceFile( + location[0], location[1], addNext=True) + except TypeError: + # backward compatibility; <= 17.03 + e5App().getObject("ViewManager").openSourceFile( + location[0], location[1], next=True) + else: + e5App().getObject("UserInterface").statusBar().showMessage( + self.tr('No definition found'), 5000) + + def __queryImplementations(self): + """ + Private slot to handle the Find Implementations action. + """ + aw = e5App().getObject("ViewManager").activeWindow() + + if aw is None: + return + + title = self.tr("Find Implementations") + + if not self.confirmAllBuffersSaved(): + return + + filename = aw.getFileName() + line, index = aw.getCursorPosition() + offset = self.__getOffset(aw, line, index) + + self.sendJson("QueryImplementations", { + "Title": title, + "FileName": filename, + "Offset": offset, + }) + + def __queryImplementationsResult(self, result): + """ + Private method to handle the "Query Implementations" result sent by + the client. + + @param result dictionary containing the result data + @type dict + """ + if self.handleRopeError(result): + title = result["Title"] + if result["EntriesCount"] > 0: + from MatchesDialog import MatchesDialog + self.dlg = MatchesDialog(self.__ui, True) + self.dlg.show() + for occurrence in result["Entries"]: + self.dlg.addEntry( + # file name, lineno, unsure + occurrence[0], occurrence[1], occurrence[2]) + else: + E5MessageBox.warning( + self.__ui, title, self.tr("No implementations found.")) + + ##################################################### + ## Various actions + ##################################################### + + def __editConfig(self): + """ + Private slot to open the rope configuration file in an editor. + """ + ropedir = self.__ropeConfig["RopeFolderName"] + configfile = "" + if ropedir and os.path.exists(ropedir): + configfile = os.path.join(ropedir, "config.py") + if os.path.exists(configfile): + from QScintilla.MiniEditor import MiniEditor + self.__editor = MiniEditor(configfile) + self.__editor.show() + self.__editor.editorSaved.connect(self.__configChanged) + else: + E5MessageBox.critical( + self.__ui, + self.tr("Configure Rope"), + self.tr("""The Rope configuration file '{0}' does""" + """ not exist.""").format(configfile)) + else: + E5MessageBox.critical( + self.__ui, + self.tr("Configure Rope"), + self.tr("""The Rope admin directory does not exist.""")) + + def __updateConfig(self): + """ + Private slot to update the configuration file. + """ + res = E5MessageBox.yesNo( + self.__ui, + self.tr("Update Configuration"), + self.tr("""Shall rope's current configuration be replaced """ + """by a new default configuration?""")) + if res: + src = self.__defaultConfig() + cname = self.__ropeConfigFile() + if src != "" and cname is not None: + try: + f = open(cname, "w") + f.write(src) + f.close() + self.__configChanged() + self.__editConfig() + except IOError as err: + E5MessageBox.critical( + None, + self.tr("Update Configuration"), + self.tr("""<p>The configuration could not be""" + """ updated.</p><p>Reason: {0}</p>""") + .format(str(err))) + + def __showRopeHelp(self): + """ + Private slot to show help about the refactorings offered by Rope. + """ + if self.__helpDialog is None: + from HelpDialog import HelpDialog + self.__helpDialog = \ + HelpDialog(self.tr("Help about rope refactorings"), + self.__ropeConfig["RopeHelpFile"]) + self.__helpDialog.show() + + def __performSOA(self): + """ + Private slot to perform SOA on all modules. + """ + title = self.tr("Analyse all modules") + res = E5MessageBox.yesNo( + self.__ui, + title, + self.tr("""This action might take some time. """ + """Do you really want to perform SOA?""")) + if res: + + self.sendJson("PerformSoa", { + "Title": title, + }) + + def __soaFinished(self, result): + """ + Private method to handle the "Soa Finished" result sent by + the client. + + @param result dictionary containing the result data + @type dict + """ + if self.handleRopeError(result): + title = result["Title"] + + E5MessageBox.information( + self.__ui, + title, + self.tr("""Static object analysis (SOA) done. """ + """SOA database updated.""")) + + ################################################################## + ## methods below are private utility methods + ################################################################## + + def __processProgress(self, params): + """ + Private method to handle Progress commands. + + @param params dictionary containing the progress data + @type dict + """ + subcommand = params["Subcommand"] + if subcommand == "Init": + if self.__progressDialog is not None: + self.__progressDialog.reset() + + progressDialog = RopeProgressDialog( + self, params["Title"], params["Interruptable"]) + progressDialog.show() + progressDialog.raise_() + self.__progressDialog = progressDialog + QApplication.processEvents() + + elif subcommand == "Progress": + if self.__progressDialog is not None: + self.__progressDialog.updateProgress(params) + self.__progressDialog.raise_() + + elif subcommand == "Reset": + if self.__progressDialog is not None: + self.__progressDialog.reset() + + def __setConfig(self, params): + """ + Private method to set the rope client configuration data. + + @param params dictionary containing the configuration data + @type dict + """ + self.__ropeConfig = params + # keys: RopeFolderName, DefaultConfig, RopeHelpFile, + # RopeInfo, RopeVersion, RopeCopyright + + 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.__ropeConfig: + ropedir = self.__ropeConfig["RopeFolderName"] + if ropedir: + configfile = os.path.join(ropedir, "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.sendJson("ConfigChanged", {}) + + 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.__ropeConfig and "DefaultConfig" in self.__ropeConfig: + return self.__ropeConfig["DefaultConfig"] + 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() + + ok = False + + if self.__projectLanguage.startswith("Python"): + # get interpreter from project first + interpreter = self.__e5project.getDebugProperty("INTERPRETER") + if not interpreter or not Utilities.isinpath(interpreter): + # get it from debugger settings second + if self.__projectLanguage == "Python2": + interpreter = Preferences.getDebugger("PythonInterpreter") + elif self.__projectLanguage == "Python3": + interpreter = Preferences.getDebugger("Python3Interpreter") + else: + interpreter = "" + if interpreter: + ok = self.__startRefactoringClient(interpreter) + if not ok: + self.__ui.appendToStderr(self.tr( + "Project language '{0}' is not supported because" + " the configured interpreter could not be started." + " Refactoring is disabled." + ).format(self.__projectLanguage)) + else: + for act in self.actions: + act.setEnabled(True) + else: + self.__ui.appendToStderr(self.tr( + "Project language '{0}' is not supported because no" + " suitable interpreter is configured. Refactoring is" + " disabled." + ).format(self.__projectLanguage)) + else: + self.__ui.appendToStderr(self.tr( + "Refactoring for project language '{0}' is not supported." + ).format(self.__projectLanguage)) + + self.__mainMenu.menuAction().setEnabled(ok) + + def projectClosed(self): + """ + Public slot to handle the projectClosed signal. + """ + for act in self.actions: + act.setEnabled(False) + self.__mainMenu.menuAction().setEnabled(False) + + if self.__helpDialog is not None: + self.__helpDialog.close() + self.__helpDialog = None + if self.__historyDialog is not None: + self.__historyDialog.close() + self.__historyDialog = None + for dlg in self.__refactoringDialogs.values(): + dlg.close() + self.__refactoringDialogs = {} + + self.sendJson("CloseProject", {}, flush=True) + + self.__projectopen = False + self.__projectpath = '' + self.__projectLanguage = "" + self.__ropeConfig = {} + + self.stopClient() + + 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.sendJson("Validate", {}) + return res + + def confirmAllBuffersSaved(self): + """ + Public 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.sendJson("Validate", {}) + return res + + def refreshEditors(self, changedFiles): + """ + Public method to refresh modified editors. + + @param changedFiles list of changed files + @type list of str + """ + vm = e5App().getObject("ViewManager") + openFiles = [Utilities.normcasepath(f) for f in vm.getOpenFilenames()] + + for fileName in changedFiles: + normfile = Utilities.normcasepath(fileName) + 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) + + def reportChanged(self, filename, oldSource): + """ + Public slot to report some changed sources. + + @param filename file name of the changed source (string) + @param oldSource source code before the change (string) + """ + if self.__e5project.isOpen() and \ + self.__e5project.isProjectFile(filename): + self.sendJson("ReportChanged", { + "FileName": filename, + "OldSource": oldSource, + }) + + ####################################################################### + ## Methods below handle the network connection + ####################################################################### + + def handleCall(self, method, params): + """ + Public method to handle a method call from the client. + + Note: This is an empty implementation that must be overridden in + derived classes. + + @param method requested method name + @type str + @param params dictionary with method specific parameters + @type dict + """ + self.__methodMapping[method](params) + + def __processClientException(self, params): + """ + Private method to handle exceptions of the refactoring client. + + @param params dictionary containing the exception data + @type dict + """ + if params["ExceptionType"] == "ProtocolError": + E5MessageBox.critical( + None, + self.tr("Refactoring Protocol Error"), + self.tr("""<p>The data received from the refactoring""" + """ server could not be decoded. Please report""" + """ this issue with the received data to the""" + """ eric bugs email address.</p>""" + """<p>Error: {0}</p>""" + """<p>Data:<br/>{0}</p>""").format( + params["ExceptionValue"], + Utilities.html_encode(params["ProtocolData"])), + E5MessageBox.StandardButtons( + E5MessageBox.Ok)) + else: + E5MessageBox.critical( + None, + self.tr("Refactoring Client Error"), + self.tr("<p>An exception happened in the refactoring" + " client. Please report it to the eric bugs" + " email address.</p>" + "<p>Exception: {0}</p>" + "<p>Value: {1}</p>" + "Traceback: {2}</p>").format( + Utilities.html_encode(params["ExceptionType"]), + params["ExceptionValue"], + params["Traceback"].replace("\r\n", "<br/>") + .replace("\n", "<br/>").replace("\r", "<br/>"), + ), + E5MessageBox.StandardButtons( + E5MessageBox.Ok)) + + def __startRefactoringClient(self, interpreter): + """ + Private method to start the refactoring client. + + @param interpreter interpreter to be used for the refactoring client + @type str + @return flag indicating a successful client start + @rtype bool + """ + client = os.path.join(os.path.dirname(__file__), + "RefactoringClient.py") + ok = self.startClient(interpreter, client, [self.__projectpath]) + return ok