novelWriter 2.6.3__py3-none-any.whl → 2.7__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 (348) hide show
  1. novelwriter/__init__.py +98 -75
  2. novelwriter/assets/i18n/nw_cs_CZ.qm +0 -0
  3. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  4. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  5. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  6. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  7. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  8. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  9. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  10. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  11. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  12. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  13. novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
  14. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  15. novelwriter/assets/i18n/project_de_DE.json +3 -1
  16. novelwriter/assets/i18n/project_en_GB.json +2 -0
  17. novelwriter/assets/i18n/project_en_US.json +2 -0
  18. novelwriter/assets/i18n/project_it_IT.json +2 -0
  19. novelwriter/assets/i18n/project_ja_JP.json +2 -0
  20. novelwriter/assets/i18n/project_nb_NO.json +2 -0
  21. novelwriter/assets/i18n/project_nn_NO.json +5 -0
  22. novelwriter/assets/i18n/project_pl_PL.json +2 -0
  23. novelwriter/assets/i18n/project_pt_BR.json +2 -0
  24. novelwriter/assets/i18n/project_ru_RU.json +2 -0
  25. novelwriter/assets/i18n/project_zh_CN.json +2 -0
  26. novelwriter/assets/icons/font_awesome.icons +109 -0
  27. novelwriter/assets/icons/material_filled_bold.icons +109 -0
  28. novelwriter/assets/icons/material_filled_normal.icons +109 -0
  29. novelwriter/assets/icons/material_filled_thin.icons +109 -0
  30. novelwriter/assets/icons/material_rounded_bold.icons +109 -0
  31. novelwriter/assets/icons/material_rounded_normal.icons +109 -0
  32. novelwriter/assets/icons/material_rounded_thin.icons +109 -0
  33. novelwriter/assets/icons/remix_filled.icons +109 -0
  34. novelwriter/assets/icons/remix_outline.icons +109 -0
  35. novelwriter/assets/images/splash.png +0 -0
  36. novelwriter/assets/manual.pdf +0 -0
  37. novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
  38. novelwriter/assets/sample.zip +0 -0
  39. novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
  40. novelwriter/assets/syntax/snazzy.conf +3 -3
  41. novelwriter/assets/text/credits_en.htm +12 -6
  42. novelwriter/assets/themes/cyberpunk_night.conf +23 -7
  43. novelwriter/assets/themes/default_dark.conf +20 -4
  44. novelwriter/assets/themes/default_light.conf +21 -5
  45. novelwriter/assets/themes/dracula.conf +20 -4
  46. novelwriter/assets/themes/snazzy.conf +48 -0
  47. novelwriter/assets/themes/solarized_dark.conf +24 -8
  48. novelwriter/assets/themes/solarized_light.conf +22 -6
  49. novelwriter/common.py +43 -27
  50. novelwriter/config.py +201 -139
  51. novelwriter/constants.py +67 -36
  52. novelwriter/core/buildsettings.py +26 -17
  53. novelwriter/core/coretools.py +52 -41
  54. novelwriter/core/docbuild.py +20 -13
  55. novelwriter/core/document.py +2 -2
  56. novelwriter/core/index.py +166 -432
  57. novelwriter/core/indexdata.py +406 -0
  58. novelwriter/core/item.py +50 -32
  59. novelwriter/core/itemmodel.py +43 -38
  60. novelwriter/core/novelmodel.py +225 -0
  61. novelwriter/core/options.py +1 -1
  62. novelwriter/core/project.py +24 -25
  63. novelwriter/core/projectdata.py +47 -29
  64. novelwriter/core/projectxml.py +18 -8
  65. novelwriter/core/sessions.py +32 -15
  66. novelwriter/core/spellcheck.py +4 -3
  67. novelwriter/core/status.py +12 -15
  68. novelwriter/core/storage.py +1 -1
  69. novelwriter/core/tree.py +55 -13
  70. novelwriter/dialogs/about.py +19 -22
  71. novelwriter/dialogs/docmerge.py +23 -24
  72. novelwriter/dialogs/docsplit.py +26 -26
  73. novelwriter/dialogs/editlabel.py +19 -20
  74. novelwriter/dialogs/preferences.py +143 -69
  75. novelwriter/dialogs/projectsettings.py +51 -54
  76. novelwriter/dialogs/quotes.py +14 -19
  77. novelwriter/dialogs/wordlist.py +25 -30
  78. novelwriter/enum.py +8 -0
  79. novelwriter/error.py +16 -16
  80. novelwriter/extensions/configlayout.py +24 -20
  81. novelwriter/extensions/eventfilters.py +9 -5
  82. novelwriter/extensions/modified.py +34 -15
  83. novelwriter/extensions/novelselector.py +18 -5
  84. novelwriter/extensions/pagedsidebar.py +39 -49
  85. novelwriter/extensions/progressbars.py +10 -8
  86. novelwriter/extensions/statusled.py +6 -13
  87. novelwriter/extensions/switch.py +31 -41
  88. novelwriter/extensions/switchbox.py +8 -3
  89. novelwriter/extensions/versioninfo.py +4 -4
  90. novelwriter/formats/shared.py +1 -1
  91. novelwriter/formats/todocx.py +14 -10
  92. novelwriter/formats/tohtml.py +7 -5
  93. novelwriter/formats/tokenizer.py +37 -33
  94. novelwriter/formats/tomarkdown.py +6 -2
  95. novelwriter/formats/toodt.py +27 -22
  96. novelwriter/formats/toqdoc.py +19 -14
  97. novelwriter/formats/toraw.py +6 -2
  98. novelwriter/gui/doceditor.py +314 -305
  99. novelwriter/gui/dochighlight.py +46 -45
  100. novelwriter/gui/docviewer.py +102 -104
  101. novelwriter/gui/docviewerpanel.py +47 -51
  102. novelwriter/gui/editordocument.py +8 -5
  103. novelwriter/gui/itemdetails.py +11 -14
  104. novelwriter/gui/mainmenu.py +146 -145
  105. novelwriter/gui/noveltree.py +246 -406
  106. novelwriter/gui/outline.py +141 -72
  107. novelwriter/gui/projtree.py +139 -132
  108. novelwriter/gui/search.py +34 -31
  109. novelwriter/gui/sidebar.py +13 -18
  110. novelwriter/gui/statusbar.py +27 -21
  111. novelwriter/gui/theme.py +554 -436
  112. novelwriter/guimain.py +56 -38
  113. novelwriter/shared.py +32 -23
  114. novelwriter/splash.py +74 -0
  115. novelwriter/text/comments.py +70 -0
  116. novelwriter/text/patterns.py +4 -4
  117. novelwriter/tools/dictionaries.py +20 -29
  118. novelwriter/tools/lipsum.py +18 -18
  119. novelwriter/tools/manusbuild.py +39 -42
  120. novelwriter/tools/manuscript.py +102 -115
  121. novelwriter/tools/manussettings.py +129 -102
  122. novelwriter/tools/noveldetails.py +60 -72
  123. novelwriter/tools/welcome.py +57 -75
  124. novelwriter/tools/writingstats.py +112 -102
  125. novelwriter/types.py +5 -7
  126. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/METADATA +6 -6
  127. novelwriter-2.7.dist-info/RECORD +162 -0
  128. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/WHEEL +1 -1
  129. novelWriter-2.6.3.dist-info/RECORD +0 -363
  130. novelwriter/assets/icons/typicons_dark/README.md +0 -29
  131. novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
  132. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
  133. novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
  134. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
  135. novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
  136. novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
  137. novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
  138. novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
  139. novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
  140. novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
  141. novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
  142. novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
  143. novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
  144. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
  145. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
  146. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
  147. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
  148. novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
  149. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
  150. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
  151. novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
  152. novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
  153. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
  154. novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
  155. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
  156. novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
  157. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
  158. novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
  159. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
  160. novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
  161. novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
  162. novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
  163. novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
  164. novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
  165. novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
  166. novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
  167. novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
  168. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
  169. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
  170. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
  171. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
  172. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
  173. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
  174. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
  175. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
  176. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
  177. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
  178. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
  179. novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
  180. novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
  181. novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
  182. novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
  183. novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
  184. novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
  185. novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
  186. novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
  187. novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
  188. novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
  189. novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
  190. novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
  191. novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
  192. novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
  193. novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
  194. novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
  195. novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
  196. novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
  197. novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
  198. novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
  199. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
  200. novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
  201. novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
  202. novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
  203. novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
  204. novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
  205. novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
  206. novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
  207. novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
  208. novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
  209. novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
  210. novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
  211. novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
  212. novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
  213. novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
  214. novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
  215. novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
  216. novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
  217. novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
  218. novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
  219. novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
  220. novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
  221. novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
  222. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
  223. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
  224. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
  225. novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
  226. novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
  227. novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
  228. novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
  229. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
  230. novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
  231. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
  232. novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
  233. novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
  234. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
  235. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
  236. novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
  237. novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
  238. novelwriter/assets/icons/typicons_light/README.md +0 -29
  239. novelwriter/assets/icons/typicons_light/icons.conf +0 -134
  240. novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
  241. novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
  242. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
  243. novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
  244. novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
  245. novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
  246. novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
  247. novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
  248. novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
  249. novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
  250. novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
  251. novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
  252. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
  253. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
  254. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
  255. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
  256. novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
  257. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
  258. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
  259. novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
  260. novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
  261. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
  262. novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
  263. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
  264. novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
  265. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
  266. novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
  267. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
  268. novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
  269. novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
  270. novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
  271. novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
  272. novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
  273. novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
  274. novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
  275. novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
  276. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
  277. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
  278. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
  279. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
  280. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
  281. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
  282. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
  283. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
  284. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
  285. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
  286. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
  287. novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
  288. novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
  289. novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
  290. novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
  291. novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
  292. novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
  293. novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
  294. novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
  295. novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
  296. novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
  297. novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
  298. novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
  299. novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
  300. novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
  301. novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
  302. novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
  303. novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
  304. novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
  305. novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
  306. novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
  307. novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
  308. novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
  309. novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
  310. novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
  311. novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
  312. novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
  313. novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
  314. novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
  315. novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
  316. novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
  317. novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
  318. novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
  319. novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
  320. novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
  321. novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
  322. novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
  323. novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
  324. novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
  325. novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
  326. novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
  327. novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
  328. novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
  329. novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
  330. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
  331. novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
  332. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
  333. novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
  334. novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
  335. novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
  336. novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
  337. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
  338. novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
  339. novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
  340. novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
  341. novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
  342. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
  343. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
  344. novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
  345. novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
  346. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/entry_points.txt +0 -0
  347. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info/licenses}/LICENSE.md +0 -0
  348. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/top_level.txt +0 -0
