PluginRefactoringRope.py

Tue, 20 Dec 2022 14:33:49 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 20 Dec 2022 14:33:49 +0100
branch
eric7
changeset 409
65153bf17e8d
parent 406
923d7f711cae
child 411
8cccb49bba7b
permissions
-rw-r--r--

Fixed a bug and resorted the imports with isort.

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

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

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

import contextlib
import os

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

from eric7 import Preferences, Utilities
from eric7.EricWidgets.EricApplication import ericApp

# Start-Of-Header
name = "Refactoring Rope Plugin"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "10.6.0"
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