@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ class ToHtml(Tokenizer):
|
|||||||
def doAutoReplace(self):
|
def doAutoReplace(self):
|
||||||
Tokenizer.doAutoReplace(self)
|
Tokenizer.doAutoReplace(self)
|
||||||
|
|
||||||
theDict = {
|
repDict = {
|
||||||
"<" : "<",
|
"<" : "<",
|
||||||
">" : ">",
|
">" : ">",
|
||||||
"&" : "&",
|
"&" : "&",
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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?"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -2,3 +2,4 @@ pyqt5
|
|||||||
appdirs
|
appdirs
|
||||||
lxml
|
lxml
|
||||||
pyenchant
|
pyenchant
|
||||||
|
pycountry
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user