novelWriter 2.4.3__py3-none-any.whl → 2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/METADATA +4 -5
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/RECORD +122 -112
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/i18n/project_pl_PL.json +116 -0
- novelwriter/assets/i18n/project_pt_BR.json +74 -74
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +52 -41
- novelwriter/assets/themes/cyberpunk_night.conf +3 -0
- novelwriter/assets/themes/default_dark.conf +2 -0
- novelwriter/assets/themes/default_light.conf +2 -0
- novelwriter/assets/themes/dracula.conf +48 -0
- novelwriter/assets/themes/solarized_dark.conf +2 -0
- novelwriter/assets/themes/solarized_light.conf +2 -0
- novelwriter/common.py +33 -12
- novelwriter/config.py +184 -98
- novelwriter/constants.py +47 -35
- novelwriter/core/buildsettings.py +68 -69
- novelwriter/core/coretools.py +5 -23
- novelwriter/core/docbuild.py +52 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +37 -61
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +139 -105
- novelwriter/core/tokenizer.py +278 -122
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +257 -166
- novelwriter/core/toqdoc.py +419 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +11 -18
- novelwriter/dialogs/docmerge.py +17 -19
- novelwriter/dialogs/docsplit.py +17 -19
- novelwriter/dialogs/editlabel.py +6 -10
- novelwriter/dialogs/preferences.py +200 -164
- novelwriter/dialogs/projectsettings.py +225 -189
- novelwriter/dialogs/quotes.py +12 -9
- novelwriter/dialogs/wordlist.py +9 -15
- novelwriter/enum.py +35 -30
- novelwriter/error.py +8 -15
- novelwriter/extensions/configlayout.py +55 -21
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +70 -14
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
- novelwriter/extensions/statusled.py +40 -26
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +120 -139
- novelwriter/gui/dochighlight.py +231 -186
- novelwriter/gui/docviewer.py +69 -108
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +22 -18
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +15 -26
- novelwriter/gui/projtree.py +39 -65
- novelwriter/gui/search.py +10 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +29 -37
- novelwriter/gui/theme.py +26 -48
- novelwriter/guimain.py +162 -160
- novelwriter/shared.py +36 -19
- novelwriter/text/patterns.py +113 -0
- novelwriter/tools/dictionaries.py +10 -20
- novelwriter/tools/lipsum.py +10 -16
- novelwriter/tools/manusbuild.py +9 -11
- novelwriter/tools/manuscript.py +75 -149
- novelwriter/tools/manussettings.py +74 -76
- novelwriter/tools/noveldetails.py +16 -21
- novelwriter/tools/welcome.py +21 -26
- novelwriter/tools/writingstats.py +9 -12
- novelwriter/types.py +49 -4
- novelwriter/extensions/simpleprogress.py +0 -55
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/top_level.txt +0 -0
novelwriter/gui/dochighlight.py
CHANGED
@@ -28,26 +28,28 @@ import logging
|
|
28
28
|
|
29
29
|
from time import time
|
30
30
|
|
31
|
-
from PyQt5.QtCore import
|
31
|
+
from PyQt5.QtCore import QRegularExpression, Qt
|
32
32
|
from PyQt5.QtGui import (
|
33
33
|
QBrush, QColor, QFont, QSyntaxHighlighter, QTextBlockUserData,
|
34
34
|
QTextCharFormat, QTextDocument
|
35
35
|
)
|
36
36
|
|
37
37
|
from novelwriter import CONFIG, SHARED
|
38
|
-
from novelwriter.enum import nwComment
|
39
38
|
from novelwriter.common import checkInt
|
40
|
-
from novelwriter.constants import nwRegEx, nwUnicode
|
39
|
+
from novelwriter.constants import nwHeaders, nwRegEx, nwUnicode
|
41
40
|
from novelwriter.core.index import processComment
|
41
|
+
from novelwriter.enum import nwComment
|
42
|
+
from novelwriter.text.patterns import REGEX_PATTERNS
|
43
|
+
from novelwriter.types import QRegExUnicode
|
42
44
|
|
43
45
|
logger = logging.getLogger(__name__)
|
44
46
|
|
45
47
|
SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b")
|
46
|
-
SPELLRX.setPatternOptions(
|
48
|
+
SPELLRX.setPatternOptions(QRegExUnicode)
|
47
49
|
SPELLSC = QRegularExpression(nwRegEx.FMT_SC)
|
48
|
-
SPELLSC.setPatternOptions(
|
50
|
+
SPELLSC.setPatternOptions(QRegExUnicode)
|
49
51
|
SPELLSV = QRegularExpression(nwRegEx.FMT_SV)
|
50
|
-
SPELLSV.setPatternOptions(
|
52
|
+
SPELLSV.setPatternOptions(QRegExUnicode)
|
51
53
|
|
52
54
|
BLOCK_NONE = 0
|
53
55
|
BLOCK_TEXT = 1
|
@@ -57,8 +59,10 @@ BLOCK_TITLE = 4
|
|
57
59
|
|
58
60
|
class GuiDocHighlighter(QSyntaxHighlighter):
|
59
61
|
|
60
|
-
__slots__ = (
|
61
|
-
|
62
|
+
__slots__ = (
|
63
|
+
"_tHandle", "_isNovel", "_isInactive", "_spellCheck", "_spellErr",
|
64
|
+
"_hStyles", "_minRules", "_txtRules", "_cmnRules",
|
65
|
+
)
|
62
66
|
|
63
67
|
def __init__(self, document: QTextDocument) -> None:
|
64
68
|
super().__init__(document)
|
@@ -66,13 +70,15 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
66
70
|
logger.debug("Create: GuiDocHighlighter")
|
67
71
|
|
68
72
|
self._tHandle = None
|
73
|
+
self._isNovel = False
|
69
74
|
self._isInactive = False
|
70
75
|
self._spellCheck = False
|
71
76
|
self._spellErr = QTextCharFormat()
|
72
77
|
|
73
|
-
self._hRules: list[tuple[str, dict]] = []
|
74
78
|
self._hStyles: dict[str, QTextCharFormat] = {}
|
75
|
-
self.
|
79
|
+
self._minRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
80
|
+
self._txtRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
81
|
+
self._cmnRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
|
76
82
|
|
77
83
|
self.initHighlighter()
|
78
84
|
|
@@ -90,145 +96,164 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
90
96
|
colBreak = QColor(SHARED.theme.colEmph)
|
91
97
|
colBreak.setAlpha(64)
|
92
98
|
|
93
|
-
|
94
|
-
self.
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
99
|
+
# Create Character Formats
|
100
|
+
self._addCharFormat("text", SHARED.theme.colText)
|
101
|
+
self._addCharFormat("header1", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[1])
|
102
|
+
self._addCharFormat("header2", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[2])
|
103
|
+
self._addCharFormat("header3", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[3])
|
104
|
+
self._addCharFormat("header4", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[4])
|
105
|
+
self._addCharFormat("head1h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[1])
|
106
|
+
self._addCharFormat("head2h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[2])
|
107
|
+
self._addCharFormat("head3h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[3])
|
108
|
+
self._addCharFormat("head4h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[4])
|
109
|
+
self._addCharFormat("bold", colEmph, "b")
|
110
|
+
self._addCharFormat("italic", colEmph, "i")
|
111
|
+
self._addCharFormat("strike", SHARED.theme.colHidden, "s")
|
112
|
+
self._addCharFormat("mspaces", SHARED.theme.colError, "err")
|
113
|
+
self._addCharFormat("nobreak", colBreak, "bg")
|
114
|
+
self._addCharFormat("altdialog", SHARED.theme.colDialA)
|
115
|
+
self._addCharFormat("dialog", SHARED.theme.colDialN)
|
116
|
+
self._addCharFormat("replace", SHARED.theme.colRepTag)
|
117
|
+
self._addCharFormat("hidden", SHARED.theme.colHidden)
|
118
|
+
self._addCharFormat("markup", SHARED.theme.colHidden)
|
119
|
+
self._addCharFormat("note", SHARED.theme.colNote)
|
120
|
+
self._addCharFormat("code", SHARED.theme.colCode)
|
121
|
+
self._addCharFormat("keyword", SHARED.theme.colKey)
|
122
|
+
self._addCharFormat("tag", SHARED.theme.colTag)
|
123
|
+
self._addCharFormat("modifier", SHARED.theme.colMod)
|
124
|
+
self._addCharFormat("value", SHARED.theme.colVal)
|
125
|
+
self._addCharFormat("optional", SHARED.theme.colOpt)
|
126
|
+
self._addCharFormat("invalid", None, "err")
|
121
127
|
|
122
128
|
# Cache Spell Error Format
|
123
129
|
self._spellErr = QTextCharFormat()
|
124
130
|
self._spellErr.setUnderlineColor(SHARED.theme.colSpell)
|
125
131
|
self._spellErr.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
|
126
132
|
|
133
|
+
self._txtRules.clear()
|
134
|
+
self._cmnRules.clear()
|
135
|
+
|
127
136
|
# Multiple or Trailing Spaces
|
128
137
|
if CONFIG.showMultiSpaces:
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
138
|
+
rxRule = QRegularExpression(r"[ ]{2,}|[ ]*$")
|
139
|
+
rxRule.setPatternOptions(QRegExUnicode)
|
140
|
+
hlRule = {
|
141
|
+
0: self._hStyles["mspaces"],
|
142
|
+
}
|
143
|
+
self._minRules.append((rxRule, hlRule))
|
144
|
+
self._txtRules.append((rxRule, hlRule))
|
145
|
+
self._cmnRules.append((rxRule, hlRule))
|
134
146
|
|
135
147
|
# Non-Breaking Spaces
|
136
|
-
|
137
|
-
|
138
|
-
|
148
|
+
rxRule = QRegularExpression(f"[{nwUnicode.U_NBSP}{nwUnicode.U_THNBSP}]+")
|
149
|
+
rxRule.setPatternOptions(QRegExUnicode)
|
150
|
+
hlRule = {
|
151
|
+
0: self._hStyles["nobreak"],
|
152
|
+
}
|
153
|
+
self._minRules.append((rxRule, hlRule))
|
154
|
+
self._txtRules.append((rxRule, hlRule))
|
155
|
+
self._cmnRules.append((rxRule, hlRule))
|
156
|
+
|
157
|
+
# Dialogue
|
158
|
+
if CONFIG.dialogStyle > 0:
|
159
|
+
rxRule = REGEX_PATTERNS.dialogStyle
|
160
|
+
hlRule = {
|
161
|
+
0: self._hStyles["dialog"],
|
139
162
|
}
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
fmtSngO = CONFIG.fmtSQuoteOpen
|
147
|
-
fmtSngC = CONFIG.fmtSQuoteClose
|
148
|
-
|
149
|
-
# Straight Quotes
|
150
|
-
if not (fmtDblO == fmtDblC == "\""):
|
151
|
-
self._hRules.append((
|
152
|
-
"(\\B\")(.*?)(\"\\B)", {
|
153
|
-
0: self._hStyles["dialogue1"],
|
154
|
-
}
|
155
|
-
))
|
156
|
-
|
157
|
-
# Double Quotes
|
158
|
-
dblEnd = "|$" if CONFIG.allowOpenDQuote else ""
|
159
|
-
self._hRules.append((
|
160
|
-
f"(\\B{fmtDblO})(.*?)({fmtDblC}\\B{dblEnd})", {
|
161
|
-
0: self._hStyles["dialogue2"],
|
162
|
-
}
|
163
|
-
))
|
164
|
-
|
165
|
-
# Single Quotes
|
166
|
-
sngEnd = "|$" if CONFIG.allowOpenSQuote else ""
|
167
|
-
self._hRules.append((
|
168
|
-
f"(\\B{fmtSngO})(.*?)({fmtSngC}\\B{sngEnd})", {
|
169
|
-
0: self._hStyles["dialogue3"],
|
170
|
-
}
|
171
|
-
))
|
172
|
-
|
173
|
-
# Markdown Syntax
|
174
|
-
self._hRules.append((
|
175
|
-
nwRegEx.FMT_EI, {
|
176
|
-
1: self._hStyles["hidden"],
|
177
|
-
2: self._hStyles["italic"],
|
178
|
-
3: self._hStyles["hidden"],
|
163
|
+
self._txtRules.append((rxRule, hlRule))
|
164
|
+
|
165
|
+
if CONFIG.dialogLine:
|
166
|
+
rxRule = REGEX_PATTERNS.dialogLine
|
167
|
+
hlRule = {
|
168
|
+
0: self._hStyles["dialog"],
|
179
169
|
}
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
170
|
+
self._txtRules.append((rxRule, hlRule))
|
171
|
+
|
172
|
+
if CONFIG.narratorBreak:
|
173
|
+
rxRule = REGEX_PATTERNS.narratorBreak
|
174
|
+
hlRule = {
|
175
|
+
0: self._hStyles["text"],
|
186
176
|
}
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
177
|
+
self._txtRules.append((rxRule, hlRule))
|
178
|
+
|
179
|
+
if CONFIG.altDialogOpen and CONFIG.altDialogClose:
|
180
|
+
rxRule = REGEX_PATTERNS.altDialogStyle
|
181
|
+
hlRule = {
|
182
|
+
0: self._hStyles["altdialog"],
|
193
183
|
}
|
194
|
-
|
184
|
+
self._txtRules.append((rxRule, hlRule))
|
185
|
+
|
186
|
+
# Markdown Italic
|
187
|
+
rxRule = REGEX_PATTERNS.markdownItalic
|
188
|
+
hlRule = {
|
189
|
+
1: self._hStyles["markup"],
|
190
|
+
2: self._hStyles["italic"],
|
191
|
+
3: self._hStyles["markup"],
|
192
|
+
}
|
193
|
+
self._minRules.append((rxRule, hlRule))
|
194
|
+
self._txtRules.append((rxRule, hlRule))
|
195
|
+
self._cmnRules.append((rxRule, hlRule))
|
196
|
+
|
197
|
+
# Markdown Bold
|
198
|
+
rxRule = REGEX_PATTERNS.markdownBold
|
199
|
+
hlRule = {
|
200
|
+
1: self._hStyles["markup"],
|
201
|
+
2: self._hStyles["bold"],
|
202
|
+
3: self._hStyles["markup"],
|
203
|
+
}
|
204
|
+
self._minRules.append((rxRule, hlRule))
|
205
|
+
self._txtRules.append((rxRule, hlRule))
|
206
|
+
self._cmnRules.append((rxRule, hlRule))
|
207
|
+
|
208
|
+
# Markdown Strikethrough
|
209
|
+
rxRule = REGEX_PATTERNS.markdownStrike
|
210
|
+
hlRule = {
|
211
|
+
1: self._hStyles["markup"],
|
212
|
+
2: self._hStyles["strike"],
|
213
|
+
3: self._hStyles["markup"],
|
214
|
+
}
|
215
|
+
self._minRules.append((rxRule, hlRule))
|
216
|
+
self._txtRules.append((rxRule, hlRule))
|
217
|
+
self._cmnRules.append((rxRule, hlRule))
|
195
218
|
|
196
219
|
# Shortcodes
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
))
|
220
|
+
rxRule = REGEX_PATTERNS.shortcodePlain
|
221
|
+
hlRule = {
|
222
|
+
1: self._hStyles["code"],
|
223
|
+
}
|
224
|
+
self._minRules.append((rxRule, hlRule))
|
225
|
+
self._txtRules.append((rxRule, hlRule))
|
226
|
+
self._cmnRules.append((rxRule, hlRule))
|
202
227
|
|
203
228
|
# Shortcodes w/Value
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
))
|
229
|
+
rxRule = REGEX_PATTERNS.shortcodeValue
|
230
|
+
hlRule = {
|
231
|
+
1: self._hStyles["code"],
|
232
|
+
2: self._hStyles["value"],
|
233
|
+
3: self._hStyles["code"],
|
234
|
+
}
|
235
|
+
self._minRules.append((rxRule, hlRule))
|
236
|
+
self._txtRules.append((rxRule, hlRule))
|
237
|
+
self._cmnRules.append((rxRule, hlRule))
|
211
238
|
|
212
239
|
# Alignment Tags
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
240
|
+
rxRule = QRegularExpression(r"(^>{1,2}|<{1,2}$)")
|
241
|
+
rxRule.setPatternOptions(QRegExUnicode)
|
242
|
+
hlRule = {
|
243
|
+
1: self._hStyles["markup"],
|
244
|
+
}
|
245
|
+
self._minRules.append((rxRule, hlRule))
|
246
|
+
self._txtRules.append((rxRule, hlRule))
|
218
247
|
|
219
248
|
# Auto-Replace Tags
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
self.
|
228
|
-
for regEx, regRules in self._hRules:
|
229
|
-
hReg = QRegularExpression(regEx)
|
230
|
-
hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
231
|
-
self._rxRules.append((hReg, regRules))
|
249
|
+
rxRule = QRegularExpression(r"<(\S+?)>")
|
250
|
+
rxRule.setPatternOptions(QRegExUnicode)
|
251
|
+
hlRule = {
|
252
|
+
0: self._hStyles["replace"],
|
253
|
+
}
|
254
|
+
self._minRules.append((rxRule, hlRule))
|
255
|
+
self._txtRules.append((rxRule, hlRule))
|
256
|
+
self._cmnRules.append((rxRule, hlRule))
|
232
257
|
|
233
258
|
return
|
234
259
|
|
@@ -244,9 +269,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
244
269
|
def setHandle(self, tHandle: str) -> None:
|
245
270
|
"""Set the handle of the currently highlighted document."""
|
246
271
|
self._tHandle = tHandle
|
247
|
-
self.
|
248
|
-
|
249
|
-
|
272
|
+
self._isNovel = False
|
273
|
+
self._isInactive = False
|
274
|
+
if item := SHARED.project.tree[tHandle]:
|
275
|
+
self._isNovel = item.isDocumentLayout()
|
276
|
+
self._isInactive = item.isInactiveClass()
|
250
277
|
logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
|
251
278
|
return
|
252
279
|
|
@@ -282,6 +309,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
282
309
|
if self._tHandle is None or not text:
|
283
310
|
return
|
284
311
|
|
312
|
+
xOff = 0
|
313
|
+
hRules = None
|
285
314
|
if text.startswith("@"): # Keywords and commands
|
286
315
|
self.setCurrentBlockState(BLOCK_META)
|
287
316
|
index = SHARED.project.index
|
@@ -295,12 +324,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
295
324
|
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
296
325
|
elif isGood[n] and not self._isInactive:
|
297
326
|
one, two = index.parseValue(bit)
|
298
|
-
self.setFormat(xPos, len(one), self._hStyles["
|
327
|
+
self.setFormat(xPos, len(one), self._hStyles["tag"])
|
299
328
|
if two:
|
300
329
|
yPos = xPos + len(bit) - len(two)
|
301
330
|
self.setFormat(yPos, len(two), self._hStyles["optional"])
|
302
331
|
elif not self._isInactive:
|
303
|
-
self.setFormat(xPos, xLen, self._hStyles["
|
332
|
+
self.setFormat(xPos, xLen, self._hStyles["invalid"])
|
304
333
|
|
305
334
|
# We never want to run the spell checker on keyword/values,
|
306
335
|
# so we force a return here
|
@@ -339,43 +368,58 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
339
368
|
|
340
369
|
elif text.startswith("%"): # Comments
|
341
370
|
self.setCurrentBlockState(BLOCK_TEXT)
|
342
|
-
|
371
|
+
hRules = self._cmnRules
|
372
|
+
|
373
|
+
cStyle, cMod, _, cDot, cPos = processComment(text)
|
374
|
+
cLen = len(text) - cPos
|
375
|
+
xOff = cPos
|
343
376
|
if cStyle == nwComment.PLAIN:
|
344
|
-
self.setFormat(0,
|
377
|
+
self.setFormat(0, cLen, self._hStyles["hidden"])
|
378
|
+
elif cStyle == nwComment.IGNORE:
|
379
|
+
self.setFormat(0, cLen, self._hStyles["strike"])
|
380
|
+
return # No more processing for these
|
381
|
+
elif cMod:
|
382
|
+
self.setFormat(0, cDot, self._hStyles["modifier"])
|
383
|
+
self.setFormat(cDot, cPos - cDot, self._hStyles["value"])
|
384
|
+
self.setFormat(cPos, cLen, self._hStyles["note"])
|
345
385
|
else:
|
346
386
|
self.setFormat(0, cPos, self._hStyles["modifier"])
|
347
|
-
self.setFormat(cPos,
|
387
|
+
self.setFormat(cPos, cLen, self._hStyles["note"])
|
348
388
|
|
349
|
-
|
389
|
+
elif text.startswith("["): # Special Command
|
390
|
+
self.setCurrentBlockState(BLOCK_TEXT)
|
391
|
+
hRules = self._txtRules if self._isNovel else self._minRules
|
392
|
+
|
393
|
+
sText = text.rstrip().lower()
|
394
|
+
if sText in ("[newpage]", "[new page]", "[vspace]"):
|
395
|
+
self.setFormat(0, len(text), self._hStyles["code"])
|
396
|
+
return
|
397
|
+
elif sText.startswith("[vspace:") and sText.endswith("]"):
|
398
|
+
tLen = len(sText)
|
399
|
+
tVal = checkInt(sText[8:-1], 0)
|
400
|
+
cVal = "value" if tVal > 0 else "invalid"
|
401
|
+
self.setFormat(0, 8, self._hStyles["code"])
|
402
|
+
self.setFormat(8, tLen-9, self._hStyles[cVal])
|
403
|
+
self.setFormat(tLen-1, tLen, self._hStyles["code"])
|
404
|
+
return
|
350
405
|
|
351
|
-
|
352
|
-
sText = text.rstrip().lower()
|
353
|
-
if sText in ("[newpage]", "[new page]", "[vspace]"):
|
354
|
-
self.setFormat(0, len(text), self._hStyles["code"])
|
355
|
-
return
|
356
|
-
elif sText.startswith("[vspace:") and sText.endswith("]"):
|
357
|
-
tLen = len(sText)
|
358
|
-
tVal = checkInt(sText[8:-1], 0)
|
359
|
-
cVal = "codevalue" if tVal > 0 else "codeinval"
|
360
|
-
self.setFormat(0, 8, self._hStyles["code"])
|
361
|
-
self.setFormat(8, tLen-9, self._hStyles[cVal])
|
362
|
-
self.setFormat(tLen-1, tLen, self._hStyles["code"])
|
363
|
-
return
|
364
|
-
|
365
|
-
# Regular Text
|
406
|
+
else: # Text Paragraph
|
366
407
|
self.setCurrentBlockState(BLOCK_TEXT)
|
367
|
-
|
368
|
-
|
408
|
+
hRules = self._txtRules if self._isNovel else self._minRules
|
409
|
+
|
410
|
+
if hRules:
|
411
|
+
for rX, hRule in hRules:
|
412
|
+
rxItt = rX.globalMatch(text, xOff)
|
369
413
|
while rxItt.hasNext():
|
370
414
|
rxMatch = rxItt.next()
|
371
|
-
for xM in
|
415
|
+
for xM, hFmt in hRule.items():
|
372
416
|
xPos = rxMatch.capturedStart(xM)
|
373
|
-
|
374
|
-
for x in range(xPos,
|
375
|
-
|
376
|
-
if
|
377
|
-
|
378
|
-
self.setFormat(x, 1,
|
417
|
+
xEnd = rxMatch.capturedEnd(xM)
|
418
|
+
for x in range(xPos, xEnd):
|
419
|
+
cFmt = self.format(x)
|
420
|
+
if cFmt.fontStyleName() != "markup":
|
421
|
+
cFmt.merge(hFmt)
|
422
|
+
self.setFormat(x, 1, cFmt)
|
379
423
|
|
380
424
|
data = self.currentBlockUserData()
|
381
425
|
if not isinstance(data, TextBlockData):
|
@@ -383,11 +427,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
383
427
|
self.setCurrentBlockUserData(data)
|
384
428
|
|
385
429
|
if self._spellCheck:
|
386
|
-
for xPos, xLen in data.spellCheck(text):
|
430
|
+
for xPos, xLen in data.spellCheck(text, xOff):
|
387
431
|
for x in range(xPos, xPos+xLen):
|
388
|
-
|
389
|
-
|
390
|
-
self.setFormat(x, 1,
|
432
|
+
cFmt = self.format(x)
|
433
|
+
cFmt.merge(self._spellErr)
|
434
|
+
self.setFormat(x, 1, cFmt)
|
391
435
|
|
392
436
|
return
|
393
437
|
|
@@ -395,36 +439,37 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
395
439
|
# Internal Functions
|
396
440
|
##
|
397
441
|
|
398
|
-
def
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
"""
|
442
|
+
def _addCharFormat(
|
443
|
+
self, name: str, color: QColor | None = None,
|
444
|
+
style: str | None = None, size: float | None = None
|
445
|
+
) -> None:
|
446
|
+
"""Generate a highlighter character format."""
|
403
447
|
charFormat = QTextCharFormat()
|
448
|
+
charFormat.setFontStyleName(name)
|
404
449
|
|
405
|
-
if color
|
450
|
+
if color:
|
406
451
|
charFormat.setForeground(color)
|
407
452
|
|
408
|
-
if style
|
453
|
+
if style:
|
409
454
|
styles = style.split(",")
|
410
|
-
if "
|
455
|
+
if "b" in styles:
|
411
456
|
charFormat.setFontWeight(QFont.Weight.Bold)
|
412
|
-
if "
|
457
|
+
if "i" in styles:
|
413
458
|
charFormat.setFontItalic(True)
|
414
|
-
if "
|
459
|
+
if "s" in styles:
|
415
460
|
charFormat.setFontStrikeOut(True)
|
416
|
-
if "
|
461
|
+
if "err" in styles:
|
417
462
|
charFormat.setUnderlineColor(SHARED.theme.colError)
|
418
463
|
charFormat.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
|
419
|
-
if "
|
464
|
+
if "bg" in styles and color is not None:
|
420
465
|
charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern))
|
421
466
|
|
422
|
-
if size
|
423
|
-
charFormat.setFontPointSize(
|
467
|
+
if size:
|
468
|
+
charFormat.setFontPointSize(round(size*CONFIG.textFont.pointSize()))
|
424
469
|
|
425
|
-
|
470
|
+
self._hStyles[name] = charFormat
|
426
471
|
|
427
|
-
|
472
|
+
return
|
428
473
|
|
429
474
|
|
430
475
|
class TextBlockData(QTextBlockUserData):
|
@@ -441,14 +486,14 @@ class TextBlockData(QTextBlockUserData):
|
|
441
486
|
"""Return spell error data from last check."""
|
442
487
|
return self._spellErrors
|
443
488
|
|
444
|
-
def spellCheck(self, text: str) -> list[tuple[int, int]]:
|
489
|
+
def spellCheck(self, text: str, offset: int) -> list[tuple[int, int]]:
|
445
490
|
"""Run the spell checker and cache the result, and return the
|
446
491
|
list of spell check errors.
|
447
492
|
"""
|
448
493
|
if "[" in text:
|
449
494
|
# Strip shortcodes
|
450
495
|
for rX in [SPELLSC, SPELLSV]:
|
451
|
-
rxItt = rX.globalMatch(text,
|
496
|
+
rxItt = rX.globalMatch(text, offset)
|
452
497
|
while rxItt.hasNext():
|
453
498
|
rxMatch = rxItt.next()
|
454
499
|
xPos = rxMatch.capturedStart(0)
|
@@ -457,12 +502,12 @@ class TextBlockData(QTextBlockUserData):
|
|
457
502
|
text = text[:xPos] + " "*xLen + text[xEnd:]
|
458
503
|
|
459
504
|
self._spellErrors = []
|
460
|
-
rxSpell = SPELLRX.globalMatch(text.replace("_", " "),
|
505
|
+
rxSpell = SPELLRX.globalMatch(text.replace("_", " "), offset)
|
461
506
|
while rxSpell.hasNext():
|
462
507
|
rxMatch = rxSpell.next()
|
463
508
|
if not SHARED.spelling.checkWord(rxMatch.captured(0)):
|
464
509
|
if not rxMatch.captured(0).isnumeric() and not rxMatch.captured(0).isupper():
|
465
|
-
self._spellErrors.append(
|
510
|
+
self._spellErrors.append(
|
511
|
+
(rxMatch.capturedStart(0), rxMatch.capturedLength(0))
|
512
|
+
)
|
466
513
|
return self._spellErrors
|
467
|
-
|
468
|
-
# END Class TextBlockData
|