diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index e4c95c6b..7681fe62 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -1007,7 +1007,7 @@ class GuiDocEditor(QTextEdit): """ if self.mainConf.spellTool == "enchant": - from nw.tools.spellenchant import NWSpellEnchant + from nw.tools.spellcheck import NWSpellEnchant self.theDict = NWSpellEnchant() else: self.theDict = NWSpellSimple() diff --git a/nw/tools/__init__.py b/nw/tools/__init__.py index d43736f9..14ddbc7c 100644 --- a/nw/tools/__init__.py +++ b/nw/tools/__init__.py @@ -4,8 +4,8 @@ from nw.tools.analyse import TextAnalysis from nw.tools.legacy import projectMaintenance from nw.tools.optionstate import OptionState from nw.tools.spellcheck import NWSpellCheck -from nw.tools.spellenchant import NWSpellEnchant -from nw.tools.spellsimple import NWSpellSimple +from nw.tools.spellcheck import NWSpellEnchant +from nw.tools.spellcheck import NWSpellSimple from nw.tools.translate import numberToWord from nw.tools.wordcount import countWords diff --git a/nw/tools/spellcheck.py b/nw/tools/spellcheck.py index a5b534cd..f540fdc4 100644 --- a/nw/tools/spellcheck.py +++ b/nw/tools/spellcheck.py @@ -28,7 +28,8 @@ import logging import nw -from os import path +from os import path, listdir +from difflib import get_close_matches from nw.constants import isoLanguage @@ -108,3 +109,168 @@ class NWSpellCheck(): return # END Class NWSpellCheck + +# ================================================================================================ # +# Enchant Based SpellChecking +# ================================================================================================ # + +class NWSpellEnchant(NWSpellCheck): + + def __init__(self): + NWSpellCheck.__init__(self) + logger.debug("Enchant spell checking activated") + return + + def setLanguage(self, theLang, projectDict=None): + """Load a dictionary for the language specified in the config. + If that fails, we load a dummy dictionary so that lookups don't + crash. + """ + try: + import enchant + self.theDict = enchant.Dict(theLang) + self.spellLanguage = theLang + logger.debug("Enchant spell checking for language %s loaded" % theLang) + except: + logger.error("Failed to load enchant spell checking for language %s" % theLang) + self.theDict = NWSpellEnchantDummy() + self.spellLanguage = None + + self._readProjectDictionary(projectDict) + for pWord in self.PROJW: + self.theDict.add_to_session(pWord) + + return + + def checkWord(self, theWord): + return self.theDict.check(theWord) + + def suggestWords(self, theWord): + return self.theDict.suggest(theWord) + + def addWord(self, newWord): + self.theDict.add_to_session(newWord) + NWSpellCheck.addWord(self, newWord) + return + + def listDictionaries(self): + retList = [] + for spTag, spProvider in enchant.list_dicts(): + spName = "%s [%s]" % (self.expandLanguage(spTag), spProvider.name) + retList.append((spTag, spName)) + return retList + +# END Class NWSpellEnchant + +class NWSpellEnchantDummy: + + def __init__(self): + return + + def check(self, theWord): + return True + + def suggest(self, theWord): + return [] + + def add_to_session(self, theWord): + return + +# END Class NWSpellEnchantDummy + +# ================================================================================================ # +# Fallback SpellChecking Using difflib +# ================================================================================================ # + +class NWSpellSimple(NWSpellCheck): + + WORDS = [] + + def __init__(self): + NWSpellCheck.__init__(self) + logger.debug("Simple spell checking activated") + return + + def setLanguage(self, theLang, projectDict=None): + + self.WORDS = [] + dictFile = path.join(self.mainConf.dictPath,theLang+".dict") + try: + with open(dictFile,mode="r",encoding="utf-8") as wordsFile: + for theLine in wordsFile: + if len(theLine) == 0 or theLine.startswith("#"): + continue + self.WORDS.append(theLine.strip().lower()) + logger.debug("Spell check word list for language %s loaded" % theLang) + logger.debug("Word list contains %d words" % len(self.WORDS)) + self.spellLanguage = theLang + except Exception as e: + logger.error("Failed to load spell check word list for language %s" % theLang) + logger.error(str(e)) + self.spellLanguage = None + + self._readProjectDictionary(projectDict) + for pWord in self.PROJW: + if pWord not in self.WORDS: + self.WORDS.append(pWord) + + return + + def checkWord(self, theWord): + """Check if a word exists in the word list. Make sure to keep + this function as fast as possible as it is called for every + word by the syntax highlighter. + """ + theWord = theWord.replace(self.mainConf.fmtApostrophe,"'").lower() + return theWord in self.WORDS + + def suggestWords(self, theWord): + """Get suggestions for correct word from difflib, and make sure + the first character is upper case if that was also the case for + the word be3ing checked. Also make sure the apostrophe is + changed to the one in the dictionary, and then put back in the + results. + """ + theWord = theWord.strip() + if len(theWord) == 0: + return [] + + firstUp = theWord[0] == theWord[0].upper() + theWord = theWord.lower() + + theMatches = get_close_matches(theWord, self.WORDS, n=10, cutoff=0.75) + theOptions = [] + for aWord in theMatches: + if len(aWord) == 0: + continue + if firstUp: + aWord = aWord[0].upper() + aWord[1:] + aWord = aWord.replace("'",self.mainConf.fmtApostrophe) + theOptions.append(aWord) + + return theOptions + + def addWord(self, newWord): + newWord = newWord.strip().lower() + if newWord not in self.WORDS: + self.WORDS.append(newWord) + NWSpellCheck.addWord(self, newWord) + return + + def listDictionaries(self): + + retList = [] + for dictFile in listdir(self.mainConf.dictPath): + + theBits = path.splitext(dictFile) + if len(theBits) != 2: + continue + if theBits[1] != ".dict": + continue + + spName = "%s [Internal]" % self.expandLanguage(theBits[0]) + retList.append((theBits[0], spName)) + + return retList + +# END Class NWSpellSimple diff --git a/nw/tools/spellenchant.py b/nw/tools/spellenchant.py deleted file mode 100644 index 4e7400a0..00000000 --- a/nw/tools/spellenchant.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Spell Check Wrapper : pyEnchant - - novelWriter – Spell Check Wrapper : pyEnchant -=============================================== - Wrapper class for spell checking with pyEnchant - - File History: - Created: 2019-06-11 [0.1.5] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw -try: - import enchant -except: - # No need to do anything - # setLanguage will fall back to dummy dictionary - pass - -from nw.tools.spellcheck import NWSpellCheck - -logger = logging.getLogger(__name__) - -class NWSpellEnchant(NWSpellCheck): - - def __init__(self): - NWSpellCheck.__init__(self) - logger.debug("Enchant spell checking activated") - return - - def setLanguage(self, theLang, projectDict=None): - """Load a dictionary for the language specified in the config. - If that fails, we load a dummy dictionary so that lookups don't - crash. - """ - try: - self.theDict = enchant.Dict(theLang) - self.spellLanguage = theLang - logger.debug("Enchant spell checking for language %s loaded" % theLang) - except: - logger.error("Failed to load enchant spell checking for language %s" % theLang) - self.theDict = NWSpellEnchantDummy() - self.spellLanguage = None - - self._readProjectDictionary(projectDict) - for pWord in self.PROJW: - self.theDict.add_to_session(pWord) - - return - - def checkWord(self, theWord): - return self.theDict.check(theWord) - - def suggestWords(self, theWord): - return self.theDict.suggest(theWord) - - def addWord(self, newWord): - self.theDict.add_to_session(newWord) - NWSpellCheck.addWord(self, newWord) - return - - def listDictionaries(self): - retList = [] - for spTag, spProvider in enchant.list_dicts(): - spName = "%s [%s]" % (self.expandLanguage(spTag), spProvider.name) - retList.append((spTag, spName)) - return retList - -# END Class NWSpellEnchant - -class NWSpellEnchantDummy: - - def __init__(self): - return - - def check(self, theWord): - return True - - def suggest(self, theWord): - return [] - - def add_to_session(self, theWord): - return - -# END Class NWSpellEnchantDummy diff --git a/nw/tools/spellsimple.py b/nw/tools/spellsimple.py deleted file mode 100644 index 63a8847b..00000000 --- a/nw/tools/spellsimple.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Spell Check Simple - - novelWriter – Spell Check Simple -================================== - Simple spell checker based on difflib - - File History: - Created: 2019-06-11 [0.1.5] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from os import path, listdir -from difflib import get_close_matches - -logger = logging.getLogger(__name__) - -from nw.tools.spellcheck import NWSpellCheck - -class NWSpellSimple(NWSpellCheck): - - WORDS = [] - - def __init__(self): - NWSpellCheck.__init__(self) - logger.debug("Simple spell checking activated") - return - - def setLanguage(self, theLang, projectDict=None): - - self.WORDS = [] - dictFile = path.join(self.mainConf.dictPath,theLang+".dict") - try: - with open(dictFile,mode="r",encoding="utf-8") as wordsFile: - for theLine in wordsFile: - if len(theLine) == 0 or theLine.startswith("#"): - continue - self.WORDS.append(theLine.strip().lower()) - logger.debug("Spell check word list for language %s loaded" % theLang) - logger.debug("Word list contains %d words" % len(self.WORDS)) - self.spellLanguage = theLang - except Exception as e: - logger.error("Failed to load spell check word list for language %s" % theLang) - logger.error(str(e)) - self.spellLanguage = None - - self._readProjectDictionary(projectDict) - for pWord in self.PROJW: - if pWord not in self.WORDS: - self.WORDS.append(pWord) - - return - - def checkWord(self, theWord): - """Check if a word exists in the word list. Make sure to keep - this function as fast as possible as it is called for every - word by the syntax highlighter. - """ - theWord = theWord.replace(self.mainConf.fmtApostrophe,"'").lower() - return theWord in self.WORDS - - def suggestWords(self, theWord): - """Get suggestions for correct word from difflib, and make sure - the first character is upper case if that was also the case for - the word be3ing checked. Also make sure the apostrophe is - changed to the one in the dictionary, and then put back in the - results. - """ - theWord = theWord.strip() - if len(theWord) == 0: - return [] - - firstUp = theWord[0] == theWord[0].upper() - theWord = theWord.lower() - - theMatches = get_close_matches(theWord, self.WORDS, n=10, cutoff=0.75) - theOptions = [] - for aWord in theMatches: - if len(aWord) == 0: - continue - if firstUp: - aWord = aWord[0].upper() + aWord[1:] - aWord = aWord.replace("'",self.mainConf.fmtApostrophe) - theOptions.append(aWord) - - return theOptions - - def addWord(self, newWord): - newWord = newWord.strip().lower() - if newWord not in self.WORDS: - self.WORDS.append(newWord) - NWSpellCheck.addWord(self, newWord) - return - - def listDictionaries(self): - - retList = [] - for dictFile in listdir(self.mainConf.dictPath): - - theBits = path.splitext(dictFile) - if len(theBits) != 2: - continue - if theBits[1] != ".dict": - continue - - spName = "%s [Internal]" % self.expandLanguage(theBits[0]) - retList.append((theBits[0], spName)) - - return retList - -# END Class NWSpellSimple