novelwriter/core/index.py CHANGED
@@ -3,9 +3,7 @@ novelWriter – Project Index
3
3
  ===========================
4
4
 
5
5
  File History:
6
- Created: 2019-05-27 [0.1.4] NWIndex
7
- Created: 2022-05-28 [2.0rc1] IndexItem
8
- Created: 2022-05-28 [2.0rc1] IndexHeading
6
+ Created: 2019-05-27 [0.1.4] Index
9
7
  Created: 2022-05-29 [2.0rc1] TagsIndex
10
8
  Created: 2022-05-29 [2.0rc1] ItemIndex
11
9
 
@@ -31,35 +29,33 @@ import json
31
29
  import logging
32
30
  import random
33
31
 
34
- from collections.abc import ItemsView, Iterable
35
32
  from pathlib import Path
36
33
  from time import time
37
- from typing import TYPE_CHECKING, Literal
34
+ from typing import TYPE_CHECKING
38
35
 
39
36
  from novelwriter import SHARED
40
- from novelwriter.common import (
41
- checkInt, isHandle, isItemClass, isListInstance, isTitleTag, jsonEncode
42
- )
37
+ from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode
43
38
  from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
44
- from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
39
+ from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes
40
+ from novelwriter.core.novelmodel import NovelModel
41
+ from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType, nwNovelExtra
45
42
  from novelwriter.error import logException
