From 8dd9644c135c60d32fbb5a44e362d57d429bf931 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:22:54 +0200 Subject: [PATCH 1/3] Fix issue with overlapping formatting (#2012) --- novelwriter/core/tokenizer.py | 5 +++-- tests/test_core/test_core_tokenizer.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 6e420f5d5..36d42eb85 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -1150,8 +1150,9 @@ def _extractFormats(self, text: str, skip: int = 0) -> tuple[str, T_Formats]: formats = [] for pos, n, fmt, key in reversed(sorted(temp, key=lambda x: x[0])): if fmt > 0: - result = result[:pos] + result[pos+n:] - formats = [(p-n, f, k) for p, f, k in formats] + if n > 0: + result = result[:pos] + result[pos+n:] + formats = [(p-n if p > pos else p, f, k) for p, f, k in formats] formats.insert(0, (pos, fmt, key)) return result, formats diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index 2932fe451..30d069063 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -1161,6 +1161,24 @@ def testCoreToken_Dialogue(mockGUI): Tokenizer.A_NONE )] + # Special Cases + # ============= + + # Dialogue + formatting on same index (Issue #2012) + tokens._text = "[i]\u201cDialogue text.\u201d[/i]\n" + tokens.tokenizeText() + assert tokens._tokens == [( + Tokenizer.T_TEXT, 0, + "\u201cDialogue text.\u201d", + [ + (0, Tokenizer.FMT_I_B, ""), + (0, Tokenizer.FMT_DL_B, ""), + (16, Tokenizer.FMT_I_E, ""), + (16, Tokenizer.FMT_DL_E, ""), + ], + Tokenizer.A_NONE + )] + @pytest.mark.core def testCoreToken_SpecialFormat(mockGUI): From ece73784c208c815d52d770e38ecfedc11420e5c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:32:37 +0200 Subject: [PATCH 2/3] Only highlight dialogue in novel text (#2011 and #2013) --- novelwriter/core/tokenizer.py | 13 +++++++++---- tests/test_core/test_core_tokenizer.py | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 36d42eb85..b977d9621 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -832,7 +832,7 @@ def tokenizeText(self) -> None: sAlign |= self.A_IND_R # Process formats - tLine, tFmt = self._extractFormats(aLine) + tLine, tFmt = self._extractFormats(aLine, hDialog=self._isNovel) tokens.append(( self.T_TEXT, nHead, tLine, tFmt, sAlign )) @@ -1098,8 +1098,13 @@ def saveRawMarkdownJSON(self, path: str | Path) -> None: # Internal Functions ## - def _extractFormats(self, text: str, skip: int = 0) -> tuple[str, T_Formats]: - """Extract format markers from a text paragraph.""" + def _extractFormats( + self, text: str, skip: int = 0, hDialog: bool = False + ) -> tuple[str, T_Formats]: + """Extract format markers from a text paragraph. In order to + also process dialogue highlighting, the hDialog flag must be set + to True. See issues #2011 and #2013. + """ temp: list[tuple[int, int, int, str]] = [] # Match Markdown @@ -1137,7 +1142,7 @@ def _extractFormats(self, text: str, skip: int = 0) -> tuple[str, T_Formats]: )) # Match Dialogue - if self._rxDialogue: + if self._rxDialogue and hDialog: for regEx, fmtB, fmtE in self._rxDialogue: rxItt = regEx.globalMatch(text, 0) while rxItt.hasNext(): diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index 30d069063..31ced9096 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -1100,6 +1100,7 @@ def testCoreToken_Dialogue(mockGUI): project = NWProject() tokens = BareTokenizer(project) tokens.setDialogueHighlight(True) + tokens._isNovel = True # Single quotes tokens._text = "Text with \u2018dialogue one,\u2019 and \u2018dialogue two.\u2019\n" From e414b6458de38a02d25db06d0f512c71bb2f185e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:38:04 +0200 Subject: [PATCH 3/3] Refresh tab stop distance after loading document (#1996) --- novelwriter/gui/docviewer.py | 6 ++---- novelwriter/tools/manuscript.py | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index f013db0b2..aa4cbb31b 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -229,15 +229,13 @@ def loadText(self, tHandle: str, updateHistory: bool = True) -> bool: QApplication.restoreOverrideCursor() return False - # Refresh the tab stops - self.setTabStopDistance(CONFIG.getTabWidth()) - - # Must be before setHtml + # Must be before setDocument if updateHistory: self.docHistory.append(tHandle) self.setDocumentTitle(tHandle) self.setDocument(qDoc.document) + self.setTabStopDistance(CONFIG.getTabWidth()) if self._docHandle == tHandle: # This is a refresh, so we set the scrollbar back to where it was diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 4c2167428..f13ef236e 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -823,6 +823,7 @@ def setContent(self, document: QTextDocument) -> None: document.setDocumentMargin(CONFIG.getTextMargin()) self.setDocument(document) + self.setTabStopDistance(CONFIG.getTabWidth()) self._docTime = int(time()) self._updateBuildAge()