PluginPyright.py

Sat, 23 Dec 2023 15:48:45 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 23 Dec 2023 15:48:45 +0100
branch
eric7
changeset 11
55bc88e0aea0
parent 9
1ef1797f5f58
child 13
3a1f3fcfaf31
permissions
-rw-r--r--

Updated copyright for 2024.

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

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

"""
Module implementing the pyright plug-in.
"""

import contextlib
import os

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

from eric7 import Preferences
from eric7.EricGui.EricAction import EricAction
from eric7.EricWidgets.EricApplication import ericApp
from eric7.Project.ProjectBrowserModel import ProjectBrowserFileItem
from eric7.SystemUtilities import PythonUtilities

# Start-Of-Header
__header__ = {
    "name": "Python Typing Checker Plug-in",
    "author": "Detlev Offenbach <detlev@die-offenbachs.de>",
    "autoactivate": True,
    "deactivateable": True,
    "version": "10.0.1",
    "className": "PyrightPlugin",
    "packageName": "PyrightChecker",
    "shortDescription": "Plug-in to check Python sources for typing issues.",
    "longDescription": ("""Plug-in to check Python sources for typing issues."""),
    "needsRestart": False,
    "pyqtApi": 2,
}
# End-Of-Header

error = ""  # noqa: U200


def exeDisplayData():
    """
    Public method to support the display of some executable info.

    @return dictionary containing the data to query the presence of
        the executable
    @rtype dict
    """
    data = {
        "programEntry": True,
        "header": QCoreApplication.translate(
            "PyrightPlugin", "Checkers - Pyright"
        ),
        "exe": PythonUtilities.getPythonExecutable(),
        "versionCommand": "--version",
        "versionStartsWith": "pyright",
        "versionPosition": -1,
        "version": "",
        "versionCleanup": None,
        "exeModule": ["-m", "pyright"]
    }

    return data


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