43
+ from novelwriter.text.comments import processComment
46
44
  from novelwriter.text.counting import standardCounter
47
45
 
48
- if TYPE_CHECKING: # pragma: no cover
46
+ if TYPE_CHECKING:
47
+ from collections.abc import ItemsView, Iterable
48
+
49
49
  from novelwriter.core.item import NWItem
50
50
  from novelwriter.core.project import NWProject
51
51
 
52
52
  logger = logging.getLogger(__name__)
53
53
 
54
- T_NoteTypes = Literal["footnotes", "comments"]
55
-
56
- TT_NONE = "T0000" # Default title key
57
54
  MAX_RETRY = 1000 # Key generator recursion limit
58
55
  KEY_SOURCE = "0123456789bcdfghjklmnpqrstvwxz"
59
- NOTE_TYPES: list[T_NoteTypes] = ["footnotes", "comments"]
60
56
 
61
57
 
62
- class NWIndex:
58
+ class Index:
63
59
  """Core: Project Index
64
60
 
65
61
  This class holds the entire index for a given project. The index
@@ -71,8 +67,8 @@ class NWIndex:
71
67
  this data is set directly by the indexer class in the NWItem object.
72
68
 
73
69
  The primary index data is contained in a single instance of the
74
- ItemIndex class. This object contains an IndexItem representing each
75
- NWItem of the project. Each IndexItem holds an IndexHeading object
70
+ ItemIndex class. This object contains an IndexNode representing each
71
+ NWItem of the project. Each IndexNode holds an IndexHeading object
76
72
  for each heading of the item's text.
77
73
 
78
74
  A reverse index of all tags is contained in a single instance of the
@@ -92,9 +88,13 @@ class NWIndex:
92
88
 
93
89
  # Storage and State
94
90
  self._tagsIndex = TagsIndex()
95
- self._itemIndex = ItemIndex(project)
91
+ self._itemIndex = ItemIndex(project, self._tagsIndex)
96
92
  self._indexBroken = False
97
93
 
94
+ # Models
95
+ self._novelModels: dict[str, NovelModel] = {}
96
+ self._novelExtra = nwNovelExtra.HIDDEN
97
+
98
98
  # TimeStamps
99
99
  self._indexChange = 0.0
100
100
  self._rootChange = {}
@@ -102,7 +102,7 @@ class NWIndex:
102
102
  return
103
103
 
104
104
  def __repr__(self) -> str:
105
- return f"<NWIndex project='{self._project.data.name}'>"
105
+ return f"<Index project='{self._project.data.name}'>"
106
106
 
107
107
  ##
108
108
  # Properties
@@ -112,6 +112,31 @@ class NWIndex:
112
112
  def indexBroken(self) -> bool:
113
113
  return self._indexBroken
114
114
 
115
+ ##
116
+ # Getters
117
+ ##
118
+
119
+ def getNovelModel(self, tHandle: str) -> NovelModel | None:
120
+ """Get the model for a specific novel root."""
121
+ if tHandle not in self._novelModels:
122
+ self._generateNovelModel(tHandle)
123
+ return self._novelModels.get(tHandle)
124
+
125
+ ##
126
+ # Setters
127
+ ##
128
+
129
+ def setNovelModelExtraColumn(self, extra: nwNovelExtra) -> None:
130
+ """Set the data content type of the novel model extra column."""
131
+ self._novelExtra = extra
132
+ return
133
+
134
+ def setItemClass(self, tHandle: str, itemClass: nwItemClass) -> None:
135
+ """Update the class for all tags of a handle."""
136
+ logger.info("Updating class for '%s'", tHandle)
137
+ self._tagsIndex.updateClass(tHandle, itemClass.name)
138
+ return
139
+
115
140
  ##
116
141
  # Public Methods
117
142
  ##
@@ -134,6 +159,8 @@ class NWIndex:
134
159
  self.scanText(nwItem.itemHandle, text, blockSignal=True)
135
160
  self._indexBroken = False
136
161
  SHARED.emitIndexAvailable(self._project)
162
+ for tHandle in self._novelModels:
163
+ self.refreshNovelModel(tHandle)
137
164
  return
138
165
 
139
166
  def deleteHandle(self, tHandle: str) -> None:
@@ -168,6 +195,30 @@ class NWIndex:
168
195
  return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime)
169
196
  return False
170
197
 
198
+ def refreshNovelModel(self, tHandle: str | None) -> None:
199
+ """Refresh a novel model."""
200
+ if tHandle and (model := self.getNovelModel(tHandle)):
201
+ logger.info("Refreshing novel model '%s'", tHandle)
202
+ model.beginResetModel()
203
+ model.clear()
204
+ model.setExtraColumn(self._novelExtra)
205
+ self._appendSubTreeToModel(tHandle, model)
206
+ model.endResetModel()
207
+ return
208
+
209
+ def updateNovelModelData(self, nwItem: NWItem) -> bool:
210
+ """Refresh a novel model."""
211
+ if (
212
+ (rHandle := nwItem.itemRoot)
213
+ and (model := self._novelModels.get(rHandle))
214
+ and (node := self._itemIndex[nwItem.itemHandle])
215
+ and node.item.isDocumentLayout()
216
+ and node.item.isActive
217
+ ):
218
+ logger.info("Updating novel model data '%s'", nwItem.itemHandle)
219
+ return model.refresh(node)
220
+ return False
221
+
171
222
  ##
172
223
  # Load and Save Index to/from File
173
224
  ##
@@ -288,6 +339,10 @@ class NWIndex:
288
339
  else:
289
340
  self._scanActive(tHandle, tItem, text, itemTags)
290
341
 
342
+ if tItem.itemClass == nwItemClass.NOVEL and not blockSignal:
343
+ if not self.updateNovelModelData(tItem):
344
+ self.refreshNovelModel(tItem.itemRoot)
345
+
291
346
  # Update timestamps for index changes
292
347
  nowTime = time()
293
348
  self._indexChange = nowTime
@@ -338,10 +393,10 @@ class NWIndex:
338
393
 
339
394
  elif line.startswith("%"):
340
395
  cStyle, cKey, cText, _, _ = processComment(line)
341
- if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT):
342
- self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText)
343
- elif cStyle == nwComment.FOOTNOTE:
396
+ if cStyle == nwComment.FOOTNOTE:
344
397
  self._itemIndex.addNoteKey(tHandle, "footnotes", cKey)
398
+ else:
399
+ self._itemIndex.setHeadingComment(tHandle, cTitle, cStyle, cKey, cText)
345
400
 
346
401
  # Count words for remaining text after last heading
347
402
  if pTitle != TT_NONE:
@@ -402,8 +457,9 @@ class NWIndex:
402
457
  self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
403
458
  return
404
459
 
405
- def _indexKeyword(self, tHandle: str, line: str, sTitle: str,
406
- itemClass: nwItemClass, tags: dict[str, bool]) -> None:
460
+ def _indexKeyword(
461
+ self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict[str, bool]
462
+ ) -> None:
407
463
  """Validate and save the information about a reference to a tag
