PluginRefactoringRope.py

Sat, 25 Jun 2022 17:55:41 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 25 Jun 2022 17:55:41 +0200
branch
eric7
changeset 385
9bbd74b51d88
parent 383
a89b3f379703
child 387
d8b788ce1f10
permissions
-rw-r--r--

Compatibility fixes for rope >= 1.2.0.

# -*- coding: utf-8 -*-

# Copyright (c) 2010 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the Rope refactoring plugin.
"""

import os
import contextlib

from PyQt6.QtCore import Qt, QObject, QTranslator, QCoreApplication

from EricWidgets.EricApplication import ericApp

import Preferences
import Utilities

# Start-Of-Header
name = "Refactoring Rope Plugin"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "10.2.2"
className = "RefactoringRopePlugin"
packageName = "RefactoringRope"
internalPackages = "rope"
shortDescription = "Refactoring and Code Assist using the Rope library."
longDescription = (
    """This plug-in implements refactoring functionality using the Rope"""
    """ refactoring library. Additionally it implements an auto-completion,"""
    """ call-tips and code documentation provider as well as a mouse"""
    """ click handler."""
)
pyqtApi = 2
doNotCompile = True
# End-Of-Header

error = ""

refactoringRopePluginObject = None


def createAutoCompletionPage(configDlg):
    """
    Module function to create the autocompletion configuration page.
    
    @param configDlg reference to the configuration dialog
    @type ConfigurationWidget
    @return reference to the configuration page
    @rtype AutoCompletionRopePage
    """
    global refactoringRopePluginObject
    from RefactoringRope.ConfigurationPage.AutoCompletionRopePage import (
        AutoCompletionRopePage
    )
    page = AutoCompletionRopePage(refactoringRopePluginObject)
    return page


def createCallTipsPage(configDlg):
    """
    Module function to create the calltips configuration page.
    
    @param configDlg reference to the configuration dialog
    @type ConfigurationWidget
    @return reference to the configuration page
    @rtype CallTipsRopePage
    """
    global refactoringRopePluginObject
    from RefactoringRope.ConfigurationPage.CallTipsRopePage import (
        CallTipsRopePage
    )
    page = CallTipsRopePage(refactoringRopePluginObject)
    return page


def createMouseClickHandlerPage(configDlg):
    """
    Module function to create the mouse click handler configuration page.
    
    @param configDlg reference to the configuration dialog
    @type ConfigurationWidget
    @return reference to the configuration page
    @rtype MouseClickHandlerRopePage
    """
    global refactoringRopePluginObject
    from RefactoringRope.ConfigurationPage.MouseClickHandlerRopePage import (
        MouseClickHandlerRopePage
    )
    page = MouseClickHandlerRopePage(
        refactoringRopePluginObject)
    return page


def getConfigData():
    """
    Module function returning data as required by the configuration dialog.
    
    @return dictionary containing the relevant data
    @rtype dict
    """
    try:
        usesDarkPalette = ericApp().usesDarkPalette()
    except AttributeError:
        from PyQt6.QtGui import QPalette
        palette = ericApp().palette()
        lightness = palette.color(QPalette.Window).lightness()
        usesDarkPalette = lightness <= 128
    iconSuffix = "dark" if usesDarkPalette else "light"
    
    data = {
        "ropeAutoCompletionPage": [
            QCoreApplication.translate("RefactoringRopePlugin", "Rope"),
            os.path.join("RefactoringRope", "ConfigurationPage",
                         "preferences-refactoring-{0}".format(iconSuffix)),
            createAutoCompletionPage, "1editorAutocompletionPage", None],
        "ropeCallTipsPage": [
            QCoreApplication.translate("RefactoringRopePlugin", "Rope"),
            os.path.join("RefactoringRope", "ConfigurationPage",
                         "preferences-refactoring-{0}".format(iconSuffix)),
            createCallTipsPage, "1editorCalltipsPage", None],
        "ropeMouseClickHandlerPage": [
            QCoreApplication.translate("RefactoringRopePlugin", "Rope"),
            os.path.join("RefactoringRope", "ConfigurationPage",
                         "preferences-refactoring-{0}".format(iconSuffix)),
            createMouseClickHandlerPage, "1editorMouseClickHandlers", None],
    }
    
    return data


def prepareUninstall():
    """
    Module function to prepare for an uninstallation.
    """
    Preferences.Prefs.settings.remove(RefactoringRopePlugin.PreferencesKey)


class RefactoringRopePlugin(QObject):
    """
    Class implementing the Rope refactoring plugin.
    """
    PreferencesKey = "RefactoringRope"
    
    def __init__(self, ui):
        """
        Constructor
        
        @param ui reference to the user interface object
        @type UserInterface
        """
        QObject.__init__(self, ui)
        self.__ui = ui
        self.__initialize()
        
        self.__defaults = {
            "CodeAssistEnabled": False,
            "MaxFixes": 10,
            
            "CodeAssistCalltipsEnabled": False,
            "CalltipsMaxFixes": 10,
            
            "MouseClickEnabled": True,
            "MouseClickGotoModifiers": (
                Qt.KeyboardModifier.ControlModifier |
                Qt.KeyboardModifier.AltModifier
            ),
            "MouseClickGotoButton": Qt.MouseButton.LeftButton,
        }
        
        self.__translator = None
        self.__loadTranslator()
    
    def __initialize(self):
        """
        Private slot to (re)initialize the plugin.
        """
        self.__refactoringServer = None
        self.__codeAssistServer = None
        
        self.__editors = []
        
        self.__currentEditor = None
        self.__savedEditorName = None
        self.__oldEditorText = ""
    
    def activate(self):
        """
        Public method to activate this plugin.
        
        @return tuple of None and activation status
        @rtype tuple of (None, bool)
        """
        global refactoringRopePluginObject
        refactoringRopePluginObject = self
        
        ericApp().getObject("PluginManager").shutdown.connect(
            self.__shutdown)
        
        from RefactoringRope.CodeAssistServer import CodeAssistServer
        self.__codeAssistServer = CodeAssistServer(self, self.__ui)
        self.__codeAssistServer.activate()
        
        from RefactoringRope.RefactoringServer import RefactoringServer
        self.__refactoringServer = RefactoringServer(self, self.__ui)
        self.__refactoringServer.activate()
        
        ericApp().getObject("PluginManager").shutdown.connect(
            self.__shutdown)
        ericApp().getObject("ViewManager").editorOpenedEd.connect(
            self.__editorOpened)
        ericApp().getObject("ViewManager").editorClosedEd.connect(
            self.__editorClosed)
        
        for editor in ericApp().getObject("ViewManager").getOpenEditors():
            self.__editorOpened(editor)
        
        return None, True
    
    def deactivate(self):
        """
        Public method to deactivate this plugin.
        """
        if self.__refactoringServer:
            self.__refactoringServer.deactivate()
        
        if self.__codeAssistServer:
            self.__codeAssistServer.deactivate()
        
        ericApp().getObject("PluginManager").shutdown.disconnect(
            self.__shutdown)
        ericApp().getObject("ViewManager").editorOpenedEd.disconnect(
            self.__editorOpened)
        ericApp().getObject("ViewManager").editorClosedEd.disconnect(
            self.__editorClosed)
        
        for editor in self.__editors[:]:
            self.__editorClosed(editor)
        
        self.__initialize()
    
    def __shutdown(self):
        """
        Private slot handling the shutdown signal of the plug-in manager.
        """
        if self.__codeAssistServer:
            self.__codeAssistServer.deactivate()
    
    def __loadTranslator(self):
        """
        Private method to load the translation file.
        """
        if self.__ui is not None:
            loc = self.__ui.getLocale()
            if loc and loc != "C":
                locale_dir = os.path.join(os.path.dirname(__file__),
                                          "RefactoringRope", "i18n")
                translation = "rope_{0}".format(loc)
                translator = QTranslator(None)
                loaded = translator.load(translation, locale_dir)
                if loaded:
                    self.__translator = translator
                    ericApp().installTranslator(self.__translator)
                else:
                    print("Warning: translation file '{0}' could not"
                          " be loaded.".format(translation))
                    print("Using default.")
    
    def getPreferences(self, key):
        """
        Public method to retrieve the various refactoring settings.
        
        @param key the key of the value to get
        @type str
        @return the requested refactoring setting
        @rtype Any
        """
        if key in ["CodeAssistEnabled", "CodeAssistCalltipsEnabled",
                   "MouseClickEnabled"]:
            return Preferences.toBool(Preferences.Prefs.settings.value(
                self.PreferencesKey + "/" + key, self.__defaults[key]))
        elif key in ["CalltipsMaxFixes", "MaxFixes"]:
            return int(Preferences.Prefs.settings.value(
                self.PreferencesKey + "/" + key, self.__defaults[key]))
        else:
            return Preferences.Prefs.settings.value(
                self.PreferencesKey + "/" + key, self.__defaults[key])
    
    def setPreferences(self, key, value):
        """
        Public method to store the various refactoring settings.
        
        @param key the key of the setting to be set
        @type str
        @param value the value to be set
        @type Any
        """
        Preferences.Prefs.settings.setValue(
            self.PreferencesKey + "/" + key, value)
        
        if key in ["MouseClickGotoModifiers", "MouseClickGotoButton"]:
            for editor in self.__editors:
                self.__disconnectMouseClickHandler(editor)
                self.__connectMouseClickHandler(editor)
    
    def __determineLanguage(self):
        """
        Private method to determine the valid language strings.
        
        @return list of valid language strings
        @rtype list of str
        """
        return ["Python3", "MicroPython",
                "Pygments|Python", "Pygments|Python 2.x",
                "Cython"]
    
    def __editorOpened(self, editor):
        """
        Private slot called, when a new editor was opened.
        
        @param editor reference to the new editor
        @type Editor
        """
        languages = self.__determineLanguage()
        
        if editor.getLanguage() in languages:
            self.__connectEditor(editor)
        
        editor.languageChanged.connect(self.__editorLanguageChanged)
        self.__editors.append(editor)
    
    def __editorClosed(self, editor):
        """
        Private slot called, when an editor was closed.
        
        @param editor reference to the editor
        @type Editor
        """
        if editor in self.__editors:
            editor.languageChanged.disconnect(self.__editorLanguageChanged)
            self.__disconnectEditor(editor)
            self.__editors.remove(editor)
    
    def __editorLanguageChanged(self, language):
        """
        Private slot to handle the language change of an editor.
        
        @param language programming language of the editor
        @type str
        """
        editor = self.sender()
        languages = self.__determineLanguage()
        
        self.__disconnectEditor(editor)
        if language in languages:
            self.__connectEditor(editor)
    
    def __connectEditor(self, editor):
        """
        Private method to connect an editor.
        
        @param editor reference to the editor
        @type Editor
        """
        editor.editorAboutToBeSaved.connect(self.__editorAboutToBeSaved)
        editor.editorSaved.connect(self.__editorSaved)
        
        self.__setAutoCompletionHook(editor)
        self.__setCalltipsHook(editor)
        
        self.__connectMouseClickHandler(editor)
    
    def __disconnectEditor(self, editor):
        """
        Private method to disconnect an editor.
        
        @param editor reference to the editor
        @type Editor
        """
        with contextlib.suppress(TypeError):
            editor.editorAboutToBeSaved.disconnect(self.__editorAboutToBeSaved)
            editor.editorSaved.disconnect(self.__editorSaved)
        
        self.__unsetAutoCompletionHook(editor)
        self.__unsetCalltipsHook(editor)
        
        self.__disconnectMouseClickHandler(editor)
    
    def __connectMouseClickHandler(self, editor):
        """
        Private method to connect the mouse click handler to an editor.
        
        @param editor reference to the editor
        @type Editor
        """
        if self.getPreferences("MouseClickGotoButton"):
            editor.setMouseClickHandler(
                "rope",
                self.getPreferences("MouseClickGotoModifiers"),
                self.getPreferences("MouseClickGotoButton"),
                self.__codeAssistServer.gotoDefinition
            )
    
    def __disconnectMouseClickHandler(self, editor):
        """
        Private method to disconnect the mouse click handler from an editor.
        
        @param editor reference to the editor
        @type Editor
        """
        editor.removeMouseClickHandlers("rope")
    
    def __setAutoCompletionHook(self, editor):
        """
        Private method to set the autocompletion hook.
        
        @param editor reference to the editor
        @type Editor
        """
        editor.addCompletionListHook(
            "rope", self.__codeAssistServer.requestCompletions, True)
    
    def __unsetAutoCompletionHook(self, editor):
        """
        Private method to unset the autocompletion hook.
        
        @param editor reference to the editor
        @type Editor
        """
        editor.removeCompletionListHook("rope")
    
    def __setCalltipsHook(self, editor):
        """
        Private method to set the calltip hook.
        
        @param editor reference to the editor
        @type Editor
        """
        editor.addCallTipHook("rope", self.__codeAssistServer.getCallTips)
    
    def __unsetCalltipsHook(self, editor):
        """
        Private method to unset the calltip hook.
        
        @param editor reference to the editor
        @type Editor
        """
        editor.removeCallTipHook("rope")
    
    def __editorAboutToBeSaved(self, filename):
        """
        Private slot to get the old contents of the named file.
        
        @param filename name of the file about to be saved
        @type str
        """
        if filename and os.path.exists(filename):
            try:
                self.__oldEditorText = Utilities.readEncodedFile(filename)[0]
            except OSError:
                self.__oldEditorText = ""
            self.__savedEditorName = filename
        else:
            self.__savedEditorName = ""
            self.__oldEditorText = ""
    
    def __editorSaved(self, filename):
        """
        Private slot to activate SOA.
        
        @param filename name of the file that was saved
        @type str
        """
        if filename == self.__savedEditorName and self.__oldEditorText:
            self.__refactoringServer.reportChanged(self.__savedEditorName,
                                                   self.__oldEditorText)
            self.__codeAssistServer.reportChanged(self.__savedEditorName,
                                                  self.__oldEditorText)
        else:
            self.__refactoringServer.reportChanged(filename, "")
            self.__codeAssistServer.reportChanged(filename, "")
    
    def getCodeAssistServer(self):
        """
        Public method to get a reference to the code assist server.
        
        @return reference to the code assist server
        @rtype CodeAssistServer
        """
        return self.__codeAssistServer


def installDependencies(pipInstall):
    """
    Function to install dependencies of this plug-in.
    
    @param pipInstall function to be called with a list of package names.
    @type function
    """
    try:
        import rope         # __IGNORE_WARNING__
    except ImportError:
        pipInstall(["rope"])

#
# eflag: noqa = M801

eric ide

mercurial