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