novelWriter 2.7.5__py3-none-any.whl → 2.8b1__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 (196) hide show
  1. novelwriter/__init__.py +8 -7
  2. novelwriter/assets/icons/font_awesome.icons +22 -4
  3. novelwriter/assets/icons/material_filled_normal.icons +20 -2
  4. novelwriter/assets/icons/material_filled_thin.icons +20 -2
  5. novelwriter/assets/icons/material_rounded_normal.icons +20 -2
  6. novelwriter/assets/icons/material_rounded_thin.icons +20 -2
  7. novelwriter/assets/icons/material_sharp_normal.icons +20 -2
  8. novelwriter/assets/icons/material_sharp_thin.icons +20 -2
  9. novelwriter/assets/icons/remix_filled.icons +20 -2
  10. novelwriter/assets/icons/remix_outline.icons +20 -2
  11. novelwriter/assets/images/welcome.webp +0 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/manual_fr.pdf +0 -0
  14. novelwriter/assets/sample.zip +0 -0
  15. novelwriter/assets/text/credits_en.htm +61 -11
  16. novelwriter/assets/themes/aura.conf +97 -0
  17. novelwriter/assets/themes/aura_bright.conf +95 -0
  18. novelwriter/assets/themes/aura_soft.conf +97 -0
  19. novelwriter/assets/themes/b2t_garden_dark.conf +97 -0
  20. novelwriter/assets/themes/b2t_garden_light.conf +97 -0
  21. novelwriter/assets/themes/b2t_suburb_dark.conf +97 -0
  22. novelwriter/assets/themes/b2t_suburb_light.conf +97 -0
  23. novelwriter/assets/themes/b4t_classic_o_dark.conf +97 -0
  24. novelwriter/assets/themes/b4t_classic_o_light.conf +97 -0
  25. novelwriter/assets/themes/b4t_modern_c_dark.conf +97 -0
  26. novelwriter/assets/themes/b4t_modern_c_light.conf +97 -0
  27. novelwriter/assets/themes/blue_streak_dark.conf +97 -0
  28. novelwriter/assets/themes/blue_streak_light.conf +97 -0
  29. novelwriter/assets/themes/castle_day.conf +95 -0
  30. novelwriter/assets/themes/castle_night.conf +95 -0
  31. novelwriter/assets/themes/catppuccin_latte.conf +97 -0
  32. novelwriter/assets/themes/catppuccin_mocha.conf +97 -0
  33. novelwriter/assets/themes/chalky_soil.conf +95 -0
  34. novelwriter/assets/themes/chernozem.conf +95 -0
  35. novelwriter/assets/themes/cyberpunk_night.conf +88 -40
  36. novelwriter/assets/themes/default_dark.conf +89 -41
  37. novelwriter/assets/themes/default_light.conf +89 -41
  38. novelwriter/assets/themes/dracula.conf +91 -42
  39. novelwriter/assets/themes/espresso.conf +97 -0
  40. novelwriter/assets/themes/everforest_dark.conf +97 -0
  41. novelwriter/assets/themes/everforest_light.conf +97 -0
  42. novelwriter/assets/themes/floral_daydream.conf +95 -0
  43. novelwriter/assets/themes/floral_midnight.conf +95 -0
  44. novelwriter/assets/themes/full_moon.conf +95 -0
  45. novelwriter/assets/themes/grey_dark.conf +97 -0
  46. novelwriter/assets/themes/grey_light.conf +97 -0
  47. novelwriter/assets/themes/horizon_dark.conf +97 -0
  48. novelwriter/assets/themes/horizon_light.conf +97 -0
  49. novelwriter/assets/themes/jewel_case_dark.conf +95 -0
  50. novelwriter/assets/themes/jewel_case_light.conf +95 -0
  51. novelwriter/assets/themes/lcars.conf +97 -0
  52. novelwriter/assets/themes/light_owl.conf +117 -0
  53. novelwriter/assets/themes/new_moon.conf +97 -0
  54. novelwriter/assets/themes/night_owl.conf +117 -0
  55. novelwriter/assets/themes/noctis.conf +129 -0
  56. novelwriter/assets/themes/noctis_lux.conf +129 -0
  57. novelwriter/assets/themes/nord.conf +97 -0
  58. novelwriter/assets/themes/nordlicht.conf +95 -0
  59. novelwriter/assets/themes/otium_dark.conf +95 -0
  60. novelwriter/assets/themes/otium_light.conf +95 -0
  61. novelwriter/assets/themes/paragon.conf +96 -0
  62. novelwriter/assets/themes/primer_light.conf +97 -0
  63. novelwriter/assets/themes/primer_night.conf +97 -0
  64. novelwriter/assets/themes/rose_pine.conf +97 -0
  65. novelwriter/assets/themes/rose_pine_dawn.conf +97 -0
  66. novelwriter/assets/themes/ruby_day.conf +95 -0
  67. novelwriter/assets/themes/ruby_night.conf +95 -0
  68. novelwriter/assets/themes/selenium_dark.conf +95 -0
  69. novelwriter/assets/themes/selenium_light.conf +95 -0
  70. novelwriter/assets/themes/sepia_dark.conf +95 -0
  71. novelwriter/assets/themes/sepia_light.conf +95 -0
  72. novelwriter/assets/themes/snazzy.conf +102 -40
  73. novelwriter/assets/themes/solarized_dark.conf +108 -40
  74. novelwriter/assets/themes/solarized_light.conf +108 -40
  75. novelwriter/assets/themes/sultana_light.conf +95 -0
  76. novelwriter/assets/themes/sultana_night.conf +95 -0
  77. novelwriter/assets/themes/tango_dark.conf +111 -0
  78. novelwriter/assets/themes/tango_light.conf +111 -0
  79. novelwriter/assets/themes/tomorrow.conf +117 -0
  80. novelwriter/assets/themes/tomorrow_night.conf +117 -0
  81. novelwriter/assets/themes/tomorrow_night_blue.conf +117 -0
  82. novelwriter/assets/themes/tomorrow_night_bright.conf +117 -0
  83. novelwriter/assets/themes/tomorrow_night_eighties.conf +117 -0
  84. novelwriter/assets/themes/vivid_black_green.conf +97 -0
  85. novelwriter/assets/themes/vivid_black_red.conf +97 -0
  86. novelwriter/assets/themes/vivid_white_green.conf +97 -0
  87. novelwriter/assets/themes/vivid_white_red.conf +97 -0
  88. novelwriter/assets/themes/warpgate.conf +96 -0
  89. novelwriter/assets/themes/waterlily_dark.conf +95 -0
  90. novelwriter/assets/themes/waterlily_light.conf +95 -0
  91. novelwriter/common.py +47 -17
  92. novelwriter/config.py +57 -62
  93. novelwriter/constants.py +32 -6
  94. novelwriter/core/buildsettings.py +3 -23
  95. novelwriter/core/coretools.py +21 -25
  96. novelwriter/core/docbuild.py +4 -9
  97. novelwriter/core/document.py +2 -6
  98. novelwriter/core/index.py +33 -53
  99. novelwriter/core/indexdata.py +17 -22
  100. novelwriter/core/item.py +11 -35
  101. novelwriter/core/itemmodel.py +5 -21
  102. novelwriter/core/novelmodel.py +3 -7
  103. novelwriter/core/options.py +3 -4
  104. novelwriter/core/project.py +31 -21
  105. novelwriter/core/projectdata.py +2 -21
  106. novelwriter/core/projectxml.py +13 -21
  107. novelwriter/core/sessions.py +2 -4
  108. novelwriter/core/spellcheck.py +12 -13
  109. novelwriter/core/status.py +27 -20
  110. novelwriter/core/storage.py +5 -10
  111. novelwriter/core/tree.py +6 -15
  112. novelwriter/dialogs/about.py +9 -10
  113. novelwriter/dialogs/docmerge.py +17 -14
  114. novelwriter/dialogs/docsplit.py +18 -14
  115. novelwriter/dialogs/editlabel.py +15 -9
  116. novelwriter/dialogs/preferences.py +69 -68
  117. novelwriter/dialogs/projectsettings.py +88 -67
  118. novelwriter/dialogs/quotes.py +15 -10
  119. novelwriter/dialogs/wordlist.py +18 -21
  120. novelwriter/enum.py +75 -30
  121. novelwriter/error.py +6 -11
  122. novelwriter/extensions/configlayout.py +8 -34
  123. novelwriter/extensions/eventfilters.py +3 -3
  124. novelwriter/extensions/modified.py +87 -32
  125. novelwriter/extensions/novelselector.py +13 -12
  126. novelwriter/extensions/pagedsidebar.py +10 -18
  127. novelwriter/extensions/progressbars.py +5 -11
  128. novelwriter/extensions/statusled.py +3 -6
  129. novelwriter/extensions/switch.py +8 -11
  130. novelwriter/extensions/switchbox.py +2 -11
  131. novelwriter/extensions/versioninfo.py +6 -7
  132. novelwriter/formats/shared.py +10 -2
  133. novelwriter/formats/todocx.py +15 -37
  134. novelwriter/formats/tohtml.py +52 -61
  135. novelwriter/formats/tokenizer.py +33 -64
  136. novelwriter/formats/tomarkdown.py +4 -11
  137. novelwriter/formats/toodt.py +12 -71
  138. novelwriter/formats/toqdoc.py +11 -21
  139. novelwriter/formats/toraw.py +2 -6
  140. novelwriter/gui/doceditor.py +160 -225
  141. novelwriter/gui/dochighlight.py +142 -101
  142. novelwriter/gui/docviewer.py +53 -84
  143. novelwriter/gui/docviewerpanel.py +18 -41
  144. novelwriter/gui/editordocument.py +12 -17
  145. novelwriter/gui/itemdetails.py +5 -14
  146. novelwriter/gui/mainmenu.py +24 -32
  147. novelwriter/gui/noveltree.py +13 -51
  148. novelwriter/gui/outline.py +20 -61
  149. novelwriter/gui/projtree.py +40 -96
  150. novelwriter/gui/search.py +9 -24
  151. novelwriter/gui/sidebar.py +54 -22
  152. novelwriter/gui/statusbar.py +7 -22
  153. novelwriter/gui/theme.py +482 -368
  154. novelwriter/guimain.py +87 -101
  155. novelwriter/shared.py +79 -48
  156. novelwriter/splash.py +9 -5
  157. novelwriter/text/comments.py +1 -1
  158. novelwriter/text/counting.py +9 -5
  159. novelwriter/text/patterns.py +20 -15
  160. novelwriter/tools/dictionaries.py +18 -16
  161. novelwriter/tools/lipsum.py +15 -17
  162. novelwriter/tools/manusbuild.py +25 -45
  163. novelwriter/tools/manuscript.py +94 -95
  164. novelwriter/tools/manussettings.py +149 -104
  165. novelwriter/tools/noveldetails.py +10 -24
  166. novelwriter/tools/welcome.py +24 -72
  167. novelwriter/tools/writingstats.py +17 -26
  168. novelwriter/types.py +23 -13
  169. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/METADATA +7 -7
  170. novelwriter-2.8b1.dist-info/RECORD +212 -0
  171. novelwriter/assets/images/welcome-dark.jpg +0 -0
  172. novelwriter/assets/images/welcome-light.jpg +0 -0
  173. novelwriter/assets/syntax/cyberpunk_night.conf +0 -28
  174. novelwriter/assets/syntax/default_dark.conf +0 -42
  175. novelwriter/assets/syntax/default_light.conf +0 -42
  176. novelwriter/assets/syntax/dracula.conf +0 -44
  177. novelwriter/assets/syntax/grey_dark.conf +0 -29
  178. novelwriter/assets/syntax/grey_light.conf +0 -29
  179. novelwriter/assets/syntax/light_owl.conf +0 -49
  180. novelwriter/assets/syntax/night_owl.conf +0 -49
  181. novelwriter/assets/syntax/snazzy.conf +0 -42
  182. novelwriter/assets/syntax/solarized_dark.conf +0 -29
  183. novelwriter/assets/syntax/solarized_light.conf +0 -29
  184. novelwriter/assets/syntax/tango.conf +0 -39
  185. novelwriter/assets/syntax/tomorrow.conf +0 -49
  186. novelwriter/assets/syntax/tomorrow_night.conf +0 -49
  187. novelwriter/assets/syntax/tomorrow_night_blue.conf +0 -49
  188. novelwriter/assets/syntax/tomorrow_night_bright.conf +0 -49
  189. novelwriter/assets/syntax/tomorrow_night_eighties.conf +0 -49
  190. novelwriter/assets/themes/default.conf +0 -3
  191. novelwriter-2.7.5.dist-info/RECORD +0 -163
  192. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
  193. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
  194. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
  195. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
  196. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
