src/eric7/EricWidgets/EricSpellCheckedTextEdit.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
diff -r e9e7eca7efee -r bf71ee032bb4 src/eric7/EricWidgets/EricSpellCheckedTextEdit.py
--- a/src/eric7/EricWidgets/EricSpellCheckedTextEdit.py	Wed Jul 13 11:16:20 2022 +0200
+++ b/src/eric7/EricWidgets/EricSpellCheckedTextEdit.py	Wed Jul 13 14:55:47 2022 +0200
@@ -15,32 +15,39 @@
     import enchant.tokenize
     from enchant.errors import TokenizerNotFoundError, DictNotFoundError
     from enchant.utils import trim_suggestions
+
     ENCHANT_AVAILABLE = True
 except ImportError:
     ENCHANT_AVAILABLE = False
 
 from PyQt6.QtCore import pyqtSlot, Qt, QCoreApplication
 from PyQt6.QtGui import (
-    QAction, QActionGroup, QSyntaxHighlighter, QTextBlockUserData,
-    QTextCharFormat, QTextCursor
+    QAction,
+    QActionGroup,
+    QSyntaxHighlighter,
+    QTextBlockUserData,
+    QTextCharFormat,
+    QTextCursor,
 )
 from PyQt6.QtWidgets import QMenu, QTextEdit, QPlainTextEdit
 
 if ENCHANT_AVAILABLE:
-    class SpellCheckMixin():
+
+    class SpellCheckMixin:
         """
         Class implementing the spell-check mixin for the widget classes.
         """
+
         # don't show more than this to keep the menu manageable
         MaxSuggestions = 20
-        
+
         # default language to be used when no other is set
         DefaultLanguage = None
-        
+
         # default user lists
         DefaultUserWordList = None
         DefaultUserExceptionList = None
-        
+
         def __init__(self):
             """
             Constructor
@@ -52,7 +59,7 @@
                 spellDict = enchant.DictWithPWL(
                     SpellCheckMixin.DefaultLanguage,
                     SpellCheckMixin.DefaultUserWordList,
-                    SpellCheckMixin.DefaultUserExceptionList
+                    SpellCheckMixin.DefaultUserExceptionList,
                 )
             except DictNotFoundError:
                 try:
@@ -61,59 +68,60 @@
                     spellDict = enchant.DictWithPWL(
                         "en",
                         SpellCheckMixin.DefaultUserWordList,
-                        SpellCheckMixin.DefaultUserExceptionList
+                        SpellCheckMixin.DefaultUserExceptionList,
                     )
                 except DictNotFoundError:
                     # Still no dictionary could be found. Forget about spell
                     # checking.
                     spellDict = None
-            
+
             self.__highlighter.setDict(spellDict)
-        
+
         def contextMenuEvent(self, evt):
             """
             Protected method to handle context menu events to add a spelling
             suggestions submenu.
-            
+
             @param evt reference to the context menu event
             @type QContextMenuEvent
             """
             menu = self.__createSpellcheckContextMenu(evt.pos())
             menu.exec(evt.globalPos())
-        
+
         def __createSpellcheckContextMenu(self, pos):
             """
             Private method to create the spell-check context menu.
-            
+
             @param pos position of the mouse pointer
             @type QPoint
             @return context menu with additional spell-check entries
             @rtype QMenu
             """
             menu = self.createStandardContextMenu(pos)
-            
+
             # Add a submenu for setting the spell-check language and
             # document format.
             menu.addSeparator()
             self.__addRemoveEntry(self.__cursorForPosition(pos), menu)
             menu.addMenu(self.__createLanguagesMenu(menu))
             menu.addMenu(self.__createFormatsMenu(menu))
-            
+
             # Try to retrieve a menu of corrections for the right-clicked word
             spellMenu = self.__createCorrectionsMenu(
-                self.__cursorForMisspelling(pos), menu)
-            
+                self.__cursorForMisspelling(pos), menu
+            )
+
             if spellMenu:
                 menu.insertSeparator(menu.actions()[0])
                 menu.insertMenu(menu.actions()[0], spellMenu)
-            
+
             return menu
-        
+
         def __createCorrectionsMenu(self, cursor, parent=None):
             """
             Private method to create a menu for corrections of the selected
             word.
-            
+
             @param cursor reference to the text cursor
             @type QTextCursor
             @param parent reference to the parent widget (defaults to None)