408
464
  in another file, or the setting of a tag in the file. A record
409
465
  of active tags is updated so that no longer used tags can be
@@ -428,6 +484,26 @@ class NWIndex:
428
484
 
429
485
  return
430
486
 
487
+ def _generateNovelModel(self, tHandle: str) -> None:
488
+ """Generate a novel model for a specific handle."""
489
+ if (item := self._project.tree[tHandle]) and item.isRootType() and item.isNovelLike():
490
+ model = NovelModel()
491
+ model.setExtraColumn(self._novelExtra)
492
+ self._appendSubTreeToModel(tHandle, model)
493
+ self._novelModels[tHandle] = model
494
+ return
495
+
496
+ def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None:
497
+ """Append all active novel documents to a novel model."""
498
+ for handle in self._project.tree.subTree(tHandle):
499
+ if (
500
+ (node := self._itemIndex[handle])
501
+ and node.item.isDocumentLayout()
502
+ and node.item.isActive
503
+ ):
504
+ model.append(node)
505
+ return
506
+
431
507
  ##
432
508
  # Check @ Lines
433
509
  ##
@@ -522,7 +598,7 @@ class NWIndex:
522
598
  # Extract Data
523
599
  ##
524
600
 
525
- def getItemData(self, tHandle: str) -> IndexItem | None:
601
+ def getItemData(self, tHandle: str) -> IndexNode | None:
526
602
  """Get the index data for a given item."""
527
603
  return self._itemIndex[tHandle]
528
604
 
@@ -536,7 +612,15 @@ class NWIndex:
536
612
  """Get all headings for a specific item."""
537
613
  if tItem := self._itemIndex[tHandle]:
538
614
  yield from tItem.items()
539
- return []
615
+ return
616
+
617
+ def getStoryKeys(self) -> set[str]:
618
+ """Return all story structure keys."""
619
+ return self._itemIndex.allStoryKeys()
620
+
621
+ def getNoteKeys(self) -> set[str]:
622
+ """Return all note comment keys."""
623
+ return self._itemIndex.allNoteKeys()
540
624
 
