Add line break shortcode support
This commit is contained in:
@@ -61,10 +61,11 @@ class nwConst:
|
||||
class nwRegEx:
|
||||
|
||||
WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
|
||||
BREAK = r"(?i)(?<!\\)(\[br\]\n?)"
|
||||
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_ST = r"(?<![\w\\])(~{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:b|i|s|u|m|sup|sub)\])"
|
||||
FMT_SC = r"(?i)(?<!\\)(\[(?:b|/b|i|/i|s|/s|u|/u|m|/m|sup|/sup|sub|/sub|br)\])"
|
||||
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote):)(.+?)(?<!\\)(\])"
|
||||
|
||||
|
||||
@@ -84,6 +85,7 @@ class nwShortcode:
|
||||
SUP_C = "[/sup]"
|
||||
SUB_O = "[sub]"
|
||||
SUB_C = "[/sub]"
|
||||
BREAK = "[br]"
|
||||
|
||||
FOOTNOTE_B = "[footnote:"
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ class nwDocInsert(Enum):
|
||||
VSPACE_M = 9
|
||||
LIPSUM = 10
|
||||
FOOTNOTE = 11
|
||||
LINE_BRK = 12
|
||||
|
||||
|
||||
class nwView(Enum):
|
||||
|
||||
@@ -56,7 +56,6 @@ class ComStyle(NamedTuple):
|
||||
textClass: str = ""
|
||||
|
||||
|
||||
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
|
||||
COMMENT_STYLE = {
|
||||
nwComment.PLAIN: ComStyle("Comment", "comment", "comment"),
|
||||
nwComment.IGNORE: ComStyle(),
|
||||
@@ -67,13 +66,12 @@ COMMENT_STYLE = {
|
||||
nwComment.COMMENT: ComStyle(),
|
||||
nwComment.STORY: ComStyle("", "modifier", "note"),
|
||||
}
|
||||
|
||||
# Lookups
|
||||
HEADINGS = [BlockTyp.TITLE, BlockTyp.HEAD1, BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4]
|
||||
SKIP_INDENT = [
|
||||
BlockTyp.TITLE, BlockTyp.HEAD1, BlockTyp.HEAD2, BlockTyp.HEAD2, BlockTyp.HEAD3,
|
||||
BlockTyp.HEAD4, BlockTyp.SEP, BlockTyp.SKIP,
|
||||
]
|
||||
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
|
||||
|
||||
|
||||
class Tokenizer(ABC):
|
||||
@@ -182,8 +180,6 @@ class Tokenizer(ABC):
|
||||
(REGEX_PATTERNS.markdownBold, [0, TextFmt.B_B, 0, TextFmt.B_E]),
|
||||
(REGEX_PATTERNS.markdownStrike, [0, TextFmt.D_B, 0, TextFmt.D_E]),
|
||||
]
|
||||
self._rxShortCodes = REGEX_PATTERNS.shortcodePlain
|
||||
self._rxShortCodeVals = REGEX_PATTERNS.shortcodeValue
|
||||
|
||||
self._shortCodeFmt = {
|
||||
nwShortcode.ITALIC_O: TextFmt.I_B, nwShortcode.ITALIC_C: TextFmt.I_E,
|
||||
@@ -1136,12 +1132,12 @@ class Tokenizer(ABC):
|
||||
# Post-process text and format
|
||||
result = text
|
||||
formats = []
|
||||
for pos, end, fmt, key in reversed(sorted(temp, key=lambda x: x[0])):
|
||||
for pos, end, fmt, meta in reversed(sorted(temp, key=lambda x: x[0])):
|
||||
if fmt > 0:
|
||||
if end > pos:
|
||||
result = result[:pos] + result[end:]
|
||||
formats = [(p+pos-end if p > pos else p, f, k) for p, f, k in formats]
|
||||
formats.insert(0, (pos, fmt, key))
|
||||
formats = [(p+pos-end if p > pos else p, f, m) for p, f, m in formats]
|
||||
formats.insert(0, (pos, fmt, meta))
|
||||
|
||||
return result, formats
|
||||
|
||||
|
||||
@@ -877,6 +877,8 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
after = False
|
||||
elif insert == nwDocInsert.FOOTNOTE:
|
||||
self._insertCommentStructure(nwComment.FOOTNOTE)
|
||||
elif insert == nwDocInsert.LINE_BRK:
|
||||
text = nwShortcode.BREAK
|
||||
|
||||
if text:
|
||||
if block:
|
||||
|
||||
@@ -570,8 +570,8 @@ class GuiMainMenu(QMenuBar):
|
||||
lambda: self.requestDocInsert.emit(nwDocInsert.SHORT)
|
||||
)
|
||||
|
||||
# Insert > Symbols
|
||||
self.mInsBreaks = self.insMenu.addMenu(self.tr("Page Break and Space"))
|
||||
# Insert > Breaks and Vertical Space
|
||||
self.mInsBreaks = self.insMenu.addMenu(self.tr("Breaks and Vertical Space"))
|
||||
|
||||
# Insert > New Page
|
||||
self.aInsNewPage = self.mInsBreaks.addAction(self.tr("Page Break"))
|
||||
@@ -579,6 +579,12 @@ class GuiMainMenu(QMenuBar):
|
||||
lambda: self.requestDocInsert.emit(nwDocInsert.NEW_PAGE)
|
||||
)
|
||||
|
||||
# Insert > Forced Line Break
|
||||
self.aInsLineBreak = self.mInsBreaks.addAction(self.tr("Forced Line Break"))
|
||||
self.aInsLineBreak.triggered.connect(
|
||||
lambda: self.requestDocInsert.emit(nwDocInsert.LINE_BRK)
|
||||
)
|
||||
|
||||
# Insert > Vertical Space (Single)
|
||||
self.aInsVSpaceS = self.mInsBreaks.addAction(self.tr("Vertical Space (Single)"))
|
||||
self.aInsVSpaceS.triggered.connect(
|
||||
|
||||
@@ -33,6 +33,7 @@ class RegExPatterns:
|
||||
|
||||
# Static RegExes
|
||||
_rxWords = re.compile(nwRegEx.WORDS, re.UNICODE)
|
||||
_rxBreak = re.compile(nwRegEx.BREAK, re.UNICODE)
|
||||
_rxItalic = re.compile(nwRegEx.FMT_EI, re.UNICODE)
|
||||
_rxBold = re.compile(nwRegEx.FMT_EB, re.UNICODE)
|
||||
_rxStrike = re.compile(nwRegEx.FMT_ST, re.UNICODE)
|
||||
@@ -44,6 +45,11 @@ class RegExPatterns:
|
||||
"""Split text into words."""
|
||||
return self._rxWords
|
||||
|
||||
@property
|
||||
def lineBreak(self) -> re.Pattern:
|
||||
"""Find forced line break."""
|
||||
return self._rxBreak
|
||||
|
||||
@property
|
||||
def markdownItalic(self) -> re.Pattern:
|
||||
"""Markdown italic style."""
|
||||
|
||||
@@ -522,13 +522,18 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
|
||||
nwGUI.mainMenu.aInsShort.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Stuff\n%Short: \n"
|
||||
|
||||
# Insert Break or Space
|
||||
# =====================
|
||||
# Breaks and Vertical Space
|
||||
# =========================
|
||||
|
||||
nwGUI.docEditor.setPlainText("### Stuff\n")
|
||||
nwGUI.mainMenu.aInsNewPage.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "[newpage]\n### Stuff\n"
|
||||
|
||||
nwGUI.docEditor.setPlainText("Line OneLine Two\n")
|
||||
nwGUI.docEditor.setCursorPosition(8)
|
||||
nwGUI.mainMenu.aInsLineBreak.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Line One[br]Line Two\n"
|
||||
|
||||
nwGUI.docEditor.setPlainText("### Stuff\n")
|
||||
nwGUI.mainMenu.aInsVSpaceS.activate(QAction.ActionEvent.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "[vspace]\n### Stuff\n"
|
||||
|
||||
@@ -47,6 +47,7 @@ def testTextCounting_preProcessText():
|
||||
"[vspace:3]\n\n"
|
||||
"[New Page]\n\n"
|
||||
"[footnote:abcd]\n\n"
|
||||
"[br]\n\n"
|
||||
"Dashes\u2013and even longer\u2014dashes.\n\n"
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ def testTextCounting_preProcessText():
|
||||
"#### Heading Four",
|
||||
"", "", "",
|
||||
"A paragraph.", "",
|
||||
"", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"Dashes and even longer dashes.", ""
|
||||
]
|
||||
|
||||
@@ -68,7 +69,7 @@ def testTextCounting_preProcessText():
|
||||
assert preProcessText(text, keepHeaders=False) == [
|
||||
"", "", "",
|
||||
"A paragraph.", "",
|
||||
"", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"Dashes and even longer dashes.", ""
|
||||
]
|
||||
|
||||
|
||||
@@ -198,6 +198,17 @@ def testTextPatterns_ShortcodesPlain():
|
||||
|
||||
assert allMatches(regEx, "one [x]two[/x] three") == []
|
||||
|
||||
# Line Break Substitution
|
||||
# =======================
|
||||
regEx = REGEX_PATTERNS.lineBreak
|
||||
|
||||
assert regEx.sub("\n", "one[br]two") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[br]\ntwo") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[br]\n\ntwo") == "one\n\ntwo"
|
||||
assert regEx.sub("\n", "one[BR]two") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[BR]\ntwo") == "one\ntwo"
|
||||
assert regEx.sub("\n", "one[BR]\n\ntwo") == "one\n\ntwo"
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testTextPatterns_ShortcodesValue():
|
||||
|
||||
Reference in New Issue
Block a user