@@ -123,36 +131,39 @@
             """
             if cursor is None:
                 return None
-            
+
             text = cursor.selectedText()
             suggestions = trim_suggestions(
-                text, self.__highlighter.dict().suggest(text),
-                SpellCheckMixin.MaxSuggestions)
-            
+                text,
+                self.__highlighter.dict().suggest(text),
+                SpellCheckMixin.MaxSuggestions,
+            )
+
             spellMenu = QMenu(
-                QCoreApplication.translate("SpellCheckMixin",
-                                           "Spelling Suggestions"),
-                parent)
+                QCoreApplication.translate("SpellCheckMixin", "Spelling Suggestions"),
+                parent,
+            )
             for word in suggestions:
                 act = spellMenu.addAction(word)
                 act.setData((cursor, word))
-            
+
             if suggestions:
                 spellMenu.addSeparator()
-            
+
             # add management entry
-            act = spellMenu.addAction(QCoreApplication.translate(
-                "SpellCheckMixin", "Add to Dictionary"))
+            act = spellMenu.addAction(
+                QCoreApplication.translate("SpellCheckMixin", "Add to Dictionary")
+            )
             act.setData((cursor, text, "add"))
-            
+
             spellMenu.triggered.connect(self.__spellMenuTriggered)
             return spellMenu
-        
+
         def __addRemoveEntry(self, cursor, menu):
             """
             Private method to create a menu entry to remove the word at the
             menu position.
-            
+
             @param cursor reference to the text cursor for the misspelled word
             @type QTextCursor
             @param menu reference to the context menu
@@ -160,18 +171,20 @@
             """
             if cursor is None:
                 return
-            
+
             text = cursor.selectedText()
-            menu.addAction(QCoreApplication.translate(
-                "SpellCheckMixin",
-                "Remove '{0}' from Dictionary").format(text),
-                lambda: self.__addToUserDict(text, "remove"))
-        
+            menu.addAction(
+                QCoreApplication.translate(
+                    "SpellCheckMixin", "Remove '{0}' from Dictionary"
+                ).format(text),
+                lambda: self.__addToUserDict(text, "remove"),
+            )
+
         def __createLanguagesMenu(self, parent=None):
             """
             Private method to create a menu for selecting the spell-check
             language.
-            
+
             @param parent reference to the parent widget (defaults to None)
             @type QWidget (optional)
             @return menu with spell-check languages
@@ -179,55 +192,56 @@
             """
             curLanguage = self.__highlighter.dict().tag.lower()
             languageMenu = QMenu(
-                QCoreApplication.translate("SpellCheckMixin", "Language"),
-                parent)
+                QCoreApplication.translate("SpellCheckMixin", "Language"), parent
+            )
             languageActions = QActionGroup(languageMenu)
-            
+
             for language in sorted(enchant.list_languages()):
                 act = QAction(language, languageActions)
                 act.setCheckable(True)
                 act.setChecked(language.lower() == curLanguage)
                 act.setData(language)
                 languageMenu.addAction(act)
-            
+
             languageMenu.triggered.connect(self.__setLanguage)
             return languageMenu
-        
+
         def __createFormatsMenu(self, parent=None):
             """
             Private method to create a menu for selecting the document format.
-            
+
             @param parent reference to the parent widget (defaults to None)
             @type QWidget (optional)
             @return menu with document formats
             @rtype QMenu
             """
             formatMenu = QMenu(
-                QCoreApplication.translate("SpellCheckMixin", "Format"),
-                parent)
+                QCoreApplication.translate("SpellCheckMixin", "Format"), parent
+            )
             formatActions = QActionGroup(formatMenu)
-            
+
             curFormat = self.__highlighter.chunkers()
             for name, chunkers in (
-                (QCoreApplication.translate("SpellCheckMixin", "Text"),
-                 []),
-                (QCoreApplication.translate("SpellCheckMixin", "HTML"),
-                 [enchant.tokenize.HTMLChunker])
+                (QCoreApplication.translate("SpellCheckMixin", "Text"), []),
+                (
+                    QCoreApplication.translate("SpellCheckMixin", "HTML"),
+                    [enchant.tokenize.HTMLChunker],
+                ),
             ):
                 act = QAction(name, formatActions)
                 act.setCheckable(True)
                 act.setChecked(chunkers == curFormat)
                 act.setData(chunkers)
                 formatMenu.addAction(act)
-            
+
             formatMenu.triggered.connect(self.__setFormat)
             return formatMenu
-        
+
         def __cursorForPosition(self, pos):
             """
             Private method to create a text cursor selecting the word at the
             given position.