541
625
  def novelStructure(
542
626
  self, rootHandle: str | None = None, activeOnly: bool = True
@@ -568,13 +652,6 @@ class NWIndex:
568
652
  hCount[iLevel] += 1
569
653
  return hCount
570
654
 
571
- def getHandleHeaderCount(self, tHandle: str) -> int:
572
- """Get the number of headers in an item."""
573
- tItem = self._itemIndex[tHandle]
574
- if isinstance(tItem, IndexItem):
575
- return len(tItem)
576
- return 0
577
-
578
655
  def getTableOfContents(
579
656
  self, rHandle: str | None, maxDepth: int, activeOnly: bool = True
580
657
  ) -> list[tuple[str, int, str, int]]:
@@ -683,7 +760,7 @@ class NWIndex:
683
760
 
684
761
  def getTagsData(
685
762
  self, activeOnly: bool = True
686
- ) -> Iterable[tuple[str, str, str, IndexItem | None, IndexHeading | None]]:
763
+ ) -> Iterable[tuple[str, str, str, IndexNode | None, IndexHeading | None]]:
687
764
  """Return all known tags."""
688
765
  for tag, data in self._tagsIndex.items():
689
766
  iItem = self._itemIndex[data.get("handle")]
@@ -692,7 +769,7 @@ class NWIndex:
692
769
  yield tag, data.get("name", ""), data.get("class", ""), iItem, hItem
693
770
  return
694
771
 
695
- def getSingleTag(self, tagKey: str) -> tuple[str, str, IndexItem | None, IndexHeading | None]:
772
+ def getSingleTag(self, tagKey: str) -> tuple[str, str, IndexNode | None, IndexHeading | None]:
696
773
  """Return tag data for a specific tag."""
697
774
  tName = self._tagsIndex.tagName(tagKey)
698
775
  tClass = self._tagsIndex.tagClass(tagKey)
@@ -715,7 +792,7 @@ class TagsIndex:
715
792
  control of the keys.
716
793
  """
717
794
 
718
- __slots__ = ("_tags")
795
+ __slots__ = ("_tags",)
719
796
 
720
797
  def __init__(self) -> None:
721
798
  self._tags: dict[str, dict[str, str]] = {}
@@ -756,13 +833,13 @@ class TagsIndex:
756
833
  }
757
834
  return
758
835
 
759
- def tagName(self, tagKey: str) -> str:
836
+ def tagName(self, tagKey: str, default: str = "") -> str:
760
837
  """Get the name of a given tag."""
761
- return self._tags.get(tagKey.lower(), {}).get("name", "")
838
+ return self._tags.get(tagKey.lower(), {}).get("name", default)
762
839
 
763
- def tagDisplay(self, tagKey: str) -> str:
840
+ def tagDisplay(self, tagKey: str, default: str = "") -> str:
764
841
  """Get the display name of a given tag."""
765
- return self._tags.get(tagKey.lower(), {}).get("display", "")
842
+ return self._tags.get(tagKey.lower(), {}).get("display", default)
766
843
 
767
844
  def tagHandle(self, tagKey: str) -> str | None:
768
845
  """Get the handle of a given tag."""
@@ -787,6 +864,15 @@ class TagsIndex:
787
864
  x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
788
865
  ]
789
866
 
867
+ def updateClass(self, tHandle: str, className: str) -> None:
868
+ """Update the class name of an item. This must be called when a
869
+ document moves to another class.
870
+ """
871
+ for entry in self._tags.values():
872
+ if entry.get("handle") == tHandle:
873
+ entry["class"] = className
874
+ return
875
+
790
876
  ##
791
877
  # Pack/Unpack
792
878
  ##
@@ -831,6 +917,22 @@ class TagsIndex:
831
917
  return
832
918
 
833
919
 
920
+ class IndexCache:
921
+ """Core: Item Index Lookup Data Class
922
+
923
+ A small data class passed between all objects of the Item Index
924
+ which provides lookup capabilities and caching for shared data.
925
+ """
926
+
927
+ __slots__ = ("note", "story", "tags")
928
+
929
+ def __init__(self, tagsIndex: TagsIndex) -> None:
930
+ self.tags: TagsIndex = tagsIndex
931
+ self.story: set[str] = set()
932
+ self.note: set[str] = set()
933
+ return
934
+
935
+
834
936
  # The Item Index Objects
835
937
  # ======================
836
938
 
@@ -840,15 +942,16 @@ class ItemIndex:
840
942
  A wrapper object holding the indexed items. This is a wrapper
841
943
  class around a single storage dictionary with a set of utility
842
944
  functions for setting and accessing the index data. Each indexed
843
- item is stored in an IndexItem object, which again holds an
945
+ item is stored in an IndexNode object, which again holds an
844
946
  IndexHeading object for each heading of the text.
845
947
  """
846
948
 
847
- __slots__ = ("_project", "_items")
949
+ __slots__ = ("_cache", "_items", "_project")
848
950
 
849
- def __init__(self, project: NWProject) -> None:
951
+ def __init__(self, project: NWProject, tagsIndex: TagsIndex) -> None:
850
952
  self._project = project
851
- self._items: dict[str, IndexItem] = {}
953
+ self._cache = IndexCache(tagsIndex)
954
+ self._items: dict[str, IndexNode] = {}
852
955
  return
853
956
 
854
957
  def __contains__(self, tHandle: str) -> bool:
@@ -858,7 +961,7 @@ class ItemIndex:
858
961
  self._items.pop(tHandle, None)
859
962
  return
860
963
 
861
- def __getitem__(self, tHandle: str) -> IndexItem | None:
964
+ def __getitem__(self, tHandle: str) -> IndexNode | None:
862
965
  return self._items.get(tHandle, None)
863
966
 
864
967
  ##
@@ -874,9 +977,17 @@ class ItemIndex:
874
977
  """Add a new item to the index. This will overwrite the item if
875
978
  it already exists.
876
979
  """
