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