novelwriter/core/index.py CHANGED
@@ -22,7 +22,7 @@ General Public License for more details.
22
22
 
23
23
  You should have received a copy of the GNU General Public License
24
24
  along with this program. If not, see <https://www.gnu.org/licenses/>.
25
- """
25
+ """ # noqa
26
26
  from __future__ import annotations
27
27
 
28
28
  import json
@@ -33,8 +33,10 @@ from pathlib import Path
33
33
  from time import time
34
34
  from typing import TYPE_CHECKING
35
35
 
36
- from novelwriter import SHARED
37
- from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode
36
+ from novelwriter import SHARED, __hexversion__
37
+ from novelwriter.common import (
38
+ formatTimeStamp, isHandle, isItemClass, isTitleTag, jsonCombine, jsonEncode
39
+ )
38
40
  from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
39
41
  from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes
40
42
  from novelwriter.core.novelmodel import NovelModel
@@ -56,7 +58,7 @@ KEY_SOURCE = "0123456789bcdfghjklmnpqrstvwxz"
56
58
 
57
59
 
58
60
  class Index:
59
- """Core: Project Index
61
+ """Core: Project Index.
60
62
 
61
63
  This class holds the entire index for a given project. The index
62
64
  contains the data that isn't stored in the project items themselves.
