From e998cb634e6f4b70f73ffa2c7c461729cacdb298 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 23 May 2024 20:48:42 +0200 Subject: [PATCH] Add working meta data blocks --- novelwriter/core/tokenizer.py | 5 +- novelwriter/core/toqdoc.py | 98 +++++++++++++++++++--------- tests/test_gui/test_gui_docviewer.py | 14 ++-- 3 files changed, 79 insertions(+), 38 deletions(-) diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 72e68449..ba004659 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -597,7 +597,10 @@ class Tokenizer(ABC): # are automatically skipped. valid, bits, _ = self._project.index.scanThis(aLine) - if valid and bits and bits[0] not in self._skipKeywords: + if ( + valid and bits and bits[0] in nwLabels.KEY_NAME + and bits[0] not in self._skipKeywords + ): tokens.append(( self.T_KEYWORD, nHead, aLine[1:].strip(), [], sAlign )) diff --git a/novelwriter/core/toqdoc.py b/novelwriter/core/toqdoc.py index dac1b532..411a2029 100644 --- a/novelwriter/core/toqdoc.py +++ b/novelwriter/core/toqdoc.py @@ -26,12 +26,12 @@ from __future__ import annotations import logging from PyQt5.QtGui import ( - QColor, QFont, QFontMetrics, QTextBlockFormat, QTextCharFormat, - QTextCursor, QTextDocument + QFont, QFontMetrics, QTextBlockFormat, QTextCharFormat, QTextCursor, + QTextDocument ) from novelwriter import SHARED -from novelwriter.constants import nwHeaders +from novelwriter.constants import nwHeaders, nwHeadFmt, nwKeyWords, nwLabels, nwUnicode from novelwriter.core.project import NWProject from novelwriter.core.tokenizer import T_Formats, Tokenizer from novelwriter.types import ( @@ -99,6 +99,24 @@ class ToQTextDocument(Tokenizer): self._defaultChar = QTextCharFormat() self._defaultChar.setForeground(SHARED.theme.colText) + self._comChar = QTextCharFormat() + self._comChar.setForeground(SHARED.theme.colHidden) + + self._noteChar = QTextCharFormat() + self._noteChar.setForeground(SHARED.theme.colNote) + + self._modChar = QTextCharFormat() + self._modChar.setForeground(SHARED.theme.colMod) + + self._keyChar = QTextCharFormat() + self._keyChar.setForeground(SHARED.theme.colKey) + + self._tagChar = QTextCharFormat() + self._tagChar.setForeground(SHARED.theme.colTag) + + self._optChar = QTextCharFormat() + self._optChar.setForeground(SHARED.theme.colOpt) + self._defaultBlock = QTextBlockFormat() self._defaultBlock.setTopMargin(self._mText[0]) self._defaultBlock.setBottomMargin(self._mText[1]) @@ -139,7 +157,7 @@ class ToQTextDocument(Tokenizer): else: cursor.setBlockFormat(bFmt) - for tType, _, tText, tFormat, tStyle in self._tokens: + for tType, nHead, tText, tFormat, tStyle in self._tokens: # Styles bFmt = QTextBlockFormat(self._defaultBlock) @@ -173,9 +191,9 @@ class ToQTextDocument(Tokenizer): self._insertFragments(tText, tFormat, cursor, self._defaultChar) elif tType in self.L_HEADINGS: - bFmt, cFmt = self._genHeadStyle(tType, bFmt) + bFmt, cFmt = self._genHeadStyle(tType, nHead, bFmt) newBlock(bFmt) - cursor.insertText(tText, cFmt) + cursor.insertText(tText.replace(nwHeadFmt.BR, "\n"), cFmt) elif tType == self.T_SEP: newBlock(bFmt) @@ -183,20 +201,30 @@ class ToQTextDocument(Tokenizer): elif tType == self.T_SKIP: newBlock(bFmt) + cursor.insertText(nwUnicode.U_NBSP, self._defaultChar) elif tType == self.T_SYNOPSIS and self._doSynopsis: - pass + newBlock(bFmt) + prefix = self._localLookup("Synopsis") + cursor.insertText(f"{prefix}: ", self._modChar) + self._insertFragments(tText, tFormat, cursor, self._noteChar) elif tType == self.T_SHORT and self._doSynopsis: - pass + newBlock(bFmt) + prefix = self._localLookup("Short Description") + cursor.insertText(f"{prefix}: ", self._modChar) + self._insertFragments(tText, tFormat, cursor, self._noteChar) elif tType == self.T_COMMENT and self._doComments: - pass + newBlock(bFmt) + self._insertFragments(tText, tFormat, cursor, self._comChar) elif tType == self.T_KEYWORD and self._doKeywords: - pass + newBlock(bFmt) + self._insertKeywords(tText, cursor) self._document.blockSignals(False) + print(self._document.toHtml()) return @@ -226,16 +254,16 @@ class ToQTextDocument(Tokenizer): ## def _insertFragments( - self, text: str, tFmt: T_Formats, cursor: QTextCursor, - dFmt: QTextCharFormat, bgCol: QColor = QtTransparent + self, text: str, tFmt: T_Formats, cursor: QTextCursor, dFmt: QTextCharFormat ) -> None: """Apply formatting tags to text.""" cFmt = QTextCharFormat(dFmt) start = 0 + temp = text.replace("\n", nwUnicode.U_LSEP) for pos, fmt, data in tFmt: # Insert buffer with previous format - cursor.insertText(text[start:pos], cFmt) + cursor.insertText(temp[start:pos], cFmt) # Construct next format if fmt == self.FMT_B_B: @@ -257,7 +285,7 @@ class ToQTextDocument(Tokenizer): elif fmt == self.FMT_M_B: cFmt.setBackground(SHARED.theme.colMark) elif fmt == self.FMT_M_E: - cFmt.setBackground(bgCol) + cFmt.setBackground(QtTransparent) elif fmt == self.FMT_SUP_B: cFmt.setVerticalAlignment(QtVAlignSuper) elif fmt == self.FMT_SUP_E: @@ -280,27 +308,35 @@ class ToQTextDocument(Tokenizer): start = pos # Insert whatever is left in the buffer - cursor.insertText(text[start:], cFmt) + cursor.insertText(temp[start:], cFmt) return - def _formatKeywords(self, text: str, style: int) -> str: + def _insertKeywords(self, text: str, cursor: QTextCursor) -> None: """Apply Markdown formatting to keywords.""" - # valid, bits, _ = self._project.index.scanThis("@"+text) - # if not valid or not bits: - # return "" + valid, bits, _ = self._project.index.scanThis("@"+text) + if valid and bits: + key = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: " + cursor.insertText(key, self._keyChar) + if (num := len(bits)) > 1: + if bits[0] == nwKeyWords.TAG_KEY: + one, two = self._project.index.parseValue(bits[1]) + cursor.insertText(one, self._tagChar) + if two: + cursor.insertText(" | ", self._defaultChar) + cursor.insertText(two, self._optChar) + else: + for n, bit in enumerate(bits[1:], 2): + cFmt = QTextCharFormat(self._tagChar) + cFmt.setFontUnderline(True) + cFmt.setAnchor(True) + cFmt.setAnchorHref(f"#{bits[0][1:]}={bit}") + cursor.insertText(bit, cFmt) + if n < num: + cursor.insertText(", ", self._defaultChar) + return - result = "" - # if bits[0] in nwLabels.KEY_NAME: - # result += f"**{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:** " - # if len(bits) > 1: - # result += ", ".join(bits[1:]) - - # result += " \n" if style & self.A_Z_BTMMRG else "\n\n" - - return result - - def _genHeadStyle(self, level: int, rFmt: QTextBlockFormat) -> T_TextStyle: + def _genHeadStyle(self, level: int, nHead: int, rFmt: QTextBlockFormat) -> T_TextStyle: """Generate a heading style set.""" mTop, mBottom = self._mHead.get(level, (0.0, 0.0)) @@ -312,5 +348,7 @@ class ToQTextDocument(Tokenizer): cFmt.setForeground(SHARED.theme.colHead) cFmt.setFontWeight(QFont.Weight.Bold) cFmt.setFontPointSize(self._sHead.get(level, 1.0)) + cFmt.setAnchorNames([f"{self._handle}:T{nHead:04d}"]) + cFmt.setAnchor(True) return bFmt, cFmt diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py index b8d0bb4c..2fa287f6 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -27,7 +27,7 @@ from PyQt5.QtGui import QMouseEvent, QTextCursor from PyQt5.QtWidgets import QAction, QApplication, QMenu from novelwriter import CONFIG, SHARED -from novelwriter.core.tohtml import ToHtml +from novelwriter.core.toqdoc import ToQTextDocument from novelwriter.enum import nwDocAction from novelwriter.gui.docviewer import GuiDocViewer from novelwriter.types import QtModeNone, QtMouseLeft @@ -119,7 +119,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): # Select All assert docViewer.docAction(nwDocAction.SEL_ALL) is True cursor = docViewer.textCursor() - assert len(cursor.selectedText()) == 3061 + assert len(cursor.selectedText()) == 3060 # Other actions assert docViewer.docAction(nwDocAction.NO_ACTION) is False @@ -196,19 +196,19 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): # Document footer show/hide synopsis assert nwGUI.viewDocument("f96ec11c6a3da") is True - assert len(docViewer.toPlainText()) == 4315 + assert len(docViewer.toPlainText()) == 4314 docViewer.docFooter._doToggleSynopsis(False) - assert len(docViewer.toPlainText()) == 4099 + assert len(docViewer.toPlainText()) == 4098 # Document footer show/hide comments assert nwGUI.viewDocument("846352075de7d") is True - assert len(docViewer.toPlainText()) == 675 + assert len(docViewer.toPlainText()) == 674 docViewer.docFooter._doToggleComments(False) - assert len(docViewer.toPlainText()) == 635 + assert len(docViewer.toPlainText()) == 634 # Crash the HTML rendering with monkeypatch.context() as mp: - mp.setattr(ToHtml, "doConvert", causeException) + mp.setattr(ToQTextDocument, "doConvert", causeException) assert docViewer.loadText("846352075de7d") is False assert docViewer.toPlainText() == "An error occurred while generating the preview."