-            
+
             @param pos position of the misspelled word
             @type QPoint
             @return text cursor for the word
@@ -235,46 +249,47 @@
             """
             cursor = self.cursorForPosition(pos)
             cursor.select(QTextCursor.SelectionType.WordUnderCursor)
-            
+
             if cursor.hasSelection():
                 return cursor
             else:
                 return None
-        
+
         def __cursorForMisspelling(self, pos):
             """
             Private method to create a text cursor selecting the misspelled
             word.
-            
+
             @param pos position of the misspelled word
             @type QPoint
             @return text cursor for the misspelled word
             @rtype QTextCursor
             """
             cursor = self.cursorForPosition(pos)
-            misspelledWords = getattr(cursor.block().userData(),
-                                      "misspelled", [])
-            
+            misspelledWords = getattr(cursor.block().userData(), "misspelled", [])
+
             # If the cursor is within a misspelling, select the word
             for (start, end) in misspelledWords:
                 if start <= cursor.positionInBlock() <= end:
                     blockPosition = cursor.block().position()
-                    
-                    cursor.setPosition(blockPosition + start,
-                                       QTextCursor.MoveMode.MoveAnchor)
-                    cursor.setPosition(blockPosition + end,
-                                       QTextCursor.MoveMode.KeepAnchor)
+
+                    cursor.setPosition(
+                        blockPosition + start, QTextCursor.MoveMode.MoveAnchor
+                    )
+                    cursor.setPosition(
+                        blockPosition + end, QTextCursor.MoveMode.KeepAnchor
+                    )
                     break
-            
+
             if cursor.hasSelection():
                 return cursor
             else:
                 return None
-        
+
         def __correctWord(self, cursor, word):
             """
             Private method to replace some misspelled text.
-            
+
             @param cursor reference to the text cursor for the misspelled word
             @type QTextCursor
             @param word replacement text
@@ -284,11 +299,11 @@
             cursor.removeSelectedText()
             cursor.insertText(word)
             cursor.endEditBlock()
-        
+
         def __addToUserDict(self, word, command):
             """
             Private method to add a word to the user word or exclude list.
-            
+
             @param word text to be added
             @type str
             @param command command indicating the user dictionary type
@@ -300,14 +315,14 @@
                     dictionary.add(word)
                 elif command == "remove":
                     dictionary.remove(word)
-                
+
                 self.__highlighter.rehighlight()
-        
+
         @pyqtSlot(QAction)
         def __spellMenuTriggered(self, act):
             """
             Private slot to handle a selection of the spell menu.
-            
+
             @param act reference to the selected action
             @type QAction
             """
@@ -315,94 +330,84 @@
             if len(data) == 2:
                 # replace the misspelled word
                 self.__correctWord(*data)
-            
+
             elif len(data) == 3:
                 # dictionary management action
                 _, word, command = data
                 self.__addToUserDict(word, command)
-        
+
         @pyqtSlot(QAction)
         def __setLanguage(self, act):
             """
             Private slot to set the selected language.
-            
+
             @param act reference to the selected action
             @type QAction
             """
             language = act.data()
             self.setLanguage(language)
-        
+
         @pyqtSlot(QAction)
         def __setFormat(self, act):
             """
             Private slot to set the selected document format.
-            
+
             @param act reference to the selected action
             @type QAction
             """
             chunkers = act.data()
             self.__highlighter.setChunkers(chunkers)
-        
+
         def setFormat(self, formatName):
             """
             Public method to set the document format.
-            
+
             @param formatName name of the document format
             @type str
             """
             self.__highlighter.setChunkers(
-                [enchant.tokenize.HTMLChunker]
-                if format == "html" else
-                []
+                [enchant.tokenize.HTMLChunker] if format == "html" else []
             )
-        
+
         def dict(self):
             """
             Public method to get a reference to the dictionary in use.
-            
+
             @return reference to the current dictionary
             @rtype enchant.Dict
             """
             return self.__highlighter.dict()
-        
+
         def setDict(self, spellDict):
             """
             Public method to set the dictionary to be used.