@@ -82,6 +84,11 @@ class Index:
82
84
  a rebuild of the index data.
83
85
  """
84
86
 
87
+ __slots__ = (
88
+ "_indexBroken", "_indexChange", "_indexUpgrade", "_itemIndex", "_novelExtra",
89
+ "_novelModels", "_project", "_rootChange", "_tagsIndex",
90
+ )
91
+
85
92
  def __init__(self, project: NWProject) -> None:
86
93
 
87
94
  self._project = project
@@ -90,6 +97,7 @@ class Index:
90
97
  self._tagsIndex = TagsIndex()
91
98
  self._itemIndex = ItemIndex(project, self._tagsIndex)
92
99
  self._indexBroken = False
100
+ self._indexUpgrade = False
93
101
 
94
102
  # Models
95
103
  self._novelModels: dict[str, NovelModel] = {}
@@ -99,8 +107,6 @@ class Index:
99
107
  self._indexChange = 0.0
100
108
  self._rootChange = {}
101
109
 
102
- return
103
-
104
110
  def __repr__(self) -> str:
105
111
  return f"<Index project='{self._project.data.name}'>"
106
112
 
@@ -112,6 +118,10 @@ class Index:
112
118
  def indexBroken(self) -> bool:
113
119
  return self._indexBroken
114
120
 
121
+ @property
122
+ def indexUpgrade(self) -> bool:
123
+ return self._indexUpgrade
124
+
115
125
  ##
116
126
  # Getters
117
127
  ##
@@ -129,7 +139,6 @@ class Index:
129
139
  def setNovelModelExtraColumn(self, extra: nwNovelExtra) -> None:
130
140
  """Set the data content type of the novel model extra column."""
131
141
  self._novelExtra = extra
132
- return
133
142
 
134
143
  ##
135
144
  # Public Methods
@@ -142,20 +151,21 @@ class Index:
142
151
  self._indexChange = 0.0
143
152
  self._rootChange = {}
144
153
  SHARED.emitIndexCleared(self._project)
145
- return
146
154
 
147
155
  def rebuild(self) -> None:
148
156
  """Rebuild the entire index from scratch."""
149
157
  self.clear()
158
+ SHARED.initMainProgress(len(self._project.tree))
150
159
  for nwItem in self._project.tree:
151
160
  if nwItem.isFileType():
152
161
  text = self._project.storage.getDocumentText(nwItem.itemHandle)
153
162
  self.scanText(nwItem.itemHandle, text, blockSignal=True)
163
+ SHARED.incMainProgress()
154
164
  self._indexBroken = False
155
165
  SHARED.emitIndexAvailable(self._project)
156
166
  for tHandle in self._novelModels:
157
167
  self.refreshNovelModel(tHandle)
158
- return
168
+ SHARED.clearMainProgress()
159
169
 
160
170
  def deleteHandle(self, tHandle: str) -> None:
161
171
  """Delete all entries of a given document handle."""
@@ -165,7 +175,6 @@ class Index:
165
175
  del self._tagsIndex[tTag]
166
176
  del self._itemIndex[tHandle]
167
177
  SHARED.emitIndexChangedTags(self._project, [], delTags)
168
- return
169
178
 
170
179
  def reIndexHandle(self, tHandle: str | None) -> None:
171
180
  """Put a file back into the index. This is used when files are
