From ceadfe1e5c51df02e386e0875c7998df4faf7719 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:15:49 +0200 Subject: [PATCH 1/5] Improved regex, highlighting and HTML conversion of bold and italic text. Reverting to old syntax for italic. --- nw/constants/constants.py | 7 ++-- nw/core/tohtml.py | 5 +-- nw/core/tokenizer.py | 22 +++++++++---- nw/core/tools.py | 8 +++++ nw/gui/dochighlight.py | 68 ++++++++++++++++++--------------------- 5 files changed, 59 insertions(+), 51 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 18443c1d..d78cd5c6 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -37,10 +37,9 @@ class nwConst(): class nwRegEx(): - FMT_B = r"(?", self.FMT_I_B : "", self.FMT_I_E : "", - self.FMT_S_B : "", - self.FMT_S_E : "", self.FMT_D_B : "", self.FMT_D_E : "", } @@ -136,8 +135,6 @@ class ToHtml(Tokenizer): 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 : "", } diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index 09345e09..eba1ab65 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -44,10 +44,8 @@ class Tokenizer(): FMT_B_E = 2 # End bold FMT_I_B = 3 # Begin italics 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 + FMT_D_B = 5 # Begin strikeout + FMT_D_E = 6 # End strikeout T_EMPTY = 1 # Empty line (new paragraph) T_SYNOPSIS = 2 # Synopsis comment @@ -269,7 +267,6 @@ class Tokenizer(): def doAutoReplace(self): """Run through the user's auto-replace dictionary. """ - if len(self.theProject.autoReplace) > 0: repDict = {} for aKey, aVal in self.theProject.autoReplace.items(): @@ -280,8 +277,20 @@ class Tokenizer(): return def doPostProcessing(self): - """Do some postprocessing. Overloaded by subclasses. + """Do some postprocessing. Overloaded by subclasses. This just + does the standard escaped characters. """ + escapeDict = { + "\*" : "*", + "\~" : "~", + "\_" : "_", + } + escReplace = re.compile( + "|".join([re.escape(k) for k in escapeDict.keys()]), flags=re.DOTALL + ) + self.theResult = escReplace.sub( + lambda x: escapeDict[x.group(0)], self.theResult + ) return def tokenizeText(self): @@ -304,7 +313,6 @@ class Tokenizer(): rxFormats = [ (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]), ] diff --git a/nw/core/tools.py b/nw/core/tools.py index 8115105d..5c352fe7 100644 --- a/nw/core/tools.py +++ b/nw/core/tools.py @@ -34,6 +34,10 @@ from os import path, unlink, rmdir logger = logging.getLogger(__name__) +# =========================================================================== # +# Simple Word Counter +# =========================================================================== # + def countWords(theText): """Count words in a piece of text, skipping special syntax and comments. @@ -80,6 +84,10 @@ def countWords(theText): return charCount, wordCount, paraCount +# =========================================================================== # +# Convert an Integer to a Word Number +# =========================================================================== # + def numberToWord(numVal, theLanguage): """Wrapper for converting numbers to words for chapter headings. """ diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index 4acc0e7a..deb646b2 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -107,7 +107,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): "header4h" : self._makeFormat(self.colHeadH, "bold", 1.2), "bold" : self._makeFormat(self.colEmph, "bold"), "italic" : self._makeFormat(self.colEmph, "italic"), - "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"), @@ -137,36 +136,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): } )) - # Markdown - self.hRules.append(( - nwRegEx.FMT_I, { - 1 : self.hStyles["hidden"], - 2 : self.hStyles["italic"], - 3 : self.hStyles["hidden"], - } - )) - self.hRules.append(( - nwRegEx.FMT_B, { - 1 : self.hStyles["hidden"], - 2 : self.hStyles["bold"], - 3 : self.hStyles["hidden"], - } - )) - self.hRules.append(( - nwRegEx.FMT_BI, { - 1 : self.hStyles["hidden"], - 2 : self.hStyles["bolditalic"], - 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: self.hRules.append(( @@ -185,6 +154,29 @@ class GuiDocHighlighter(QSyntaxHighlighter): } )) + # Markdown + self.hRules.append(( + nwRegEx.FMT_I, { + 1 : self.hStyles["hidden"], + 2 : self.hStyles["italic"], + 3 : self.hStyles["hidden"], + } + )) + self.hRules.append(( + nwRegEx.FMT_B, { + 1 : self.hStyles["hidden"], + 2 : self.hStyles["bold"], + 3 : self.hStyles["hidden"], + } + )) + self.hRules.append(( + nwRegEx.FMT_ST, { + 1 : self.hStyles["hidden"], + 2 : self.hStyles["strike"], + 3 : self.hStyles["hidden"], + } + )) + # Auto-Replace Tags self.hRules.append(( r"<(\S+?)>", { @@ -302,10 +294,14 @@ class GuiDocHighlighter(QSyntaxHighlighter): rxItt = rX.globalMatch(theText, 0) while rxItt.hasNext(): rxMatch = rxItt.next() - for xM in xFmt.keys(): + for xM in xFmt: xPos = rxMatch.capturedStart(xM) xLen = rxMatch.capturedLength(xM) - self.setFormat(xPos, xLen, xFmt[xM]) + for x in range(xPos, xPos+xLen): + spFmt = self.format(x) + if spFmt != self.hStyles["hidden"]: + spFmt.merge(xFmt[xM]) + self.setFormat(x, 1, spFmt) if self.theDict is None or not self.spellCheck: return @@ -318,11 +314,11 @@ class GuiDocHighlighter(QSyntaxHighlighter): continue xPos = rxMatch.capturedStart(0) xLen = rxMatch.capturedLength(0) - for x in range(xLen): - spFmt = self.format(xPos+x) + for x in range(xPos, xPos+xLen): + spFmt = self.format(x) spFmt.setUnderlineColor(self.colSpell) spFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline) - self.setFormat(xPos+x, 1, spFmt) + self.setFormat(x, 1, spFmt) return From 564a15968c031013ebd2dcf5be14831550250259 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:52:56 +0200 Subject: [PATCH 2/5] Fixed menu and shortcut action for bold/italic --- nw/constants/enum.py | 37 +++++++++-------- nw/gui/doceditor.py | 96 ++++++++++++-------------------------------- nw/gui/mainmenu.py | 7 ---- nw/guimain.py | 1 - 4 files changed, 44 insertions(+), 97 deletions(-) diff --git a/nw/constants/enum.py b/nw/constants/enum.py index d068cb8e..4a377d7b 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -76,25 +76,24 @@ class nwDocAction(Enum): PASTE = 5 EMPH = 6 STRONG = 7 - STRONGEMPH = 8 - STRIKE = 9 - S_QUOTE = 10 - D_QUOTE = 11 - SEL_ALL = 12 - SEL_PARA = 13 - FIND = 14 - REPLACE = 15 - GO_NEXT = 16 - GO_PREV = 17 - REPL_NEXT = 18 - BLOCK_H1 = 19 - BLOCK_H2 = 20 - BLOCK_H3 = 21 - BLOCK_H4 = 22 - BLOCK_COM = 23 - BLOCK_TXT = 24 - REPL_SNG = 25 - REPL_DBL = 26 + STRIKE = 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 + REPL_SNG = 24 + REPL_DBL = 25 # END Enum nwDocAction diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index fce3d01c..7fd84a28 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -515,13 +515,11 @@ class GuiDocEditor(QTextEdit): elif theAction == nwDocAction.PASTE: self.paste() elif theAction == nwDocAction.EMPH: - self._toggleEmph(1) + self._toggleFormat(1, "_") elif theAction == nwDocAction.STRONG: - self._toggleEmph(2) - elif theAction == nwDocAction.STRONGEMPH: - self._toggleEmph(3) + self._toggleFormat(2, "*") elif theAction == nwDocAction.STRIKE: - self._toggleStrike() + self._toggleFormat(2, "~") elif theAction == nwDocAction.S_QUOTE: self._wrapSelection(self.typSQOpen, self.typSQClose) elif theAction == nwDocAction.D_QUOTE: @@ -998,69 +996,27 @@ class GuiDocEditor(QTextEdit): 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 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(): posS = theCursor.selectionStart() posE = theCursor.selectionEnd() - numB = 0 - for n in range(3): - if self.qDocument.characterAt(posS-n-1) == "*": - numB += 1 - else: - break + # Underscore counts as a part of the word, so check that the + # selection isn't wrapped in italics markers. + reSelect = False + if self.qDocument.characterAt(posS) == "_": + posS += 1 + reSelect = True + if self.qDocument.characterAt(posE) == "_": + posE -= 1 + reSelect = True + if reSelect: + theCursor.clearSelection() + theCursor.setPosition(posE-1) + theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, posE-posS-1) - numA = 0 - for n in range(3): - if self.qDocument.characterAt(posE+n) == "*": - numA += 1 - else: - break + self.setTextCursor(theCursor) + return theCursor - cLevel = min(numB, numA) - 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: - # Already at 1 or 2, increase to 3 - self._wrapSelection("*"*(3 - cLevel)) - - return - - def _toggleStrike(self): + def _toggleFormat(self, fLen, fChar): """Toggle strikethrough text. """ theCursor = self._autoSelect() @@ -1069,24 +1025,24 @@ class GuiDocEditor(QTextEdit): posE = theCursor.selectionEnd() numB = 0 - for n in range(2): - if self.qDocument.characterAt(posS-n-1) == "~": + for n in range(fLen): + if self.qDocument.characterAt(posS-n-1) == fChar: numB += 1 else: break numA = 0 - for n in range(2): - if self.qDocument.characterAt(posE+n) == "~": + for n in range(fLen): + if self.qDocument.characterAt(posE+n) == fChar: numA += 1 else: break cLevel = min(numB, numA) - if cLevel == 2: - self._clearSurrounding(theCursor, 2) + if cLevel == fLen: + self._clearSurrounding(theCursor, fLen) else: - self._wrapSelection("~~") + self._wrapSelection(fChar*fLen) return diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 2d2ecf01..5cdbf55a 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -540,13 +540,6 @@ class GuiMainMenu(QMenuBar): self.aFmtStrong.triggered.connect(lambda: self._docAction(nwDocAction.STRONG)) self.fmtMenu.addAction(self.aFmtStrong) - # Format > Very Strong Emphasis - self.aFmtStrongEmph = QAction("Very Strong Emphasis", self) - self.aFmtStrongEmph.setStatusTip("Add very strong emphasis to selected text (bold and italic)") - self.aFmtStrongEmph.setShortcut("Ctrl+Shift+B") - self.aFmtStrongEmph.triggered.connect(lambda: self._docAction(nwDocAction.STRONGEMPH)) - self.fmtMenu.addAction(self.aFmtStrongEmph) - # Format > Strikethrough self.aFmtStrike = QAction("Strikethrough", self) self.aFmtStrike.setStatusTip("Add strikethrough to selected text") diff --git a/nw/guimain.py b/nw/guimain.py index 8a115f0d..7681089c 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -1008,7 +1008,6 @@ class GuiMain(QMainWindow): # Format self.addAction(self.mainMenu.aFmtEmph) self.addAction(self.mainMenu.aFmtStrong) - self.addAction(self.mainMenu.aFmtStrongEmph) self.addAction(self.mainMenu.aFmtStrike) self.addAction(self.mainMenu.aFmtDQuote) self.addAction(self.mainMenu.aFmtSQuote) From 70078be1455e515bd405f1eb6e97733037f386ad Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:56:36 +0200 Subject: [PATCH 3/5] Fixed tests and updated sample --- sample/content/636b6aa9b697b.nwd | 6 +++--- sample/nwProject.nwx | 22 +++++++++---------- tests/lipsum/content/88d59a277361b.nwd | 2 +- tests/reference/build/1_LoremIpsum.nwd | 2 +- tests/reference/build/2_LoremIpsum.nwd | 2 +- tests/test_gui.py | 30 +------------------------- 6 files changed, 18 insertions(+), 46 deletions(-) diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index 0ab145f1..4809096d 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -7,13 +7,13 @@ 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***. You can also ~~strike through~~ text. +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. There is **some support for _nested_ emphasis**, but it isn’t fully Markdown compliant. If the syntax highlighter doesn’t show it correctly, the export tool will not either. -In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter, these can be in different colours. +In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.” 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. Regular dashes are also supported – and can be automatically inserted when typing two hyphens. +The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported – and can be automatically inserted when typing two hyphens. Thin spaces and thin non-breaking spaces are also supported from the Insert menu, and can be used to separate numbers from their units, like: 25 kg. diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index c5a194a5..e7e4a731 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,22 +1,22 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 593 - 103 - 24563 + 651 + 122 + 32698 False - True + False True 636b6aa9b697b - b3e74dbc1f584 - 982 - 606 + 636b6aa9b697b + 1022 + 646 376 B @@ -116,10 +116,10 @@ 1st Draft True SCENE - 1564 - 278 + 1811 + 318 8 - 1633 + 851 Another Scene diff --git a/tests/lipsum/content/88d59a277361b.nwd b/tests/lipsum/content/88d59a277361b.nwd index a67f49f1..f6ac2810 100644 --- a/tests/lipsum/content/88d59a277361b.nwd +++ b/tests/lipsum/content/88d59a277361b.nwd @@ -3,4 +3,4 @@ % 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/reference/build/1_LoremIpsum.nwd b/tests/reference/build/1_LoremIpsum.nwd index fbe4f132..010f3a80 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 f2a26740..d58f1021 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 diff --git a/tests/test_gui.py b/tests/test_gui.py index 5c8bf2ac..7660497f 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -746,17 +746,6 @@ def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): assert nwGUI.openProject(nwLipsum) qtbot.wait(stepDelay) - # CUT = 3 - # COPY = 4 - # PASTE = 5 - # SEL_ALL = 12 - # SEL_PARA = 13 - # FIND = 14 - # REPLACE = 15 - # GO_NEXT = 16 - # GO_PREV = 17 - # REPL_NEXT = 18 - # Split By Chapter assert nwGUI.openDocument("4c4f28287af27") assert nwGUI.docEditor.setCursorPosition(30) @@ -773,20 +762,12 @@ def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): # Italic assert nwGUI.passDocumentAction(nwDocAction.EMPH) - assert nwGUI.docEditor.getText()[27:76] == "*Pellentesque* nec erat ut nulla posuere commodo." + assert nwGUI.docEditor.getText()[27:76] == "_Pellentesque_ nec erat ut nulla posuere commodo." qtbot.wait(stepDelay) assert nwGUI.passDocumentAction(nwDocAction.EMPH) assert nwGUI.docEditor.getText()[27:74] == cleanText qtbot.wait(stepDelay) - # Bold-Italic - assert nwGUI.passDocumentAction(nwDocAction.STRONGEMPH) - assert nwGUI.docEditor.getText()[27:80] == "***Pellentesque*** nec erat ut nulla posuere commodo." - qtbot.wait(stepDelay) - assert nwGUI.passDocumentAction(nwDocAction.STRONGEMPH) - assert nwGUI.docEditor.getText()[27:74] == cleanText - qtbot.wait(stepDelay) - # Strikethrough assert nwGUI.passDocumentAction(nwDocAction.STRIKE) assert nwGUI.docEditor.getText()[27:78] == "~~Pellentesque~~ nec erat ut nulla posuere commodo." @@ -806,15 +787,6 @@ def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): assert nwGUI.docEditor.getText()[27:74] == cleanText qtbot.wait(stepDelay) - # Equivalent of the above - assert nwGUI.passDocumentAction(nwDocAction.STRONGEMPH) - qtbot.wait(stepDelay) - assert nwGUI.passDocumentAction(nwDocAction.EMPH) - qtbot.wait(stepDelay) - assert nwGUI.passDocumentAction(nwDocAction.STRONG) - assert nwGUI.docEditor.getText()[27:74] == cleanText - qtbot.wait(stepDelay) - # Double Quotes assert nwGUI.passDocumentAction(nwDocAction.D_QUOTE) assert nwGUI.docEditor.getText()[27:76] == "“Pellentesque” nec erat ut nulla posuere commodo." From fdf7f58d5f823d31474138956978e06778a2f864 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 29 Jun 2020 13:14:47 +0200 Subject: [PATCH 4/5] Updated docs --- docs/source/interface.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/source/interface.rst b/docs/source/interface.rst index 99e5b663..0a8dfd79 100644 --- a/docs/source/interface.rst +++ b/docs/source/interface.rst @@ -33,8 +33,9 @@ Markdown Format =============== The document editor uses a simplified markdown format. -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. +That is, it supports basic formatting like emphasis (italic), strong emphasis (bold) and strikethrough text, as well as four levels of headings. +It is commonly recommended style to differentiate between strong emphasis and emphasis by using ``**`` for strong emphasis and ``_`` for emphasis, although Markdown generally supports also ``__`` for strong emphasis and ``*`` fdr emphasis. +However, since the differentiation makes the highlighting and conversion significantly simpler and faster, in novelWriter this is a rule, not just a recommendation. 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,19 +49,21 @@ 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 emphasised text (italicised)." + "``_text_``", "The text is rendered as emphasised text (italicised)." "``**text**``", "The text is rendered as strongly emphasised text (bold)." - "``***text***``", "The text is rendered as very strongly emphasised text (italicised, bold)." - "``_text_``", "Alternative format for emphasised text." - "``__text__``", "Alternative format for strongly emphasised 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." -.. note:: - The emphasis and strikethrough formatting tags do not allow spaces between the words and the tag itself. +Some additional rules: + +1. The emphasis and strikethrough formatting tags do not allow spaces between the words and the tag itself. That is, ``**text**`` is valid, ``**text **`` is not. +2. More generally, the delimiters must be on the outer edge of words. + That is, ``some **text in bold** here`` is valid, ``some** text in bold** here`` is not. +3. If using both ``**`` and ``_`` to wrap the same text, the underscore must be the inner wrapper. + This is due to the underscore also being a valid word character, so if they are on the outside, they violate rule 2. The editor and viewer also supports markdown standard hard line breaks, and preserves non-breaking spaces. A hard line break is achieved by leaving two or more spaces at the end of the line. From 4355d5fd77489b074b8dd1ef9a92091b3015404b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 29 Jun 2020 13:28:15 +0200 Subject: [PATCH 5/5] Put back the non-breaking space in the sample --- sample/content/636b6aa9b697b.nwd | 2 +- sample/nwProject.nwx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index 4809096d..5936fbd3 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -13,7 +13,7 @@ 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. Regular dashes are also supported – and can be automatically inserted when typing two hyphens. +The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported – and can be automatically inserted when typing two hyphens. Thin spaces and thin non-breaking spaces are also supported from the Insert menu, and can be used to separate numbers from their units, like: 25 kg. diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index e7e4a731..82c542c6 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 651 + 652 122 - 32698 + 32716 False @@ -119,7 +119,7 @@ 1811 318 8 - 851 + 1143 Another Scene