novelWriter 2.1.1__py3-none-any.whl → 2.2rc1__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.1.1.dist-info → novelWriter-2.2rc1.dist-info}/METADATA +3 -3
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/RECORD +105 -76
- novelwriter/__init__.py +6 -24
- novelwriter/assets/i18n/project_de_DE.json +10 -0
- novelwriter/assets/i18n/project_en_GB.json +11 -0
- novelwriter/assets/i18n/project_en_US.json +10 -0
- novelwriter/assets/i18n/project_ja_JP.json +11 -1
- novelwriter/assets/i18n/project_nb_NO.json +10 -0
- novelwriter/assets/i18n/project_nn_NO.json +10 -0
- novelwriter/assets/icons/novelwriter.ico +0 -0
- novelwriter/assets/icons/novelwriter.svg +8 -183
- novelwriter/assets/icons/typicons_dark/icons.conf +17 -2
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +17 -2
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
- novelwriter/assets/icons/x-novelwriter-project.svg +7 -206
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +7 -0
- novelwriter/assets/text/release_notes.htm +7 -37
- novelwriter/common.py +22 -1
- novelwriter/config.py +27 -42
- novelwriter/constants.py +45 -7
- novelwriter/core/buildsettings.py +40 -24
- novelwriter/core/coretools.py +8 -1
- novelwriter/core/docbuild.py +2 -6
- novelwriter/core/index.py +264 -175
- novelwriter/core/options.py +8 -3
- novelwriter/core/project.py +2 -2
- novelwriter/core/projectdata.py +3 -3
- novelwriter/core/tohtml.py +60 -59
- novelwriter/core/tokenizer.py +110 -70
- novelwriter/core/tomd.py +51 -38
- novelwriter/core/toodt.py +184 -147
- novelwriter/dialogs/preferences.py +75 -106
- novelwriter/dialogs/projsettings.py +101 -110
- novelwriter/dialogs/updates.py +25 -14
- novelwriter/enum.py +28 -3
- novelwriter/extensions/novelselector.py +1 -1
- novelwriter/gui/doceditor.py +1345 -1235
- novelwriter/gui/dochighlight.py +98 -62
- novelwriter/gui/docviewer.py +151 -340
- novelwriter/gui/docviewerpanel.py +457 -0
- novelwriter/gui/editordocument.py +126 -0
- novelwriter/gui/mainmenu.py +350 -300
- novelwriter/gui/noveltree.py +101 -125
- novelwriter/gui/outline.py +154 -171
- novelwriter/gui/projtree.py +480 -380
- novelwriter/gui/sidebar.py +106 -75
- novelwriter/gui/statusbar.py +1 -1
- novelwriter/gui/theme.py +114 -75
- novelwriter/guimain.py +353 -254
- novelwriter/shared.py +36 -3
- novelwriter/tools/dictionaries.py +268 -0
- novelwriter/tools/manusbuild.py +17 -6
- novelwriter/tools/manuscript.py +11 -3
- novelwriter/tools/manussettings.py +0 -14
- novelwriter/tools/projwizard.py +16 -2
- novelwriter/tools/writingstats.py +1 -1
- novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-menu.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-menu.svg +0 -4
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/top_level.txt +0 -0
novelwriter/gui/dochighlight.py
CHANGED
@@ -29,15 +29,21 @@ from time import time
|
|
29
29
|
|
30
30
|
from PyQt5.QtCore import Qt, QRegularExpression
|
31
31
|
from PyQt5.QtGui import (
|
32
|
-
|
32
|
+
QBrush, QColor, QFont, QSyntaxHighlighter, QTextBlockUserData,
|
33
|
+
QTextCharFormat, QTextDocument
|
33
34
|
)
|
34
35
|
|
35
36
|
from novelwriter import CONFIG, SHARED
|
36
37
|
from novelwriter.common import checkInt
|
37
38
|
from novelwriter.constants import nwRegEx, nwUnicode
|
39
|
+
from novelwriter.core.index import processComment
|
40
|
+
from novelwriter.enum import nwComment
|
38
41
|
|
39
42
|
logger = logging.getLogger(__name__)
|
40
43
|
|
44
|
+
SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b")
|
45
|
+
SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
46
|
+
|
41
47
|
|
42
48
|
class GuiDocHighlighter(QSyntaxHighlighter):
|
43
49
|
|
@@ -51,9 +57,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
51
57
|
|
52
58
|
logger.debug("Create: GuiDocHighlighter")
|
53
59
|
|
60
|
+
self._tItem = None
|
54
61
|
self._tHandle = None
|
55
62
|
self._spellCheck = False
|
56
|
-
self._spellRx = QRegularExpression()
|
57
63
|
|
58
64
|
self._hRules: list[tuple[str, dict]] = []
|
59
65
|
self._hStyles: dict[str, QTextCharFormat] = {}
|
@@ -65,6 +71,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
65
71
|
self._colDialD = QColor(0, 0, 0)
|
66
72
|
self._colDialS = QColor(0, 0, 0)
|
67
73
|
self._colHidden = QColor(0, 0, 0)
|
74
|
+
self._colCode = QColor(0, 0, 0)
|
68
75
|
self._colKey = QColor(0, 0, 0)
|
69
76
|
self._colVal = QColor(0, 0, 0)
|
70
77
|
self._colSpell = QColor(0, 0, 0)
|
@@ -79,11 +86,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
79
86
|
|
80
87
|
return
|
81
88
|
|
82
|
-
@property
|
83
|
-
def spellCheck(self) -> bool:
|
84
|
-
"""Check if spell checking is enabled."""
|
85
|
-
return self._spellCheck
|
86
|
-
|
87
89
|
def initHighlighter(self) -> None:
|
88
90
|
"""Initialise the syntax highlighter, setting all the colour
|
89
91
|
rules and building the RegExes.
|
@@ -96,6 +98,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
96
98
|
self._colDialD = QColor(*SHARED.theme.colDialD)
|
97
99
|
self._colDialS = QColor(*SHARED.theme.colDialS)
|
98
100
|
self._colHidden = QColor(*SHARED.theme.colHidden)
|
101
|
+
self._colCode = QColor(*SHARED.theme.colCode)
|
99
102
|
self._colKey = QColor(*SHARED.theme.colKey)
|
100
103
|
self._colVal = QColor(*SHARED.theme.colVal)
|
101
104
|
self._colSpell = QColor(*SHARED.theme.colSpell)
|
@@ -128,6 +131,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
128
131
|
"dialogue3": self._makeFormat(self._colDialS),
|
129
132
|
"replace": self._makeFormat(self._colRepTag),
|
130
133
|
"hidden": self._makeFormat(self._colHidden),
|
134
|
+
"code": self._makeFormat(self._colCode),
|
131
135
|
"keyword": self._makeFormat(self._colKey),
|
132
136
|
"modifier": self._makeFormat(self._colMod),
|
133
137
|
"value": self._makeFormat(self._colVal, "underline"),
|
@@ -206,6 +210,22 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
206
210
|
}
|
207
211
|
))
|
208
212
|
|
213
|
+
# Shortcodes
|
214
|
+
self._hRules.append((
|
215
|
+
nwRegEx.FMT_SC, {
|
216
|
+
1: self._hStyles["code"],
|
217
|
+
}
|
218
|
+
))
|
219
|
+
|
220
|
+
# Shortcodes w/Value
|
221
|
+
self._hRules.append((
|
222
|
+
nwRegEx.FMT_SV, {
|
223
|
+
1: self._hStyles["code"],
|
224
|
+
2: self._hStyles["codevalue"],
|
225
|
+
3: self._hStyles["code"],
|
226
|
+
}
|
227
|
+
))
|
228
|
+
|
209
229
|
# Alignment Tags
|
210
230
|
self._hRules.append((
|
211
231
|
r"(^>{1,2}|<{1,2}$)", {
|
@@ -227,13 +247,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
227
247
|
hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
228
248
|
self.rxRules.append((hReg, regRules))
|
229
249
|
|
230
|
-
# Build a QRegExp for the spell checker
|
231
|
-
# Include additional characters that the highlighter should
|
232
|
-
# consider to be word separators
|
233
|
-
uCode = nwUnicode.U_ENDASH + nwUnicode.U_EMDASH
|
234
|
-
self._spellRx = QRegularExpression(r"\b[^\s\-\+\/" + uCode + r"]+\b")
|
235
|
-
self._spellRx.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
236
|
-
|
237
250
|
return
|
238
251
|
|
239
252
|
##
|
@@ -248,6 +261,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
248
261
|
def setHandle(self, tHandle: str) -> None:
|
249
262
|
"""Set the handle of the currently highlighted document."""
|
250
263
|
self._tHandle = tHandle
|
264
|
+
self._tItem = SHARED.project.tree[tHandle]
|
265
|
+
logger.debug(
|
266
|
+
"Syntax highlighter %s for item '%s'",
|
267
|
+
"enabled" if self._tItem else "disabled", tHandle
|
268
|
+
)
|
251
269
|
return
|
252
270
|
|
253
271
|
##
|
@@ -284,27 +302,24 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
284
302
|
|
285
303
|
if text.startswith("@"): # Keywords and commands
|
286
304
|
self.setCurrentBlockState(self.BLOCK_META)
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
305
|
+
if self._tItem:
|
306
|
+
pIndex = SHARED.project.index
|
307
|
+
isValid, theBits, thePos = pIndex.scanThis(text)
|
308
|
+
isGood = pIndex.checkThese(theBits, self._tItem)
|
309
|
+
if isValid:
|
310
|
+
for n, theBit in enumerate(theBits):
|
311
|
+
xPos = thePos[n]
|
312
|
+
xLen = len(theBit)
|
313
|
+
if isGood[n]:
|
314
|
+
if n == 0:
|
315
|
+
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
316
|
+
else:
|
317
|
+
self.setFormat(xPos, xLen, self._hStyles["value"])
|
301
318
|
else:
|
302
|
-
self.
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
307
|
-
self.setFormat(xPos, xLen, kwFmt)
|
319
|
+
kwFmt = self.format(xPos)
|
320
|
+
kwFmt.setUnderlineColor(self._colError)
|
321
|
+
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
322
|
+
self.setFormat(xPos, xLen, kwFmt)
|
308
323
|
|
309
324
|
# We never want to run the spell checker on keyword/values,
|
310
325
|
# so we force a return here
|
@@ -329,7 +344,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
329
344
|
self.setFormat(0, 4, self._hStyles["header4h"])
|
330
345
|
self.setFormat(4, len(text), self._hStyles["header4"])
|
331
346
|
|
332
|
-
|
347
|
+
elif text.startswith("#! "): # Title
|
333
348
|
self.setFormat(0, 2, self._hStyles["header1h"])
|
334
349
|
self.setFormat(2, len(text), self._hStyles["header1"])
|
335
350
|
|
@@ -339,35 +354,30 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
339
354
|
|
340
355
|
elif text.startswith("%"): # Comments
|
341
356
|
self.setCurrentBlockState(self.BLOCK_TEXT)
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
cLen = len(toCheck)
|
346
|
-
cOff = tLen - cLen
|
347
|
-
if synTag == "synopsis:":
|
348
|
-
self.setFormat(0, cOff+9, self._hStyles["modifier"])
|
349
|
-
self.setFormat(cOff+9, tLen, self._hStyles["hidden"])
|
357
|
+
cStyle, _, cPos = processComment(text)
|
358
|
+
if cStyle == nwComment.PLAIN:
|
359
|
+
self.setFormat(0, len(text), self._hStyles["hidden"])
|
350
360
|
else:
|
351
|
-
self.setFormat(0,
|
361
|
+
self.setFormat(0, cPos, self._hStyles["modifier"])
|
362
|
+
self.setFormat(cPos, len(text), self._hStyles["hidden"])
|
352
363
|
|
353
364
|
else: # Text Paragraph
|
354
365
|
|
355
366
|
if text.startswith("["): # Special Command
|
356
|
-
sText = text.rstrip()
|
357
|
-
if sText in ("[
|
358
|
-
self.setFormat(0, len(text), self._hStyles["
|
367
|
+
sText = text.rstrip().lower()
|
368
|
+
if sText in ("[newpage]", "[new page]", "[vspace]"):
|
369
|
+
self.setFormat(0, len(text), self._hStyles["code"])
|
359
370
|
return
|
360
|
-
|
361
|
-
elif sText.startswith("[VSPACE:") and sText.endswith("]"):
|
371
|
+
elif sText.startswith("[vspace:") and sText.endswith("]"):
|
362
372
|
tLen = len(sText)
|
363
373
|
tVal = checkInt(sText[8:-1], 0)
|
364
374
|
cVal = "codevalue" if tVal > 0 else "codeinval"
|
365
|
-
self.setFormat(0, 8, self._hStyles["
|
375
|
+
self.setFormat(0, 8, self._hStyles["code"])
|
366
376
|
self.setFormat(8, tLen-9, self._hStyles[cVal])
|
367
|
-
self.setFormat(tLen-1, tLen, self._hStyles["
|
377
|
+
self.setFormat(tLen-1, tLen, self._hStyles["code"])
|
368
378
|
return
|
369
379
|
|
370
|
-
# Regular
|
380
|
+
# Regular Text
|
371
381
|
self.setCurrentBlockState(self.BLOCK_TEXT)
|
372
382
|
for rX, xFmt in self.rxRules:
|
373
383
|
rxItt = rX.globalMatch(text, 0)
|
@@ -382,17 +392,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
382
392
|
spFmt.merge(xFmt[xM])
|
383
393
|
self.setFormat(x, 1, spFmt)
|
384
394
|
|
385
|
-
|
386
|
-
|
395
|
+
data = self.currentBlockUserData()
|
396
|
+
if not isinstance(data, TextBlockData):
|
397
|
+
data = TextBlockData()
|
398
|
+
self.setCurrentBlockUserData(data)
|
387
399
|
|
388
|
-
|
389
|
-
|
390
|
-
rxMatch = rxSpell.next()
|
391
|
-
if not SHARED.spelling.checkWord(rxMatch.captured(0)):
|
392
|
-
if rxMatch.captured(0).isupper() or rxMatch.captured(0).isnumeric():
|
393
|
-
continue
|
394
|
-
xPos = rxMatch.capturedStart(0)
|
395
|
-
xLen = rxMatch.capturedLength(0)
|
400
|
+
if self._spellCheck:
|
401
|
+
for xPos, xLen in data.spellCheck(text):
|
396
402
|
for x in range(xPos, xPos+xLen):
|
397
403
|
spFmt = self.format(x)
|
398
404
|
spFmt.setUnderlineColor(self._colSpell)
|
@@ -437,3 +443,33 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
437
443
|
return charFormat
|
438
444
|
|
439
445
|
# END Class GuiDocHighlighter
|
446
|
+
|
447
|
+
|
448
|
+
class TextBlockData(QTextBlockUserData):
|
449
|
+
|
450
|
+
__slots__ = ("_spellErrors")
|
451
|
+
|
452
|
+
def __init__(self) -> None:
|
453
|
+
super().__init__()
|
454
|
+
self._spellErrors: list[tuple[int, int]] = []
|
455
|
+
return
|
456
|
+
|
457
|
+
@property
|
458
|
+
def spellErrors(self) -> list[tuple[int, int]]:
|
459
|
+
"""Return spell error data from last check."""
|
460
|
+
return self._spellErrors
|
461
|
+
|
462
|
+
def spellCheck(self, text: str) -> list[tuple[int, int]]:
|
463
|
+
"""Run the spell checker and cache the result, and return the
|
464
|
+
list of spell check errors.
|
465
|
+
"""
|
466
|
+
self._spellErrors = []
|
467
|
+
rxSpell = SPELLRX.globalMatch(text.replace("_", " "), 0)
|
468
|
+
while rxSpell.hasNext():
|
469
|
+
rxMatch = rxSpell.next()
|
470
|
+
if not SHARED.spelling.checkWord(rxMatch.captured(0)):
|
471
|
+
if not rxMatch.captured(0).isnumeric() and not rxMatch.captured(0).isupper():
|
472
|
+
self._spellErrors.append((rxMatch.capturedStart(0), rxMatch.capturedLength(0)))
|
473
|
+
return self._spellErrors
|
474
|
+
|
475
|
+
# END Class TextBlockData
|