@@ -175,7 +184,6 @@ class Index:
175
184
  if tHandle and self._project.tree.checkType(tHandle, nwItemType.FILE):
176
185
  logger.debug("Re-indexing item '%s'", tHandle)
177
186
  self.scanText(tHandle, self._project.storage.getDocumentText(tHandle))
178
- return
179
187
 
180
188
  def refreshHandle(self, tHandle: str) -> None:
181
189
  """Update the class for all tags of a handle."""
@@ -185,7 +193,6 @@ class Index:
185
193
  self.deleteHandle(tHandle)
186
194
  else:
187
195
  self._tagsIndex.updateClass(tHandle, item.itemClass.name)
188
- return
189
196
 
190
197
  def indexChangedSince(self, checkTime: int | float) -> bool:
191
198
  """Check if the index has changed since a given time."""
@@ -208,7 +215,6 @@ class Index:
208
215
  model.setExtraColumn(self._novelExtra)
209
216
  self._appendSubTreeToModel(tHandle, model)
210
217
  model.endResetModel()
211
- return
212
218
 
213
219
  def updateNovelModelData(self, nwItem: NWItem) -> bool:
214
220
  """Refresh a novel model."""
@@ -247,6 +253,8 @@ class Index:
247
253
  return False
248
254
 
249
255
  try:
