diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 7ca7afe5..89938e69 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -336,7 +336,7 @@ class NWIndex: elif line.startswith("%"): if cTitle != TT_NONE: - cStyle, cText, _ = processComment(line) + cStyle, cMod, cText, _, _ = processComment(line) if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT): self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText) @@ -1303,17 +1303,23 @@ class IndexHeading: # =============================================================================================== # CLASSIFIERS = { - "short": nwComment.SHORT, "synopsis": nwComment.SYNOPSIS, + "summary": nwComment.SUMMARY, + "short": nwComment.SHORT, + "note": nwComment.NOTE, } +TERMS = ["note"] -def processComment(text: str) -> tuple[nwComment, str, int]: + +def processComment(text: str) -> tuple[nwComment, str, str, int, int]: """Extract comment style and text. Should only be called on text starting with a %. """ check = text[1:].lstrip() classifier, _, content = check.partition(":") + classifier, _, term = classifier.partition(".") if content and (clean := classifier.strip().lower()) in CLASSIFIERS: - return CLASSIFIERS[clean], content.strip(), text.find(":") + 1 - return nwComment.PLAIN, check, 0 + term = "ERR" if term and clean not in TERMS else term.strip().lower() + return CLASSIFIERS[clean], term, content.strip(), text.find(".") + 1, text.find(":") + 1 + return nwComment.PLAIN, "", check, 0, 0 diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index d3f2b690..abb6fc28 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -533,7 +533,7 @@ class Tokenizer(ABC): if aLine.startswith("%~"): continue - cStyle, cText, _ = processComment(aLine) + cStyle, cMod, cText, _, _ = processComment(aLine) if cStyle == nwComment.SYNOPSIS: self._tokens.append(( self.T_SYNOPSIS, nHead, cText, [], sAlign diff --git a/novelwriter/enum.py b/novelwriter/enum.py index d905e587..703df55a 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -66,7 +66,9 @@ class nwComment(Enum): PLAIN = 0 SYNOPSIS = 1 - SHORT = 2 + SUMMARY = 2 + SHORT = 3 + NOTE = 4 # END Enum nwComment diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 27ae33be..62f88f61 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -276,6 +276,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): if self._tHandle is None or not text: return + xOff = 0 if text.startswith("@"): # Keywords and commands self.setCurrentBlockState(BLOCK_META) index = SHARED.project.index @@ -333,12 +334,20 @@ class GuiDocHighlighter(QSyntaxHighlighter): elif text.startswith("%"): # Comments self.setCurrentBlockState(BLOCK_TEXT) - cStyle, _, cPos = processComment(text) - if cStyle == nwComment.PLAIN: - self.setFormat(0, len(text), self._hStyles["hidden"]) + cStyle, cMod, _, cDot, cPos = processComment(text) + cLen = len(text) - cPos + xOff = cPos + if cMod == "ERR": + self.setFormat(0, cPos, self._hStyles["codeinval"]) + elif cStyle == nwComment.PLAIN: + self.setFormat(0, cLen, self._hStyles["hidden"]) + elif cMod: + self.setFormat(0, cDot, self._hStyles["modifier"]) + self.setFormat(cDot, cPos - cDot, self._hStyles["optional"]) + self.setFormat(cPos, cLen, self._hStyles["hidden"]) else: self.setFormat(0, cPos, self._hStyles["modifier"]) - self.setFormat(cPos, len(text), self._hStyles["hidden"]) + self.setFormat(cPos, cLen, self._hStyles["hidden"]) else: # Text Paragraph @@ -377,7 +386,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): self.setCurrentBlockUserData(data) if self._spellCheck: - for xPos, xLen in data.spellCheck(text): + for xPos, xLen in data.spellCheck(text, xOff): for x in range(xPos, xPos+xLen): spFmt = self.format(x) spFmt.merge(self._spellErr) @@ -435,17 +444,19 @@ class TextBlockData(QTextBlockUserData): """Return spell error data from last check.""" return self._spellErrors - def spellCheck(self, text: str) -> list[tuple[int, int]]: + def spellCheck(self, text: str, offset: int) -> list[tuple[int, int]]: """Run the spell checker and cache the result, and return the list of spell check errors. """ self._spellErrors = [] - rxSpell = SPELLRX.globalMatch(text.replace("_", " "), 0) + rxSpell = SPELLRX.globalMatch(text[offset:].replace("_", " "), 0) while rxSpell.hasNext(): rxMatch = rxSpell.next() if not SHARED.spelling.checkWord(rxMatch.captured(0)): if not rxMatch.captured(0).isnumeric() and not rxMatch.captured(0).isupper(): - self._spellErrors.append((rxMatch.capturedStart(0), rxMatch.capturedLength(0))) + self._spellErrors.append( + (rxMatch.capturedStart(0) + offset, rxMatch.capturedLength(0)) + ) return self._spellErrors # END Class TextBlockData diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index b04f8c44..e637ee95 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -1322,26 +1322,35 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): def testCoreIndex_processComment(): """Test the comment processing function.""" # Regular comment - assert processComment("%Hi") == (nwComment.PLAIN, "Hi", 0) - assert processComment("% Hi") == (nwComment.PLAIN, "Hi", 0) - assert processComment("% Hi:You") == (nwComment.PLAIN, "Hi:You", 0) + assert processComment("%Hi") == (nwComment.PLAIN, "", "Hi", 0, 0) + assert processComment("% Hi") == (nwComment.PLAIN, "", "Hi", 0, 0) + assert processComment("% Hi:You") == (nwComment.PLAIN, "", "Hi:You", 0, 0) + assert processComment("% Hi.You:There") == (nwComment.PLAIN, "", "Hi.You:There", 0, 0) - # Synopsis - assert processComment("%synopsis:") == (nwComment.PLAIN, "synopsis:", 0) - assert processComment("%synopsis: Hi") == (nwComment.SYNOPSIS, "Hi", 10) - assert processComment("% synopsis: Hi") == (nwComment.SYNOPSIS, "Hi", 11) - assert processComment("% synopsis : Hi") == (nwComment.SYNOPSIS, "Hi", 13) - assert processComment("% Synopsis : Hi") == (nwComment.SYNOPSIS, "Hi", 15) - assert processComment("% \t SYNOPSIS : Hi") == (nwComment.SYNOPSIS, "Hi", 16) - assert processComment("% \t SYNOPSIS : Hi:You") == (nwComment.SYNOPSIS, "Hi:You", 16) + # Check Non-Term + assert processComment("%summary: Hi") == (nwComment.SUMMARY, "", "Hi", 0, 9) + assert processComment("%summary.term: Hi") == (nwComment.SUMMARY, "ERR", "Hi", 9, 14) - # Short Description - assert processComment("%short:") == (nwComment.PLAIN, "short:", 0) - assert processComment("%short: Hi") == (nwComment.SHORT, "Hi", 7) - assert processComment("% short: Hi") == (nwComment.SHORT, "Hi", 8) - assert processComment("% short : Hi") == (nwComment.SHORT, "Hi", 10) - assert processComment("% Short : Hi") == (nwComment.SHORT, "Hi", 12) - assert processComment("% \t SHORT : Hi") == (nwComment.SHORT, "Hi", 13) - assert processComment("% \t SHORT : Hi:You") == (nwComment.SHORT, "Hi:You", 13) + # Check Term + assert processComment("%note: Hi") == (nwComment.NOTE, "", "Hi", 0, 6) + assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11) + + # Check Padding + assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7) + assert processComment("% short: Hi") == (nwComment.SHORT, "", "Hi", 0, 8) + assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 10) + assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 12) + assert processComment("% \t short : Hi") == (nwComment.SHORT, "", "Hi", 0, 13) + + assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11) + assert processComment("% note.term: Hi") == (nwComment.NOTE, "term", "Hi", 7, 12) + assert processComment("% note . term : Hi") == (nwComment.NOTE, "term", "Hi", 9, 16) + assert processComment("% note . term : Hi") == (nwComment.NOTE, "term", "Hi", 11, 20) + + # Check Classifiers + assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7) + assert processComment("%synopsis: Hi") == (nwComment.SYNOPSIS, "", "Hi", 0, 10) + assert processComment("%summary: Hi") == (nwComment.SUMMARY, "", "Hi", 0, 9) + assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11) # END Test testCoreIndex_processComment