Merge pull request #31 from vkbo/spell_check

Spell Check Update
This commit is contained in:
Veronica K. Berglyd Olsen
2019-06-11 21:10:22 +02:00
committed by GitHub
15 changed files with 172 additions and 29 deletions
+9 -5
View File
@@ -80,12 +80,16 @@ Most shortcuts are labelled with the corresponding dropdown menu entry.
## Dependencies ## Dependencies
For the apt package manager, the following Python3 packages are needed. For the apt package manager, the following Python3 packages are needed:
* python3-pyqt5 * `python3-pyqt5` for the GUI
* python3-appdirs * `python3-appdirs` for locating the system's config folder
* python3-lxml * `python3-lxml` for writing project files
* python3-enchant
These are optional, but recommended:
* `python3-enchant` for spell checking
* `python3-pycountry` for translating language codes to language names
Alternatively, the packages can be installed with `pip` by running Alternatively, the packages can be installed with `pip` by running
``` ```
+8 -3
View File
@@ -29,12 +29,17 @@ except:
print("ERROR: Failed to load dependency python3-appdirs") print("ERROR: Failed to load dependency python3-appdirs")
exit(1) exit(1)
spellPack = None
try: try:
import enchant import enchant
spellPack = "enchant"
except: except:
print("ERROR: Failed to load dependency python3-enchant") print("WARNING: No spell check library found.")
exit(1) print("Please install python3-enchant if you want to use spell checking")
if __name__ == "__main__": if __name__ == "__main__":
import nw import nw
nw.main(sys.argv[1:]) inArgs = sys.argv[1:]
if spellPack is not None:
inArgs.append("--spell=%s" % spellPack)
nw.main(inArgs)
+5
View File
@@ -82,6 +82,7 @@ def main(sysArgs):
"version", "version",
"config=", "config=",
"testmode", "testmode",
"spell=",
] ]
helpMsg = ( helpMsg = (
@@ -117,6 +118,7 @@ def main(sysArgs):
confPath = None confPath = None
testMode = False testMode = False
debugGUI = False debugGUI = False
spellTool = None
# Parse Options # Parse Options
try: try:
@@ -149,6 +151,8 @@ def main(sysArgs):
confPath = inArg confPath = inArg
elif inOpt in ("--testmode"): elif inOpt in ("--testmode"):
testMode = True testMode = True
elif inOpt in ("--spell"):
spellTool = inArg
elif inOpt in ("-D","--debuggui"): elif inOpt in ("-D","--debuggui"):
debugLevel = logging.DEBUG debugLevel = logging.DEBUG
debugStr = "{name:>20}:{lineno:<4d} {levelname:8} {message:}" debugStr = "{name:>20}:{lineno:<4d} {levelname:8} {message:}"
@@ -158,6 +162,7 @@ def main(sysArgs):
CONFIG.showGUI = not testMode CONFIG.showGUI = not testMode
CONFIG.debugGUI = debugGUI CONFIG.debugGUI = debugGUI
CONFIG.debugInfo = debugLevel < logging.INFO CONFIG.debugInfo = debugLevel < logging.INFO
CONFIG.spellTool = spellTool
# Set Logging # Set Logging
if showTime: debugStr = timeStr+debugStr if showTime: debugStr = timeStr+debugStr
+6
View File
@@ -35,6 +35,7 @@ class Config:
self.showGUI = True self.showGUI = True
self.debugGUI = False self.debugGUI = False
self.debugInfo = False self.debugInfo = False
self.spellTool = None
# Set Paths # Set Paths
self.confPath = None self.confPath = None
@@ -124,6 +125,11 @@ class Config:
# If it does not exist, save a copy of the defaults # If it does not exist, save a copy of the defaults
self.saveConfig() self.saveConfig()
if self.spellTool is None:
logger.warning("No spell check tool available")
else:
logger.debug("Using spell check tool '%s'" % self.spellTool)
return True return True
def loadConfig(self): def loadConfig(self):
+3 -3
View File
@@ -28,13 +28,13 @@ class ToHtml(Tokenizer):
def doAutoReplace(self): def doAutoReplace(self):
Tokenizer.doAutoReplace(self) Tokenizer.doAutoReplace(self)
theDict = { repDict = {
"<" : "&lt;", "<" : "&lt;",
">" : "&gt;", ">" : "&gt;",
"&" : "&amp;", "&" : "&amp;",
} }
xRep = re.compile("|".join([re.escape(k) for k in theDict.keys()]), flags=re.DOTALL) xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: theDict[x.group(0)], self.theText) self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
return return
+4 -4
View File
@@ -60,11 +60,11 @@ class Tokenizer():
def doAutoReplace(self): def doAutoReplace(self):
if len(self.theProject.autoReplace) > 0: if len(self.theProject.autoReplace) > 0:
theDict = {} repDict = {}
for aKey, aVal in self.theProject.autoReplace.items(): for aKey, aVal in self.theProject.autoReplace.items():
theDict["<%s>" % aKey] = aVal repDict["<%s>" % aKey] = aVal
xRep = re.compile("|".join([re.escape(k) for k in theDict.keys()]), flags=re.DOTALL) xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: theDict[x.group(0)], self.theText) self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
return return
def tokenizeText(self): def tokenizeText(self):
+2 -3
View File
@@ -11,7 +11,6 @@
""" """
import logging import logging
import enchant
import nw import nw
from os import path from os import path
@@ -139,8 +138,8 @@ class GuiConfigEditGeneral(QWidget):
self.spellLang.setLayout(self.spellLangForm) self.spellLang.setLayout(self.spellLangForm)
self.spellLangList = QComboBox(self) self.spellLangList = QComboBox(self)
for spTag, spProvider in enchant.list_dicts(): for spTag, spName in self.theParent.docEditor.theDict.listDictionaries():
self.spellLangList.addItem("%s [%s]" % (spTag, spProvider.name), spTag) self.spellLangList.addItem(spName, spTag)
spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage) spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage)
if spellIdx != -1: if spellIdx != -1:
self.spellLangList.setCurrentIndex(spellIdx) self.spellLangList.setCurrentIndex(spellIdx)
+12 -7
View File
@@ -12,7 +12,6 @@
import logging import logging
import nw import nw
import enchant
from time import time from time import time
@@ -23,6 +22,7 @@ from PyQt5.QtGui import QTextCursor, QTextOption, QIcon, QKeySequence, Q
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.gui.dochighlight import GuiDocHighlighter from nw.gui.dochighlight import GuiDocHighlighter
from nw.gui.wordcounter import WordCounter from nw.gui.wordcounter import WordCounter
from nw.tools.spellcheck import NWSpellCheck
from nw.enum import nwDocAction, nwAlert from nw.enum import nwDocAction, nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -58,7 +58,13 @@ class GuiDocEditor(QTextEdit):
# Core Elements # Core Elements
self.theQDoc = self.document() self.theQDoc = self.document()
self.theDict = enchant.Dict(self.mainConf.spellLanguage) if self.mainConf.spellTool == "enchant":
from nw.tools.spellenchant import NWSpellEnchant
self.theDict = NWSpellEnchant()
else:
self.theDict = NWSpellCheck()
self.theDict.setLanguage(self.mainConf.spellLanguage)
self.hLight = GuiDocHighlighter(self.theQDoc, self.theParent) self.hLight = GuiDocHighlighter(self.theQDoc, self.theParent)
self.hLight.setDict(self.theDict) self.hLight.setDict(self.theDict)
@@ -215,8 +221,7 @@ class GuiDocEditor(QTextEdit):
def setPwl(self, pwlFile): def setPwl(self, pwlFile):
if pwlFile is not None: if pwlFile is not None:
self.pwlFile = pwlFile self.pwlFile = pwlFile
self.theDict = enchant.DictWithPWL(self.mainConf.spellLanguage,pwlFile) self.theDict.setLanguage(self.mainConf.spellLanguage, pwlFile)
self.hLight.setDict(self.theDict)
return True return True
def setSpellCheck(self, theMode): def setSpellCheck(self, theMode):
@@ -314,7 +319,7 @@ class GuiDocEditor(QTextEdit):
theWord = theCursor.selectedText() theWord = theCursor.selectedText()
if theWord == "": if theWord == "":
return return
if self.theDict.check(theWord): if self.theDict.checkWord(theWord):
return return
mnuSuggest = QMenu() mnuSuggest = QMenu()
@@ -322,7 +327,7 @@ class GuiDocEditor(QTextEdit):
mnuHead = QAction(spIcon,"Spelling Suggestion", mnuSuggest) mnuHead = QAction(spIcon,"Spelling Suggestion", mnuSuggest)
mnuSuggest.addAction(mnuHead) mnuSuggest.addAction(mnuHead)
mnuSuggest.addSeparator() mnuSuggest.addSeparator()
theSuggest = self.theDict.suggest(theWord) theSuggest = self.theDict.suggestWords(theWord)
if len(theSuggest) > 0: if len(theSuggest) > 0:
for aWord in theSuggest: for aWord in theSuggest:
mnuWord = QAction(aWord, mnuSuggest) mnuWord = QAction(aWord, mnuSuggest)
@@ -353,7 +358,7 @@ class GuiDocEditor(QTextEdit):
def _addWord(self, theCursor): def _addWord(self, theCursor):
theWord = theCursor.selectedText().strip() theWord = theCursor.selectedText().strip()
logger.info("Added '%s' to project dictionary" % theWord) logger.info("Added '%s' to project dictionary" % theWord)
self.theDict.add_to_pwl(theWord) self.theDict.addWord(theWord)
self.hLight.setDict(self.theDict) self.hLight.setDict(self.theDict)
self.hLight.rehighlightBlock(theCursor.block()) self.hLight.rehighlightBlock(theCursor.block())
return return
+1 -1
View File
@@ -234,7 +234,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
rxSpell = self.spellRx.globalMatch(theText.replace("_"," "), 0) rxSpell = self.spellRx.globalMatch(theText.replace("_"," "), 0)
while rxSpell.hasNext(): while rxSpell.hasNext():
rxMatch = rxSpell.next() rxMatch = rxSpell.next()
if not self.theDict.check(rxMatch.captured(0)): if not self.theDict.checkWord(rxMatch.captured(0)):
if rxMatch.captured(0) == rxMatch.captured(0).upper(): if rxMatch.captured(0) == rxMatch.captured(0).upper():
continue continue
xPos = rxMatch.capturedStart(0) xPos = rxMatch.capturedStart(0)
+1 -1
View File
@@ -520,7 +520,7 @@ class GuiMain(QMainWindow):
def closeMain(self): def closeMain(self):
if self.mainConf.showGUI: if self.mainConf.showGUI and self.hasProject:
msgBox = QMessageBox() msgBox = QMessageBox()
msgRes = msgBox.question( msgRes = msgBox.question(
self, "Exit", "Do you want to save changes and exit?" self, "Exit", "Do you want to save changes and exit?"
+40
View File
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
"""novelWriter Spell Check Wrapper
novelWriter Spell Check Wrapper
===================================
Wrapper class for spell checking
File History:
Created: 2019-06-11 [0.1.5]
"""
import logging
import nw
logger = logging.getLogger(__name__)
class NWSpellCheck():
theDict = None
def __init__(self):
return
def setLanguage(self, theLang, projectDict=None):
return
def checkWord(self, theWord):
return True
def suggestWords(self, theWord):
return []
def addWord(self, newWord):
return
def listDictionaries(self):
return []
# END Class NWSpellCheck
+71
View File
@@ -0,0 +1,71 @@
# -*- 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]
"""
import logging
import enchant
import nw
try:
import pycountry
hasPyCountry = True
except:
hasPyCountry = False
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):
if projectDict is None:
self.theDict = enchant.Dict(theLang)
else:
self.theDict = enchant.DictWithPWL(theLang, projectDict)
logger.debug("Enchant spell checking for language %s loaded" % theLang)
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_pwl(newWord)
return
def listDictionaries(self):
retList = []
for spTag, spProvider in enchant.list_dicts():
if hasPyCountry:
spList = []
try:
langObj = pycountry.languages.get(alpha_2 = spTag[:2])
spList.append(langObj.name)
except:
spList.append(spTag[:2])
if len(spTag) > 3:
spList.append("(%s)" % spTag[3:])
spList.append("[%s]" % spProvider.name)
spName = " ".join(spList)
else:
spName = "%s [%s]" % (spTag, spProvider.name)
retList.append((spTag, spName))
return retList
# END Class NWSpellEnchant
+1
View File
@@ -2,3 +2,4 @@ pyqt5
appdirs appdirs
lxml lxml
pyenchant pyenchant
pycountry
+7
View File
@@ -210,3 +210,10 @@ Start: 2019-06-10 21:48:51 End: 2019-06-10 21:49:07 Words: 0
Start: 2019-06-10 22:17:58 End: 2019-06-10 22:18:44 Words: 0 Start: 2019-06-10 22:17:58 End: 2019-06-10 22:18:44 Words: 0
Start: 2019-06-10 22:46:35 End: 2019-06-10 22:49:34 Words: 0 Start: 2019-06-10 22:46:35 End: 2019-06-10 22:49:34 Words: 0
Start: 2019-06-11 14:44:55 End: 2019-06-11 14:53:47 Words: 0 Start: 2019-06-11 14:44:55 End: 2019-06-11 14:53:47 Words: 0
Start: 2019-06-11 19:26:07 End: 2019-06-11 19:26:18 Words: 0
Start: 2019-06-11 19:26:44 End: 2019-06-11 19:26:50 Words: 0
Start: 2019-06-11 19:30:30 End: 2019-06-11 19:30:37 Words: 0
Start: 2019-06-11 19:31:16 End: 2019-06-11 19:31:50 Words: 0
Start: 2019-06-11 20:04:39 End: 2019-06-11 20:05:00 Words: 0
Start: 2019-06-11 20:05:04 End: 2019-06-11 20:08:49 Words: 0
Start: 2019-06-11 20:20:52 End: 2019-06-11 20:21:18 Words: 0
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.5" fileVersion="1.0" timeStamp="2019-06-11 14:46:19"> <novelWriterXML appVersion="0.1.5" fileVersion="1.0" timeStamp="2019-06-11 20:21:18">
<project> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
@@ -80,7 +80,7 @@
<charCount>736</charCount> <charCount>736</charCount>
<wordCount>137</wordCount> <wordCount>137</wordCount>
<paraCount>6</paraCount> <paraCount>6</paraCount>
<cursorPos>738</cursorPos> <cursorPos>473</cursorPos>
</item> </item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a"> <item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>New File</name> <name>New File</name>