256
+ meta = data.get("novelWriter.meta", {})
257
+ self._indexUpgrade = meta.get("version") != __hexversion__
250
258
  self._tagsIndex.unpackData(data["novelWriter.tagsIndex"])
251
259
  self._itemIndex.unpackData(data["novelWriter.itemIndex"])
252
260
  except Exception:
@@ -279,23 +287,22 @@ class Index:
279
287
  return False
280
288
 
281
289
  logger.debug("Saving index file")
282
- tStart = time()
290
+ start = time()
283
291
 
284
292
  try:
285
- tagsIndex = jsonEncode(self._tagsIndex.packData(), n=1, nmax=2)
286
- itemIndex = jsonEncode(self._itemIndex.packData(), n=1, nmax=4)
293
+ meta = {"version": __hexversion__, "timestamp": formatTimeStamp(start)}
287
294
  with open(indexFile, mode="w+", encoding="utf-8") as outFile:
288
- outFile.write("{\n")
289
- outFile.write(f' "novelWriter.tagsIndex": {tagsIndex},\n')
290
- outFile.write(f' "novelWriter.itemIndex": {itemIndex}\n')
291
- outFile.write("}\n")
292
-
295
+ outFile.write(jsonCombine({
296
+ "novelWriter.meta": jsonEncode(meta, n=1),
297
+ "novelWriter.tagsIndex": jsonEncode(self._tagsIndex.packData(), n=1, nmax=2),
298
+ "novelWriter.itemIndex": jsonEncode(self._itemIndex.packData(), n=1, nmax=4),
299
+ }))
293
300
  except Exception:
294
301
  logger.error("Failed to save index file")
295
302
  logException()
296
303
  return False
297
304
 
298
- logger.debug("Index saved in %.3f ms", (time() - tStart)*1000)
305
+ logger.debug("Index saved in %.3f ms", (time() - start)*1000)
299
306
 
300
307
  return True
301
308
 
@@ -425,8 +432,6 @@ class Index:
425
432
  if updated or deleted:
426
433
  SHARED.emitIndexChangedTags(self._project, updated, deleted)
427
434
 
428
- return
429
-
430
435
  def _scanInactive(self, nwItem: NWItem, text: str) -> None:
431
436
  """Scan an inactive document for meta data."""
432
437
  for line in text.splitlines():
@@ -435,7 +440,6 @@ class Index:
435
440
  if hDepth != "H0":
436
441
  nwItem.setMainHeading(hDepth)
437
442
  break
438
- return
439
443
 
440
444
  def _splitHeading(self, line: str) -> tuple[str, str]:
441
445
  """Split a heading into its heading level and text value."""
@@ -459,7 +463,6 @@ class Index:
459
463
  """Count text stats and save the counts to the index."""
460
464
  cC, wC, pC = standardCounter(text)
461
465
  self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
462
- return
463
466
 
464
467
  def _indexKeyword(
465
468
  self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict[str, bool]
@@ -495,7 +498,6 @@ class Index:
495
498
  model.setExtraColumn(self._novelExtra)
496
499
  self._appendSubTreeToModel(tHandle, model)
497
500
  self._novelModels[tHandle] = model
498
- return
499
501
 
500
502
  def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None:
501
503
  """Append all active novel documents to a novel model."""
@@ -506,7 +508,6 @@ class Index:
506
508
  and node.item.isActive
507
509
  ):
508
510
  model.append(node)
509
- return
510
511
 
511
512
  ##
512
513
  # Check @ Lines
