PluginSplitMergeCamelCase.py

Wed, 21 Sep 2022 11:24:09 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 21 Sep 2022 11:24:09 +0200
branch
eric7
changeset 54
a29b5f01c2fa
parent 52
8dcfbc830b52
child 55
dbc20f5da446
permissions
-rw-r--r--

Reformatted source code with 'Black'.

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

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

"""
Module implementing the split, merge or convert camel case plug-in.
"""

import contextlib
import os
import re

from PyQt6.QtCore import QObject, QTranslator
from PyQt6.QtWidgets import QMenu

from EricWidgets.EricApplication import ericApp

# Start-Of-Header
name = "Camel Case Handling Plug-in"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "10.0.0"
className = "SplitMergeCamelCasePlugin"
packageName = "SplitMergeCamelCase"
shortDescription = "Split, merge or convert camel/snake case text"
longDescription = (
    """This plug-in implements a tool to split or merge camel case """
    """or snake case text. It also offers the capability to convert"""
    """ text between these case variants. It works with the text of"""
    """ the current editor. The menu entries will only be enabled,"""
    """ if the current editor has some selected text."""
)
needsRestart = False
pyqtApi = 2
# End-Of-Header

error = ""


class SplitMergeCamelCasePlugin(QObject):
    """
    Class implementing the split, merge or convert camel case plug-in.
    """

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

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

        self.__translator = None
        self.__loadTranslator()

        self.__initMenu()

        self.__editors = {}
        self.__mainActions = []

    def activate(self):
        """
        Public method to activate this plugin.

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

        self.__ui.showMenu.connect(self.__populateMenu)

        menu = self.__ui.getMenu("plugin_tools")
        if menu is not None:
            if not menu.isEmpty():
                act = menu.addSeparator()
                self.__mainActions.append(act)
            act = menu.addMenu(self.__menu)
            self.__mainActions.append(act)

        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.
        """
        self.__ui.showMenu.disconnect(self.__populateMenu)

        menu = self.__ui.getMenu("plugin_tools")
        if menu is not None:
            for act in self.__mainActions:
                menu.removeAction(act)
        self.__mainActions = []

        ericApp().getObject("ViewManager").editorOpenedEd.disconnect(
            self.__editorOpened
        )
        ericApp().getObject("ViewManager").editorClosedEd.disconnect(
            self.__editorClosed
        )

        for editor, acts in self.__editors.items():
            editor.showMenu.disconnect(self.__editorShowMenu)
            menu = editor.getMenu("Tools")
            if menu is not None:
                for act in acts:
                    menu.removeAction(act)
        self.__editors = {}

    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__), "SplitMergeCamelCase", "i18n"
                )
                translation = "splitmergecamelcase_{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 __initMenu(self):
        """
        Private method to initialize the camel case handling menu.
        """
        self.__menu = QMenu(self.tr("Camel/Snake Case Handling"))
        self.__menu.addAction(self.tr("Split from Camel Case"), self.__splitCamelCase)
        self.__menu.addAction(self.tr("Merge to Camel Case"), self.__mergeCamelCase)
        self.__menu.addAction(
            self.tr("Merge to Camel Case (upper case first)"),
            self.__mergeCamelCaseUppercaseFirst,
        )
        self.__menu.addSeparator()
        self.__menu.addAction(self.tr("Split from Snake Case"), self.__splitSnakeCase)
        self.__menu.addAction(self.tr("Merge to Snake Case"), self.__mergeSnakeCase)
        self.__menu.addSeparator()
        self.__menu.addAction(
            self.tr("Camel Case to Snake Case"), self.__camelCaseToSnakeCase
        )
        self.__menu.addAction(
            self.tr("Snake Case to Camel Case"), self.__snakeCaseToCamelCase
        )
        self.__menu.addAction(
            self.tr("Snake Case to Camel Case (upper case first)"),
            self.__snakeCaseToCamelCaseUppercaseFirst,
        )

    def __populateMenu(self, name, menu):
        """
        Private slot to populate the tools menu with our entries.

        @param name name of the menu
        @type str
        @param menu reference to the menu to be populated
        @type QMenu
        """
        if name not in ["Tools", "PluginTools"]:
            return

        editor = ericApp().getObject("ViewManager").activeWindow()

        if name == "Tools":
            if not menu.isEmpty():
                menu.addSeparator()
            act = menu.addMenu(self.__menu)
            act.setEnabled(editor is not None and editor.hasSelectedText())
        elif name == "PluginTools" and self.__mainActions:
            self.__menu.setEnabled(editor is not None and editor.hasSelectedText())

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

        @param editor reference to the new editor
        @type Editor
        """
        menu = editor.getMenu("Tools")
        if menu is not None:
            self.__editors[editor] = []
            if not menu.isEmpty():
                act = menu.addSeparator()
                self.__editors[editor].append(act)
            act = menu.addMenu(self.__menu)
            self.__editors[editor].append(act)
            editor.showMenu.connect(self.__editorShowMenu)

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

        @param editor reference to the editor
        @type Editor
        """
        with contextlib.suppress(KeyError):
            del self.__editors[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 Editor
        """
        if menuName == "Tools":
            if self.__menu.menuAction() not in menu.actions():
                # Re-add our menu
                self.__editors[editor] = []
                if not menu.isEmpty():
                    act = menu.addSeparator()
                    self.__editors[editor].append(act)
                act = menu.addMenu(self.__menu)
                self.__editors[editor].append(act)

            self.__menu.setEnabled(editor.hasSelectedText())

    def __applyChange(self, newText, editor):
        """
        Private method to change the selected text.

        @param newText new (converted) text
        @type str
        @param editor reference to the editor to apply the text
            change to
        @type Editor
        """
        editor.beginUndoAction()
        editor.replaceSelectedText(newText)
        editor.endUndoAction()

    def __splitCamelCase(self):
        """
        Private slot to split the selected camel case text.
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if editor is None:
            return

        text = editor.selectedText()
        if text:
            newText = re.sub(r"([A-Z])", r" \1", text).lstrip(" ")
            if newText != text:
                self.__applyChange(newText, editor)

    def __mergeCamelCase(self, uppercaseFirst=False):
        """
        Private slot to merge the selected text to camel case.

        @param uppercaseFirst flag indicating to upper case the
            first character
        @type bool
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if editor is None:
            return

        text = editor.selectedText()
        if text:
            newText = "".join(w[0].upper() + w[1:] for w in text.split())
            if not uppercaseFirst:
                newText = newText[0].lower() + newText[1:]
            if newText != text:
                self.__applyChange(newText, editor)

    def __mergeCamelCaseUppercaseFirst(self):
        """
        Private slot to merge the selected text to camel case upper casing
        the first character.
        """
        self.__mergeCamelCase(True)

    def __splitSnakeCase(self):
        """
        Private slot to split the selected snake case text.
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if editor is None:
            return

        text = editor.selectedText()
        if text:
            newText = text.replace("_", " ").lstrip(" ")
            if newText != text:
                self.__applyChange(newText, editor)

    def __mergeSnakeCase(self):
        """
        Private slot to merge the selected text to snake case.
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if editor is None:
            return

        text = editor.selectedText()
        if text:
            newText = "_".join(w.lower() for w in text.split())
            if newText != text:
                self.__applyChange(newText, editor)

    def __camelCaseToSnakeCase(self):
        """
        Private slot to convert camel case text to underscore separated text.
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if editor is None:
            return

        text = editor.selectedText()
        if text:
            newText = re.sub(r"([A-Z])", r"_\1", text).lower().lstrip("_")
            if text.startswith("__"):
                newText = "__" + newText
            elif text.startswith("_"):
                newText = "_" + newText
            if newText != text:
                self.__applyChange(newText, editor)

    def __snakeCaseToCamelCase(self, uppercaseFirst=False):
        """
        Private slot to convert underscore separated text to camel case.

        @param uppercaseFirst flag indicating to upper case the
            first character
        @type bool
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if editor is None:
            return

        text = editor.selectedText()
        if text:
            newText = "".join(s.capitalize() for s in text.split("_"))
            if not uppercaseFirst:
                newText = newText[0].lower() + newText[1:]
            if text.startswith("__"):
                newText = "__" + newText
            elif text.startswith("_"):
                newText = "_" + newText
            if newText != text:
                self.__applyChange(newText, editor)

    def __snakeCaseToCamelCaseUppercaseFirst(self):
        """
        Private slot to convert underscore separated text to camel case
        upper casing the first character.
        """
        self.__snakeCaseToCamelCase(True)


#
# eflag: noqa = M801

eric ide

mercurial