877
- self._items[tHandle] = IndexItem(tHandle, nwItem)
980
+ self._items[tHandle] = IndexNode(self._cache, tHandle, nwItem)
878
981
  return
879
982
 
983
+ def allStoryKeys(self) -> set[str]:
984
+ """Return all story structure keys."""
985
+ return self._cache.story.copy()
986
+
987
+ def allNoteKeys(self) -> set[str]:
988
+ """Return all note comment keys."""
989
+ return self._cache.note.copy()
990
+
880
991
  def allItemTags(self, tHandle: str) -> list[str]:
881
992
  """Get all tags set for headings of an item."""
882
993
  if tHandle in self._items:
@@ -932,7 +1043,7 @@ class ItemIndex:
932
1043
  if tHandle in self._items:
933
1044
  tItem = self._items[tHandle]
934
1045
  sTitle = tItem.nextHeading()
935
- tItem.addHeading(IndexHeading(sTitle, lineNo, level, text))
1046
+ tItem.addHeading(IndexHeading(self._cache, sTitle, lineNo, level, text))
936
1047
  return sTitle
937
1048
  return TT_NONE
938
1049
 
@@ -944,10 +1055,13 @@ class ItemIndex:
944
1055
  self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
945
1056
  return
946
1057
 
947
- def setHeadingSynopsis(self, tHandle: str, sTitle: str, text: str) -> None:
948
- """Set the synopsis text for a heading on a given item."""
1058
+ def setHeadingComment(
1059
+ self, tHandle: str, sTitle: str,
1060
+ comment: nwComment, key: str, text: str,
1061
+ ) -> None:
1062
+ """Set a story comment for a heading on a given item."""
949
1063
  if tHandle in self._items:
950
- self._items[tHandle].setHeadingSynopsis(sTitle, text)
1064
+ self._items[tHandle].setHeadingComment(sTitle, comment, key, text)
951
1065
  return
952
1066
 
953
1067
  def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None:
@@ -991,7 +1105,7 @@ class ItemIndex:
991
1105
 
992
1106
  def unpackData(self, data: dict) -> None:
993
1107
  """Iterate through the itemIndex loaded from cache and check
994
- that it's valid. This will raise errors if there is a problem.
1108
+ that it's valid. This will raise errors if there are problems.
995
1109
  """
996
1110
  self._items = {}
997
1111
  if not isinstance(data, dict):
@@ -1003,388 +1117,8 @@ class ItemIndex:
1003
1117
 
1004
1118
  nwItem = self._project.tree[tHandle]
1005
1119
  if nwItem is not None:
1006
- tItem = IndexItem(tHandle, nwItem)
1120
+ tItem = IndexNode(self._cache, tHandle, nwItem)
1007
1121
  tItem.unpackData(tData)
1008
1122
  self._items[tHandle] = tItem
1009
1123
 
1010
1124
  return