-            
+
             @param spellDict reference to the spell-check dictionary
             @type emchant.Dict
             """
             self.__highlighter.setDict(spellDict)
-        
+
         @pyqtSlot(str)
         def setLanguage(self, language):
             """
             Public slot to set the spellchecker language.
-            
+
             @param language language to be set
             @type str
             """
             epwl = self.dict().pwl
-            pwl = (
-                epwl.provider.file
-                if isinstance(epwl, enchant.Dict) else
-                None
-            )
-            
+            pwl = epwl.provider.file if isinstance(epwl, enchant.Dict) else None
+
             epel = self.dict().pel
-            pel = (
-                epel.provider.file
-                if isinstance(epel, enchant.Dict) else
-                None
-            )
+            pel = epel.provider.file if isinstance(epel, enchant.Dict) else None
             self.setLanguageWithPWL(language, pwl, pel)
-        
+
         @pyqtSlot(str, str, str)
         def setLanguageWithPWL(self, language, pwl, pel):
             """
             Public slot to set the spellchecker language and associated user
             word lists.
-            
+
             @param language language to be set
             @type str
             @param pwl file name of the personal word list
@@ -422,12 +427,12 @@
                     # checking.
                     spellDict = None
             self.__highlighter.setDict(spellDict)
-        
+
         @classmethod
         def setDefaultLanguage(cls, language, pwl=None, pel=None):
             """
             Class method to set the default spell-check language.
-            
+
             @param language language to be set as default
             @type str
             @param pwl file name of the personal word list
@@ -438,7 +443,7 @@
             with contextlib.suppress(DictNotFoundError):
                 cls.DefaultUserWordList = pwl
                 cls.DefaultUserExceptionList = pel
-                
+
                 # set default language only, if a dictionary is available
                 enchant.Dict(language)
                 cls.DefaultLanguage = language
@@ -448,60 +453,61 @@
         Class implementing a QSyntaxHighlighter subclass that consults a
         pyEnchant dictionary to highlight misspelled words.
         """
-        TokenFilters = (enchant.tokenize.EmailFilter,
-                        enchant.tokenize.URLFilter)
+
+        TokenFilters = (enchant.tokenize.EmailFilter, enchant.tokenize.URLFilter)
 
         # Define the spell-check style once and just assign it as necessary
         ErrorFormat = QTextCharFormat()
         ErrorFormat.setUnderlineColor(Qt.GlobalColor.red)
         ErrorFormat.setUnderlineStyle(
-            QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
-        
+            QTextCharFormat.UnderlineStyle.SpellCheckUnderline
+        )
+
         def __init__(self, *args):
             """
             Constructor
-            
+
             @param *args list of arguments for the QSyntaxHighlighter
             @type list
             """
             QSyntaxHighlighter.__init__(self, *args)
-            
+
             self.__spellDict = None
             self.__tokenizer = None
             self.__chunkers = []
-        
+
         def chunkers(self):
             """
             Public method to get the chunkers in use.
-            
+
             @return list of chunkers in use
             @rtype list
             """
             return self.__chunkers
-        
+
         def setChunkers(self, chunkers):
             """
             Public method to set the chunkers to be used.
-            
+
             @param chunkers chunkers to be used
             @type list
             """
             self.__chunkers = chunkers
             self.setDict(self.dict())
-        
+
         def dict(self):
             """
             Public method to get the spelling dictionary in use.
-            
+
             @return spelling dictionary
             @rtype enchant.Dict
             """
             return self.__spellDict
-        
+
         def setDict(self, spellDict):
             """
             Public method to set the spelling dictionary to be used.
-            
+
             @param spellDict spelling dictionary
             @type enchant.Dict
             """
@@ -510,39 +516,40 @@
                     self.__tokenizer = enchant.tokenize.get_tokenizer(
                         spellDict.tag,
                         chunkers=self.__chunkers,
-                        filters=EnchantHighlighter.TokenFilters)
+                        filters=EnchantHighlighter.TokenFilters,
+                    )
                 except TokenizerNotFoundError:
                     # Fall back to the "good for most euro languages"
                     # English tokenizer
                     self.__tokenizer = enchant.tokenize.get_tokenizer(
                         chunkers=self.__chunkers,
-                        filters=EnchantHighlighter.TokenFilters)
+                        filters=EnchantHighlighter.TokenFilters,
+                    )
             else:
                 self.__tokenizer = None
