--- a/src/eric7/QScintilla/SpellChecker.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/QScintilla/SpellChecker.py Wed Jul 13 14:55:47 2022 +0200 @@ -25,15 +25,15 @@ """ Class implementing a pyenchant based spell checker. """ + # class attributes to be used as defaults _spelling_lang = None _spelling_dict = None - - def __init__(self, editor, indicator, defaultLanguage=None, - checkRegion=None): + + def __init__(self, editor, indicator, defaultLanguage=None, checkRegion=None): """ Constructor - + @param editor reference to the editor object (QScintilla.Editor) @param indicator spell checking indicator @param defaultLanguage the language to be used as the default @@ -43,7 +43,7 @@ region """ super().__init__(editor) - + self.editor = editor self.indicator = indicator if defaultLanguage is not None: @@ -54,55 +54,53 @@ self.__checkRegion = lambda r: True self.minimumWordSize = 3 self.lastCheckedLine = -1 - + self.__ignoreWords = [] self.__replaceWords = {} - + @classmethod def getAvailableLanguages(cls): """ Class method to get all available languages. - + @return list of available languages (list of strings) """ with contextlib.suppress(NameError): return enchant.list_languages() return [] - + @classmethod def isAvailable(cls): """ Class method to check, if spellchecking is available. - + @return flag indicating availability (boolean) """ if Preferences.getEditor("SpellCheckingEnabled"): with contextlib.suppress(NameError, AttributeError): return len(enchant.list_languages()) > 0 return False - + @classmethod def getDefaultPath(cls, isException=False): """ Class method to get the default path names of the user dictionaries. - + @param isException flag indicating to return the name of the default exception dictionary (boolean) @return file name of the default user dictionary or the default user exception dictionary (string) """ if isException: - return os.path.join( - Utilities.getConfigDir(), "spelling", "pel.dic") + return os.path.join(Utilities.getConfigDir(), "spelling", "pel.dic") else: - return os.path.join( - Utilities.getConfigDir(), "spelling", "pwl.dic") - + return os.path.join(Utilities.getConfigDir(), "spelling", "pwl.dic") + @classmethod def getUserDictionaryPath(cls, isException=False): """ Class method to get the path name of a user dictionary file. - + @param isException flag indicating to return the name of the user exception dictionary (boolean) @return file name of the user dictionary or the user exception @@ -117,12 +115,12 @@ if not dicFile: dicFile = SpellChecker.getDefaultPath() return dicFile - + @classmethod def _getDict(cls, lang, pwl="", pel=""): """ Protected class method to get a new dictionary. - + @param lang the language to be used as the default (string). The string should be in language locale format (e.g. en_US, de). @param pwl name of the personal/project word list (string) @@ -134,13 +132,13 @@ d = os.path.dirname(pwl) if not os.path.exists(d): os.makedirs(d) - + if not pel: pel = SpellChecker.getUserDictionaryPath(False) d = os.path.dirname(pel) if not os.path.exists(d): os.makedirs(d) - + try: d = enchant.DictWithPWL(lang, pwl, pel) except Exception: @@ -148,53 +146,52 @@ # can't catch the enchant.DictNotFound error. d = None return d - + @classmethod def setDefaultLanguage(cls, language): """ Class method to set the default language. - + @param language the language to be used as the default (string). The string should be in language locale format (e.g. en_US, de). """ cls._spelling_lang = language cls._spelling_dict = cls._getDict(language) - + def setLanguage(self, language, pwl="", pel=""): """ Public method to set the current language. - + @param language the language to be used as the default (string). The string should be in language locale format (e.g. en_US, de). @param pwl name of the personal/project word list (string) @param pel name of the personal/project exclude list (string) """ self._spelling_lang = language - self._spelling_dict = self._getDict(language, pwl=pwl, - pel=pel) - + self._spelling_dict = self._getDict(language, pwl=pwl, pel=pel) + def getLanguage(self): """ Public method to get the current language. - + @return current language in language locale format (string) """ return self._spelling_lang - + def setMinimumWordSize(self, size): """ Public method to set the minimum word size. - + @param size minimum word size (integer) """ if size > 0: self.minimumWordSize = size - + def __getNextWord(self, pos, endPosition): """ Private method to get the next word in the text after the given position. - + @param pos position to start word extraction (integer) @param endPosition position to stop word extraction (integer) @return tuple of three values (the extracted word (string), @@ -202,7 +199,7 @@ """ if pos < 0 or pos >= endPosition: return "", -1, -1 - + ch = self.editor.charAt(pos) # 1. skip non-word characters while pos < endPosition and not ch.isalnum(): @@ -211,7 +208,7 @@ if pos == endPosition: return "", -1, -1 startPos = pos - + # 2. extract the word word = "" while pos < endPosition and ch.isalnum(): @@ -223,11 +220,11 @@ return self.__getNextWord(endPos, endPosition) else: return word, startPos, endPos - + def getContext(self, wordStart, wordEnd): """ Public method to get the context of a faulty word. - + @param wordStart the starting position of the word (integer) @param wordEnd the ending position of the word (integer) @return tuple of the leading and trailing context (string, string) @@ -236,80 +233,79 @@ eline, eindex = self.editor.lineIndexFromPosition(wordEnd) text = self.editor.text(sline) return (text[:sindex], text[eindex:]) - + def getError(self): """ Public method to get information about the last error found. - + @return tuple of last faulty word (string), starting position of the faulty word (integer) and ending position of the faulty word (integer) """ return (self.word, self.wordStart, self.wordEnd) - + def initCheck(self, startPos, endPos): """ Public method to initialize a spell check. - + @param startPos position to start at (integer) @param endPos position to end at (integer) @return flag indicating successful initialization (boolean) """ if startPos == endPos: return False - + spell = self._spelling_dict if spell is None: return False - - self.editor.clearIndicatorRange( - self.indicator, startPos, endPos - startPos) - + + self.editor.clearIndicatorRange(self.indicator, startPos, endPos - startPos) + self.pos = startPos self.endPos = endPos self.word = "" self.wordStart = -1 self.wordEnd = -1 return True - + def __checkDocumentPart(self, startPos, endPos): """ Private method to check some part of the document. - + @param startPos position to start at (integer) @param endPos position to end at (integer) """ if not self.initCheck(startPos, endPos): return - + while True: try: next(self) - self.editor.setIndicatorRange(self.indicator, self.wordStart, - self.wordEnd - self.wordStart) + self.editor.setIndicatorRange( + self.indicator, self.wordStart, self.wordEnd - self.wordStart + ) except StopIteration: break - + def __incrementalCheck(self): """ Private method to check the document incrementally. """ if self.lastCheckedLine < 0: return - + linesChunk = Preferences.getEditor("AutoSpellCheckChunkSize") - self.checkLines(self.lastCheckedLine, - self.lastCheckedLine + linesChunk) + self.checkLines(self.lastCheckedLine, self.lastCheckedLine + linesChunk) self.lastCheckedLine = self.lastCheckedLine + linesChunk + 1 if self.lastCheckedLine >= self.editor.lines(): self.lastCheckedLine = -1 else: QTimer.singleShot(0, self.__incrementalCheck) - + def checkWord(self, pos, atEnd=False): """ Public method to check the word at position pos. - + @param pos position to check at (integer) @param atEnd flag indicating the position is at the end of the word to check (boolean) @@ -317,20 +313,19 @@ spell = self._spelling_dict if spell is None: return - + if atEnd: pos = self.editor.positionBefore(pos) - + if pos >= 0 and self.__checkRegion(pos): pos0 = pos - pos1 = 0xffffffff + pos1 = 0xFFFFFFFF if not self.editor.charAt(pos).isalnum(): line, index = self.editor.lineIndexFromPosition(pos) - self.editor.clearIndicator( - self.indicator, line, index, line, index + 1) + self.editor.clearIndicator(self.indicator, line, index, line, index + 1) pos1 = self.editor.positionAfter(pos) pos0 = self.editor.positionBefore(pos) - + for pos in [pos0, pos1]: if self.editor.charAt(pos).isalnum(): line, index = self.editor.lineIndexFromPosition(pos) @@ -343,36 +338,37 @@ else: ok = True start, end = self.editor.getWordBoundaries( - line, index, useWordChars=False) + line, index, useWordChars=False + ) if ok: self.editor.clearIndicator( - self.indicator, line, start, line, end) + self.indicator, line, start, line, end + ) else: # spell check indicated an error - self.editor.setIndicator( - self.indicator, line, start, line, end) - + self.editor.setIndicator(self.indicator, line, start, line, end) + def checkLines(self, firstLine, lastLine): """ Public method to check some lines of text. - + @param firstLine line number of first line to check (integer) @param lastLine line number of last line to check (integer) """ startPos = self.editor.positionFromLineIndex(firstLine, 0) - + if lastLine >= self.editor.lines(): lastLine = self.editor.lines() - 1 endPos = self.editor.lineEndPosition(lastLine) - + self.__checkDocumentPart(startPos, endPos) - + def checkDocument(self): """ Public method to check the complete document. """ self.__checkDocumentPart(0, self.editor.length()) - + def checkDocumentIncrementally(self): """ Public method to check the document incrementally. @@ -380,29 +376,32 @@ spell = self._spelling_dict if spell is None: return - + if Preferences.getEditor("AutoSpellCheckingEnabled"): self.lastCheckedLine = 0 QTimer.singleShot(0, self.__incrementalCheck) - + def stopIncrementalCheck(self): """ Public method to stop an incremental check. """ self.lastCheckedLine = -1 - + def checkSelection(self): """ Public method to check the current selection. """ - selStartLine, selStartIndex, selEndLine, selEndIndex = ( - self.editor.getSelection() - ) + ( + selStartLine, + selStartIndex, + selEndLine, + selEndIndex, + ) = self.editor.getSelection() self.__checkDocumentPart( self.editor.positionFromLineIndex(selStartLine, selStartIndex), - self.editor.positionFromLineIndex(selEndLine, selEndIndex) + self.editor.positionFromLineIndex(selEndLine, selEndIndex), ) - + def checkCurrentPage(self): """ Public method to check the currently visible page. @@ -410,18 +409,17 @@ startLine = self.editor.firstVisibleLine() endLine = startLine + self.editor.linesOnScreen() self.checkLines(startLine, endLine) - + def clearAll(self): """ Public method to clear all spelling markers. """ - self.editor.clearIndicatorRange( - self.indicator, 0, self.editor.length()) - + self.editor.clearIndicatorRange(self.indicator, 0, self.editor.length()) + def getSuggestions(self, word): """ Public method to get suggestions for the given word. - + @param word word to get suggestions for (string) @return list of suggestions (list of strings) """ @@ -431,11 +429,11 @@ with contextlib.suppress(enchant.errors.Error): suggestions = spell.suggest(word) return suggestions - + def add(self, word=None): """ Public method to add a word to the personal word list. - + @param word word to add (string) """ spell = self._spelling_dict @@ -443,34 +441,34 @@ if word is None: word = self.word spell.add(word) - + def remove(self, word): """ Public method to add a word to the personal exclude list. - + @param word word to add (string) """ spell = self._spelling_dict if spell: spell.remove(word) - + def ignoreAlways(self, word=None): """ Public method to tell the checker, to always ignore the given word or the current word. - + @param word word to be ignored (string) """ if word is None: word = self.word if word not in self.__ignoreWords: self.__ignoreWords.append(word) - + def replace(self, replacement): """ Public method to tell the checker to replace the current word with the replacement string. - + @param replacement replacement string (string) """ sline, sindex = self.editor.lineIndexFromPosition(self.wordStart) @@ -481,45 +479,43 @@ self.editor.insert(replacement) self.editor.endUndoAction() self.pos += len(replacement) - len(self.word) - + def replaceAlways(self, replacement): """ Public method to tell the checker to always replace the current word with the replacement string. - + @param replacement replacement string (string) """ self.__replaceWords[self.word] = replacement self.replace(replacement) - + ################################################################## ## Methods below implement the iterator protocol ################################################################## - + def __iter__(self): """ Special method to create an iterator. - + @return self """ return self - + def __next__(self): """ Special method to advance to the next error. - + @return self @exception StopIteration raised to indicate the end of the iteration """ spell = self._spelling_dict if spell: while self.pos < self.endPos and self.pos >= 0: - word, wordStart, wordEnd = self.__getNextWord( - self.pos, self.endPos) + word, wordStart, wordEnd = self.__getNextWord(self.pos, self.endPos) self.pos = wordEnd - if ( - (wordEnd - wordStart) >= self.minimumWordSize and - self.__checkRegion(wordStart) + if (wordEnd - wordStart) >= self.minimumWordSize and self.__checkRegion( + wordStart ): with contextlib.suppress(enchant.errors.Error): if spell.check(word): @@ -533,5 +529,5 @@ self.replace(self.__replaceWords[word]) continue return self - + raise StopIteration