From e841e0211aa58f31f98c7584829bf40f338a4e01 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 15 Nov 2019 21:39:06 +0100 Subject: [PATCH 1/5] We should read back the language dictionary setting from the spell class before updating status bar --- nw/gui/elements/doceditor.py | 2 +- nw/gui/statusbar.py | 5 ++++- nw/tools/spellcheck.py | 8 ++++---- nw/tools/spellenchant.py | 2 ++ nw/tools/spellsimple.py | 4 +++- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index c961f301..3d0446e1 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -315,7 +315,7 @@ class GuiDocEditor(QTextEdit): def setDictionaries(self): self.theDict.setLanguage(self.mainConf.spellLanguage, self.theProject.projDict) - self.theParent.statusBar.setLanguage(self.mainConf.spellLanguage) + self.theParent.statusBar.setLanguage(self.theDict.spellLanguage) return True def setSpellCheck(self, theMode): diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index 08b30870..81dd6a7c 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -116,7 +116,10 @@ class GuiMainStatus(QStatusBar): return def setLanguage(self, theLanguage): - self.langBox.setText(NWSpellCheck.expandLanguage(theLanguage)) + if theLanguage is None: + self.langBox.setText("None") + else: + self.langBox.setText(NWSpellCheck.expandLanguage(theLanguage)) return def setProjectStatus(self, isChanged): diff --git a/nw/tools/spellcheck.py b/nw/tools/spellcheck.py index fd5142c8..ec95a4f6 100644 --- a/nw/tools/spellcheck.py +++ b/nw/tools/spellcheck.py @@ -29,6 +29,7 @@ class NWSpellCheck(): def __init__(self): self.mainConf = nw.CONFIG self.projectDict = None + self.spellLanguage = None return def setLanguage(self, theLang, projectDict=None): @@ -45,11 +46,10 @@ class NWSpellCheck(): newWord = newWord.strip() self.PROJW.append(newWord) try: - with open(self.projectDict,mode="w+",encoding="utf-8") as outFile: - for pWord in self.PROJW: - outFile.write("%s\n" % pWord) + with open(self.projectDict,mode="a+",encoding="utf-8") as outFile: + outFile.write("%s\n" % newWord) except Exception as e: - logger.error("Failed to write to project word list at %s" % str(self.projectDict)) + logger.error("Failed to add word to project word list %s" % str(self.projectDict)) logger.error(str(e)) return diff --git a/nw/tools/spellenchant.py b/nw/tools/spellenchant.py index 1bff2329..7c60c8dd 100644 --- a/nw/tools/spellenchant.py +++ b/nw/tools/spellenchant.py @@ -32,10 +32,12 @@ class NWSpellEnchant(NWSpellCheck): """ 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: diff --git a/nw/tools/spellsimple.py b/nw/tools/spellsimple.py index d98947e5..ab4bf52c 100644 --- a/nw/tools/spellsimple.py +++ b/nw/tools/spellsimple.py @@ -41,9 +41,11 @@ class NWSpellSimple(NWSpellCheck): 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: @@ -104,7 +106,7 @@ class NWSpellSimple(NWSpellCheck): if theBits[1] != ".dict": continue - spName = "%s [nternal]" % self.expandLanguage(theBits[0]) + spName = "%s [Internal]" % self.expandLanguage(theBits[0]) retList.append((theBits[0], spName)) return retList From ec0f4880fa9367b83e4fcb038f451236bf4e6ff5 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 15 Nov 2019 22:16:58 +0100 Subject: [PATCH 2/5] Let the document editor handle the status of spell checking, and let it set all other classes from there --- nw/gui/elements/doceditor.py | 24 ++++++++++++++++++++++++ nw/gui/mainmenu.py | 11 +++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index 3d0446e1..ab6c5ada 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -314,17 +314,41 @@ class GuiDocEditor(QTextEdit): ## def setDictionaries(self): + """Set the spell checker dictionary language, and update the + status bar to show the one actually loaded by the spell checker + class. + """ self.theDict.setLanguage(self.mainConf.spellLanguage, self.theProject.projDict) self.theParent.statusBar.setLanguage(self.theDict.spellLanguage) return True def setSpellCheck(self, theMode): + """This is the master spell check setting function, and this one + should call all other setSpellCheck functions in other classes. + If the spell check mode is not defined, then toggle the current + status saved in the class. + """ + + if theMode is None: + theMode = not self.spellCheck + + if self.theDict.spellLanguage is None: + theMode = False + self.spellCheck = theMode + self.theParent.mainMenu.setSpellCheck(theMode) + self.theProject.setSpellCheck(theMode) self.hLight.setSpellCheck(theMode) self.hLight.rehighlight() + + logger.verbose("Spell check is set to %s" % str(theMode)) + return True def updateSpellCheck(self): + """Rerun the highlighter to update spell checking status of the + currently loaded text. + """ if self.spellCheck: self.hLight.rehighlight() return True diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 4205c8ad..23feab0b 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -91,6 +91,10 @@ class GuiMainMenu(QMenuBar): return + def setSpellCheck(self, theMode): + self.aSpellCheck.setChecked(theMode) + return + def updateSpellCheck(self): if self.theParent.hasProject: self.aSpellCheck.setChecked(self.theProject.spellCheck) @@ -106,12 +110,7 @@ class GuiMainMenu(QMenuBar): return def _toggleSpellCheck(self): - if self.theParent.hasProject: - self.theProject.setSpellCheck(self.aSpellCheck.isChecked()) - self.theParent.docEditor.setSpellCheck(self.aSpellCheck.isChecked()) - logger.verbose("Spell check is set to %s" % str(self.theProject.spellCheck)) - else: - self.aSpellCheck.setChecked(False) + self.theParent.docEditor.setSpellCheck(None) return True def _toggleViewComments(self): From ed0c4c6e49730cf32c7e65f888699f52ccb83ea7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 15 Nov 2019 22:36:44 +0100 Subject: [PATCH 3/5] Fixed a recursion issue, and added more comments --- nw/gui/mainmenu.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 23feab0b..179a6077 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -68,7 +68,6 @@ class GuiMainMenu(QMenuBar): def updateMenu(self): self.updateRecentProjects() - self.updateSpellCheck() return def updateRecentProjects(self): @@ -92,15 +91,13 @@ class GuiMainMenu(QMenuBar): return def setSpellCheck(self, theMode): + """Set the spell check check box to theMode. This is controlled + by the document editor class, which holds the master spell check + flag. + """ self.aSpellCheck.setChecked(theMode) return - def updateSpellCheck(self): - if self.theParent.hasProject: - self.aSpellCheck.setChecked(self.theProject.spellCheck) - logger.verbose("Spell check is set to %s" % str(self.theProject.spellCheck)) - return - ## # Menu Action ## @@ -110,6 +107,10 @@ class GuiMainMenu(QMenuBar): return def _toggleSpellCheck(self): + """Toggle spell checking. The active status of the spell check + flag is handled by the document editor class, so we make no + decision, just pass a None to the function and let it decide. + """ self.theParent.docEditor.setSpellCheck(None) return True @@ -608,7 +609,8 @@ class GuiMainMenu(QMenuBar): self.aSpellCheck.setStatusTip("Toggle check spelling") self.aSpellCheck.setCheckable(True) self.aSpellCheck.setChecked(self.theProject.spellCheck) - self.aSpellCheck.toggled.connect(self._toggleSpellCheck) + # Here we must used triggered, not toggled, to avoid recursion + self.aSpellCheck.triggered.connect(self._toggleSpellCheck) self.aSpellCheck.setShortcut("Ctrl+F7") self.toolsMenu.addAction(self.aSpellCheck) From e0fff7673d0c2ca9170c474b3cb906499c12a13b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 15 Nov 2019 23:00:12 +0100 Subject: [PATCH 4/5] Added some more comments and debug output --- nw/gui/elements/doceditor.py | 3 +++ nw/gui/tools/dochighlight.py | 4 +++- nw/tools/spellcheck.py | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index ab6c5ada..7cc4a186 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -530,9 +530,12 @@ class GuiDocEditor(QTextEdit): theCursor = self.cursorForPosition(thePos) theCursor.select(QTextCursor.WordUnderCursor) + theWord = theCursor.selectedText().strip().strip(self.nonWord) if theWord == "": return + + logger.verbose("Looking up '%s' in the dictionary" % theWord) if self.theDict.checkWord(theWord): return diff --git a/nw/gui/tools/dochighlight.py b/nw/gui/tools/dochighlight.py index b151ac94..270a5d15 100644 --- a/nw/gui/tools/dochighlight.py +++ b/nw/gui/tools/dochighlight.py @@ -201,12 +201,14 @@ class GuiDocHighlighter(QSyntaxHighlighter): } )) - # Build a QRegExp for each pattern and for the spell checker + # Build a QRegExp for each highlight pattern self.rxRules = [] for regEx, regRules in self.hRules: hReg = QRegularExpression(regEx) hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) self.rxRules.append((hReg, regRules)) + + # Build a QRegExp for spell checker self.spellRx = QRegularExpression(r"\b[^\s]+\b") self.spellRx.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) diff --git a/nw/tools/spellcheck.py b/nw/tools/spellcheck.py index ec95a4f6..7979f2b9 100644 --- a/nw/tools/spellcheck.py +++ b/nw/tools/spellcheck.py @@ -13,6 +13,8 @@ import logging import nw +from os import path + from nw.constants import isoLanguage logger = logging.getLogger(__name__) @@ -75,6 +77,8 @@ class NWSpellCheck(): self.PROJW = [] if projectDict is not None: self.projectDict = projectDict + if not path.isfile(projectDict): + return try: with open(projectDict,mode="r",encoding="utf-8") as wordsFile: for theLine in wordsFile: From 2ab782817926f1856fe5276fac91e43835c9afb1 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 16 Nov 2019 12:31:49 +0100 Subject: [PATCH 5/5] Added more characters as valid word separators to the highlighter regex --- nw/gui/tools/dochighlight.py | 10 ++++++++-- sample/sampleNovel/data_6/36b6aa9b697b_main.nwd | 2 ++ sample/sampleNovel/nwProject.nwx | 16 ++++++++-------- tests/profilestats.py | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/nw/gui/tools/dochighlight.py b/nw/gui/tools/dochighlight.py index 270a5d15..fd093933 100644 --- a/nw/gui/tools/dochighlight.py +++ b/nw/gui/tools/dochighlight.py @@ -18,6 +18,8 @@ from PyQt5.QtGui import ( QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush ) +from nw.constants import nwUnicode + logger = logging.getLogger(__name__) class GuiDocHighlighter(QSyntaxHighlighter): @@ -150,7 +152,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Non-breaking Space self.hRules.append(( - "[\u00a0]+", { + "[%s]+" % nwUnicode.U_NBSP, { 0 : self.hStyles["nobreak"], } )) @@ -209,7 +211,11 @@ class GuiDocHighlighter(QSyntaxHighlighter): self.rxRules.append((hReg, regRules)) # Build a QRegExp for spell checker - self.spellRx = QRegularExpression(r"\b[^\s]+\b") + # Include additional characters that the highlighter should + # consider to be word separators + wordSep = "_+" + wordSep += nwUnicode.U_EMDASH + self.spellRx = QRegularExpression("\\b[^\\s%s]+\\b" % wordSep) self.spellRx.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) return True diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd index 70e5eafa..e1578fec 100644 --- a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd +++ b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd @@ -12,6 +12,8 @@ In addition, the editor supports automatic formatting of “quotes”, both doub If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, let’s auto-replace this A with , and this C with . While is just . Press Ctrl+R to see what this looks like in the view pane. +The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. + #### Some Section Here If you need to split a scene file up into further pieces, you can do so with the level four heading, like above. This is referred to as a section. diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index 63b80110..fe093bba 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -9,9 +9,9 @@ True - 96b68994dfa3d + 636b6aa9b697b b3e74dbc1f584 - 855 + 875 B E @@ -79,10 +79,10 @@ 1st Draft False SCENE - 1076 - 196 - 6 - 59 + 1199 + 216 + 7 + 949 Another Scene @@ -118,7 +118,7 @@ 1692 313 6 - 216 + 530 Chapter Two diff --git a/tests/profilestats.py b/tests/profilestats.py index f55c7b01..cbb18aeb 100755 --- a/tests/profilestats.py +++ b/tests/profilestats.py @@ -13,4 +13,4 @@ print("") profMainWindows = pstats.Stats(path.join(profDir,"testMainWindows.prof")) profMainWindows.sort_stats("cumtime") -profMainWindows.print_stats("nw",50) +profMainWindows.print_stats("nw/")