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.
Files changed (109) hide show
  1. {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/METADATA +3 -3
  2. {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/RECORD +105 -76
  3. novelwriter/__init__.py +6 -24
  4. novelwriter/assets/i18n/project_de_DE.json +10 -0
  5. novelwriter/assets/i18n/project_en_GB.json +11 -0
  6. novelwriter/assets/i18n/project_en_US.json +10 -0
  7. novelwriter/assets/i18n/project_ja_JP.json +11 -1
  8. novelwriter/assets/i18n/project_nb_NO.json +10 -0
  9. novelwriter/assets/i18n/project_nn_NO.json +10 -0
  10. novelwriter/assets/icons/novelwriter.ico +0 -0
  11. novelwriter/assets/icons/novelwriter.svg +8 -183
  12. novelwriter/assets/icons/typicons_dark/icons.conf +17 -2
  13. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
  14. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
  15. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
  16. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
  17. novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
  18. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +4 -0
  19. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +8 -0
  21. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +8 -0
  22. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +4 -0
  23. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +5 -0
  24. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +5 -0
  25. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +5 -0
  26. novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
  27. novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +4 -0
  28. novelwriter/assets/icons/typicons_light/icons.conf +17 -2
  29. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
  30. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
  31. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
  32. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
  33. novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
  34. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +4 -0
  35. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +4 -0
  36. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +8 -0
  37. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +8 -0
  38. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +5 -0
  40. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +5 -0
  41. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +5 -0
  42. novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
  43. novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +4 -0
  44. novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
  45. novelwriter/assets/icons/x-novelwriter-project.svg +7 -206
  46. novelwriter/assets/manual.pdf +0 -0
  47. novelwriter/assets/sample.zip +0 -0
  48. novelwriter/assets/syntax/default_dark.conf +1 -0
  49. novelwriter/assets/syntax/default_light.conf +1 -0
  50. novelwriter/assets/syntax/grey_dark.conf +1 -0
  51. novelwriter/assets/syntax/grey_light.conf +1 -0
  52. novelwriter/assets/syntax/light_owl.conf +1 -0
  53. novelwriter/assets/syntax/night_owl.conf +1 -0
  54. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  55. novelwriter/assets/syntax/solarized_light.conf +1 -0
  56. novelwriter/assets/syntax/tomorrow.conf +1 -0
  57. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  58. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  59. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  60. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  61. novelwriter/assets/text/credits_en.htm +7 -0
  62. novelwriter/assets/text/release_notes.htm +7 -37
  63. novelwriter/common.py +22 -1
  64. novelwriter/config.py +27 -42
  65. novelwriter/constants.py +45 -7
  66. novelwriter/core/buildsettings.py +40 -24
  67. novelwriter/core/coretools.py +8 -1
  68. novelwriter/core/docbuild.py +2 -6
  69. novelwriter/core/index.py +264 -175
  70. novelwriter/core/options.py +8 -3
  71. novelwriter/core/project.py +2 -2
  72. novelwriter/core/projectdata.py +3 -3
  73. novelwriter/core/tohtml.py +60 -59
  74. novelwriter/core/tokenizer.py +110 -70
  75. novelwriter/core/tomd.py +51 -38
  76. novelwriter/core/toodt.py +184 -147
  77. novelwriter/dialogs/preferences.py +75 -106
  78. novelwriter/dialogs/projsettings.py +101 -110
  79. novelwriter/dialogs/updates.py +25 -14
  80. novelwriter/enum.py +28 -3
  81. novelwriter/extensions/novelselector.py +1 -1
  82. novelwriter/gui/doceditor.py +1345 -1235
  83. novelwriter/gui/dochighlight.py +98 -62
  84. novelwriter/gui/docviewer.py +151 -340
  85. novelwriter/gui/docviewerpanel.py +457 -0
  86. novelwriter/gui/editordocument.py +126 -0
  87. novelwriter/gui/mainmenu.py +350 -300
  88. novelwriter/gui/noveltree.py +101 -125
  89. novelwriter/gui/outline.py +154 -171
  90. novelwriter/gui/projtree.py +480 -380
  91. novelwriter/gui/sidebar.py +106 -75
  92. novelwriter/gui/statusbar.py +1 -1
  93. novelwriter/gui/theme.py +114 -75
  94. novelwriter/guimain.py +353 -254
  95. novelwriter/shared.py +36 -3
  96. novelwriter/tools/dictionaries.py +268 -0
  97. novelwriter/tools/manusbuild.py +17 -6
  98. novelwriter/tools/manuscript.py +11 -3
  99. novelwriter/tools/manussettings.py +0 -14
  100. novelwriter/tools/projwizard.py +16 -2
  101. novelwriter/tools/writingstats.py +1 -1
  102. novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
  103. novelwriter/assets/icons/typicons_dark/typ_th-menu.svg +0 -4
  104. novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
  105. novelwriter/assets/icons/typicons_light/typ_th-menu.svg +0 -4
  106. {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/LICENSE.md +0 -0
  107. {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/WHEEL +0 -0
  108. {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/entry_points.txt +0 -0
  109. {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/top_level.txt +0 -0
@@ -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
- QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush, QTextDocument
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
- pIndex = SHARED.project.index
288
- tItem = SHARED.project.tree[self._tHandle]
289
- if tItem is None:
290
- return
291
-
292
- isValid, theBits, thePos = pIndex.scanThis(text)
293
- isGood = pIndex.checkThese(theBits, tItem)
294
- if isValid:
295
- for n, theBit in enumerate(theBits):
296
- xPos = thePos[n]
297
- xLen = len(theBit)
298
- if isGood[n]:
299
- if n == 0:
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.setFormat(xPos, xLen, self._hStyles["value"])
303
- else:
304
- kwFmt = self.format(xPos)
305
- kwFmt.setUnderlineColor(self._colError)
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
- if text.startswith("#! "): # Title
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
- toCheck = text[1:].lstrip()
343
- synTag = toCheck[:9].lower()
344
- tLen = len(text)
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, tLen, self._hStyles["hidden"])
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 ("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]"):
358
- self.setFormat(0, len(text), self._hStyles["keyword"])
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["keyword"])
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["keyword"])
377
+ self.setFormat(tLen-1, tLen, self._hStyles["code"])
368
378
  return
369
379
 
370
- # Regular text
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
- if not self._spellCheck:
386
- return
395
+ data = self.currentBlockUserData()
396
+ if not isinstance(data, TextBlockData):
397
+ data = TextBlockData()
398
+ self.setCurrentBlockUserData(data)
387
399
 
388
- rxSpell = self._spellRx.globalMatch(text.replace("_", " "), 0)
389
- while rxSpell.hasNext():
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