1011
-
1012
-
1013
- class IndexItem:
1014
- """Core: Single Index Item Class
1015
-
1016
- This object represents the index data of a project item (NWItem).
1017
- It holds a record of all the headings in the text, and the meta data
1018
- associated with each heading. It also holds a pointer to the project
1019
- item. The main heading level of the item is also held here since it
1020
- must be reset each time the item is re-indexed.
1021
- """
1022
-
1023
- __slots__ = ("_handle", "_item", "_headings", "_count", "_notes")
1024
-
1025
- def __init__(self, tHandle: str, nwItem: NWItem) -> None:
1026
- self._handle = tHandle
1027
- self._item = nwItem
1028
- self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(TT_NONE)}
1029
- self._notes: dict[str, set[str]] = {}
1030
- self._count = 0
1031
- return
1032
-
1033
- def __repr__(self) -> str:
1034
- return f"<IndexItem handle='{self._handle}'>"
1035
-
1036
- def __len__(self) -> int:
1037
- return len(self._headings)
1038
-
1039
- def __getitem__(self, sTitle: str) -> IndexHeading | None:
1040
- return self._headings.get(sTitle, None)
1041
-
1042
- def __contains__(self, sTitle: str) -> bool:
1043
- return sTitle in self._headings
1044
-
1045
- ##
1046
- # Properties
1047
- ##
1048
-
1049
- @property
1050
- def handle(self) -> str:
1051
- """Return the item handle of the index item."""
1052
- return self._handle
1053
-
1054
- @property
1055
- def item(self) -> NWItem:
1056
- """Return the project item of the index item."""
1057
- return self._item
1058
-
1059
- ##
1060
- # Setters
1061
- ##
1062
-
1063
- def addHeading(self, tHeading: IndexHeading) -> None:
1064
- """Add a heading to the item. Also remove the placeholder entry
1065
- if it exists.
1066
- """
1067
- if TT_NONE in self._headings:
1068
- self._headings.pop(TT_NONE)
1069
- self._headings[tHeading.key] = tHeading
1070
- return
1071
-
1072
- def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None:
1073
- """Set the character, word and paragraph count of a heading."""
1074
- if sTitle in self._headings:
1075
- self._headings[sTitle].setCounts(cCount, wCount, pCount)
1076
- return
1077
-
1078
- def setHeadingSynopsis(self, sTitle: str, text: str) -> None:
1079
- """Set the synopsis text of a heading."""
1080
- if sTitle in self._headings:
1081
- self._headings[sTitle].setSynopsis(text)
1082
- return
1083
-
1084
- def setHeadingTag(self, sTitle: str, tagKey: str) -> None:
1085
- """Set the tag of a heading."""
1086
- if sTitle in self._headings:
1087
- self._headings[sTitle].setTag(tagKey)
1088
- return
1089
-
1090
- def addHeadingRef(self, sTitle: str, tagKeys: list[str], refType: str) -> None:
1091
- """Add a reference key and all its types to a heading."""
1092
- if sTitle in self._headings:
1093
- for tagKey in tagKeys:
1094
- self._headings[sTitle].addReference(tagKey, refType)
1095
- return
1096
-
1097
- def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
1098
- """Add a note key to the index."""
1099
- if style not in self._notes:
1100
- self._notes[style] = set()
1101
- self._notes[style].add(key)
1102
- return
1103
-
1104
- ##
1105
- # Data Methods
1106
- ##
1107
-
1108
- def items(self) -> ItemsView[str, IndexHeading]:
1109
- """Return IndexHeading items."""
1110
- return self._headings.items()
1111
-
1112
- def headings(self) -> list[str]:
1113
- """Return heading keys in sorted order."""
1114
- return sorted(self._headings.keys())
1115
-
1116
- def allTags(self) -> list[str]:
1117
- """Return a list of all tags in the current item."""
1118
- return [h.tag for h in self._headings.values() if h.tag]
1119
-
1120
- def nextHeading(self) -> str:
1121
- """Return the next heading key to be used."""
1122
- self._count += 1
1123
- return f"T{self._count:04d}"
1124
-
1125
- def noteKeys(self, style: T_NoteTypes) -> set[str]:
1126
- """Return a set of all note keys."""
1127
- return self._notes.get(style, set())
1128
-
1129
- ##
1130
- # Pack/Unpack
1131
- ##
1132
-
1133
- def packData(self) -> dict:
1134
- """Pack the indexed item's data into a dictionary."""
1135
- heads = {}
1136
- refs = {}
1137
- for sTitle, hItem in self._headings.items():
1138
- heads[sTitle] = hItem.packData()
1139
- hRefs = hItem.packReferences()
1140
- if hRefs:
1141
- refs[sTitle] = hRefs
1142
-
1143
- data = {}
1144
- data["headings"] = heads
1145
- if refs:
1146
- data["references"] = refs
1147
- if self._notes:
1148
- data["notes"] = {style: list(keys) for style, keys in self._notes.items()}
1149
-
1150
- return data
1151
-
1152
- def unpackData(self, data: dict) -> None:
1153
- """Unpack an item entry from the data."""
1154
- references = data.get("references", {})
1155
- for sTitle, hData in data.get("headings", {}).items():
1156
- if not isTitleTag(sTitle):
1157
- raise ValueError("The itemIndex contains an invalid title key")
1158
- tHeading = IndexHeading(sTitle)
1159
- tHeading.unpackData(hData)
1160
- tHeading.unpackReferences(references.get(sTitle, {}))
1161
- self.addHeading(tHeading)
1162
-
1163
- for style, keys in data.get("notes", {}).items():
1164
- if style not in NOTE_TYPES:
1165
- raise ValueError("The notes style is invalid")
1166
- if not isListInstance(keys, str):
1167
- raise ValueError("The notes keys must be a list of strings")
1168
- self._notes[style] = set(keys)
1169
-
1170
- return
1171
-
1172
-
1173
- class IndexHeading:
1174
- """Core: Single Index Heading Class
1175
-
1176
- This object represents a section of text in a project item
1177
- associated with a single (valid) heading. It holds a separate record
1178
- of all references made under the heading.
1179
- """
1180
-
1181
- __slots__ = (
1182
- "_key", "_line", "_level", "_title", "_charCount", "_wordCount",
1183
- "_paraCount", "_synopsis", "_tag", "_refs",
1184
- )
1185
-
1186
- def __init__(self, key: str, line: int = 0, level: str = "H0", title: str = "") -> None:
1187
- self._key = key
1188
- self._line = line
1189
- self._level = level
1190
- self._title = title
1191
-
1192
- self._charCount = 0
1193
- self._wordCount = 0
1194
- self._paraCount = 0
1195
- self._synopsis = ""
1196
-
1197
- self._tag = ""
1198
- self._refs: dict[str, set[str]] = {}
1199
-
1200
- return
1201
-
1202
- def __repr__(self) -> str:
1203
- return f"<IndexHeading key='{self._key}'>"
1204
-
1205
- ##
1206
- # Properties
1207
- ##
1208
-
1209
- @property
1210
- def key(self) -> str:
1211
- return self._key
1212
-
1213
- @property
1214
- def line(self) -> int:
1215
- return self._line
1216
-
1217
- @property
1218
- def level(self) -> str:
1219
- return self._level
1220
-
1221
- @property
1222
- def title(self) -> str:
1223
- return self._title
1224
-
1225
- @property
1226
- def charCount(self) -> int:
1227
- return self._charCount
1228
-
1229
- @property
1230
- def wordCount(self) -> int:
1231
- return self._wordCount
1232
-
1233
- @property
1234
- def paraCount(self) -> int:
1235
- return self._paraCount
1236
-
1237
- @property
1238
- def synopsis(self) -> str:
1239
- return self._synopsis
1240
-
1241
- @property
1242
- def tag(self) -> str:
1243
- return self._tag
1244
-
1245
- @property
1246
- def references(self) -> dict[str, set[str]]:
1247
- return self._refs
1248
-
1249
- ##
1250
- # Setters
1251
- ##
1252
-
1253
- def setLevel(self, level: str) -> None:
1254
- """Set the level of the heading if it's a valid value."""
1255
- if level in nwStyles.H_VALID:
1256
- self._level = level
1257
- return
1258
-
1259
- def setLine(self, line: int) -> None:
1260
- """Set the line number of a heading."""
1261
- self._line = max(0, checkInt(line, 0))
1262
- return
1263
-
1264
- def setCounts(self, charCount: int, wordCount: int, paraCount: int) -> None:
1265
- """Set the character, word and paragraph count. Make sure the
1266
- value is an integer and is not smaller than 0.
1267
- """
1268
- self._charCount = max(0, checkInt(charCount, 0))
1269
- self._wordCount = max(0, checkInt(wordCount, 0))
1270
- self._paraCount = max(0, checkInt(paraCount, 0))
1271
- return
1272
-
1273
- def setSynopsis(self, text: str) -> None:
1274
- """Set the synopsis text and make sure it is a string."""
1275
- self._synopsis = str(text)
1276
- return
1277
-
1278
- def setTag(self, tagKey: str) -> None:
1279
- """Set the tag for references, and make sure it is a string."""
1280
- self._tag = str(tagKey).lower()
1281
- return
1282
-
1283
- def addReference(self, tagKey: str, refType: str) -> None:
1284
- """Add a record of a reference tag, and what keyword types it is
1285
- associated with.
1286
- """
1287
- if refType in nwKeyWords.VALID_KEYS:
1288
- tagKey = tagKey.lower()
1289
- if tagKey not in self._refs:
1290
- self._refs[tagKey] = set()
1291
- self._refs[tagKey].add(refType)
1292
- return
1293
-
1294
- ##
1295
- # Data Methods
1296
- ##
1297
-
1298
- def packData(self) -> dict:
1299
- """Pack the values into a dictionary for saving to cache."""
1300
- return {
1301
- "level": self._level,
1302
- "title": self._title,
1303
- "line": self._line,
1304
- "tag": self._tag,
1305
- "cCount": self._charCount,
1306
- "wCount": self._wordCount,
1307
- "pCount": self._paraCount,
1308
- "synopsis": self._synopsis,
1309
- }
1310
-
1311
- def packReferences(self) -> dict[str, str]:
1312
- """Pack references into a dictionary for saving to cache.
1313
- Multiple types are packed into a sorted, comma separated string.
1314
- It is sorted to prevent creating unnecessary diffs as the order
1315
- of a set is not guaranteed.
1316
- """
1317
- return {key: ",".join(sorted(list(value))) for key, value in self._refs.items()}
1318
-
1319
- def unpackData(self, data: dict) -> None:
1320
- """Unpack a heading entry from a dictionary."""
1321
- self.setLevel(data.get("level", "H0"))
1322
- self._title = str(data.get("title", ""))
1323
- self._tag = str(data.get("tag", ""))
1324
- self.setLine(data.get("line", 0))
1325
- self.setCounts(
1326
- data.get("cCount", 0),
1327
- data.get("wCount", 0),
1328
- data.get("pCount", 0),
1329
- )
1330
- self._synopsis = str(data.get("synopsis", ""))
1331
- return
1332
-
1333
- def unpackReferences(self, data: dict) -> None:
1334
- """Unpack a set of references from a dictionary."""
1335
- for tagKey, refTypes in data.items():
1336
- if not isinstance(tagKey, str):
1337
- raise ValueError("itemIndex reference key must be a string")
1338
- if not isinstance(refTypes, str):
1339
- raise ValueError("itemIndex reference type must be a string")
1340
- for refType in refTypes.split(","):
1341
- if refType in nwKeyWords.VALID_KEYS:
1342
- self.addReference(tagKey, refType)
1343
- else:
1344
- raise ValueError("The itemIndex contains an invalid reference type")
1345
- return
1346
-
1347
-
1348
- # Text Processing Functions
1349
- # =========================
1350
-
1351
- MODIFIERS = {
1352
- "synopsis": nwComment.SYNOPSIS,
1353
- "short": nwComment.SHORT,
1354
- "note": nwComment.NOTE,
1355
- "footnote": nwComment.FOOTNOTE,
1356
- }
1357
- KEY_REQ = {
1358
- "synopsis": 0, # Key not allowed
1359
- "short": 0, # Key not allowed
1360
- "note": 1, # Key optional
1361
- "footnote": 2, # Key required
1362
- }
1363
-
1364
-
1365
- def _checkModKey(modifier: str, key: str) -> bool:
1366
- """Check if a modifier and key set are ok."""
1367
- if modifier in MODIFIERS:
1368
- if key == "":
1369
- return KEY_REQ[modifier] < 2
1370
- elif key.replace("_", "").isalnum():
1371
- return KEY_REQ[modifier] > 0
1372
- return False
1373
-
1374
-
1375
- def processComment(text: str) -> tuple[nwComment, str, str, int, int]:
1376
- """Extract comment style, key and text. Should only be called on
1377
- text starting with a %.
1378
- """
1379
- if text[:2] == "%~":
1380
- return nwComment.IGNORE, "", text[2:].lstrip(), 0, 0
1381
-
1382
- check = text[1:].strip()
1383
- start, _, content = check.partition(":")
1384
- modifier, _, key = start.rstrip().partition(".")
1385
- if content and (clean := modifier.lower()) and _checkModKey(clean, key):
1386
- col = text.find(":") + 1
1387
- dot = text.find(".", 0, col) + 1
1388
- return MODIFIERS[clean], key, content.lstrip(), dot, col
1389
-
1390
- return nwComment.PLAIN, "", check, 0, 0