class PyrightPlugin(QObject):
    """
    Class documentation goes here.
    """

    PreferencesKey = "Pyright"

    def __init__(self, ui):
        """
        Constructor

        @param ui reference to the user interface object
        @type UI.UserInterface
        """
        super().__init__(ui)
        self.__ui = ui
        self.__initialize()

        self.__translator = None
        self.__loadTranslator()

    def __initialize(self):
        """
        Private slot to (re)initialize the plug-in.
        """
        self.__projectAct = None
        self.__projectPyrightCheckerDialog = None

        self.__projectBrowserAct = None
        self.__projectBrowserMenu = None
        self.__projectBrowserPyrightCheckerDialog = None

        self.__editors = []
        self.__editorAct = None
        self.__editorPyrightCheckerDialog = None

    def activate(self):
        """
        Public method to activate this plug-in.

        @return tuple of None and activation status
        @rtype bool
        """
        global error
        error = ""  # clear previous error

        menu = ericApp().getObject("Project").getMenu("Checks")
        if menu:
            self.__projectAct = EricAction(
                self.tr("Static Type Check"),
                self.tr("Static &Typing..."),
                0,
                0,
                self,
                "project_check_pyright",
            )
            self.__projectAct.setStatusTip(self.tr("Check for typing issues"))
            self.__projectAct.setWhatsThis(
                self.tr(
                    """<b>Static Type Check...</b>"""
                    """<p>This checks a Python project for static typing issues.</p>"""
                )
            )
            self.__projectAct.triggered.connect(self.__projectPyrightCheck)
            ericApp().getObject("Project").addEricActions([self.__projectAct])
            menu.addAction(self.__projectAct)

        self.__editorAct = EricAction(
            self.tr("Static Type Check"), self.tr("Static &Typing..."), 0, 0, self, ""
        )
        self.__editorAct.setWhatsThis(
            self.tr(
                """<b>Static Type Check...</b>"""
                """<p>This checks a Python file for static typing issues.</p>"""
            )
        )
        self.__editorAct.triggered.connect(self.__editorPyrightCheck)

        ericApp().getObject("Project").showMenu.connect(self.__projectShowMenu)
        ericApp().getObject("ProjectBrowser").getProjectBrowser(
            "sources"
        ).showMenu.connect(self.__projectBrowserShowMenu)
        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 plug-in.
        """
        ericApp().getObject("Project").showMenu.disconnect(self.__projectShowMenu)
        ericApp().getObject("ProjectBrowser").getProjectBrowser(
            "sources"
        ).showMenu.disconnect(self.__projectBrowserShowMenu)

        menu = ericApp().getObject("Project").getMenu("Checks")
        if menu is not None and self.__projectAct is not None:
            menu.removeAction(self.__projectAct)
            ericApp().getObject("Project").removeEricActions([self.__projectAct])

        if self.__projectBrowserMenu and self.__projectBrowserAct:
            self.__projectBrowserMenu.removeAction(self.__projectBrowserAct)

        for editor in self.__editors:
            editor.showMenu.disconnect(self.__editorShowMenu)
            menu = editor.getMenu("Checks")
            if menu is not None:
                menu.removeAction(self.__editorAct)

        self.__initialize()

    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__), "PyrightChecker", "i18n"
                )
                translation = "pyright_{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 __projectShowMenu(
        self,
        menuName,
        menu,  # noqa: U100
    ):
        """
        Private slot called, when the the project menu or a submenu is
        about to be shown.

        @param menuName name of the menu to be shown
        @type str
        @param menu reference to the menu
        @type QMenu
        """
        if menuName == "Check" and self.__projectAct is not None:
            self.__projectAct.setEnabled(
                ericApp().getObject("Project").getProjectLanguage() == "Python3"
            )

    def __projectBrowserShowMenu(self, menuName, menu):
        """
        Private slot called, when the the project browser menu or a submenu is
        about to be shown.

        @param menuName name of the menu to be shown
        @type str
        @param menu reference to the menu
        @type QMenu
        """
        if (
            menuName == "Checks"
            and ericApp().getObject("Project").getProjectLanguage() == "Python3"
        ):
            self.__projectBrowserMenu = menu
            if self.__projectBrowserAct is None:
                self.__projectBrowserAct = EricAction(
                    self.tr("Static Type Check"),
                    self.tr("Static &Typing..."),
                    0,
                    0,
                    self,
                    "",
                )
                self.__projectBrowserAct.setWhatsThis(
                    self.tr(
                        """<b>Static Type Check...</b>"""
                        """<p>This checks Python files for static typing issues.</p>"""
                    )
                )
                self.__projectBrowserAct.triggered.connect(
                    self.__projectBrowserPyrightCheck
                )
            if self.__projectBrowserAct not in menu.actions():
                menu.addAction(self.__projectBrowserAct)

    @pyqtSlot()
    def __projectPyrightCheck(self):
        """
        Private slot used to check the project for static typing issues.
        """
        from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog

        project = ericApp().getObject("Project")
        project.saveAllScripts()

        if self.__projectPyrightCheckerDialog is None:
            self.__projectPyrightCheckerDialog = PyrightCheckerDialog(self)
        self.__projectPyrightCheckerDialog.show()
        self.__projectPyrightCheckerDialog.prepare(project)

    @pyqtSlot()
    def __projectBrowserPyrightCheck(self):
        """
        Private method to handle the static typing check context menu action of
        the project sources browser.
        """
        from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog

        browser = ericApp().getObject("ProjectBrowser").getProjectBrowser("sources")
        if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1:
            fn = []
            for itm in browser.getSelectedItems([ProjectBrowserFileItem]):
                fn.append(itm.fileName())
        else:
            itm = browser.model().item(browser.currentIndex())
            try:
                fn = [itm.fileName()]
            except AttributeError:
                fn = [itm.dirName()]

        self.__projectBrowserPyrightCheckerDialog = PyrightCheckerDialog(self)
        self.__projectBrowserPyrightCheckerDialog.show()
        self.__projectBrowserPyrightCheckerDialog.start(fn, save=True)

    def __editorOpened(self, editor):
        """
        Private slot called, when a new editor was opened.

        @param editor reference to the new editor
        @type QScintilla.Editor
        """
        menu = editor.getMenu("Checks")
        if menu is not None:
            menu.addAction(self.__editorAct)
            editor.showMenu.connect(self.__editorShowMenu)
            self.__editors.append(editor)

    def __editorClosed(self, editor):
        """
        Private slot called, when an editor was closed.

        @param editor reference to the editor
        @type QScintilla.Editor
        """
        with contextlib.suppress(ValueError):
            self.__editors.remove(editor)

    def __editorShowMenu(self, menuName, menu, editor):
        """
        Private slot called, when the the editor context menu or a submenu is
        about to be shown.

        @param menuName name of the menu to be shown
        @type str
        @param menu reference to the menu
        @type QMenu
        @param editor reference to the editor
        @type QScintilla.Editor
        """
        if menuName == "Checks":
            if self.__editorAct not in menu.actions():
                menu.addAction(self.__editorAct)
            self.__editorAct.setEnabled(editor.isPyFile())

    def __editorPyrightCheck(self):
        """
        Private slot to handle the code style check context menu action
        of the editors.
        """
        from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog

        editor = ericApp().getObject("ViewManager").activeWindow()
        if (
            editor is not None
            and editor.checkDirty()
            and editor.getFileName() is not None
        ):
            self.__editorPyrightCheckerDialog = PyrightCheckerDialog(self)
            self.__editorPyrightCheckerDialog.show()
            self.__editorPyrightCheckerDialog.start([editor.getFileName()], save=True)


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 pyright  # __IGNORE_WARNING__
    except ImportError:
        pipInstall(["pyright"])
    try:
        import tomlkit  # __IGNORE_WARNING__
    except ImportError:
        pipInstall(["tomlkit"])


#
# eflag: noqa = M801

eric ide

mercurial