Extend dialogue highlighting (#1908)
This commit is contained in:
@@ -61,10 +61,10 @@ class nwConst:
|
|||||||
class nwRegEx:
|
class nwRegEx:
|
||||||
|
|
||||||
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)(?<!\\)(\[[\/\!]?(?:i|b|s|u|m|sup|sub)\])"
|
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:b|i|s|u|m|sup|sub)\])"
|
||||||
FMT_SV = r"(?<!\\)(\[(?i)(?:footnote):)(.+?)(?<!\\)(\])"
|
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote):)(.+?)(?<!\\)(\])"
|
||||||
|
|
||||||
|
|
||||||
class nwShortcode:
|
class nwShortcode:
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ SETTINGS_TEMPLATE = {
|
|||||||
"format.stripUnicode": (bool, False),
|
"format.stripUnicode": (bool, False),
|
||||||
"format.replaceTabs": (bool, False),
|
"format.replaceTabs": (bool, False),
|
||||||
"format.keepBreaks": (bool, True),
|
"format.keepBreaks": (bool, True),
|
||||||
|
"format.showDialogue": (bool, False),
|
||||||
"format.firstLineIndent": (bool, False),
|
"format.firstLineIndent": (bool, False),
|
||||||
"format.firstIndentWidth": (float, 1.4),
|
"format.firstIndentWidth": (float, 1.4),
|
||||||
"format.indentFirstPar": (bool, False),
|
"format.indentFirstPar": (bool, False),
|
||||||
@@ -131,6 +132,7 @@ SETTINGS_LABELS = {
|
|||||||
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
|
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
|
||||||
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
|
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
|
||||||
"format.keepBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
|
"format.keepBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
|
||||||
|
"format.showDialogue": QT_TRANSLATE_NOOP("Builds", "Apply Dialogue Highlighting"),
|
||||||
|
|
||||||
"format.grpParIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
|
"format.grpParIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
|
||||||
"format.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "Enable Indent"),
|
"format.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "Enable Indent"),
|
||||||
|
|||||||
@@ -338,6 +338,7 @@ class NWBuildDocument:
|
|||||||
bldObj.setJustify(self._build.getBool("format.justifyText"))
|
bldObj.setJustify(self._build.getBool("format.justifyText"))
|
||||||
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
|
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
|
||||||
bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks"))
|
bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks"))
|
||||||
|
bldObj.setDialogueHighlight(self._build.getBool("format.showDialogue"))
|
||||||
bldObj.setFirstLineIndent(
|
bldObj.setFirstLineIndent(
|
||||||
self._build.getBool("format.firstLineIndent"),
|
self._build.getBool("format.firstLineIndent"),
|
||||||
self._build.getFloat("format.firstIndentWidth"),
|
self._build.getFloat("format.firstIndentWidth"),
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ HTML5_TAGS = {
|
|||||||
Tokenizer.FMT_SUP_E: "</sup>",
|
Tokenizer.FMT_SUP_E: "</sup>",
|
||||||
Tokenizer.FMT_SUB_B: "<sub>",
|
Tokenizer.FMT_SUB_B: "<sub>",
|
||||||
Tokenizer.FMT_SUB_E: "</sub>",
|
Tokenizer.FMT_SUB_E: "</sub>",
|
||||||
|
Tokenizer.FMT_DL_B: "<span class='dialog'>",
|
||||||
|
Tokenizer.FMT_DL_E: "</span>",
|
||||||
|
Tokenizer.FMT_ADL_B: "<span class='altdialog'>",
|
||||||
|
Tokenizer.FMT_ADL_E: "</span>",
|
||||||
Tokenizer.FMT_STRIP: "",
|
Tokenizer.FMT_STRIP: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,6 +435,8 @@ class ToHtml(Tokenizer):
|
|||||||
styles.append(".break {text-align: left;}")
|
styles.append(".break {text-align: left;}")
|
||||||
styles.append(".synopsis {font-style: italic;}")
|
styles.append(".synopsis {font-style: italic;}")
|
||||||
styles.append(".comment {font-style: italic; color: rgb(100, 100, 100);}")
|
styles.append(".comment {font-style: italic; color: rgb(100, 100, 100);}")
|
||||||
|
styles.append(".dialog {color: rgb(66, 113, 174);}")
|
||||||
|
styles.append(".altdialog {color: rgb(129, 55, 9);}")
|
||||||
|
|
||||||
return styles
|
return styles
|
||||||
|
|
||||||
@@ -451,7 +457,7 @@ class ToHtml(Tokenizer):
|
|||||||
else:
|
else:
|
||||||
html = "<sup>ERR</sup>"
|
html = "<sup>ERR</sup>"
|
||||||
else:
|
else:
|
||||||
html = HTML5_TAGS.get(fmt, "ERR")
|
html = HTML5_TAGS.get(fmt, "")
|
||||||
temp = f"{temp[:pos]}{html}{temp[pos:]}"
|
temp = f"{temp[:pos]}{html}{temp[pos:]}"
|
||||||
temp = temp.replace("\n", "<br>")
|
temp = temp.replace("\n", "<br>")
|
||||||
return stripEscape(temp)
|
return stripEscape(temp)
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ from time import time
|
|||||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
||||||
from PyQt5.QtGui import QFont
|
from PyQt5.QtGui import QFont
|
||||||
|
|
||||||
|
from novelwriter import CONFIG
|
||||||
from novelwriter.common import checkInt, formatTimeStamp, numberToRoman
|
from novelwriter.common import checkInt, formatTimeStamp, numberToRoman
|
||||||
from novelwriter.constants import (
|
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst
|
||||||
nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst
|
|
||||||
)
|
|
||||||
from novelwriter.core.index import processComment
|
from novelwriter.core.index import processComment
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.enum import nwComment, nwItemLayout
|
from novelwriter.enum import nwComment, nwItemLayout
|
||||||
|
from novelwriter.text.patterns import REGEX_PATTERNS
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -85,8 +85,12 @@ class Tokenizer(ABC):
|
|||||||
FMT_SUP_E = 12 # End superscript
|
FMT_SUP_E = 12 # End superscript
|
||||||
FMT_SUB_B = 13 # Begin subscript
|
FMT_SUB_B = 13 # Begin subscript
|
||||||
FMT_SUB_E = 14 # End subscript
|
FMT_SUB_E = 14 # End subscript
|
||||||
FMT_FNOTE = 15 # Footnote marker
|
FMT_DL_B = 15 # Begin dialogue
|
||||||
FMT_STRIP = 16 # Strip the format code
|
FMT_DL_E = 16 # End dialogue
|
||||||
|
FMT_ADL_B = 17 # Begin alt dialogue
|
||||||
|
FMT_ADL_E = 18 # End alt dialogue
|
||||||
|
FMT_FNOTE = 19 # Footnote marker
|
||||||
|
FMT_STRIP = 20 # Strip the format code
|
||||||
|
|
||||||
# Block Type
|
# Block Type
|
||||||
T_EMPTY = 1 # Empty line (new paragraph)
|
T_EMPTY = 1 # Empty line (new paragraph)
|
||||||
@@ -193,7 +197,8 @@ class Tokenizer(ABC):
|
|||||||
|
|
||||||
# Instance Variables
|
# Instance Variables
|
||||||
self._hFormatter = HeadingFormatter(self._project)
|
self._hFormatter = HeadingFormatter(self._project)
|
||||||
self._noSep = True # Flag to indicate that we don't want a scene separator
|
self._noSep = True # Flag to indicate that we don't want a scene separator
|
||||||
|
self._showDialog = False # Flag for dialogue highlighting
|
||||||
|
|
||||||
# This File
|
# This File
|
||||||
self._isNovel = False # Document is a novel document
|
self._isNovel = False # Document is a novel document
|
||||||
@@ -208,12 +213,12 @@ class Tokenizer(ABC):
|
|||||||
|
|
||||||
# Format RegEx
|
# Format RegEx
|
||||||
self._rxMarkdown = [
|
self._rxMarkdown = [
|
||||||
(QRegularExpression(nwRegEx.FMT_EI), [0, self.FMT_I_B, 0, self.FMT_I_E]),
|
(REGEX_PATTERNS.markdownItalic, [0, self.FMT_I_B, 0, self.FMT_I_E]),
|
||||||
(QRegularExpression(nwRegEx.FMT_EB), [0, self.FMT_B_B, 0, self.FMT_B_E]),
|
(REGEX_PATTERNS.markdownBold, [0, self.FMT_B_B, 0, self.FMT_B_E]),
|
||||||
(QRegularExpression(nwRegEx.FMT_ST), [0, self.FMT_D_B, 0, self.FMT_D_E]),
|
(REGEX_PATTERNS.markdownStrike, [0, self.FMT_D_B, 0, self.FMT_D_E]),
|
||||||
]
|
]
|
||||||
self._rxShortCodes = QRegularExpression(nwRegEx.FMT_SC)
|
self._rxShortCodes = REGEX_PATTERNS.shortcodePlain
|
||||||
self._rxShortCodeVals = QRegularExpression(nwRegEx.FMT_SV)
|
self._rxShortCodeVals = REGEX_PATTERNS.shortcodeValue
|
||||||
|
|
||||||
self._shortCodeFmt = {
|
self._shortCodeFmt = {
|
||||||
nwShortcode.ITALIC_O: self.FMT_I_B, nwShortcode.ITALIC_C: self.FMT_I_E,
|
nwShortcode.ITALIC_O: self.FMT_I_B, nwShortcode.ITALIC_C: self.FMT_I_E,
|
||||||
@@ -228,6 +233,8 @@ class Tokenizer(ABC):
|
|||||||
nwShortcode.FOOTNOTE_B: self.FMT_FNOTE,
|
nwShortcode.FOOTNOTE_B: self.FMT_FNOTE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self._rxDialogue: list[tuple[QRegularExpression, int, int]] = []
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -349,6 +356,29 @@ class Tokenizer(ABC):
|
|||||||
self._doJustify = state
|
self._doJustify = state
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setDialogueHighlight(self, state: bool) -> None:
|
||||||
|
"""Enable or disable dialogue highlighting."""
|
||||||
|
self._rxDialogue = []
|
||||||
|
self._showDialog = state
|
||||||
|
if state:
|
||||||
|
if CONFIG.dialogStyle > 0:
|
||||||
|
self._rxDialogue.append((
|
||||||
|
REGEX_PATTERNS.dialogStyle, self.FMT_DL_B, self.FMT_DL_E
|
||||||
|
))
|
||||||
|
if CONFIG.dialogLine:
|
||||||
|
self._rxDialogue.append((
|
||||||
|
REGEX_PATTERNS.dialogLine, self.FMT_DL_B, self.FMT_DL_E
|
||||||
|
))
|
||||||
|
if CONFIG.narratorBreak:
|
||||||
|
self._rxDialogue.append((
|
||||||
|
REGEX_PATTERNS.narratorBreak, self.FMT_DL_E, self.FMT_DL_B
|
||||||
|
))
|
||||||
|
if CONFIG.altDialogOpen and CONFIG.altDialogClose:
|
||||||
|
self._rxDialogue.append((
|
||||||
|
REGEX_PATTERNS.altDialogStyle, self.FMT_ADL_B, self.FMT_ADL_E
|
||||||
|
))
|
||||||
|
return
|
||||||
|
|
||||||
def setTitleMargins(self, upper: float, lower: float) -> None:
|
def setTitleMargins(self, upper: float, lower: float) -> None:
|
||||||
"""Set the upper and lower title margin."""
|
"""Set the upper and lower title margin."""
|
||||||
self._marginTitle = (float(upper), float(lower))
|
self._marginTitle = (float(upper), float(lower))
|
||||||
@@ -1106,6 +1136,15 @@ class Tokenizer(ABC):
|
|||||||
f"{tHandle}:{rxMatch.captured(2)}",
|
f"{tHandle}:{rxMatch.captured(2)}",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Match Dialogue
|
||||||
|
if self._rxDialogue:
|
||||||
|
for regEx, fmtB, fmtE in self._rxDialogue:
|
||||||
|
rxItt = regEx.globalMatch(text, 0)
|
||||||
|
while rxItt.hasNext():
|
||||||
|
rxMatch = rxItt.next()
|
||||||
|
temp.append((rxMatch.capturedStart(0), 0, fmtB, ""))
|
||||||
|
temp.append((rxMatch.capturedEnd(0), 0, fmtE, ""))
|
||||||
|
|
||||||
# Post-process text and format
|
# Post-process text and format
|
||||||
result = text
|
result = text
|
||||||
formats = []
|
formats = []
|
||||||
|
|||||||
+45
-14
@@ -82,13 +82,15 @@ TAG_SPAN = _mkTag("text", "span")
|
|||||||
TAG_STNM = _mkTag("text", "style-name")
|
TAG_STNM = _mkTag("text", "style-name")
|
||||||
|
|
||||||
# Formatting Codes
|
# Formatting Codes
|
||||||
X_BLD = 0x01 # Bold format
|
X_BLD = 0x001 # Bold format
|
||||||
X_ITA = 0x02 # Italic format
|
X_ITA = 0x002 # Italic format
|
||||||
X_DEL = 0x04 # Strikethrough format
|
X_DEL = 0x004 # Strikethrough format
|
||||||
X_UND = 0x08 # Underline format
|
X_UND = 0x008 # Underline format
|
||||||
X_MRK = 0x10 # Marked format
|
X_MRK = 0x010 # Marked format
|
||||||
X_SUP = 0x20 # Superscript
|
X_SUP = 0x020 # Superscript
|
||||||
X_SUB = 0x40 # Subscript
|
X_SUB = 0x040 # Subscript
|
||||||
|
X_DLG = 0x080 # Dialogue
|
||||||
|
X_DLA = 0x100 # Alt. Dialogue
|
||||||
|
|
||||||
# Formatting Masks
|
# Formatting Masks
|
||||||
M_BLD = ~X_BLD
|
M_BLD = ~X_BLD
|
||||||
@@ -98,6 +100,8 @@ M_UND = ~X_UND
|
|||||||
M_MRK = ~X_MRK
|
M_MRK = ~X_MRK
|
||||||
M_SUP = ~X_SUP
|
M_SUP = ~X_SUP
|
||||||
M_SUB = ~X_SUB
|
M_SUB = ~X_SUB
|
||||||
|
M_DLG = ~X_DLG
|
||||||
|
M_DLA = ~X_DLA
|
||||||
|
|
||||||
# ODT Styles
|
# ODT Styles
|
||||||
S_TITLE = "Title"
|
S_TITLE = "Title"
|
||||||
@@ -216,13 +220,15 @@ class ToOdt(Tokenizer):
|
|||||||
self._mDocRight = "2.000cm"
|
self._mDocRight = "2.000cm"
|
||||||
|
|
||||||
# Colour
|
# Colour
|
||||||
self._colHead12 = None
|
self._colHead12 = None
|
||||||
self._opaHead12 = None
|
self._opaHead12 = None
|
||||||
self._colHead34 = None
|
self._colHead34 = None
|
||||||
self._opaHead34 = None
|
self._opaHead34 = None
|
||||||
self._colMetaTx = None
|
self._colMetaTx = None
|
||||||
self._opaMetaTx = None
|
self._opaMetaTx = None
|
||||||
self._markText = "#ffffa6"
|
self._colDialogM = None
|
||||||
|
self._colDialogA = None
|
||||||
|
self._markText = "#ffffa6"
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -324,6 +330,10 @@ class ToOdt(Tokenizer):
|
|||||||
self._colMetaTx = "#813709"
|
self._colMetaTx = "#813709"
|
||||||
self._opaMetaTx = "100%"
|
self._opaMetaTx = "100%"
|
||||||
|
|
||||||
|
if self._showDialog:
|
||||||
|
self._colDialogM = "#2a6099"
|
||||||
|
self._colDialogA = "#813709"
|
||||||
|
|
||||||
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
|
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
|
||||||
self._fBlockIndent = self._emToCm(self._blockIndent)
|
self._fBlockIndent = self._emToCm(self._blockIndent)
|
||||||
self._fTextIndent = self._emToCm(self._firstWidth)
|
self._fTextIndent = self._emToCm(self._firstWidth)
|
||||||
@@ -684,6 +694,14 @@ class ToOdt(Tokenizer):
|
|||||||
xFmt |= X_SUB
|
xFmt |= X_SUB
|
||||||
elif fFmt == self.FMT_SUB_E:
|
elif fFmt == self.FMT_SUB_E:
|
||||||
xFmt &= M_SUB
|
xFmt &= M_SUB
|
||||||
|
elif fFmt == self.FMT_DL_B:
|
||||||
|
xFmt |= X_DLG
|
||||||
|
elif fFmt == self.FMT_DL_E:
|
||||||
|
xFmt &= M_DLG
|
||||||
|
elif fFmt == self.FMT_ADL_B:
|
||||||
|
xFmt |= X_DLA
|
||||||
|
elif fFmt == self.FMT_ADL_E:
|
||||||
|
xFmt &= M_DLA
|
||||||
elif fFmt == self.FMT_FNOTE:
|
elif fFmt == self.FMT_FNOTE:
|
||||||
xNode = self._generateFootnote(fData)
|
xNode = self._generateFootnote(fData)
|
||||||
elif fFmt == self.FMT_STRIP:
|
elif fFmt == self.FMT_STRIP:
|
||||||
@@ -757,6 +775,10 @@ class ToOdt(Tokenizer):
|
|||||||
style.setTextPosition("super")
|
style.setTextPosition("super")
|
||||||
if hFmt & X_SUB:
|
if hFmt & X_SUB:
|
||||||
style.setTextPosition("sub")
|
style.setTextPosition("sub")
|
||||||
|
if hFmt & X_DLG:
|
||||||
|
style.setColour(self._colDialogM)
|
||||||
|
if hFmt & X_DLA:
|
||||||
|
style.setColour(self._colDialogA)
|
||||||
self._autoText[hFmt] = style
|
self._autoText[hFmt] = style
|
||||||
|
|
||||||
return style.name
|
return style.name
|
||||||
@@ -1357,6 +1379,7 @@ class ODTTextStyle:
|
|||||||
self._tAttr = {
|
self._tAttr = {
|
||||||
"font-weight": ["fo", None],
|
"font-weight": ["fo", None],
|
||||||
"font-style": ["fo", None],
|
"font-style": ["fo", None],
|
||||||
|
"color": ["fo", None],
|
||||||
"background-color": ["fo", None],
|
"background-color": ["fo", None],
|
||||||
"text-position": ["style", None],
|
"text-position": ["style", None],
|
||||||
"text-line-through-style": ["style", None],
|
"text-line-through-style": ["style", None],
|
||||||
@@ -1391,6 +1414,14 @@ class ODTTextStyle:
|
|||||||
self._tAttr["font-style"][1] = None
|
self._tAttr["font-style"][1] = None
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setColour(self, value: str | None) -> None:
|
||||||
|
"""Set text colour."""
|
||||||
|
if value and len(value) == 7 and value[0] == "#":
|
||||||
|
self._tAttr["color"][1] = value
|
||||||
|
else:
|
||||||
|
self._tAttr["color"][1] = None
|
||||||
|
return
|
||||||
|
|
||||||
def setBackgroundColour(self, value: str | None) -> None:
|
def setBackgroundColour(self, value: str | None) -> None:
|
||||||
"""Set text background colour."""
|
"""Set text background colour."""
|
||||||
if value and len(value) == 7 and value[0] == "#":
|
if value and len(value) == 7 and value[0] == "#":
|
||||||
|
|||||||
@@ -45,16 +45,18 @@ T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat]
|
|||||||
|
|
||||||
|
|
||||||
class TextDocumentTheme:
|
class TextDocumentTheme:
|
||||||
text: QColor = QtBlack
|
text: QColor = QtBlack
|
||||||
highlight: QColor = QtTransparent
|
highlight: QColor = QtTransparent
|
||||||
head: QColor = QtBlack
|
head: QColor = QtBlack
|
||||||
comment: QColor = QtBlack
|
comment: QColor = QtBlack
|
||||||
note: QColor = QtBlack
|
note: QColor = QtBlack
|
||||||
code: QColor = QtBlack
|
code: QColor = QtBlack
|
||||||
modifier: QColor = QtBlack
|
modifier: QColor = QtBlack
|
||||||
keyword: QColor = QtBlack
|
keyword: QColor = QtBlack
|
||||||
tag: QColor = QtBlack
|
tag: QColor = QtBlack
|
||||||
optional: QColor = QtBlack
|
optional: QColor = QtBlack
|
||||||
|
dialog: QColor = QtBlack
|
||||||
|
altdialog: QColor = QtBlack
|
||||||
|
|
||||||
|
|
||||||
def newBlock(cursor: QTextCursor, bFmt: QTextBlockFormat) -> None:
|
def newBlock(cursor: QTextCursor, bFmt: QTextBlockFormat) -> None:
|
||||||
@@ -340,6 +342,14 @@ class ToQTextDocument(Tokenizer):
|
|||||||
cFmt.setVerticalAlignment(QtVAlignSub)
|
cFmt.setVerticalAlignment(QtVAlignSub)
|
||||||
elif fmt == self.FMT_SUB_E:
|
elif fmt == self.FMT_SUB_E:
|
||||||
cFmt.setVerticalAlignment(QtVAlignNormal)
|
cFmt.setVerticalAlignment(QtVAlignNormal)
|
||||||
|
elif fmt == self.FMT_DL_B:
|
||||||
|
cFmt.setForeground(self._theme.dialog)
|
||||||
|
elif fmt == self.FMT_DL_E:
|
||||||
|
cFmt.setForeground(self._theme.text)
|
||||||
|
elif fmt == self.FMT_ADL_B:
|
||||||
|
cFmt.setForeground(self._theme.altdialog)
|
||||||
|
elif fmt == self.FMT_ADL_E:
|
||||||
|
cFmt.setForeground(self._theme.text)
|
||||||
elif fmt == self.FMT_FNOTE:
|
elif fmt == self.FMT_FNOTE:
|
||||||
xFmt = QTextCharFormat(self._cCode)
|
xFmt = QTextCharFormat(self._cCode)
|
||||||
xFmt.setVerticalAlignment(QtVAlignSuper)
|
xFmt.setVerticalAlignment(QtVAlignSuper)
|
||||||
|
|||||||
@@ -2030,6 +2030,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
cursor.movePosition(QtMoveLeft, QtKeepAnchor, nDelete)
|
cursor.movePosition(QtMoveLeft, QtKeepAnchor, nDelete)
|
||||||
cursor.insertText(tInsert)
|
cursor.insertText(tInsert)
|
||||||
|
|
||||||
|
# Re-highlight, since the auto-replace sometimes interferes with it
|
||||||
|
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from novelwriter.common import checkInt
|
|||||||
from novelwriter.constants import nwHeaders, nwRegEx, nwUnicode
|
from novelwriter.constants import nwHeaders, nwRegEx, nwUnicode
|
||||||
from novelwriter.core.index import processComment
|
from novelwriter.core.index import processComment
|
||||||
from novelwriter.enum import nwComment
|
from novelwriter.enum import nwComment
|
||||||
|
from novelwriter.text.patterns import REGEX_PATTERNS
|
||||||
from novelwriter.types import QRegExUnicode
|
from novelwriter.types import QRegExUnicode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -59,8 +60,8 @@ BLOCK_TITLE = 4
|
|||||||
class GuiDocHighlighter(QSyntaxHighlighter):
|
class GuiDocHighlighter(QSyntaxHighlighter):
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
"_tHandle", "_isInactive", "_spellCheck", "_spellErr", "_hStyles",
|
"_tHandle", "_isNovel", "_isInactive", "_spellCheck", "_spellErr",
|
||||||
"_txtRules", "_cmnRules",
|
"_hStyles", "_minRules", "_txtRules", "_cmnRules",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, document: QTextDocument) -> None:
|
def __init__(self, document: QTextDocument) -> None:
|
||||||
@@ -69,11 +70,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
logger.debug("Create: GuiDocHighlighter")
|
logger.debug("Create: GuiDocHighlighter")
|
||||||
|
|
||||||
self._tHandle = None
|
self._tHandle = None
|
||||||
|
self._isNovel = False
|
||||||
self._isInactive = False
|
self._isInactive = False
|
||||||
self._spellCheck = False
|
self._spellCheck = False
|
||||||
self._spellErr = QTextCharFormat()
|
self._spellErr = QTextCharFormat()
|
||||||
|
|
||||||
self._hStyles: dict[str, QTextCharFormat] = {}
|
self._hStyles: dict[str, QTextCharFormat] = {}
|
||||||
|
self._minRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
||||||
self._txtRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
self._txtRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
||||||
self._cmnRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
self._cmnRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
||||||
|
|
||||||
@@ -137,6 +140,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
hlRule = {
|
hlRule = {
|
||||||
0: self._hStyles["mspaces"],
|
0: self._hStyles["mspaces"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
@@ -146,106 +150,89 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
hlRule = {
|
hlRule = {
|
||||||
0: self._hStyles["nobreak"],
|
0: self._hStyles["nobreak"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
# Dialogue
|
# Dialogue
|
||||||
if CONFIG.dialogStyle > 0:
|
if CONFIG.dialogStyle > 0:
|
||||||
symO = ""
|
rxRule = REGEX_PATTERNS.dialogStyle
|
||||||
symC = ""
|
|
||||||
if CONFIG.dialogStyle in (1, 3):
|
|
||||||
symO += CONFIG.fmtSQuoteOpen
|
|
||||||
symC += CONFIG.fmtSQuoteClose
|
|
||||||
if CONFIG.dialogStyle in (2, 3):
|
|
||||||
symO += CONFIG.fmtDQuoteOpen
|
|
||||||
symC += CONFIG.fmtDQuoteClose
|
|
||||||
|
|
||||||
rxEnd = "|$" if CONFIG.allowOpenDial else ""
|
|
||||||
rxRule = QRegularExpression(f"\\B[{symO}].*?[{symC}]\\B{rxEnd}")
|
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
0: self._hStyles["dialog"],
|
0: self._hStyles["dialog"],
|
||||||
}
|
}
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
if CONFIG.dialogLine:
|
if CONFIG.dialogLine:
|
||||||
sym = QRegularExpression.escape(CONFIG.dialogLine)
|
rxRule = REGEX_PATTERNS.dialogLine
|
||||||
rxRule = QRegularExpression(f"^{sym}.*?$")
|
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
0: self._hStyles["dialog"],
|
0: self._hStyles["dialog"],
|
||||||
}
|
}
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
if CONFIG.narratorBreak:
|
if CONFIG.narratorBreak:
|
||||||
sym = QRegularExpression.escape(CONFIG.narratorBreak)
|
rxRule = REGEX_PATTERNS.narratorBreak
|
||||||
rxRule = QRegularExpression(f"({sym}\\b)(.*?)(\\b{sym})")
|
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
0: self._hStyles["text"],
|
0: self._hStyles["text"],
|
||||||
}
|
}
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
if CONFIG.altDialogOpen and CONFIG.altDialogClose:
|
if CONFIG.altDialogOpen and CONFIG.altDialogClose:
|
||||||
symO = QRegularExpression.escape(CONFIG.altDialogOpen)
|
rxRule = REGEX_PATTERNS.altDialogStyle
|
||||||
symC = QRegularExpression.escape(CONFIG.altDialogClose)
|
|
||||||
rxRule = QRegularExpression(f"\\B{symO}.*?{symC}\\B")
|
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
0: self._hStyles["altdialog"],
|
0: self._hStyles["altdialog"],
|
||||||
}
|
}
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
# Markdown Italic
|
# Markdown Italic
|
||||||
rxRule = QRegularExpression(nwRegEx.FMT_EI)
|
rxRule = REGEX_PATTERNS.markdownItalic
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
1: self._hStyles["markup"],
|
1: self._hStyles["markup"],
|
||||||
2: self._hStyles["italic"],
|
2: self._hStyles["italic"],
|
||||||
3: self._hStyles["markup"],
|
3: self._hStyles["markup"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
# Markdown Bold
|
# Markdown Bold
|
||||||
rxRule = QRegularExpression(nwRegEx.FMT_EB)
|
rxRule = REGEX_PATTERNS.markdownBold
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
1: self._hStyles["markup"],
|
1: self._hStyles["markup"],
|
||||||
2: self._hStyles["bold"],
|
2: self._hStyles["bold"],
|
||||||
3: self._hStyles["markup"],
|
3: self._hStyles["markup"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
# Markdown Strikethrough
|
# Markdown Strikethrough
|
||||||
rxRule = QRegularExpression(nwRegEx.FMT_ST)
|
rxRule = REGEX_PATTERNS.markdownStrike
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
1: self._hStyles["markup"],
|
1: self._hStyles["markup"],
|
||||||
2: self._hStyles["strike"],
|
2: self._hStyles["strike"],
|
||||||
3: self._hStyles["markup"],
|
3: self._hStyles["markup"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
# Shortcodes
|
# Shortcodes
|
||||||
rxRule = QRegularExpression(nwRegEx.FMT_SC)
|
rxRule = REGEX_PATTERNS.shortcodePlain
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
1: self._hStyles["code"],
|
1: self._hStyles["code"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
# Shortcodes w/Value
|
# Shortcodes w/Value
|
||||||
rxRule = QRegularExpression(nwRegEx.FMT_SV)
|
rxRule = REGEX_PATTERNS.shortcodeValue
|
||||||
rxRule.setPatternOptions(QRegExUnicode)
|
|
||||||
hlRule = {
|
hlRule = {
|
||||||
1: self._hStyles["code"],
|
1: self._hStyles["code"],
|
||||||
2: self._hStyles["value"],
|
2: self._hStyles["value"],
|
||||||
3: self._hStyles["code"],
|
3: self._hStyles["code"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
@@ -255,6 +242,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
hlRule = {
|
hlRule = {
|
||||||
1: self._hStyles["markup"],
|
1: self._hStyles["markup"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
# Auto-Replace Tags
|
# Auto-Replace Tags
|
||||||
@@ -263,6 +251,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
hlRule = {
|
hlRule = {
|
||||||
0: self._hStyles["replace"],
|
0: self._hStyles["replace"],
|
||||||
}
|
}
|
||||||
|
self._minRules.append((rxRule, hlRule))
|
||||||
self._txtRules.append((rxRule, hlRule))
|
self._txtRules.append((rxRule, hlRule))
|
||||||
self._cmnRules.append((rxRule, hlRule))
|
self._cmnRules.append((rxRule, hlRule))
|
||||||
|
|
||||||
@@ -280,9 +269,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
def setHandle(self, tHandle: str) -> None:
|
def setHandle(self, tHandle: str) -> None:
|
||||||
"""Set the handle of the currently highlighted document."""
|
"""Set the handle of the currently highlighted document."""
|
||||||
self._tHandle = tHandle
|
self._tHandle = tHandle
|
||||||
self._isInactive = (
|
self._isNovel = False
|
||||||
item.isInactiveClass() if (item := SHARED.project.tree[tHandle]) else False
|
self._isInactive = False
|
||||||
)
|
if item := SHARED.project.tree[tHandle]:
|
||||||
|
self._isNovel = item.isDocumentLayout()
|
||||||
|
self._isInactive = item.isInactiveClass()
|
||||||
logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
|
logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -397,7 +388,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
elif text.startswith("["): # Special Command
|
elif text.startswith("["): # Special Command
|
||||||
self.setCurrentBlockState(BLOCK_TEXT)
|
self.setCurrentBlockState(BLOCK_TEXT)
|
||||||
hRules = self._txtRules
|
hRules = self._txtRules if self._isNovel else self._minRules
|
||||||
|
|
||||||
sText = text.rstrip().lower()
|
sText = text.rstrip().lower()
|
||||||
if sText in ("[newpage]", "[new page]", "[vspace]"):
|
if sText in ("[newpage]", "[new page]", "[vspace]"):
|
||||||
@@ -414,7 +405,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
else: # Text Paragraph
|
else: # Text Paragraph
|
||||||
self.setCurrentBlockState(BLOCK_TEXT)
|
self.setCurrentBlockState(BLOCK_TEXT)
|
||||||
hRules = self._txtRules
|
hRules = self._txtRules if self._isNovel else self._minRules
|
||||||
|
|
||||||
if hRules:
|
if hRules:
|
||||||
for rX, hRule in hRules:
|
for rX, hRule in hRules:
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self._docTheme.keyword = SHARED.theme.colKey
|
self._docTheme.keyword = SHARED.theme.colKey
|
||||||
self._docTheme.tag = SHARED.theme.colTag
|
self._docTheme.tag = SHARED.theme.colTag
|
||||||
self._docTheme.optional = SHARED.theme.colOpt
|
self._docTheme.optional = SHARED.theme.colOpt
|
||||||
|
self._docTheme.dialog = SHARED.theme.colDialN
|
||||||
|
self._docTheme.altdialog = SHARED.theme.colDialA
|
||||||
|
|
||||||
# Set default text margins
|
# Set default text margins
|
||||||
self.document().setDocumentMargin(0)
|
self.document().setDocumentMargin(0)
|
||||||
@@ -201,6 +203,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
sPos = self.verticalScrollBar().value()
|
sPos = self.verticalScrollBar().value()
|
||||||
qDoc = ToQTextDocument(SHARED.project)
|
qDoc = ToQTextDocument(SHARED.project)
|
||||||
qDoc.setJustify(CONFIG.doJustify)
|
qDoc.setJustify(CONFIG.doJustify)
|
||||||
|
qDoc.setDialogueHighlight(True)
|
||||||
qDoc.initDocument(CONFIG.textFont, self._docTheme)
|
qDoc.initDocument(CONFIG.textFont, self._docTheme)
|
||||||
qDoc.setKeywords(True)
|
qDoc.setKeywords(True)
|
||||||
qDoc.setComments(CONFIG.viewComments)
|
qDoc.setComments(CONFIG.viewComments)
|
||||||
@@ -361,8 +364,10 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
"""Process a clicked link in the document."""
|
"""Process a clicked link in the document."""
|
||||||
if link := url.url():
|
if link := url.url():
|
||||||
logger.debug("Clicked link: '%s'", link)
|
logger.debug("Clicked link: '%s'", link)
|
||||||
if (bits := link.partition("_")) and bits[2]:
|
if (bits := link.partition("_")) and bits[0] == "#tag" and bits[2]:
|
||||||
self.loadDocumentTagRequest.emit(bits[2], nwDocMode.VIEW)
|
self.loadDocumentTagRequest.emit(bits[2], nwDocMode.VIEW)
|
||||||
|
else:
|
||||||
|
self.navigateTo(link)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot("QPoint")
|
@pyqtSlot("QPoint")
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
"""
|
||||||
|
novelWriter – Text Pattern Functions
|
||||||
|
====================================
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2024-06-01 [2.5ec1]
|
||||||
|
|
||||||
|
This file is a part of novelWriter
|
||||||
|
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QRegularExpression
|
||||||
|
|
||||||
|
from novelwriter import CONFIG
|
||||||
|
from novelwriter.constants import nwRegEx
|
||||||
|
from novelwriter.types import QRegExUnicode
|
||||||
|
|
||||||
|
|
||||||
|
class RegExPatterns:
|
||||||
|
|
||||||
|
@property
|
||||||
|
def markdownItalic(self) -> QRegularExpression:
|
||||||
|
"""Markdown italic style."""
|
||||||
|
rxRule = QRegularExpression(nwRegEx.FMT_EI)
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def markdownBold(self) -> QRegularExpression:
|
||||||
|
"""Markdown bold style."""
|
||||||
|
rxRule = QRegularExpression(nwRegEx.FMT_EB)
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def markdownStrike(self) -> QRegularExpression:
|
||||||
|
"""Markdown strikethrough style."""
|
||||||
|
rxRule = QRegularExpression(nwRegEx.FMT_ST)
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shortcodePlain(self) -> QRegularExpression:
|
||||||
|
"""Plain shortcode style."""
|
||||||
|
rxRule = QRegularExpression(nwRegEx.FMT_SC)
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shortcodeValue(self) -> QRegularExpression:
|
||||||
|
"""Plain shortcode style."""
|
||||||
|
rxRule = QRegularExpression(nwRegEx.FMT_SV)
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dialogStyle(self) -> QRegularExpression:
|
||||||
|
"""Dialogue detection rule based on user settings."""
|
||||||
|
symO = ""
|
||||||
|
symC = ""
|
||||||
|
if CONFIG.dialogStyle in (1, 3):
|
||||||
|
symO += CONFIG.fmtSQuoteOpen
|
||||||
|
symC += CONFIG.fmtSQuoteClose
|
||||||
|
if CONFIG.dialogStyle in (2, 3):
|
||||||
|
symO += CONFIG.fmtDQuoteOpen
|
||||||
|
symC += CONFIG.fmtDQuoteClose
|
||||||
|
|
||||||
|
rxEnd = "|$" if CONFIG.allowOpenDial else ""
|
||||||
|
rxRule = QRegularExpression(f"\\B[{symO}].*?(?:[{symC}]\\B{rxEnd})")
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dialogLine(self) -> QRegularExpression:
|
||||||
|
"""Dialogue line rule based on user settings."""
|
||||||
|
sym = QRegularExpression.escape(CONFIG.dialogLine)
|
||||||
|
rxRule = QRegularExpression(f"^{sym}.*?$")
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def narratorBreak(self) -> QRegularExpression:
|
||||||
|
"""Dialogue narrator break rule based on user settings."""
|
||||||
|
sym = QRegularExpression.escape(CONFIG.narratorBreak)
|
||||||
|
rxRule = QRegularExpression(f"\\B{sym}\\S.*?\\S{sym}\\B")
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def altDialogStyle(self) -> QRegularExpression:
|
||||||
|
"""Dialogue alternative rule based on user settings."""
|
||||||
|
symO = QRegularExpression.escape(CONFIG.altDialogOpen)
|
||||||
|
symC = QRegularExpression.escape(CONFIG.altDialogClose)
|
||||||
|
rxRule = QRegularExpression(f"\\B{symO}.*?{symC}\\B")
|
||||||
|
rxRule.setPatternOptions(QRegExUnicode)
|
||||||
|
return rxRule
|
||||||
|
|
||||||
|
|
||||||
|
REGEX_PATTERNS = RegExPatterns()
|
||||||
@@ -341,6 +341,8 @@ class GuiManuscript(NToolDialog):
|
|||||||
theme.keyword = QColor(245, 135, 31)
|
theme.keyword = QColor(245, 135, 31)
|
||||||
theme.tag = QColor(66, 113, 174)
|
theme.tag = QColor(66, 113, 174)
|
||||||
theme.optional = QColor(66, 113, 174)
|
theme.optional = QColor(66, 113, 174)
|
||||||
|
theme.dialog = QColor(66, 113, 174)
|
||||||
|
theme.altdialog = QColor(129, 55, 9)
|
||||||
|
|
||||||
self.docPreview.beginNewBuild(len(docBuild))
|
self.docPreview.beginNewBuild(len(docBuild))
|
||||||
for step, _ in docBuild.iterBuildPreview(theme):
|
for step, _ in docBuild.iterBuildPreview(theme):
|
||||||
|
|||||||
@@ -1093,11 +1093,13 @@ class _FormatTab(NScrollableForm):
|
|||||||
self.stripUnicode = NSwitch(self, height=iPx)
|
self.stripUnicode = NSwitch(self, height=iPx)
|
||||||
self.replaceTabs = NSwitch(self, height=iPx)
|
self.replaceTabs = NSwitch(self, height=iPx)
|
||||||
self.keepBreaks = NSwitch(self, height=iPx)
|
self.keepBreaks = NSwitch(self, height=iPx)
|
||||||
|
self.showDialogue = NSwitch(self, height=iPx)
|
||||||
|
|
||||||
self.addRow(self._build.getLabel("format.justifyText"), self.justifyText)
|
self.addRow(self._build.getLabel("format.justifyText"), self.justifyText)
|
||||||
self.addRow(self._build.getLabel("format.stripUnicode"), self.stripUnicode)
|
self.addRow(self._build.getLabel("format.stripUnicode"), self.stripUnicode)
|
||||||
self.addRow(self._build.getLabel("format.replaceTabs"), self.replaceTabs)
|
self.addRow(self._build.getLabel("format.replaceTabs"), self.replaceTabs)
|
||||||
self.addRow(self._build.getLabel("format.keepBreaks"), self.keepBreaks)
|
self.addRow(self._build.getLabel("format.keepBreaks"), self.keepBreaks)
|
||||||
|
self.addRow(self._build.getLabel("format.showDialogue"), self.showDialogue)
|
||||||
|
|
||||||
# First Line Indent
|
# First Line Indent
|
||||||
# =================
|
# =================
|
||||||
@@ -1180,6 +1182,7 @@ class _FormatTab(NScrollableForm):
|
|||||||
self.stripUnicode.setChecked(self._build.getBool("format.stripUnicode"))
|
self.stripUnicode.setChecked(self._build.getBool("format.stripUnicode"))
|
||||||
self.replaceTabs.setChecked(self._build.getBool("format.replaceTabs"))
|
self.replaceTabs.setChecked(self._build.getBool("format.replaceTabs"))
|
||||||
self.keepBreaks.setChecked(self._build.getBool("format.keepBreaks"))
|
self.keepBreaks.setChecked(self._build.getBool("format.keepBreaks"))
|
||||||
|
self.showDialogue.setChecked(self._build.getBool("format.showDialogue"))
|
||||||
|
|
||||||
self.firstIndent.setChecked(self._build.getBool("format.firstLineIndent"))
|
self.firstIndent.setChecked(self._build.getBool("format.firstLineIndent"))
|
||||||
self.indentWidth.setValue(self._build.getFloat("format.firstIndentWidth"))
|
self.indentWidth.setValue(self._build.getFloat("format.firstIndentWidth"))
|
||||||
@@ -1219,6 +1222,7 @@ class _FormatTab(NScrollableForm):
|
|||||||
self._build.setValue("format.stripUnicode", self.stripUnicode.isChecked())
|
self._build.setValue("format.stripUnicode", self.stripUnicode.isChecked())
|
||||||
self._build.setValue("format.replaceTabs", self.replaceTabs.isChecked())
|
self._build.setValue("format.replaceTabs", self.replaceTabs.isChecked())
|
||||||
self._build.setValue("format.keepBreaks", self.keepBreaks.isChecked())
|
self._build.setValue("format.keepBreaks", self.keepBreaks.isChecked())
|
||||||
|
self._build.setValue("format.showDialogue", self.showDialogue.isChecked())
|
||||||
|
|
||||||
self._build.setValue("format.firstLineIndent", self.firstIndent.isChecked())
|
self._build.setValue("format.firstLineIndent", self.firstIndent.isChecked())
|
||||||
self._build.setValue("format.firstIndentWidth", self.indentWidth.value())
|
self._build.setValue("format.firstIndentWidth", self.indentWidth.value())
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ mark {background: rgb(255, 255, 166);}
|
|||||||
.break {text-align: left;}
|
.break {text-align: left;}
|
||||||
.synopsis {font-style: italic;}
|
.synopsis {font-style: italic;}
|
||||||
.comment {font-style: italic; color: rgb(100, 100, 100);}
|
.comment {font-style: italic; color: rgb(100, 100, 100);}
|
||||||
|
.dialog {color: rgb(66, 113, 174);}
|
||||||
|
.altdialog {color: rgb(129, 55, 9);}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<article>
|
<article>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"projectName": "Lorem Ipsum",
|
"projectName": "Lorem Ipsum",
|
||||||
"novelAuthor": "lipsum.com",
|
"novelAuthor": "lipsum.com",
|
||||||
"buildTime": 1716803284,
|
"buildTime": 1718057434,
|
||||||
"buildTimeStr": "2024-05-27 11:48:04"
|
"buildTimeStr": "2024-06-11 00:10:34"
|
||||||
},
|
},
|
||||||
"text": {
|
"text": {
|
||||||
"css": [
|
"css": [
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
".keyword {color: rgb(245, 135, 31); font-weight: bold;}",
|
".keyword {color: rgb(245, 135, 31); font-weight: bold;}",
|
||||||
".break {text-align: left;}",
|
".break {text-align: left;}",
|
||||||
".synopsis {font-style: italic;}",
|
".synopsis {font-style: italic;}",
|
||||||
".comment {font-style: italic; color: rgb(100, 100, 100);}"
|
".comment {font-style: italic; color: rgb(100, 100, 100);}",
|
||||||
|
".dialog {color: rgb(66, 113, 174);}",
|
||||||
|
".altdialog {color: rgb(129, 55, 9);}"
|
||||||
],
|
],
|
||||||
"html": [
|
"html": [
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import json
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from novelwriter import CONFIG
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.core.tohtml import ToHtml
|
from novelwriter.core.tohtml import ToHtml
|
||||||
|
|
||||||
@@ -260,6 +261,28 @@ def testCoreToHtml_ConvertParagraphs(mockGUI):
|
|||||||
"<a class='tag' href='#tag_Europe'>Europe</a></p>\n"
|
"<a class='tag' href='#tag_Europe'>Europe</a></p>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Dialogue
|
||||||
|
html.setDialogueHighlight(True)
|
||||||
|
html._text = "## Chapter\n\nThis text \u201chas dialogue\u201d in it.\n\n"
|
||||||
|
html.tokenizeText()
|
||||||
|
html.doConvert()
|
||||||
|
assert html.result == (
|
||||||
|
"<h1 style='page-break-before: always;'>Chapter</h1>\n"
|
||||||
|
"<p>This text <span class='dialog'>\u201chas dialogue\u201d</span> in it.</p>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Alt. Dialogue
|
||||||
|
CONFIG.altDialogOpen = "::"
|
||||||
|
CONFIG.altDialogClose = "::"
|
||||||
|
html.setDialogueHighlight(True)
|
||||||
|
html._text = "## Chapter\n\nThis text :: has alt dialogue :: in it.\n\n"
|
||||||
|
html.tokenizeText()
|
||||||
|
html.doConvert()
|
||||||
|
assert html.result == (
|
||||||
|
"<h1 style='page-break-before: always;'>Chapter</h1>\n"
|
||||||
|
"<p>This text <span class='altdialog'>:: has alt dialogue ::</span> in it.</p>\n"
|
||||||
|
)
|
||||||
|
|
||||||
# Footnotes
|
# Footnotes
|
||||||
# =========
|
# =========
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import pytest
|
|||||||
|
|
||||||
from PyQt5.QtGui import QFont
|
from PyQt5.QtGui import QFont
|
||||||
|
|
||||||
|
from novelwriter import CONFIG
|
||||||
from novelwriter.constants import nwHeadFmt
|
from novelwriter.constants import nwHeadFmt
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.core.tokenizer import HeadingFormatter, Tokenizer, stripEscape
|
from novelwriter.core.tokenizer import HeadingFormatter, Tokenizer, stripEscape
|
||||||
@@ -1016,88 +1017,151 @@ def testCoreToken_TextFormat(mockGUI):
|
|||||||
# Text Emphasis
|
# Text Emphasis
|
||||||
tokens._text = "Some **bolded text** on this lines\n"
|
tokens._text = "Some **bolded text** on this lines\n"
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
assert tokens._tokens == [
|
assert tokens._tokens == [(
|
||||||
(
|
Tokenizer.T_TEXT, 0, "Some bolded text on this lines",
|
||||||
Tokenizer.T_TEXT, 0,
|
[
|
||||||
"Some bolded text on this lines",
|
(5, Tokenizer.FMT_B_B, ""),
|
||||||
[
|
(16, Tokenizer.FMT_B_E, ""),
|
||||||
(5, Tokenizer.FMT_B_B, ""),
|
],
|
||||||
(16, Tokenizer.FMT_B_E, ""),
|
Tokenizer.A_NONE
|
||||||
],
|
)]
|
||||||
Tokenizer.A_NONE
|
|
||||||
),
|
|
||||||
]
|
|
||||||
assert tokens.allMarkdown[-1] == "Some **bolded text** on this lines\n\n"
|
assert tokens.allMarkdown[-1] == "Some **bolded text** on this lines\n\n"
|
||||||
|
|
||||||
tokens._text = "Some _italic text_ on this lines\n"
|
tokens._text = "Some _italic text_ on this lines\n"
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
assert tokens._tokens == [
|
assert tokens._tokens == [(
|
||||||
(
|
Tokenizer.T_TEXT, 0, "Some italic text on this lines",
|
||||||
Tokenizer.T_TEXT, 0,
|
[
|
||||||
"Some italic text on this lines",
|
(5, Tokenizer.FMT_I_B, ""),
|
||||||
[
|
(16, Tokenizer.FMT_I_E, ""),
|
||||||
(5, Tokenizer.FMT_I_B, ""),
|
],
|
||||||
(16, Tokenizer.FMT_I_E, ""),
|
Tokenizer.A_NONE
|
||||||
],
|
)]
|
||||||
Tokenizer.A_NONE
|
|
||||||
),
|
|
||||||
]
|
|
||||||
assert tokens.allMarkdown[-1] == "Some _italic text_ on this lines\n\n"
|
assert tokens.allMarkdown[-1] == "Some _italic text_ on this lines\n\n"
|
||||||
|
|
||||||
tokens._text = "Some **_bold italic text_** on this lines\n"
|
tokens._text = "Some **_bold italic text_** on this lines\n"
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
assert tokens._tokens == [
|
assert tokens._tokens == [(
|
||||||
(
|
Tokenizer.T_TEXT, 0, "Some bold italic text on this lines",
|
||||||
Tokenizer.T_TEXT, 0,
|
[
|
||||||
"Some bold italic text on this lines",
|
(5, Tokenizer.FMT_B_B, ""),
|
||||||
[
|
(5, Tokenizer.FMT_I_B, ""),
|
||||||
(5, Tokenizer.FMT_B_B, ""),
|
(21, Tokenizer.FMT_I_E, ""),
|
||||||
(5, Tokenizer.FMT_I_B, ""),
|
(21, Tokenizer.FMT_B_E, ""),
|
||||||
(21, Tokenizer.FMT_I_E, ""),
|
],
|
||||||
(21, Tokenizer.FMT_B_E, ""),
|
Tokenizer.A_NONE
|
||||||
],
|
)]
|
||||||
Tokenizer.A_NONE
|
|
||||||
),
|
|
||||||
]
|
|
||||||
assert tokens.allMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n"
|
assert tokens.allMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n"
|
||||||
|
|
||||||
tokens._text = "Some ~~strikethrough text~~ on this lines\n"
|
tokens._text = "Some ~~strikethrough text~~ on this lines\n"
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
assert tokens._tokens == [
|
assert tokens._tokens == [(
|
||||||
(
|
Tokenizer.T_TEXT, 0, "Some strikethrough text on this lines",
|
||||||
Tokenizer.T_TEXT, 0,
|
[
|
||||||
"Some strikethrough text on this lines",
|
(5, Tokenizer.FMT_D_B, ""),
|
||||||
[
|
(23, Tokenizer.FMT_D_E, ""),
|
||||||
(5, Tokenizer.FMT_D_B, ""),
|
],
|
||||||
(23, Tokenizer.FMT_D_E, ""),
|
Tokenizer.A_NONE
|
||||||
],
|
)]
|
||||||
Tokenizer.A_NONE
|
|
||||||
),
|
|
||||||
]
|
|
||||||
assert tokens.allMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
|
assert tokens.allMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
|
||||||
|
|
||||||
tokens._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
tokens._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
assert tokens._tokens == [
|
assert tokens._tokens == [(
|
||||||
(
|
Tokenizer.T_TEXT, 0, "Some nested bold and italic and strikethrough text here",
|
||||||
Tokenizer.T_TEXT, 0,
|
[
|
||||||
"Some nested bold and italic and strikethrough text here",
|
(5, Tokenizer.FMT_B_B, ""),
|
||||||
[
|
(21, Tokenizer.FMT_I_B, ""),
|
||||||
(5, Tokenizer.FMT_B_B, ""),
|
(27, Tokenizer.FMT_I_E, ""),
|
||||||
(21, Tokenizer.FMT_I_B, ""),
|
(32, Tokenizer.FMT_D_B, ""),
|
||||||
(27, Tokenizer.FMT_I_E, ""),
|
(45, Tokenizer.FMT_D_E, ""),
|
||||||
(32, Tokenizer.FMT_D_B, ""),
|
(50, Tokenizer.FMT_B_E, ""),
|
||||||
(45, Tokenizer.FMT_D_E, ""),
|
],
|
||||||
(50, Tokenizer.FMT_B_E, ""),
|
Tokenizer.A_NONE
|
||||||
],
|
)]
|
||||||
Tokenizer.A_NONE
|
|
||||||
),
|
|
||||||
]
|
|
||||||
assert tokens.allMarkdown[-1] == (
|
assert tokens.allMarkdown[-1] == (
|
||||||
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
|
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testCoreToken_Dialogue(mockGUI):
|
||||||
|
"""Test the tokenization of dialogue in the Tokenizer class."""
|
||||||
|
CONFIG.fmtDQuoteOpen = "\u201c"
|
||||||
|
CONFIG.fmtDQuoteClose = "\u201d"
|
||||||
|
CONFIG.fmtSQuoteOpen = "\u2018"
|
||||||
|
CONFIG.fmtSQuoteClose = "\u2019"
|
||||||
|
CONFIG.dialogStyle = 3
|
||||||
|
CONFIG.altDialogOpen = "::"
|
||||||
|
CONFIG.altDialogClose = "::"
|
||||||
|
CONFIG.dialogLine = "\u2013"
|
||||||
|
CONFIG.narratorBreak = "\u2013"
|
||||||
|
|
||||||
|
project = NWProject()
|
||||||
|
tokens = BareTokenizer(project)
|
||||||
|
tokens.setDialogueHighlight(True)
|
||||||
|
|
||||||
|
# Single quotes
|
||||||
|
tokens._text = "Text with \u2018dialogue one,\u2019 and \u2018dialogue two.\u2019\n"
|
||||||
|
tokens.tokenizeText()
|
||||||
|
assert tokens._tokens == [(
|
||||||
|
Tokenizer.T_TEXT, 0,
|
||||||
|
"Text with \u2018dialogue one,\u2019 and \u2018dialogue two.\u2019",
|
||||||
|
[
|
||||||
|
(10, Tokenizer.FMT_DL_B, ""),
|
||||||
|
(25, Tokenizer.FMT_DL_E, ""),
|
||||||
|
(30, Tokenizer.FMT_DL_B, ""),
|
||||||
|
(45, Tokenizer.FMT_DL_E, ""),
|
||||||
|
],
|
||||||
|
Tokenizer.A_NONE
|
||||||
|
)]
|
||||||
|
|
||||||
|
# Double quotes
|
||||||
|
tokens._text = "Text with \u201cdialogue one,\u201d and \u201cdialogue two.\u201d\n"
|
||||||
|
tokens.tokenizeText()
|
||||||
|
assert tokens._tokens == [(
|
||||||
|
Tokenizer.T_TEXT, 0,
|
||||||
|
"Text with \u201cdialogue one,\u201d and \u201cdialogue two.\u201d",
|
||||||
|
[
|
||||||
|
(10, Tokenizer.FMT_DL_B, ""),
|
||||||
|
(25, Tokenizer.FMT_DL_E, ""),
|
||||||
|
(30, Tokenizer.FMT_DL_B, ""),
|
||||||
|
(45, Tokenizer.FMT_DL_E, ""),
|
||||||
|
],
|
||||||
|
Tokenizer.A_NONE
|
||||||
|
)]
|
||||||
|
|
||||||
|
# Alt quotes
|
||||||
|
tokens._text = "Text with ::dialogue one,:: and ::dialogue two.::\n"
|
||||||
|
tokens.tokenizeText()
|
||||||
|
assert tokens._tokens == [(
|
||||||
|
Tokenizer.T_TEXT, 0,
|
||||||
|
"Text with ::dialogue one,:: and ::dialogue two.::",
|
||||||
|
[
|
||||||
|
(10, Tokenizer.FMT_ADL_B, ""),
|
||||||
|
(27, Tokenizer.FMT_ADL_E, ""),
|
||||||
|
(32, Tokenizer.FMT_ADL_B, ""),
|
||||||
|
(49, Tokenizer.FMT_ADL_E, ""),
|
||||||
|
],
|
||||||
|
Tokenizer.A_NONE
|
||||||
|
)]
|
||||||
|
|
||||||
|
# Dialogue line with narrator break
|
||||||
|
tokens._text = "\u2013 Dialogue with a narrator break, \u2013he said,\u2013 see?\n"
|
||||||
|
tokens.tokenizeText()
|
||||||
|
assert tokens._tokens == [(
|
||||||
|
Tokenizer.T_TEXT, 0,
|
||||||
|
"\u2013 Dialogue with a narrator break, \u2013he said,\u2013 see?",
|
||||||
|
[
|
||||||
|
(0, Tokenizer.FMT_DL_B, ""),
|
||||||
|
(34, Tokenizer.FMT_DL_E, ""),
|
||||||
|
(44, Tokenizer.FMT_DL_B, ""),
|
||||||
|
(49, Tokenizer.FMT_DL_E, ""),
|
||||||
|
],
|
||||||
|
Tokenizer.A_NONE
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreToken_SpecialFormat(mockGUI):
|
def testCoreToken_SpecialFormat(mockGUI):
|
||||||
"""Test the tokenization of special formats in the Tokenizer class."""
|
"""Test the tokenization of special formats in the Tokenizer class."""
|
||||||
@@ -1267,11 +1331,6 @@ def testCoreToken_ProcessHeaders(mockGUI):
|
|||||||
project.data.setLanguage("en")
|
project.data.setLanguage("en")
|
||||||
project._loadProjectLocalisation()
|
project._loadProjectLocalisation()
|
||||||
tokens = BareTokenizer(project)
|
tokens = BareTokenizer(project)
|
||||||
|
|
||||||
##
|
|
||||||
# Story Files
|
|
||||||
##
|
|
||||||
|
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
|
|
||||||
# Titles
|
# Titles
|
||||||
|
|||||||
@@ -234,6 +234,42 @@ def testCoreToOdt_TextFormatting(mockGUI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testCoreToOdt_DialogueFormatting(mockGUI):
|
||||||
|
"""Test formatting of dialogue."""
|
||||||
|
project = NWProject()
|
||||||
|
odt = ToOdt(project, isFlat=True)
|
||||||
|
odt.setDialogueHighlight(True)
|
||||||
|
odt.initDocument()
|
||||||
|
oStyle = ODTParagraphStyle("test")
|
||||||
|
|
||||||
|
# Regular dialogue
|
||||||
|
text = "Text with 'dialogue in it.'"
|
||||||
|
fmt = [(10, odt.FMT_DL_B, ""), (27, odt.FMT_DL_E, "")]
|
||||||
|
xTest = ET.Element(_mkTag("office", "text"))
|
||||||
|
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
|
||||||
|
assert odt.errData == []
|
||||||
|
assert xmlToText(xTest) == (
|
||||||
|
'<office:text>'
|
||||||
|
'<text:p text:style-name="Standard">Text with '
|
||||||
|
'<text:span text:style-name="T1">\'dialogue in it.\'</text:span></text:p>'
|
||||||
|
'</office:text>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Alternative dialogue
|
||||||
|
text = "Text with ::dialogue in it.::"
|
||||||
|
fmt = [(10, odt.FMT_ADL_B, ""), (29, odt.FMT_ADL_E, "")]
|
||||||
|
xTest = ET.Element(_mkTag("office", "text"))
|
||||||
|
odt._addTextPar(xTest, "Standard", oStyle, text, tFmt=fmt)
|
||||||
|
assert odt.errData == []
|
||||||
|
assert xmlToText(xTest) == (
|
||||||
|
'<office:text>'
|
||||||
|
'<text:p text:style-name="Standard">Text with '
|
||||||
|
'<text:span text:style-name="T2">::dialogue in it.::</text:span></text:p>'
|
||||||
|
'</office:text>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreToOdt_ConvertHeaders(mockGUI):
|
def testCoreToOdt_ConvertHeaders(mockGUI):
|
||||||
"""Test the converter of the ToOdt class."""
|
"""Test the converter of the ToOdt class."""
|
||||||
@@ -854,8 +890,8 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreToOdt_Format(mockGUI):
|
def testCoreToOdt_SpecialFormats(mockGUI):
|
||||||
"""Test the formatters for the ToOdt class."""
|
"""Test the special formatters for the ToOdt class."""
|
||||||
project = NWProject()
|
project = NWProject()
|
||||||
odt = ToOdt(project, isFlat=True)
|
odt = ToOdt(project, isFlat=True)
|
||||||
|
|
||||||
@@ -1168,6 +1204,17 @@ def testCoreToOdt_ODTTextStyle():
|
|||||||
txtStyle.setFontStyle("stuff")
|
txtStyle.setFontStyle("stuff")
|
||||||
assert txtStyle._tAttr["font-style"] == ["fo", None]
|
assert txtStyle._tAttr["font-style"] == ["fo", None]
|
||||||
|
|
||||||
|
# Text Color
|
||||||
|
assert txtStyle._tAttr["color"] == ["fo", None]
|
||||||
|
txtStyle.setColour("stuff")
|
||||||
|
assert txtStyle._tAttr["color"] == ["fo", None]
|
||||||
|
txtStyle.setColour("012345")
|
||||||
|
assert txtStyle._tAttr["color"] == ["fo", None]
|
||||||
|
txtStyle.setColour("#012345")
|
||||||
|
assert txtStyle._tAttr["color"] == ["fo", "#012345"]
|
||||||
|
txtStyle.setColour("stuff")
|
||||||
|
assert txtStyle._tAttr["color"] == ["fo", None]
|
||||||
|
|
||||||
# Background Color
|
# Background Color
|
||||||
assert txtStyle._tAttr["background-color"] == ["fo", None]
|
assert txtStyle._tAttr["background-color"] == ["fo", None]
|
||||||
txtStyle.setBackgroundColour("stuff")
|
txtStyle.setBackgroundColour("stuff")
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ THEME.modifier = QColor(129, 55, 9)
|
|||||||
THEME.keyword = QColor(245, 135, 31)
|
THEME.keyword = QColor(245, 135, 31)
|
||||||
THEME.tag = QColor(66, 113, 174)
|
THEME.tag = QColor(66, 113, 174)
|
||||||
THEME.optional = QColor(66, 113, 174)
|
THEME.optional = QColor(66, 113, 174)
|
||||||
|
THEME.dialog = QColor(113, 140, 0)
|
||||||
|
THEME.altdialog = QColor(234, 183, 0)
|
||||||
|
|
||||||
|
|
||||||
def charFmtInBlock(block: QTextBlock, pos: int) -> QTextCharFormat:
|
def charFmtInBlock(block: QTextBlock, pos: int) -> QTextCharFormat:
|
||||||
@@ -441,11 +443,17 @@ def testCoreToQTextDocument_TextBlockFormats(mockGUI):
|
|||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreToQTextDocument_TextCharFormats(mockGUI):
|
def testCoreToQTextDocument_TextCharFormats(mockGUI):
|
||||||
"""Test text char formats in the ToQTextDocument class."""
|
"""Test text char formats in the ToQTextDocument class."""
|
||||||
|
CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO
|
||||||
|
CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO
|
||||||
|
CONFIG.altDialogOpen = "|<"
|
||||||
|
CONFIG.altDialogClose = ">|"
|
||||||
|
|
||||||
project = NWProject()
|
project = NWProject()
|
||||||
qdoc = ToQTextDocument(project)
|
qdoc = ToQTextDocument(project)
|
||||||
|
|
||||||
# Convert before init
|
# Convert before init
|
||||||
qdoc._text = "Blabla"
|
qdoc._text = "Blabla"
|
||||||
|
qdoc.setDialogueHighlight(True)
|
||||||
qdoc.doConvert()
|
qdoc.doConvert()
|
||||||
qdoc.tokenizeText()
|
qdoc.tokenizeText()
|
||||||
assert qdoc.document.toPlainText() == ""
|
assert qdoc.document.toPlainText() == ""
|
||||||
@@ -465,10 +473,12 @@ def testCoreToQTextDocument_TextCharFormats(mockGUI):
|
|||||||
"With [m]highlighted[/m] text\n\n"
|
"With [m]highlighted[/m] text\n\n"
|
||||||
"With super[sup]script[/sup] text\n\n"
|
"With super[sup]script[/sup] text\n\n"
|
||||||
"With sub[sub]script[/sub] text\n\n"
|
"With sub[sub]script[/sub] text\n\n"
|
||||||
|
"With \u201cdialog\u201d text\n\n"
|
||||||
|
"With |<alternative dialog>| text\n\n"
|
||||||
)
|
)
|
||||||
qdoc.tokenizeText()
|
qdoc.tokenizeText()
|
||||||
qdoc.doConvert()
|
qdoc.doConvert()
|
||||||
assert qdoc.document.blockCount() == 8
|
assert qdoc.document.blockCount() == 10
|
||||||
|
|
||||||
# 0: Scene
|
# 0: Scene
|
||||||
block = qdoc.document.findBlockByNumber(0)
|
block = qdoc.document.findBlockByNumber(0)
|
||||||
@@ -544,6 +554,26 @@ def testCoreToQTextDocument_TextCharFormats(mockGUI):
|
|||||||
cFmt = charFmtInBlock(block, 15)
|
cFmt = charFmtInBlock(block, 15)
|
||||||
assert cFmt.verticalAlignment() == QtVAlignNormal
|
assert cFmt.verticalAlignment() == QtVAlignNormal
|
||||||
|
|
||||||
|
# 8: Dialogue
|
||||||
|
block = qdoc.document.findBlockByNumber(8)
|
||||||
|
assert block.text() == "With \u201cdialog\u201d text"
|
||||||
|
cFmt = charFmtInBlock(block, 1)
|
||||||
|
assert cFmt.foreground() == THEME.text
|
||||||
|
cFmt = charFmtInBlock(block, 6)
|
||||||
|
assert cFmt.foreground() == THEME.dialog
|
||||||
|
cFmt = charFmtInBlock(block, 14)
|
||||||
|
assert cFmt.foreground() == THEME.text
|
||||||
|
|
||||||
|
# 9: Alt. Dialogue
|
||||||
|
block = qdoc.document.findBlockByNumber(9)
|
||||||
|
assert block.text() == "With |<alternative dialog>| text"
|
||||||
|
cFmt = charFmtInBlock(block, 1)
|
||||||
|
assert cFmt.foreground() == THEME.text
|
||||||
|
cFmt = charFmtInBlock(block, 6)
|
||||||
|
assert cFmt.foreground() == THEME.altdialog
|
||||||
|
cFmt = charFmtInBlock(block, 28)
|
||||||
|
assert cFmt.foreground() == THEME.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreToQTextDocument_Footnotes(mockGUI):
|
def testCoreToQTextDocument_Footnotes(mockGUI):
|
||||||
|
|||||||
@@ -160,6 +160,11 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
|
|||||||
docViewer._linkClicked(QUrl("#tag_bod"))
|
docViewer._linkClicked(QUrl("#tag_bod"))
|
||||||
assert docViewer.docHandle == "4c4f28287af27"
|
assert docViewer.docHandle == "4c4f28287af27"
|
||||||
|
|
||||||
|
# Other links should just trigger a navigate call
|
||||||
|
with qtbot.waitSignal(docViewer.sourceChanged, timeout=1000) as signal:
|
||||||
|
docViewer._linkClicked(QUrl("#somewhere_else"))
|
||||||
|
assert signal.args[0].url() == "#somewhere_else"
|
||||||
|
|
||||||
# Click mouse nav buttons
|
# Click mouse nav buttons
|
||||||
qtbot.mouseClick(docViewer.viewport(), Qt.BackButton, pos=rect.center(), delay=100)
|
qtbot.mouseClick(docViewer.viewport(), Qt.BackButton, pos=rect.center(), delay=100)
|
||||||
assert docViewer.docHandle == "88243afbe5ed8"
|
assert docViewer.docHandle == "88243afbe5ed8"
|
||||||
|
|||||||
@@ -64,9 +64,6 @@ def testGuiI18n_Localisation(qtbot, monkeypatch, language, nwGUI, projPath):
|
|||||||
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(dType) is not None, timeout=1000)
|
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(dType) is not None, timeout=1000)
|
||||||
dialog = SHARED.findTopLevelWidget(dType)
|
dialog = SHARED.findTopLevelWidget(dType)
|
||||||
assert isinstance(dialog, dType)
|
assert isinstance(dialog, dType)
|
||||||
with qtbot.waitExposed(dialog):
|
|
||||||
dialog.show()
|
|
||||||
dialog.close()
|
|
||||||
|
|
||||||
showDialog(nwGUI.showWelcomeDialog, GuiWelcome)
|
showDialog(nwGUI.showWelcomeDialog, GuiWelcome)
|
||||||
showDialog(nwGUI.showPreferencesDialog, GuiPreferences)
|
showDialog(nwGUI.showPreferencesDialog, GuiPreferences)
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
"""
|
||||||
|
novelWriter – Patterns Module Tester
|
||||||
|
====================================
|
||||||
|
|
||||||
|
This file is a part of novelWriter
|
||||||
|
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QRegularExpression
|
||||||
|
|
||||||
|
from novelwriter import CONFIG
|
||||||
|
from novelwriter.constants import nwUnicode
|
||||||
|
from novelwriter.text.patterns import REGEX_PATTERNS
|
||||||
|
|
||||||
|
|
||||||
|
def allMatches(regEx: QRegularExpression, text: str) -> list[list[str]]:
|
||||||
|
"""Get all matches for a regex."""
|
||||||
|
result = []
|
||||||
|
itt = regEx.globalMatch(text, 0)
|
||||||
|
while itt.hasNext():
|
||||||
|
match = itt.next()
|
||||||
|
result.append([
|
||||||
|
(match.captured(n), match.capturedStart(n), match.capturedEnd(n))
|
||||||
|
for n in range(match.lastCapturedIndex() + 1)
|
||||||
|
])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testTextPatterns_Markdown():
|
||||||
|
"""Test the markdown pattern regexes."""
|
||||||
|
# Bold
|
||||||
|
regEx = REGEX_PATTERNS.markdownBold
|
||||||
|
assert allMatches(regEx, "one **two** three") == [
|
||||||
|
[("**two**", 4, 11), ("**", 4, 6), ("two", 6, 9), ("**", 9, 11)]
|
||||||
|
]
|
||||||
|
assert allMatches(regEx, "one **two* three") == []
|
||||||
|
assert allMatches(regEx, "one *two** three") == []
|
||||||
|
assert allMatches(regEx, "one**two**three") == []
|
||||||
|
|
||||||
|
# Italic
|
||||||
|
regEx = REGEX_PATTERNS.markdownItalic
|
||||||
|
assert allMatches(regEx, "one _two_ three") == [
|
||||||
|
[("_two_", 4, 9), ("_", 4, 5), ("two", 5, 8), ("_", 8, 9)]
|
||||||
|
]
|
||||||
|
assert allMatches(regEx, "one __two_ three") == []
|
||||||
|
assert allMatches(regEx, "one _two__ three") == [
|
||||||
|
[("_two__", 4, 10), ("_", 4, 5), ("two_", 5, 9), ("_", 9, 10)]
|
||||||
|
]
|
||||||
|
assert allMatches(regEx, "one_two_three") == []
|
||||||
|
|
||||||
|
# Strike
|
||||||
|
regEx = REGEX_PATTERNS.markdownStrike
|
||||||
|
assert allMatches(regEx, "one ~~two~~ three") == [
|
||||||
|
[("~~two~~", 4, 11), ("~~", 4, 6), ("two", 6, 9), ("~~", 9, 11)]
|
||||||
|
]
|
||||||
|
assert allMatches(regEx, "one ~~two~ three") == []
|
||||||
|
assert allMatches(regEx, "one ~two~~ three") == []
|
||||||
|
assert allMatches(regEx, "one~~two~~three") == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testTextPatterns_ShortcodesPlain():
|
||||||
|
"""Test the shortcode pattern regexes."""
|
||||||
|
regEx = REGEX_PATTERNS.shortcodePlain
|
||||||
|
|
||||||
|
# Test Usage
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
# General, normal usage
|
||||||
|
assert allMatches(regEx, "one [b]two[/b] three") == [
|
||||||
|
[("[b]", 4, 7), ("[b]", 4, 7)],
|
||||||
|
[("[/b]", 10, 14), ("[/b]", 10, 14)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# General, no spaces
|
||||||
|
assert allMatches(regEx, "one[b]two[/b]three") == [
|
||||||
|
[("[b]", 3, 6), ("[b]", 3, 6)],
|
||||||
|
[("[/b]", 9, 13), ("[/b]", 9, 13)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# General, with padding
|
||||||
|
assert allMatches(regEx, "one [b] two [/b] three") == [
|
||||||
|
[("[b]", 4, 7), ("[b]", 4, 7)],
|
||||||
|
[("[/b]", 12, 16), ("[/b]", 12, 16)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# General, with escapes
|
||||||
|
assert allMatches(regEx, "one \\[b]two[/b\\] three") == []
|
||||||
|
|
||||||
|
# Test Formats
|
||||||
|
# ============
|
||||||
|
|
||||||
|
# Bold
|
||||||
|
assert allMatches(regEx, "one [b]two[/b] three") == [
|
||||||
|
[("[b]", 4, 7), ("[b]", 4, 7)],
|
||||||
|
[("[/b]", 10, 14), ("[/b]", 10, 14)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Italic
|
||||||
|
assert allMatches(regEx, "one [i]two[/i] three") == [
|
||||||
|
[("[i]", 4, 7), ("[i]", 4, 7)],
|
||||||
|
[("[/i]", 10, 14), ("[/i]", 10, 14)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Strike
|
||||||
|
assert allMatches(regEx, "one [s]two[/s] three") == [
|
||||||
|
[("[s]", 4, 7), ("[s]", 4, 7)],
|
||||||
|
[("[/s]", 10, 14), ("[/s]", 10, 14)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Underline
|
||||||
|
assert allMatches(regEx, "one [u]two[/u] three") == [
|
||||||
|
[("[u]", 4, 7), ("[u]", 4, 7)],
|
||||||
|
[("[/u]", 10, 14), ("[/u]", 10, 14)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Mark
|
||||||
|
assert allMatches(regEx, "one [m]two[/m] three") == [
|
||||||
|
[("[m]", 4, 7), ("[m]", 4, 7)],
|
||||||
|
[("[/m]", 10, 14), ("[/m]", 10, 14)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Superscript
|
||||||
|
assert allMatches(regEx, "one [sup]two[/sup] three") == [
|
||||||
|
[("[sup]", 4, 9), ("[sup]", 4, 9)],
|
||||||
|
[("[/sup]", 12, 18), ("[/sup]", 12, 18)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Subscript
|
||||||
|
assert allMatches(regEx, "one [sub]two[/sub] three") == [
|
||||||
|
[("[sub]", 4, 9), ("[sub]", 4, 9)],
|
||||||
|
[("[/sub]", 12, 18), ("[/sub]", 12, 18)],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test Invalid
|
||||||
|
# ============
|
||||||
|
|
||||||
|
assert allMatches(regEx, "one [x]two[/x] three") == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testTextPatterns_ShortcodesValue():
|
||||||
|
"""Test the shortcode with value pattern regexes."""
|
||||||
|
regEx = REGEX_PATTERNS.shortcodeValue
|
||||||
|
|
||||||
|
assert allMatches(regEx, "one [footnote:two] three") == [
|
||||||
|
[("[footnote:two]", 4, 18), ("[footnote:", 4, 14), ("two", 14, 17), ("]", 17, 18)]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testTextPatterns_DialogueStyle():
|
||||||
|
"""Test the dialogue style pattern regexes."""
|
||||||
|
# Set the config
|
||||||
|
CONFIG.fmtSQuoteOpen = nwUnicode.U_LSQUO
|
||||||
|
CONFIG.fmtSQuoteClose = nwUnicode.U_RSQUO
|
||||||
|
CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO
|
||||||
|
CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO
|
||||||
|
|
||||||
|
CONFIG.dialogStyle = 3
|
||||||
|
|
||||||
|
# Closed
|
||||||
|
# ======
|
||||||
|
|
||||||
|
CONFIG.allowOpenDial = False
|
||||||
|
regEx = REGEX_PATTERNS.dialogStyle
|
||||||
|
|
||||||
|
# Defined single quotes are recognised
|
||||||
|
assert allMatches(regEx, "one \u2018two\u2019 three") == [
|
||||||
|
[("\u2018two\u2019", 4, 9)]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Defined double quotes are recognised
|
||||||
|
assert allMatches(regEx, "one \u201ctwo\u201d three") == [
|
||||||
|
[("\u201ctwo\u201d", 4, 9)]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Straight single quotes are ignored
|
||||||
|
assert allMatches(regEx, "one 'two' three") == []
|
||||||
|
|
||||||
|
# Straight double quotes are ignored
|
||||||
|
assert allMatches(regEx, "one \"two\" three") == []
|
||||||
|
|
||||||
|
# Skipping whitespace is not allowed
|
||||||
|
assert allMatches(regEx, "one\u2018two\u2019three") == []
|
||||||
|
|
||||||
|
# Open
|
||||||
|
# ====
|
||||||
|
|
||||||
|
CONFIG.allowOpenDial = True
|
||||||
|
regEx = REGEX_PATTERNS.dialogStyle
|
||||||
|
|
||||||
|
# Defined single quotes are recognised also when open
|
||||||
|
assert allMatches(regEx, "one \u2018two three") == [
|
||||||
|
[("\u2018two three", 4, 14)]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Defined double quotes are recognised also when open
|
||||||
|
assert allMatches(regEx, "one \u201ctwo three") == [
|
||||||
|
[("\u201ctwo three", 4, 14)]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testTextPatterns_DialogueSpecial():
|
||||||
|
"""Test the special dialogue style pattern regexes."""
|
||||||
|
# Set the config
|
||||||
|
CONFIG.fmtSQuoteOpen = nwUnicode.U_LSQUO
|
||||||
|
CONFIG.fmtSQuoteClose = nwUnicode.U_RSQUO
|
||||||
|
CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO
|
||||||
|
CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO
|
||||||
|
|
||||||
|
CONFIG.dialogStyle = 3
|
||||||
|
CONFIG.dialogLine = nwUnicode.U_ENDASH
|
||||||
|
CONFIG.narratorBreak = nwUnicode.U_ENDASH
|
||||||
|
CONFIG.altDialogOpen = "::"
|
||||||
|
CONFIG.altDialogClose = "::"
|
||||||
|
|
||||||
|
# Dialogue Line
|
||||||
|
# =============
|
||||||
|
regEx = REGEX_PATTERNS.dialogLine
|
||||||
|
|
||||||
|
# Check dialogue line in first position
|
||||||
|
assert allMatches(regEx, "\u2013 one two three") == [
|
||||||
|
[("\u2013 one two three", 0, 15)]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check dialogue line in second position
|
||||||
|
assert allMatches(regEx, " \u2013 one two three") == []
|
||||||
|
|
||||||
|
# Narrator Break
|
||||||
|
# ==============
|
||||||
|
regEx = REGEX_PATTERNS.narratorBreak
|
||||||
|
|
||||||
|
# Narrator break with no padding
|
||||||
|
assert allMatches(regEx, "one \u2013two\u2013 three") == [
|
||||||
|
[("\u2013two\u2013", 4, 9)]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Narrator break with padding
|
||||||
|
assert allMatches(regEx, "one \u2013 two \u2013 three") == []
|
||||||
|
|
||||||
|
# Alternative Dialogue
|
||||||
|
# ====================
|
||||||
|
regEx = REGEX_PATTERNS.altDialogStyle
|
||||||
|
|
||||||
|
# With no padding
|
||||||
|
assert allMatches(regEx, "one ::two:: three") == [
|
||||||
|
[("::two::", 4, 11)]
|
||||||
|
]
|
||||||
|
|
||||||
|
# With padding
|
||||||
|
assert allMatches(regEx, "one :: two :: three") == [
|
||||||
|
[(":: two ::", 4, 13)]
|
||||||
|
]
|
||||||
@@ -555,6 +555,12 @@ def testToolBuildSettings_Format(monkeypatch, qtbot, nwGUI):
|
|||||||
build.setValue("format.justifyText", False)
|
build.setValue("format.justifyText", False)
|
||||||
build.setValue("format.stripUnicode", False)
|
build.setValue("format.stripUnicode", False)
|
||||||
build.setValue("format.replaceTabs", False)
|
build.setValue("format.replaceTabs", False)
|
||||||
|
build.setValue("format.keepBreaks", True)
|
||||||
|
build.setValue("format.showDialogue", False)
|
||||||
|
|
||||||
|
build.setValue("format.firstLineIndent", False)
|
||||||
|
build.setValue("format.firstIndentWidth", 1.4)
|
||||||
|
build.setValue("format.indentFirstPar", False)
|
||||||
|
|
||||||
build.setValue("format.pageUnit", "mm")
|
build.setValue("format.pageUnit", "mm")
|
||||||
build.setValue("format.pageSize", "Custom")
|
build.setValue("format.pageSize", "Custom")
|
||||||
@@ -580,6 +586,8 @@ def testToolBuildSettings_Format(monkeypatch, qtbot, nwGUI):
|
|||||||
assert fmtTab.justifyText.isChecked() is False
|
assert fmtTab.justifyText.isChecked() is False
|
||||||
assert fmtTab.stripUnicode.isChecked() is False
|
assert fmtTab.stripUnicode.isChecked() is False
|
||||||
assert fmtTab.replaceTabs.isChecked() is False
|
assert fmtTab.replaceTabs.isChecked() is False
|
||||||
|
assert fmtTab.keepBreaks.isChecked() is True
|
||||||
|
assert fmtTab.showDialogue.isChecked() is False
|
||||||
|
|
||||||
assert fmtTab.firstIndent.isChecked() is False
|
assert fmtTab.firstIndent.isChecked() is False
|
||||||
assert fmtTab.indentWidth.value() == 1.4
|
assert fmtTab.indentWidth.value() == 1.4
|
||||||
@@ -603,6 +611,8 @@ def testToolBuildSettings_Format(monkeypatch, qtbot, nwGUI):
|
|||||||
fmtTab.justifyText.setChecked(True)
|
fmtTab.justifyText.setChecked(True)
|
||||||
fmtTab.stripUnicode.setChecked(True)
|
fmtTab.stripUnicode.setChecked(True)
|
||||||
fmtTab.replaceTabs.setChecked(True)
|
fmtTab.replaceTabs.setChecked(True)
|
||||||
|
fmtTab.keepBreaks.setChecked(False)
|
||||||
|
fmtTab.showDialogue.setChecked(True)
|
||||||
|
|
||||||
fmtTab.firstIndent.setChecked(True)
|
fmtTab.firstIndent.setChecked(True)
|
||||||
fmtTab.indentWidth.setValue(2.0)
|
fmtTab.indentWidth.setValue(2.0)
|
||||||
@@ -620,6 +630,8 @@ def testToolBuildSettings_Format(monkeypatch, qtbot, nwGUI):
|
|||||||
assert build.getBool("format.justifyText") is True
|
assert build.getBool("format.justifyText") is True
|
||||||
assert build.getBool("format.stripUnicode") is True
|
assert build.getBool("format.stripUnicode") is True
|
||||||
assert build.getBool("format.replaceTabs") is True
|
assert build.getBool("format.replaceTabs") is True
|
||||||
|
assert build.getBool("format.keepBreaks") is False
|
||||||
|
assert build.getBool("format.showDialogue") is True
|
||||||
|
|
||||||
assert build.getBool("format.firstLineIndent") is True
|
assert build.getBool("format.firstLineIndent") is True
|
||||||
assert build.getFloat("format.firstIndentWidth") == 2.0
|
assert build.getFloat("format.firstIndentWidth") == 2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user