From 1f434718ecc6f4c82bd736b0f18ffe38bf93fc85 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 12 Jun 2020 22:00:14 +0200 Subject: [PATCH 01/11] Replaced underline format with bold-italic, and all formats now use star, not underscore --- nw/constants/enum.py | 48 ++++++++++++------------ nw/gui/doceditor.py | 89 ++++++++++++++++++++++++++++++++++++++------ nw/gui/mainmenu.py | 24 ++++++------ nw/guimain.py | 4 +- 4 files changed, 116 insertions(+), 49 deletions(-) diff --git a/nw/constants/enum.py b/nw/constants/enum.py index ee33c575..605cc022 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -68,30 +68,30 @@ class nwItemLayout(Enum): class nwDocAction(Enum): - NO_ACTION = 0 - UNDO = 1 - REDO = 2 - CUT = 3 - COPY = 4 - PASTE = 5 - BOLD = 6 - ITALIC = 7 - U_LINE = 8 - S_QUOTE = 9 - D_QUOTE = 10 - SEL_ALL = 11 - SEL_PARA = 12 - FIND = 13 - REPLACE = 14 - GO_NEXT = 15 - GO_PREV = 16 - REPL_NEXT = 17 - BLOCK_H1 = 18 - BLOCK_H2 = 19 - BLOCK_H3 = 20 - BLOCK_H4 = 21 - BLOCK_COM = 22 - BLOCK_TXT = 23 + NO_ACTION = 0 + UNDO = 1 + REDO = 2 + CUT = 3 + COPY = 4 + PASTE = 5 + ITALIC = 6 + BOLD = 7 + BOLDITALIC = 8 + S_QUOTE = 9 + D_QUOTE = 10 + SEL_ALL = 11 + SEL_PARA = 12 + FIND = 13 + REPLACE = 14 + GO_NEXT = 15 + GO_PREV = 16 + REPL_NEXT = 17 + BLOCK_H1 = 18 + BLOCK_H2 = 19 + BLOCK_H3 = 20 + BLOCK_H4 = 21 + BLOCK_COM = 22 + BLOCK_TXT = 23 # END Enum nwDocAction diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index bd5da37f..e77d89c2 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -509,16 +509,16 @@ class GuiDocEditor(QTextEdit): self.copy() elif theAction == nwDocAction.PASTE: self.paste() - elif theAction == nwDocAction.BOLD: - self._wrapSelection("**","**") elif theAction == nwDocAction.ITALIC: - self._wrapSelection("_","_") - elif theAction == nwDocAction.U_LINE: - self._wrapSelection("__","__") + self._toggleEmph(1) + elif theAction == nwDocAction.BOLD: + self._toggleEmph(2) + elif theAction == nwDocAction.BOLDITALIC: + self._toggleEmph(3) elif theAction == nwDocAction.S_QUOTE: - self._wrapSelection(self.typSQOpen,self.typSQClose) + self._wrapSelection(self.typSQOpen, self.typSQClose) elif theAction == nwDocAction.D_QUOTE: - self._wrapSelection(self.typDQOpen,self.typDQClose) + self._wrapSelection(self.typDQOpen, self.typDQClose) elif theAction == nwDocAction.SEL_ALL: self._makeSelection(QTextCursor.Document) elif theAction == nwDocAction.SEL_PARA: @@ -881,18 +881,20 @@ class GuiDocEditor(QTextEdit): self.bigDoc = False return - def _wrapSelection(self, tBefore, tAfter): + def _wrapSelection(self, tBefore, tAfter=None): """Wraps the selected text in whatever is in tBefore and tAfter. If there is no selection, the autoSelect setting decides the action. AutoSelect will select the word under the cursor before wrapping it. If this feature is disabled, nothing is done. """ - theCursor = self.textCursor() - if self.mainConf.autoSelect and not theCursor.hasSelection(): - theCursor.select(QTextCursor.WordUnderCursor) + if tAfter is None: + tAfter = tBefore + + theCursor = self._autoSelect() if theCursor.hasSelection(): posS = theCursor.selectionStart() posE = theCursor.selectionEnd() + theCursor.clearSelection() theCursor.beginEditBlock() theCursor.setPosition(posE) @@ -900,10 +902,75 @@ class GuiDocEditor(QTextEdit): theCursor.setPosition(posS) theCursor.insertText(tBefore) theCursor.endEditBlock() + + theCursor.setPosition(posE + len(tBefore)) + theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, posE-posS) + self.setTextCursor(theCursor) + else: logger.warning("No selection made, nothing to do") return + def _clearSurrounding(self, theCursor, nChars): + """Clears n characters before and after the cursor. + """ + if theCursor.hasSelection(): + posS = theCursor.selectionStart() + posE = theCursor.selectionEnd() + theCursor.clearSelection() + theCursor.beginEditBlock() + theCursor.setPosition(posS) + for i in range(nChars): + theCursor.deletePreviousChar() + theCursor.setPosition(posE) + for i in range(nChars): + theCursor.deletePreviousChar() + theCursor.endEditBlock() + theCursor.clearSelection() + else: + logger.warning("No selection made, nothing to do") + return + + def _autoSelect(self): + """Returns a cursor which may or may not have a selection based + on user settings and document action. + """ + theCursor = self.textCursor() + if self.mainConf.autoSelect and not theCursor.hasSelection(): + theCursor.select(QTextCursor.WordUnderCursor) + self.setTextCursor(theCursor) + return theCursor + + def _toggleEmph(self, eLevel): + """Toggle emphasis of a given level. + """ + theCursor = self._autoSelect() + if theCursor.hasSelection(): + posS = theCursor.selectionStart() + posE = theCursor.selectionEnd() + + numB = 0 + for n in range(4): + if self.qDocument.characterAt(posS-n-1) == "*": + numB += 1 + else: + break + + numA = 0 + for n in range(4): + if self.qDocument.characterAt(posE+n) == "*": + numA += 1 + else: + break + + cLevel = min(numB, numA) + if cLevel == eLevel: + self._clearSurrounding(theCursor, eLevel) + else: + self._wrapSelection("*"*(eLevel - cLevel)) + + return + def _formatBlock(self, docAction): """Changes the block format of the block under the cursor. """ diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index ece05a58..086261d1 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -515,13 +515,6 @@ class GuiMainMenu(QMenuBar): # Format self.fmtMenu = self.addMenu("&Format") - # Format > Bold Text - self.aFmtBold = QAction("Bold Text", self) - self.aFmtBold.setStatusTip("Make selected text bold") - self.aFmtBold.setShortcut("Ctrl+B") - self.aFmtBold.triggered.connect(lambda: self._docAction(nwDocAction.BOLD)) - self.fmtMenu.addAction(self.aFmtBold) - # Format > Italic Text self.aFmtItalic = QAction("Italic Text", self) self.aFmtItalic.setStatusTip("Make selected text italic") @@ -529,12 +522,19 @@ class GuiMainMenu(QMenuBar): self.aFmtItalic.triggered.connect(lambda: self._docAction(nwDocAction.ITALIC)) self.fmtMenu.addAction(self.aFmtItalic) + # Format > Bold Text + self.aFmtBold = QAction("Bold Text", self) + self.aFmtBold.setStatusTip("Make selected text bold") + self.aFmtBold.setShortcut("Ctrl+B") + self.aFmtBold.triggered.connect(lambda: self._docAction(nwDocAction.BOLD)) + self.fmtMenu.addAction(self.aFmtBold) + # Format > Underline Text - self.aFmtULine = QAction("Underline Text", self) - self.aFmtULine.setStatusTip("Underline selected text") - self.aFmtULine.setShortcut("Ctrl+U") - self.aFmtULine.triggered.connect(lambda: self._docAction(nwDocAction.U_LINE)) - self.fmtMenu.addAction(self.aFmtULine) + self.aFmtBoldIt = QAction("Bold Italic Text", self) + self.aFmtBoldIt.setStatusTip("Make selected text bold and italic") + self.aFmtBoldIt.setShortcut("Ctrl+Shift+B") + self.aFmtBoldIt.triggered.connect(lambda: self._docAction(nwDocAction.BOLDITALIC)) + self.fmtMenu.addAction(self.aFmtBoldIt) # Edit > Separator self.fmtMenu.addSeparator() diff --git a/nw/guimain.py b/nw/guimain.py index 576cd990..cb2f19a2 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -961,9 +961,9 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aEditPaste) self.addAction(self.mainMenu.aSelectAll) self.addAction(self.mainMenu.aSelectPar) - self.addAction(self.mainMenu.aFmtBold) self.addAction(self.mainMenu.aFmtItalic) - self.addAction(self.mainMenu.aFmtULine) + self.addAction(self.mainMenu.aFmtBold) + self.addAction(self.mainMenu.aFmtBoldIt) self.addAction(self.mainMenu.aFmtDQuote) self.addAction(self.mainMenu.aFmtSQuote) self.addAction(self.mainMenu.aFmtHead1) From 013b737452728ef40320f149fb008bc950ffa816 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 12 Jun 2020 22:47:13 +0200 Subject: [PATCH 02/11] Updated syntax highlighter --- nw/gui/dochighlight.py | 62 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index afd5a77c..812aca06 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -98,28 +98,28 @@ class GuiDocHighlighter(QSyntaxHighlighter): self.colTrail.setAlpha(64) self.hStyles = { - "header1" : self._makeFormat(self.colHead, "bold",1.8), - "header2" : self._makeFormat(self.colHead, "bold",1.6), - "header3" : self._makeFormat(self.colHead, "bold",1.4), - "header4" : self._makeFormat(self.colHead, "bold",1.2), - "header1h" : self._makeFormat(self.colHeadH,"bold",1.8), - "header2h" : self._makeFormat(self.colHeadH,"bold",1.6), - "header3h" : self._makeFormat(self.colHeadH,"bold",1.4), - "header4h" : self._makeFormat(self.colHeadH,"bold",1.2), - "bold" : self._makeFormat(self.colEmph, "bold"), - "italic" : self._makeFormat(self.colEmph, "italic"), - "strike" : self._makeFormat(self.colEmph, "strike"), - "underline" : self._makeFormat(self.colEmph, "underline"), - "trailing" : self._makeFormat(self.colTrail,"background"), - "nobreak" : self._makeFormat(self.colTrail,"background"), - "dialogue1" : self._makeFormat(self.colDialN), - "dialogue2" : self._makeFormat(self.colDialD), - "dialogue3" : self._makeFormat(self.colDialS), - "replace" : self._makeFormat(self.colRepTag), - "hidden" : self._makeFormat(self.colComm), - "keyword" : self._makeFormat(self.colKey), - "modifier" : self._makeFormat(self.colMod), - "value" : self._makeFormat(self.colVal), + "header1" : self._makeFormat(self.colHead, "bold", 1.8), + "header2" : self._makeFormat(self.colHead, "bold", 1.6), + "header3" : self._makeFormat(self.colHead, "bold", 1.4), + "header4" : self._makeFormat(self.colHead, "bold", 1.2), + "header1h" : self._makeFormat(self.colHeadH, "bold", 1.8), + "header2h" : self._makeFormat(self.colHeadH, "bold", 1.6), + "header3h" : self._makeFormat(self.colHeadH, "bold", 1.4), + "header4h" : self._makeFormat(self.colHeadH, "bold", 1.2), + "bold" : self._makeFormat(self.colEmph, "bold"), + "italic" : self._makeFormat(self.colEmph, "italic"), + "strike" : self._makeFormat(self.colEmph, "strike"), + "bolditalic" : self._makeFormat(self.colEmph, ("bold","italic")), + "trailing" : self._makeFormat(self.colTrail, "background"), + "nobreak" : self._makeFormat(self.colTrail, "background"), + "dialogue1" : self._makeFormat(self.colDialN), + "dialogue2" : self._makeFormat(self.colDialD), + "dialogue3" : self._makeFormat(self.colDialS), + "replace" : self._makeFormat(self.colRepTag), + "hidden" : self._makeFormat(self.colComm), + "keyword" : self._makeFormat(self.colKey), + "modifier" : self._makeFormat(self.colMod), + "value" : self._makeFormat(self.colVal, "underline"), } self.hRules = [] @@ -139,6 +139,13 @@ class GuiDocHighlighter(QSyntaxHighlighter): )) # Markdown + self.hRules.append(( + r"(? Date: Fri, 12 Jun 2020 22:47:54 +0200 Subject: [PATCH 03/11] Improved the emphasis function for alternating between levels --- nw/gui/doceditor.py | 39 ++++++++++++++++++++++++++++---- sample/content/636b6aa9b697b.nwd | 2 +- sample/content/bc0cbd2a407f3.nwd | 3 --- sample/nwProject.nwx | 22 +++++++++--------- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index e77d89c2..26e64c82 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -942,7 +942,28 @@ class GuiDocEditor(QTextEdit): return theCursor def _toggleEmph(self, eLevel): - """Toggle emphasis of a given level. + """Toggle emphasis of a given level between 1 and 3, where 1 is + italic, 2 is bold, and 3 is bold italic. The current level in + the text is cLevel. The rules are as follows: + + cLevel | eLevel | Result + ============+============+============ + None | Italic | Italic + None | Bold | Bold + None | BoldItalic | BoldItalic + ------------+------------+------------ + Italic | Italic | None + Italic | Bold | BoldItalic + Italic | BoldItalic | BoldItalic + ------------+------------+------------ + Bold | Italic | BoldItalic + Bold | Bold | None + Bold | BoldItalic | BoldItalic + ------------+------------+------------ + BoldItalic | Italic | Bold + BoldItalic | Bold | Italic + BoldItalic | BoldItalic | None + """ theCursor = self._autoSelect() if theCursor.hasSelection(): @@ -950,24 +971,32 @@ class GuiDocEditor(QTextEdit): posE = theCursor.selectionEnd() numB = 0 - for n in range(4): + for n in range(3): if self.qDocument.characterAt(posS-n-1) == "*": numB += 1 else: break numA = 0 - for n in range(4): + for n in range(3): if self.qDocument.characterAt(posE+n) == "*": numA += 1 else: break cLevel = min(numB, numA) - if cLevel == eLevel: + if cLevel == 0: + # Has no emphasis, set to desired level + self._wrapSelection("*"*eLevel) + elif cLevel == 3: + # Has max, so reduce by desired level self._clearSurrounding(theCursor, eLevel) + elif cLevel == eLevel: + # Toggle mode, so clear what we had set + self._clearSurrounding(theCursor, cLevel) else: - self._wrapSelection("*"*(eLevel - cLevel)) + # Already at 1 or 2, increase to 3 + self._wrapSelection("*"*(3 - cLevel)) return diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index e93bbb57..7b2d92c6 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -7,7 +7,7 @@ A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. -Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and __underscore__. +Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, *italic* and ***bold italic***. In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter, these can be in different colours. diff --git a/sample/content/bc0cbd2a407f3.nwd b/sample/content/bc0cbd2a407f3.nwd index 94b036c0..4a099dfc 100644 --- a/sample/content/bc0cbd2a407f3.nwd +++ b/sample/content/bc0cbd2a407f3.nwd @@ -14,6 +14,3 @@ In fact, if you wish, you can add all the scenes in the chapter file too. All no @location: Earth This is a second scene in the same file as the previous scene. You can always split the files up later. - - - diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index cee9b2fa..0a34f9ff 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,21 +1,21 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 280 - 41 - 4312 + 313 + 47 + 5934 False True True - bc0cbd2a407f3 - b3e74dbc1f584 - 920 + 636b6aa9b697b + 14298de4d9524 + 921 B E @@ -114,10 +114,10 @@ 1st Draft True SCENE - 1199 - 216 + 1202 + 217 7 - 950 + 469 Another Scene @@ -129,7 +129,7 @@ 476 93 3 - 551 + 428 Interlude From 8adda0c4a98af2c014d27c5ea7664fcbe5db23fe Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Jun 2020 00:14:34 +0200 Subject: [PATCH 04/11] Fix regexes to be exclusive for 1, 2 and 3 stars --- nw/core/tohtml.py | 4 ++-- nw/core/tokenizer.py | 14 +++++++------- nw/gui/dochighlight.py | 6 +++--- sample/nwProject.nwx | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index 61541f42..f58c1a7a 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -121,8 +121,8 @@ class ToHtml(Tokenizer): self.FMT_B_E : "", self.FMT_I_B : "", self.FMT_I_E : "", - self.FMT_U_B : "", - self.FMT_U_E : "", + self.FMT_S_B : "", + self.FMT_S_E : "", } if self.isNovel and self.genMode != self.M_PREVIEW: diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index 70599d59..1335ed23 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -44,8 +44,8 @@ class Tokenizer(): FMT_B_E = 2 # End bold FMT_I_B = 3 # Begin italics FMT_I_E = 4 # End italics - FMT_U_B = 5 # Begin underline - FMT_U_E = 6 # End underline + FMT_S_B = 5 # Begin bold italic + FMT_S_E = 6 # End bold italic T_EMPTY = 1 # Empty line (new paragraph) T_SYNOPSIS = 2 # Synopsis comment @@ -300,14 +300,14 @@ class Tokenizer(): # RegExes for adding formatting tags within text lines # Keep in sync with the DocHighlighter class rxFormats = [( - QRegularExpression(r"(? - + Sample Project Sample Project Jane Smith Jay Doh - 313 - 47 - 5934 + 319 + 49 + 9675 False True True 636b6aa9b697b - 14298de4d9524 + 636b6aa9b697b 921 B @@ -117,7 +117,7 @@ 1202 217 7 - 469 + 248 Another Scene From 6951b3d2fa49ff891042544416d691954835a307 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Jun 2020 00:39:57 +0200 Subject: [PATCH 05/11] Fixed tests --- tests/lipsum/content/04468803b92e1.nwd | 2 +- tests/lipsum/content/2426c6f0ca922.nwd | 2 +- tests/lipsum/content/441420a886d82.nwd | 2 +- tests/lipsum/content/47666c91c7ccf.nwd | 2 +- tests/lipsum/content/4c4f28287af27.nwd | 2 +- tests/lipsum/content/7a992350f3eb6.nwd | 2 +- tests/lipsum/content/846352075de7d.nwd | 2 +- tests/lipsum/content/88243afbe5ed8.nwd | 2 +- tests/lipsum/content/88d59a277361b.nwd | 4 ++-- tests/lipsum/content/8c58a65414c23.nwd | 2 +- tests/lipsum/content/db7e733775d4d.nwd | 2 +- tests/lipsum/content/eb103bc70c90c.nwd | 2 +- tests/lipsum/content/f8c0562e50f1b.nwd | 2 +- tests/lipsum/content/f96ec11c6a3da.nwd | 2 +- tests/lipsum/content/fb609cd8319dc.nwd | 2 +- tests/lipsum/nwProject.nwx | 28 +++++++++++++------------- tests/reference/build/1_LoremIpsum.nwd | 2 +- tests/reference/build/2_LoremIpsum.nwd | 2 +- 18 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/lipsum/content/04468803b92e1.nwd b/tests/lipsum/content/04468803b92e1.nwd index 0f9e6a1f..af7504dc 100644 --- a/tests/lipsum/content/04468803b92e1.nwd +++ b/tests/lipsum/content/04468803b92e1.nwd @@ -1,4 +1,4 @@ -%%~ 04468803b92e1:60bdf227455cc:Ancient Europe +%%~ 04468803b92e1:60bdf227455cc:WORLD:NOTE:Ancient Europe # Ancient Europe @tag: Europe diff --git a/tests/lipsum/content/2426c6f0ca922.nwd b/tests/lipsum/content/2426c6f0ca922.nwd index bd573995..98ea99c3 100644 --- a/tests/lipsum/content/2426c6f0ca922.nwd +++ b/tests/lipsum/content/2426c6f0ca922.nwd @@ -1,4 +1,4 @@ -%%~ 2426c6f0ca922:6c6afb1247750:Main +%%~ 2426c6f0ca922:6c6afb1247750:PLOT:NOTE:Main # Main Plot @tag: Main diff --git a/tests/lipsum/content/441420a886d82.nwd b/tests/lipsum/content/441420a886d82.nwd index 02eb2fef..820862ba 100644 --- a/tests/lipsum/content/441420a886d82.nwd +++ b/tests/lipsum/content/441420a886d82.nwd @@ -1,4 +1,4 @@ -%%~ 441420a886d82:6bd935d2490cd:b3643d0f92e32:Chapter Two +%%~ 441420a886d82:6bd935d2490cd:b3643d0f92e32:NOVEL:CHAPTER:Chapter Two ## Chapter Two @pov: Bod diff --git a/tests/lipsum/content/47666c91c7ccf.nwd b/tests/lipsum/content/47666c91c7ccf.nwd index 1a173bb7..027b20e5 100644 --- a/tests/lipsum/content/47666c91c7ccf.nwd +++ b/tests/lipsum/content/47666c91c7ccf.nwd @@ -1,4 +1,4 @@ -%%~ 47666c91c7ccf:6bd935d2490cd:b3643d0f92e32:Scene Five +%%~ 47666c91c7ccf:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Five ### Scene Five @pov: Bod diff --git a/tests/lipsum/content/4c4f28287af27.nwd b/tests/lipsum/content/4c4f28287af27.nwd index 9d42bd6b..50f646b5 100644 --- a/tests/lipsum/content/4c4f28287af27.nwd +++ b/tests/lipsum/content/4c4f28287af27.nwd @@ -1,4 +1,4 @@ -%%~ 4c4f28287af27:67a8707f2f249:Mr. Nobody +%%~ 4c4f28287af27:67a8707f2f249:CHARACTER:NOTE:Mr. Nobody # Nobody Owens @tag: Bod diff --git a/tests/lipsum/content/7a992350f3eb6.nwd b/tests/lipsum/content/7a992350f3eb6.nwd index a6a7299d..ab7eb619 100644 --- a/tests/lipsum/content/7a992350f3eb6.nwd +++ b/tests/lipsum/content/7a992350f3eb6.nwd @@ -1,4 +1,4 @@ -%%~ 7a992350f3eb6:b3643d0f92e32:Lorem Ipusm +%%~ 7a992350f3eb6:b3643d0f92e32:NOVEL:TITLE:Lorem Ipusm # Lorem Ipsum **By lipsum.com** diff --git a/tests/lipsum/content/846352075de7d.nwd b/tests/lipsum/content/846352075de7d.nwd index e1d4f542..e7dd7e45 100644 --- a/tests/lipsum/content/846352075de7d.nwd +++ b/tests/lipsum/content/846352075de7d.nwd @@ -1,4 +1,4 @@ -%%~ 846352075de7d:b3643d0f92e32:Interlude +%%~ 846352075de7d:b3643d0f92e32:NOVEL:BOOK:Interlude ## Why do we use it? % Exctracted from the lipsum.com website. diff --git a/tests/lipsum/content/88243afbe5ed8.nwd b/tests/lipsum/content/88243afbe5ed8.nwd index 528b8e77..eba37bae 100644 --- a/tests/lipsum/content/88243afbe5ed8.nwd +++ b/tests/lipsum/content/88243afbe5ed8.nwd @@ -1,4 +1,4 @@ -%%~ 88243afbe5ed8:45e6b01ca35c1:b3643d0f92e32:Scene One +%%~ 88243afbe5ed8:45e6b01ca35c1:b3643d0f92e32:NOVEL:SCENE:Scene One ### Scene One @pov: Bod diff --git a/tests/lipsum/content/88d59a277361b.nwd b/tests/lipsum/content/88d59a277361b.nwd index db2169a5..a67f49f1 100644 --- a/tests/lipsum/content/88d59a277361b.nwd +++ b/tests/lipsum/content/88d59a277361b.nwd @@ -1,6 +1,6 @@ -%%~ 88d59a277361b:b3643d0f92e32:Prologue +%%~ 88d59a277361b:b3643d0f92e32:NOVEL:UNNUMBERED:Prologue ## Prologue % Synopsis:Explanation from the lipsum.com website. -_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. +*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. diff --git a/tests/lipsum/content/8c58a65414c23.nwd b/tests/lipsum/content/8c58a65414c23.nwd index ad83a6c1..408a3bec 100644 --- a/tests/lipsum/content/8c58a65414c23.nwd +++ b/tests/lipsum/content/8c58a65414c23.nwd @@ -1,4 +1,4 @@ -%%~ 8c58a65414c23:b3643d0f92e32:Front Matter +%%~ 8c58a65414c23:b3643d0f92e32:NOVEL:PAGE:Front Matter % Exctracted from the lipsum.com website. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of “de Finibus Bonorum et Malorum” (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, “Lorem ipsum dolor sit amet..”, comes from a line in section 1.10.32. diff --git a/tests/lipsum/content/db7e733775d4d.nwd b/tests/lipsum/content/db7e733775d4d.nwd index 7be7896e..7f4de78f 100644 --- a/tests/lipsum/content/db7e733775d4d.nwd +++ b/tests/lipsum/content/db7e733775d4d.nwd @@ -1,4 +1,4 @@ -%%~ db7e733775d4d:b3643d0f92e32:Act One +%%~ db7e733775d4d:b3643d0f92e32:NOVEL:PARTITION:Act One # Act One “Fusce maximus felis libero” \ No newline at end of file diff --git a/tests/lipsum/content/eb103bc70c90c.nwd b/tests/lipsum/content/eb103bc70c90c.nwd index 08bb88e9..db11bbf0 100644 --- a/tests/lipsum/content/eb103bc70c90c.nwd +++ b/tests/lipsum/content/eb103bc70c90c.nwd @@ -1,4 +1,4 @@ -%%~ eb103bc70c90c:6bd935d2490cd:b3643d0f92e32:Scene Three +%%~ eb103bc70c90c:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Three ### Scene Three @pov: Bod diff --git a/tests/lipsum/content/f8c0562e50f1b.nwd b/tests/lipsum/content/f8c0562e50f1b.nwd index 752bd117..15a33bc8 100644 --- a/tests/lipsum/content/f8c0562e50f1b.nwd +++ b/tests/lipsum/content/f8c0562e50f1b.nwd @@ -1,4 +1,4 @@ -%%~ f8c0562e50f1b:6bd935d2490cd:b3643d0f92e32:Scene Four +%%~ f8c0562e50f1b:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Four ### Scene Four @pov: Bod diff --git a/tests/lipsum/content/f96ec11c6a3da.nwd b/tests/lipsum/content/f96ec11c6a3da.nwd index 8390c7bd..b95163d5 100644 --- a/tests/lipsum/content/f96ec11c6a3da.nwd +++ b/tests/lipsum/content/f96ec11c6a3da.nwd @@ -1,4 +1,4 @@ -%%~ f96ec11c6a3da:45e6b01ca35c1:b3643d0f92e32:Scene Two +%%~ f96ec11c6a3da:45e6b01ca35c1:b3643d0f92e32:NOVEL:SCENE:Scene Two ### Scene Two @pov: Bod diff --git a/tests/lipsum/content/fb609cd8319dc.nwd b/tests/lipsum/content/fb609cd8319dc.nwd index d28df127..fa9626f2 100644 --- a/tests/lipsum/content/fb609cd8319dc.nwd +++ b/tests/lipsum/content/fb609cd8319dc.nwd @@ -1,4 +1,4 @@ -%%~ fb609cd8319dc:45e6b01ca35c1:b3643d0f92e32:Chapter One +%%~ fb609cd8319dc:45e6b01ca35c1:b3643d0f92e32:NOVEL:CHAPTER:Chapter One ## Chapter One @pov: Bod diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx index ef3a9bbe..ebfbde69 100644 --- a/tests/lipsum/nwProject.nwx +++ b/tests/lipsum/nwProject.nwx @@ -1,17 +1,20 @@ - + Lorem Ipsum Lorem Ipsum lipsum.com - False + 7 + 21 + 1459 + False False True - 88d59a277361b + 04468803b92e1 None - 3397 + 3847 Replace Text 1 Replace Text 2 @@ -22,9 +25,6 @@ %title% * * *
- False - False - False New @@ -81,7 +81,7 @@ 584 92 1 - 35 + 79
Act One @@ -238,9 +238,9 @@ Main True NOTE - 9 - 2 - 0 + 1369 + 195 + 2 1387 @@ -257,9 +257,9 @@ Minor True NOTE - 14 - 2 - 0 + 1770 + 259 + 3 1792 diff --git a/tests/reference/build/1_LoremIpsum.nwd b/tests/reference/build/1_LoremIpsum.nwd index 010f3a80..fbe4f132 100644 --- a/tests/reference/build/1_LoremIpsum.nwd +++ b/tests/reference/build/1_LoremIpsum.nwd @@ -14,7 +14,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t ## Prologue -_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. +*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. # Act One diff --git a/tests/reference/build/2_LoremIpsum.nwd b/tests/reference/build/2_LoremIpsum.nwd index d58f1021..f2a26740 100644 --- a/tests/reference/build/2_LoremIpsum.nwd +++ b/tests/reference/build/2_LoremIpsum.nwd @@ -16,7 +16,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t % Synopsis:Explanation from the lipsum.com website. -_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. +*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. # Act One From 49049e19d8ed36d1a063a29538da8293b7348f86 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Jun 2020 10:08:00 +0200 Subject: [PATCH 06/11] Moved the emphasis regexes to a constants class --- nw/constants/__init__.py | 3 ++- nw/constants/constants.py | 8 ++++++++ nw/gui/dochighlight.py | 8 ++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/nw/constants/__init__.py b/nw/constants/__init__.py index 1abd529c..57761e42 100644 --- a/nw/constants/__init__.py +++ b/nw/constants/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from nw.constants.iso import isoLanguage, isoCountry from nw.constants.constants import ( - nwConst, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode + nwConst, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode ) from nw.constants.enum import ( nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline @@ -11,6 +11,7 @@ __all__ = [ "isoLanguage", "isoCountry", "nwConst", + "nwRegEx", "nwFiles", "nwKeyWords", "nwLabels", diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 66b026fc..5c483657 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -34,6 +34,14 @@ class nwConst(): # END Class nwConst +class nwRegEx(): + + FMT_B = r"(? Date: Sat, 13 Jun 2020 10:08:35 +0200 Subject: [PATCH 07/11] Also in the tokenizer --- nw/core/tokenizer.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index 1335ed23..b9c283cc 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -34,7 +34,7 @@ from PyQt5.QtCore import QRegularExpression from nw.core.document import NWDoc from nw.core.tools import numberToWord -from nw.constants import nwItemLayout, nwItemType +from nw.constants import nwItemLayout, nwItemType, nwRegEx logger = logging.getLogger(__name__) @@ -298,17 +298,11 @@ class Tokenizer(): """ # RegExes for adding formatting tags within text lines - # Keep in sync with the DocHighlighter class - rxFormats = [( - QRegularExpression(r"(? Date: Sat, 13 Jun 2020 10:27:02 +0200 Subject: [PATCH 08/11] Added back in strikethrough support --- nw/constants/constants.py | 1 + nw/constants/enum.py | 31 ++++++++++++++++--------------- nw/core/tohtml.py | 30 ++++++++++++++++++++++-------- nw/core/tokenizer.py | 14 +++++++++----- nw/gui/build.py | 4 +++- nw/gui/doceditor.py | 32 ++++++++++++++++++++++++++++++++ nw/gui/dochighlight.py | 7 +++++++ nw/gui/mainmenu.py | 7 +++++++ sample/content/636b6aa9b697b.nwd | 2 +- sample/nwProject.nwx | 16 ++++++++-------- 10 files changed, 106 insertions(+), 38 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 5c483657..9bd47110 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -39,6 +39,7 @@ class nwRegEx(): FMT_B = r"(?", - self.FMT_B_E : "", - self.FMT_I_B : "", - self.FMT_I_E : "", - self.FMT_S_B : "", - self.FMT_S_E : "", - } + if self.genMode == self.M_PREVIEW: + htmlTags = { # HTML4 + CSS2 + self.FMT_B_B : "", + self.FMT_B_E : "", + self.FMT_I_B : "", + self.FMT_I_E : "", + self.FMT_S_B : "", + self.FMT_S_E : "", + self.FMT_D_B : "", + self.FMT_D_E : "", + } + else: + htmlTags = { # HTML5 + self.FMT_B_B : "", + self.FMT_B_E : "", + self.FMT_I_B : "", + self.FMT_I_E : "", + self.FMT_S_B : "", + self.FMT_S_E : "", + self.FMT_D_B : "", + self.FMT_D_E : "", + } if self.isNovel and self.genMode != self.M_PREVIEW: # For novel files for export, we bump the titles one level diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index b9c283cc..e13feb0e 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -46,6 +46,8 @@ class Tokenizer(): FMT_I_E = 4 # End italics FMT_S_B = 5 # Begin bold italic FMT_S_E = 6 # End bold italic + FMT_D_B = 7 # Begin strikeout + FMT_D_E = 8 # End strikeout T_EMPTY = 1 # Empty line (new paragraph) T_SYNOPSIS = 2 # Synopsis comment @@ -292,16 +294,18 @@ class Tokenizer(): The format of the token list is an entry with a four-tuple for each line in the file. The tuple is as follows: 1: The type of the block, self.T_* - 2: The text content of the block, without leading tags - 3: The internal formatting map of the text, self.FMT_* - 4: The style of the block, self.A_* + 2: The line in file where this block occurred + 3: The text content of the block, without leading tags + 4: The internal formatting map of the text, self.FMT_* + 5: The style of the block, self.A_* """ # RegExes for adding formatting tags within text lines rxFormats = [ - (QRegularExpression(nwRegEx.FMT_BI), [None, self.FMT_S_B, None, self.FMT_S_E]), - (QRegularExpression(nwRegEx.FMT_B), [None, self.FMT_B_B, None, self.FMT_B_E]), (QRegularExpression(nwRegEx.FMT_I), [None, self.FMT_I_B, None, self.FMT_I_E]), + (QRegularExpression(nwRegEx.FMT_B), [None, self.FMT_B_B, None, self.FMT_B_E]), + (QRegularExpression(nwRegEx.FMT_BI), [None, self.FMT_S_B, None, self.FMT_S_E]), + (QRegularExpression(nwRegEx.FMT_ST), [None, self.FMT_D_B, None, self.FMT_D_E]), ] self.theTokens = [] diff --git a/nw/gui/build.py b/nw/gui/build.py index 02841833..70ea0703 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -982,7 +982,9 @@ class GuiBuildNovelDocView(QTextBrowser): """ if isinstance(theText, list): theText = "".join(theText) - theText = theText.replace(" "," "*4) + theText = theText.replace(" ", " "*4) + theText = theText.replace("", "") + theText = theText.replace("", "") self.setHtml(theText) return diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 26e64c82..4c5d5ce7 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -515,6 +515,8 @@ class GuiDocEditor(QTextEdit): self._toggleEmph(2) elif theAction == nwDocAction.BOLDITALIC: self._toggleEmph(3) + elif theAction == nwDocAction.STRIKE: + self._toggleStrike() elif theAction == nwDocAction.S_QUOTE: self._wrapSelection(self.typSQOpen, self.typSQClose) elif theAction == nwDocAction.D_QUOTE: @@ -1000,6 +1002,36 @@ class GuiDocEditor(QTextEdit): return + def _toggleStrike(self): + """Toggle strikethrough text. + """ + theCursor = self._autoSelect() + if theCursor.hasSelection(): + posS = theCursor.selectionStart() + posE = theCursor.selectionEnd() + + numB = 0 + for n in range(2): + if self.qDocument.characterAt(posS-n-1) == "~": + numB += 1 + else: + break + + numA = 0 + for n in range(2): + if self.qDocument.characterAt(posE+n) == "~": + numA += 1 + else: + break + + cLevel = min(numB, numA) + if cLevel == 2: + self._clearSurrounding(theCursor, 2) + else: + self._wrapSelection("~~") + + return + def _formatBlock(self, docAction): """Changes the block format of the block under the cursor. """ diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index e81d9bca..4754110d 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -160,6 +160,13 @@ class GuiDocHighlighter(QSyntaxHighlighter): 3 : self.hStyles["hidden"], } )) + self.hRules.append(( + nwRegEx.FMT_ST, { + 1 : self.hStyles["hidden"], + 2 : self.hStyles["strike"], + 3 : self.hStyles["hidden"], + } + )) # Quoted Strings if self.mainConf.highlightQuotes: diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 086261d1..3ea9a85d 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -536,6 +536,13 @@ class GuiMainMenu(QMenuBar): self.aFmtBoldIt.triggered.connect(lambda: self._docAction(nwDocAction.BOLDITALIC)) self.fmtMenu.addAction(self.aFmtBoldIt) + # Format > Strikethrough + self.aFmtStrike = QAction("Strikethrough Text", self) + self.aFmtStrike.setStatusTip("Strikethrough selected text") + self.aFmtStrike.setShortcut("Ctrl+-") + self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE)) + self.fmtMenu.addAction(self.aFmtStrike) + # Edit > Separator self.fmtMenu.addSeparator() diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index 7b2d92c6..4916b753 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -7,7 +7,7 @@ A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. -Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, *italic* and ***bold italic***. +Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, *italic* and ***bold italic***. You can also ~~strike through~~ text. In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter, these can be in different colours. diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index a603b373..aeba369e 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 319 - 49 - 9675 + 332 + 56 + 10692 False @@ -15,7 +15,7 @@ True 636b6aa9b697b 636b6aa9b697b - 921 + 927 B E @@ -114,10 +114,10 @@ 1st Draft True SCENE - 1202 - 217 + 1240 + 223 7 - 248 + 403 Another Scene From 95268a8ca554fa8879fa5a42c808334855f7347b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Jun 2020 10:46:58 +0200 Subject: [PATCH 09/11] Updated changelog and documentation --- CHANGELOG.md | 6 ++++++ README.md | 2 +- docs/source/interface.rst | 15 ++++++++++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c3a2448..5b8065bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ ## Version 0.9rc1 [2020-xx-xx] +**Core Functionality** + +* Underline text formatting has been removed. It is not standard HTML5, not Markdown, and was previously implemented using the double underscore notation that in standard Markdown is renderred as bold text. Instead, novelWriter now renders a single `*` or `-` wrapping a piece of text *within* a paragraphs as italicised text, and a double `**` or `__` as bold text. The keyboard shortcuts and automatic features **only** support the `*` notation. A triple set of `***` are treated as both bold and italicised. PR #310. +* Strikethrough formatting has been added back into novelWriter using the standard Markdown `~~` wrapping. PR #310. + **User Interface** * The Open Project dialog will now ask before removing an entry from the recent projects list. PR #309. +* The text emphasis functions, either selected from the menu or via keyboard shortcuts, will now try to respond to the command in a more meaningful way. That is, the text editor will try to toggle the bold or italics features independently of eachother on the selected text. A feature to apply both at the same time has also been added. PR #310. **Other Changes** diff --git a/README.md b/README.md index bce21ca7..fd119506 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ It allows for a minimal set of formatting needed for writing text documents for These are currently limited to: * Headings level 1 to 4 using the `#` syntax only. -* Bold, italic and underline text. +* Bold, italic and strikethrough text. * Hard line breaks using two or more spaces at the end of a line. That is it. diff --git a/docs/source/interface.rst b/docs/source/interface.rst index 4b4a8662..3174a888 100644 --- a/docs/source/interface.rst +++ b/docs/source/interface.rst @@ -33,8 +33,8 @@ Markdown Format =============== The document editor uses a simplified markdown format. -That is, it supports basic formatting like bold, italics and underline, as well as four levels of headings. -The formats are listed below. +That is, it supports basic formatting like bold, italics and strikethrough, as well as four levels of headings. +The preference of novelWriter is to use `*` for wrapping emphasised text, but `_` is partially supported when typed, but not by the automatic formatting features and keyboard shortcuts. In addition to these standard markdown features, the editor also allows for comments, that is text that is ignored by the word counter and not exported or, optionally, hidden in the document viewer. If the first word of a comment is "Synopsis:" (with the colon), the comment is treated specially, and will show up in the Outline View. @@ -48,9 +48,12 @@ The editor also has a minimal set of keywords used for setting tags and referenc "``## Title``", "Heading level two. The space after the # is mandatory." "``### Title``", "Heading level three. The space after the # is mandatory." "``#### Title``", "Heading level four. The space after the # is mandatory." + "``*text*``", "The text is rendered as italicised text." "``**text**``", "The text is rendered as bold text." - "``_text_``", "The text is rendered as italicized text." - "``__text__``", "The text is rendered as underlined text." + "``***text***``", "The text is rendered as bold italicised text." + "``_text_``", "Alternative format for italicised text." + "``__text__``", "Alternative format for bold text." + "``~~text~~``", "Strikethrough text." "``% text...``", "A comment. The text is not exported by default, seen in viewer, or counted towards word counts." "``% Synopsis: text...``", "A synopsis comment. Shows up in the Synopsis column of the Outline View, but is otherwise treated as a comment." "``@keyword: value``", "A keyword argument followed by a value, or a comma separated list of values." @@ -110,6 +113,7 @@ These are as following: ":kbd:`Ctrl-.`", "Correct word under cursor." ":kbd:`Ctrl-,`", "Open the Preferences dialog." ":kbd:`Ctrl-/`", "Change block format to comment." + ":kbd:`Ctrl--`", "Strikethrough selected text, or word under cursor." ":kbd:`Ctrl-0`", "Remove block formatting for block under cursor." ":kbd:`Ctrl-1`", "Change block format to header level 1." ":kbd:`Ctrl-2`", "Change block format to header level 2." @@ -122,7 +126,7 @@ These are as following: ":kbd:`Ctrl-E`", "If in tree view, edit a document or folder settings. (Same as :kbd:`F2`)" ":kbd:`Ctrl-F`", "Open the search bar and search for selected word, if any is selected." ":kbd:`Ctrl-G`", "Find next occurrence of word in current document. (Same as :kbd:`F3`)" - ":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected. (On Mac, this is :kbd:`Cmd+=`)" + ":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected. (On Mac, this is :kbd:`Cmd-=`)" ":kbd:`Ctrl-I`", "Format selected text, or word under cursor, as italic." ":kbd:`Ctrl-N`", "Create new document." ":kbd:`Ctrl-O`", "Open selected document." @@ -143,6 +147,7 @@ These are as following: ":kbd:`Ctrl-Shift-/`", "Remove block formatting for block under cursor." ":kbd:`Ctrl-Shift-1`", "Replace occurrence of word in current document, and search for next occurrence." ":kbd:`Ctrl-Shift-A`", "Select all text in current paragraph." + ":kbd:`Ctrl-Shift-B`", "Format selected text, or word under cursor, as bold and italic." ":kbd:`Ctrl-Shift-D`", "Wrap selected text, or word under cursor, in single quotes." ":kbd:`Ctrl-Shift-G`", "Find previous occurrence of word in current document. (Same as :kbd:`Shift-F3`" ":kbd:`Ctrl-Shift-I`", "Import text to the current document from a text file." From ff6b4bcfa7d242f1bbc4678166f34b1ed6326703 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Jun 2020 20:44:27 +0200 Subject: [PATCH 10/11] Improve the word saparation detection of the spell check highlighting --- nw/gui/dochighlight.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index 4754110d..abf3c122 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -203,9 +203,10 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Build a QRegExp for spell checker # Include additional characters that the highlighter should # consider to be word separators - wordSep = "_+" + wordSep = "_\+" + wordSep += nwUnicode.U_ENDASH wordSep += nwUnicode.U_EMDASH - self.spellRx = QRegularExpression("\\b[^\\s%s]+\\b" % wordSep) + self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b") self.spellRx.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) return True From bd8a6afd911861f9b94c2735942244a63863ece3 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Jun 2020 21:14:04 +0200 Subject: [PATCH 11/11] Minor tweaks in text parsing and highlighting --- nw/core/index.py | 5 +++-- nw/core/tokenizer.py | 5 +++-- nw/gui/dochighlight.py | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nw/core/index.py b/nw/core/index.py index 632375a7..b32c8f38 100644 --- a/nw/core/index.py +++ b/nw/core/index.py @@ -305,11 +305,12 @@ class NWIndex(): elif aLine.startswith(r"%"): if nTitle > 0: - toCheck = aLine[1:].lstrip().lower() + toCheck = aLine[1:].lstrip() + synTag = toCheck[:9].lower() tLen = len(aLine) cLen = len(toCheck) cOff = tLen - cLen - if toCheck.startswith("synopsis:"): + if synTag == "synopsis:": self._indexSynopsis(tHandle, isNovel, aLine[cOff+9:].strip(), nTitle) # Count words for remaining text after last heading diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index e13feb0e..09345e09 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -327,8 +327,9 @@ class Tokenizer(): tmpMarkdown.append("\n") elif aLine[0] == "%": - cLine = aLine[1:].strip() - if cLine.lower().startswith("synopsis:"): + cLine = aLine[1:].lstrip() + synTag = cLine[:9].lower() + if synTag == "synopsis:": self.theTokens.append(( self.T_SYNOPSIS, nLine, diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index abf3c122..a76c9646 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -78,7 +78,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): """Initialise the syntax highlighter, setting all the colour rules and building the regexes. """ - logger.debug("Setting up highlighting rules") self.colHead = QColor(*self.theTheme.colHead) @@ -108,8 +107,8 @@ class GuiDocHighlighter(QSyntaxHighlighter): "header4h" : self._makeFormat(self.colHeadH, "bold", 1.2), "bold" : self._makeFormat(self.colEmph, "bold"), "italic" : self._makeFormat(self.colEmph, "italic"), - "strike" : self._makeFormat(self.colEmph, "strike"), "bolditalic" : self._makeFormat(self.colEmph, ("bold","italic")), + "strike" : self._makeFormat(self.colEmph, "strike"), "trailing" : self._makeFormat(self.colTrail, "background"), "nobreak" : self._makeFormat(self.colTrail, "background"), "dialogue1" : self._makeFormat(self.colDialN), @@ -131,7 +130,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): } )) - # Non-breaking Space + # Non-Breaking Spaces self.hRules.append(( "[%s]+" % nwUnicode.U_NBSP, { 0 : self.hStyles["nobreak"], @@ -203,7 +202,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Build a QRegExp for spell checker # Include additional characters that the highlighter should # consider to be word separators - wordSep = "_\+" + wordSep = r"_\+" wordSep += nwUnicode.U_ENDASH wordSep += nwUnicode.U_EMDASH self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b") @@ -241,7 +240,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): def highlightBlock(self, theText): """Highlight a single block. Prefer to check first character for all formats that are defined by their initial characters. This - is significantly faster than running the regex checks we use for + is significantly faster than running the regex checks used for text paragraphs. """ if self.theHandle is None or not theText: @@ -252,9 +251,9 @@ class GuiDocHighlighter(QSyntaxHighlighter): isValid, theBits, thePos = self.theIndex.scanThis(theText) isGood = self.theIndex.checkThese(theBits, tItem) if isValid: - for n in range(len(theBits)): + for n, theBit in enumerate(theBits): xPos = thePos[n] - xLen = len(theBits[n]) + xLen = len(theBit) if isGood[n]: if n == 0: self.setFormat(xPos, xLen, self.hStyles["keyword"]) @@ -287,11 +286,12 @@ class GuiDocHighlighter(QSyntaxHighlighter): self.setFormat(4, len(theText), self.hStyles["header4"]) elif theText.startswith("%"): # Comments - toCheck = theText[1:].lstrip().lower() + toCheck = theText[1:].lstrip() + synTag = toCheck[:9].lower() tLen = len(theText) cLen = len(toCheck) cOff = tLen - cLen - if toCheck.startswith("synopsis:"): + if synTag == "synopsis:": self.setFormat(0, cOff+9, self.hStyles["modifier"]) self.setFormat(cOff+9, tLen, self.hStyles["hidden"]) else: @@ -349,7 +349,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): if "underline" in fmtStyle: theFormat.setFontUnderline(True) if "background" in fmtStyle: - theFormat.setBackground(QBrush(fmtCol,Qt.SolidPattern)) + theFormat.setBackground(QBrush(fmtCol, Qt.SolidPattern)) if fmtSize is not None: theFormat.setFontPointSize(round(fmtSize*self.mainConf.textSize))