@@ -680,15 +681,13 @@ class Index:
680
681
  "words": hItem.wordCount,
681
682
  }
682
683
 
683
- result = [(
684
+ return [(
684
685
  tKey,
685
686
  tData[tKey]["level"],
686
687
  tData[tKey]["title"],
687
688
  tData[tKey]["words"]
688
689
  ) for tKey in tOrder]
689
690
 
690
- return result
691
-
692
691
  def getCounts(self, tHandle: str, sTitle: str | None = None) -> tuple[int, int, int]:
693
692
  """Return the counts for a file, or a section of a file,
694
693
  starting at title sTitle if it is provided.
@@ -793,7 +792,7 @@ class Index:
793
792
  # =====================
794
793
 
795
794
  class TagsIndex:
796
- """Core: Tags Index Wrapper Class
795
+ """Core: Tags Index Wrapper Class.
797
796
 
798
797
  A wrapper class that holds the reverse lookup tags index. This is
799
798
  just a simple wrapper around a single dictionary to keep tighter
@@ -804,14 +803,12 @@ class TagsIndex:
804
803
 
805
804
  def __init__(self) -> None:
806
805
  self._tags: dict[str, dict[str, str]] = {}
807
- return
808
806
 
809
807
  def __contains__(self, tagKey: str) -> bool:
810
808
  return tagKey.lower() in self._tags
811
809
 
812
810
  def __delitem__(self, tagKey: str) -> None:
813
811
  self._tags.pop(tagKey.lower(), None)
814
- return
815
812
 
816
813
  def __getitem__(self, tagKey: str) -> dict | None:
817
814
  return self._tags.get(tagKey.lower(), None)
@@ -823,7 +820,6 @@ class TagsIndex:
823
820
  def clear(self) -> None:
824
821
  """Clear the index."""
825
822
  self._tags = {}
826
- return
827
823
 
828
824
  def items(self) -> ItemsView:
829
825
  """Return a dictionary view of all tags."""
@@ -839,7 +835,6 @@ class TagsIndex:
839
835
  "heading": sTitle,
840
836
  "class": className,
841
837
  }
842
- return
843
838
 
844
839
  def tagName(self, tagKey: str, default: str = "") -> str:
845
840
  """Get the name of a given tag."""
@@ -879,7 +874,6 @@ class TagsIndex:
879
874
  for entry in self._tags.values():
880
875
  if entry.get("handle") == tHandle:
881
876
  entry["class"] = className
882
- return
883
877
 
884
878
  ##
885
879
  # Pack/Unpack
@@ -922,11 +916,9 @@ class TagsIndex:
922
916
 
923
917
  self.add(name, display, handle, heading, className)
924
918
 
925
- return
926
-
927
919
 
928
920
  class IndexCache:
929
- """Core: Item Index Lookup Data Class
921
+ """Core: Item Index Lookup Data Class.
930
922
 
931
923
  A small data class passed between all objects of the Item Index
932
924
  which provides lookup capabilities and caching for shared data.
@@ -938,14 +930,13 @@ class IndexCache:
938
930
  self.tags: TagsIndex = tagsIndex
939
931
  self.story: set[str] = set()
940
932
  self.note: set[str] = set()
941
- return
942
933
 
943
934
 
944
935
  # The Item Index Objects
945
936
  # ======================
946
937
 
947
938
  class ItemIndex:
948
- """Core: Item Index Wrapper Class
939
+ """Core: Item Index Wrapper Class.
949
940
 
950
941
  A wrapper object holding the indexed items. This is a wrapper
951
942
  class around a single storage dictionary with a set of utility
@@ -960,14 +951,12 @@ class ItemIndex:
960
951
  self._project = project
961
952
  self._cache = IndexCache(tagsIndex)
962
953
  self._items: dict[str, IndexNode] = {}
963
- return
964
954
 
965
955
  def __contains__(self, tHandle: str) -> bool:
966
956
  return tHandle in self._items
967
957
 
968
958
  def __delitem__(self, tHandle: str) -> None:
969
959
  self._items.pop(tHandle, None)
970
- return
971
960
 
972
961
  def __getitem__(self, tHandle: str) -> IndexNode | None:
973
962
  return self._items.get(tHandle, None)
@@ -979,14 +968,12 @@ class ItemIndex:
979
968
  def clear(self) -> None:
980
969
  """Clear the index."""
981
970
  self._items = {}
982
- return
983
971
 
984
972
  def add(self, tHandle: str, nwItem: NWItem) -> None:
985
973
  """Add a new item to the index. This will overwrite the item if
986
974
  it already exists.
987
975
  """
988
976
  self._items[tHandle] = IndexNode(self._cache, tHandle, nwItem)
989
- return
990
977
 
991
978
  def allStoryKeys(self) -> set[str]:
992
979
  """Return all story structure keys."""
@@ -1061,7 +1048,6 @@ class ItemIndex:
1061
1048
  """
1062
1049
  if tHandle in self._items:
1063
1050
  self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
1064
- return
1065
1051
 
1066
1052
  def setHeadingComment(
1067
1053
  self, tHandle: str, sTitle: str,
@@ -1070,25 +1056,21 @@ class ItemIndex:
1070
1056
  """Set a story comment for a heading on a given item."""
1071
1057
  if tHandle in self._items:
1072
1058
  self._items[tHandle].setHeadingComment(sTitle, comment, key, text)
1073
- return
1074
1059
 
1075
1060
  def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None:
1076
1061
  """Set the main tag for a heading on a given item."""
1077
1062
  if tHandle in self._items:
1078
1063
  self._items[tHandle].setHeadingTag(sTitle, tagKey)
1079
- return
1080
1064
 
1081
1065
  def addHeadingRef(self, tHandle: str, sTitle: str, tagKeys: list[str], refType: str) -> None:
1082
1066
  """Set the reference tags for a heading on a given item."""
1083
1067
  if tHandle in self._items:
1084
1068
  self._items[tHandle].addHeadingRef(sTitle, tagKeys, refType)
1085
- return
1086
1069
 
1087
1070
  def addNoteKey(self, tHandle: str, style: T_NoteTypes, key: str) -> None:
1088
1071
  """Set notes key for a given item."""
1089
1072
  if tHandle in self._items:
1090
1073
  self._items[tHandle].addNoteKey(style, key)
1091
- return
1092
1074
 
1093
1075
  def genNewNoteKey(self, tHandle: str, style: T_NoteTypes) -> str:
1094
1076
  """Set notes key for a given item."""
@@ -1128,5 +1110,3 @@ class ItemIndex:
1128
1110
  tItem = IndexNode(self._cache, tHandle, nwItem)
1129
1111
  tItem.unpackData(tData)
1130
1112
  self._items[tHandle] = tItem
1131
-
1132
- return
@@ -23,7 +23,7 @@ General Public License for more details.
23
23
 
24
24
  You should have received a copy of the GNU General Public License
25
25
  along with this program. If not, see <https://www.gnu.org/licenses/>.
26
- """
26
+ """ # noqa
27
27
  from __future__ import annotations
28
28
 
29
29
  import logging
@@ -50,7 +50,7 @@ NOTE_TYPES: list[T_NoteTypes] = ["footnotes", "comments"]
50
50
 
51
51
 
52
52
  class IndexNode:
53
- """Core: Single Index Item Node Class
53
+ """Core: Single Index Item Node Class.
54
54
 
55
55
  This object represents the index data of a project item (NWItem).
56
56
  It holds a record of all the headings in the text, and the meta data
@@ -68,7 +68,6 @@ class IndexNode:
68
68
  self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(self._cache, TT_NONE)}
69
69
  self._notes: dict[str, set[str]] = {}
70
70
  self._count = 0
71
- return
72
71
 
73
72
  def __repr__(self) -> str:
74
73
  return f"<IndexNode handle='{self._handle}'>"
@@ -107,39 +106,33 @@ class IndexNode:
107
106
  if TT_NONE in self._headings:
108
107
  self._headings.pop(TT_NONE)
109
108
  self._headings[tHeading.key] = tHeading
110
- return
111
109
 
112
110
  def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None:
113
111
  """Set the character, word and paragraph count of a heading."""
114
112
  if sTitle in self._headings:
115
113
  self._headings[sTitle].setCounts([cCount, wCount, pCount])
116
- return
117
114
 
118
115
  def setHeadingComment(self, sTitle: str, comment: nwComment, key: str, text: str) -> None:
119
116
  """Set the comment text of a heading."""
120
117
  if sTitle in self._headings:
121
118
  self._headings[sTitle].setComment(comment.name, key, text)
122
- return
123
119
 
124
120
  def setHeadingTag(self, sTitle: str, tag: str) -> None:
125
121
  """Set the tag of a heading."""
126
122
  if sTitle in self._headings:
127
123
  self._headings[sTitle].setTag(tag)
128
- return
129
124
 
130
125
  def addHeadingRef(self, sTitle: str, tags: list[str], keyword: str) -> None:
131
126
  """Add a reference key and all its types to a heading."""
132
127
  if sTitle in self._headings:
133
128
  for tag in tags:
134
129
  self._headings[sTitle].addReference(tag, keyword)
135
- return
136
130
 
137
131
  def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
138
132
  """Add a note key to the index."""
139
133
  if style not in self._notes:
140
134
  self._notes[style] = set()
141
135
  self._notes[style].add(key)
142
- return
143
136
 
144
137
  ##
145
138
  # Data Methods
@@ -195,11 +188,10 @@ class IndexNode:
195
188
  self._notes[style] = set(keys)
196
189
  else:
197
190
  raise KeyError("Index node contains an invalid key")
198
- return
199
191
 
200
192
 
201
193
  class IndexHeading:
202
- """Core: Single Index Heading Class
194
+ """Core: Single Index Heading Class.
203
195
 
204
196
  This object represents a section of text in a project item
205
197
  associated with a single (valid) heading. It holds a separate record
@@ -224,7 +216,6 @@ class IndexHeading:
224
216
  self._tag = ""
225
217
  self._refs: dict[str, set[str]] = {}
226
218
  self._comments: dict[str, str] = {}
227
- return
228
219
 
229
220
  def __repr__(self) -> str:
230
221
  return f"<IndexHeading key='{self._key}'>"
@@ -289,12 +280,10 @@ class IndexHeading:
289
280
  """Set the level of the heading if it's a valid value."""
290
281
  if level in nwStyles.H_VALID:
291
282
  self._level = level
292
- return
293
283
 
294
284
  def setLine(self, line: int) -> None:
295
285
  """Set the line number of a heading."""
296
286
  self._line = max(0, checkInt(line, 0))
297
- return
298
287
 
299
288
  def setCounts(self, counts: Sequence[int]) -> None:
300
289
  """Set the character, word and paragraph count. Make sure the
@@ -306,25 +295,22 @@ class IndexHeading:
306
295
  max(0, checkInt(counts[1], 0)),
307
296
  max(0, checkInt(counts[2], 0)),
308
297
  )
309
- return
310
298
 
311
299
  def setComment(self, comment: str, key: str, text: str) -> None:
312
300
  """Set the text for a comment and make sure it is a string."""
313
301
  match comment.lower():
314
302
  case "short" | "synopsis" | "summary":
315
- self._comments["summary"] = str(text)
303
+ self._appendCommentText("summary", text)
316
304
  case "story" if key:
317
305
  self._cache.story.add(key)
318
- self._comments[f"story.{key}"] = str(text)
306
+ self._appendCommentText(f"story.{key}", text)
319
307
  case "note" if key:
320
308
  self._cache.note.add(key)
321
- self._comments[f"note.{key}"] = str(text)
322
- return
309
+ self._appendCommentText(f"note.{key}", text)
323
310
 
324
311
  def setTag(self, tag: str) -> None:
325
312
  """Set the tag for references, and make sure it is a string."""
326
313
  self._tag = str(tag).lower()
327
- return
328
314
 
329
315
  def addReference(self, tag: str, keyword: str) -> None:
330
316
  """Add a record of a reference tag, and what keyword types it is
@@ -335,7 +321,6 @@ class IndexHeading:
335
321
  if tag not in self._refs:
336
322
  self._refs[tag] = set()
337
323
  self._refs[tag].add(keyword)
338
- return
339
324
 
340
325
  ##
341
326
  # Getters
@@ -386,6 +371,7 @@ class IndexHeading:
386
371
 
387
372
  def unpackData(self, data: dict) -> None:
388
373
  """Unpack a heading entry from a dictionary."""
374
+ self._comments = {} # These are accumulative and should be reset here
389
375
  for key, entry in data.items():
390
376
  if key == "meta":
391
377
  self.setLevel(entry.get("level", "H0"))
@@ -409,4 +395,13 @@ class IndexHeading:
409
395
  self.setComment(comment, compact(kind), str(entry))
410
396
  else:
411
397
  raise KeyError("Unknown key in heading entry")
412
- return
398
+
399
+ ##
400
+ # Internal Functions
401
+ ##
402
+
403
+ def _appendCommentText(self, key: str, text: str) -> None:
404
+ """Append text to a comment."""
405
+ if current := self._comments.get(key):
406
+ text = f"{current:s}\n\n{text:s}"
407
+ self._comments[key] = str(text)