-            
+
             self.__spellDict = spellDict
-            
+
             self.rehighlight()
-        
+
         def highlightBlock(self, text):
             """
             Public method to apply the text highlight.
-            
+
             @param text text to be spell-checked
             @type str
             """
             """Overridden QSyntaxHighlighter method to apply the highlight"""
             if self.__spellDict is None or self.__tokenizer is None:
                 return
-            
+
             # Build a list of all misspelled words and highlight them
             misspellings = []
             for (word, pos) in self.__tokenizer(text):
                 if not self.__spellDict.check(word):
-                    self.setFormat(pos, len(word),
-                                   EnchantHighlighter.ErrorFormat)
+                    self.setFormat(pos, len(word), EnchantHighlighter.ErrorFormat)
                     misspellings.append((pos, pos + len(word)))
-            
+
             # Store the list so the context menu can reuse this tokenization
             # pass (Block-relative values so editing other blocks won't
             # invalidate them)
@@ -552,10 +559,11 @@
 
 else:
 
-    class SpellCheckMixin():
+    class SpellCheckMixin:
         """
         Class implementing the spell-check mixin for the widget classes.
         """
+
         #
         # This is just a stub to provide the same API as the enchant enabled
         # one.
@@ -565,50 +573,50 @@
             Constructor
             """
             pass
-        
+
         def setFormat(self, formatName):
             """
             Public method to set the document format.
-            
+
             @param formatName name of the document format
             @type str
             """
             pass
-        
+
         def dict(self):
             """
             Public method to get a reference to the dictionary in use.
-            
+
             @return reference to the current dictionary
             @rtype enchant.Dict
             """
             pass
-        
+
         def setDict(self, spellDict):
             """
             Public method to set the dictionary to be used.
-            
+
             @param spellDict reference to the spell-check dictionary
             @type emchant.Dict
             """
             pass
-        
+
         @pyqtSlot(str)
         def setLanguage(self, language):
             """
             Public slot to set the spellchecker language.
-            
+
             @param language language to be set
             @type str
             """
             pass
-        
+
         @pyqtSlot(str, str, str)
         def setLanguageWithPWL(self, language, pwl, pel):
             """
             Public slot to set the spellchecker language and associated user
             word lists.
-            
+
             @param language language to be set
             @type str
             @param pwl file name of the personal word list
@@ -617,12 +625,12 @@
             @type str
             """
             pass
-        
+
         @classmethod
         def setDefaultLanguage(cls, language, pwl=None, pel=None):
             """
             Class method to set the default spell-check language.
-            
+
             @param language language to be set as default
             @type str
             @param pwl file name of the personal word list
@@ -637,10 +645,11 @@
     """
     Class implementing a QPlainTextEdit with built-in spell checker.
     """
+
     def __init__(self, *args):
         """
         Constructor
-        
+
         @param *args list of arguments for the QPlainTextEdit constructor.
         @type list
         """
@@ -652,43 +661,45 @@
     """
     Class implementing a QTextEdit with built-in spell checker.
     """
+
     def __init__(self, *args):
         """
         Constructor
-        
+
         @param *args list of arguments for the QPlainTextEdit constructor.
         @type list
         """
         QTextEdit.__init__(self, *args)
         SpellCheckMixin.__init__(self)
-        
+
         self.setFormat("html")
-    
+
     def setAcceptRichText(self, accept):
         """
         Public method to set the text edit mode.
-        
+
         @param accept flag indicating to accept rich text
         @type bool
         """
         QTextEdit.setAcceptRichText(self, accept)
         self.setFormat("html" if accept else "text")
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     import sys
     import os
     from PyQt6.QtWidgets import QApplication
-    
+
     if ENCHANT_AVAILABLE:
         dictPath = os.path.expanduser(os.path.join("~", ".eric7", "spelling"))
         SpellCheckMixin.setDefaultLanguage(
             "en_US",
             os.path.join(dictPath, "pwl.dic"),
-            os.path.join(dictPath, "pel.dic")
+            os.path.join(dictPath, "pel.dic"),
         )
-    
+
     app = QApplication(sys.argv)
     spellEdit = EricSpellCheckedPlainTextEdit()
     spellEdit.show()
-    
+
     sys.exit(app.exec())

eric ide

mercurial