novelWriter 2.6.3__py3-none-any.whl → 2.7b1__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 (321) hide show
  1. novelwriter/__init__.py +84 -74
  2. novelwriter/assets/i18n/project_en_GB.json +1 -0
  3. novelwriter/assets/icons/font_awesome.icons +109 -0
  4. novelwriter/assets/icons/material_filled_bold.icons +109 -0
  5. novelwriter/assets/icons/material_filled_normal.icons +109 -0
  6. novelwriter/assets/icons/material_filled_thin.icons +109 -0
  7. novelwriter/assets/icons/material_rounded_bold.icons +109 -0
  8. novelwriter/assets/icons/material_rounded_normal.icons +109 -0
  9. novelwriter/assets/icons/material_rounded_thin.icons +109 -0
  10. novelwriter/assets/icons/remix_filled.icons +108 -0
  11. novelwriter/assets/icons/remix_outline.icons +108 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
  14. novelwriter/assets/sample.zip +0 -0
  15. novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
  16. novelwriter/assets/text/credits_en.htm +6 -6
  17. novelwriter/assets/themes/cyberpunk_night.conf +23 -7
  18. novelwriter/assets/themes/default_dark.conf +20 -4
  19. novelwriter/assets/themes/default_light.conf +21 -5
  20. novelwriter/assets/themes/dracula.conf +20 -4
  21. novelwriter/assets/themes/solarized_dark.conf +24 -8
  22. novelwriter/assets/themes/solarized_light.conf +22 -6
  23. novelwriter/common.py +33 -26
  24. novelwriter/config.py +118 -127
  25. novelwriter/constants.py +50 -36
  26. novelwriter/core/buildsettings.py +23 -16
  27. novelwriter/core/coretools.py +11 -7
  28. novelwriter/core/docbuild.py +19 -13
  29. novelwriter/core/document.py +2 -2
  30. novelwriter/core/index.py +142 -432
  31. novelwriter/core/indexdata.py +403 -0
  32. novelwriter/core/item.py +35 -28
  33. novelwriter/core/itemmodel.py +27 -26
  34. novelwriter/core/novelmodel.py +223 -0
  35. novelwriter/core/options.py +1 -1
  36. novelwriter/core/project.py +10 -11
  37. novelwriter/core/projectdata.py +5 -5
  38. novelwriter/core/projectxml.py +1 -1
  39. novelwriter/core/sessions.py +3 -2
  40. novelwriter/core/spellcheck.py +4 -3
  41. novelwriter/core/status.py +12 -15
  42. novelwriter/core/storage.py +1 -1
  43. novelwriter/core/tree.py +46 -8
  44. novelwriter/dialogs/about.py +19 -22
  45. novelwriter/dialogs/docmerge.py +21 -23
  46. novelwriter/dialogs/docsplit.py +20 -23
  47. novelwriter/dialogs/editlabel.py +9 -13
  48. novelwriter/dialogs/preferences.py +111 -48
  49. novelwriter/dialogs/projectsettings.py +48 -54
  50. novelwriter/dialogs/quotes.py +14 -19
  51. novelwriter/dialogs/wordlist.py +25 -30
  52. novelwriter/enum.py +8 -0
  53. novelwriter/error.py +16 -16
  54. novelwriter/extensions/configlayout.py +18 -18
  55. novelwriter/extensions/eventfilters.py +9 -5
  56. novelwriter/extensions/modified.py +34 -15
  57. novelwriter/extensions/novelselector.py +18 -5
  58. novelwriter/extensions/pagedsidebar.py +39 -49
  59. novelwriter/extensions/progressbars.py +10 -8
  60. novelwriter/extensions/statusled.py +6 -13
  61. novelwriter/extensions/switch.py +19 -30
  62. novelwriter/extensions/switchbox.py +7 -3
  63. novelwriter/extensions/versioninfo.py +4 -4
  64. novelwriter/formats/shared.py +1 -1
  65. novelwriter/formats/todocx.py +14 -10
  66. novelwriter/formats/tohtml.py +7 -5
  67. novelwriter/formats/tokenizer.py +36 -33
  68. novelwriter/formats/tomarkdown.py +6 -2
  69. novelwriter/formats/toodt.py +27 -22
  70. novelwriter/formats/toqdoc.py +19 -14
  71. novelwriter/formats/toraw.py +6 -2
  72. novelwriter/gui/doceditor.py +216 -265
  73. novelwriter/gui/dochighlight.py +46 -45
  74. novelwriter/gui/docviewer.py +102 -104
  75. novelwriter/gui/docviewerpanel.py +47 -51
  76. novelwriter/gui/editordocument.py +8 -5
  77. novelwriter/gui/itemdetails.py +11 -14
  78. novelwriter/gui/mainmenu.py +146 -145
  79. novelwriter/gui/noveltree.py +239 -405
  80. novelwriter/gui/outline.py +133 -72
  81. novelwriter/gui/projtree.py +138 -132
  82. novelwriter/gui/search.py +33 -31
  83. novelwriter/gui/sidebar.py +13 -18
  84. novelwriter/gui/statusbar.py +13 -15
  85. novelwriter/gui/theme.py +533 -430
  86. novelwriter/guimain.py +27 -29
  87. novelwriter/shared.py +32 -23
  88. novelwriter/text/comments.py +70 -0
  89. novelwriter/text/patterns.py +4 -4
  90. novelwriter/tools/dictionaries.py +20 -29
  91. novelwriter/tools/lipsum.py +16 -17
  92. novelwriter/tools/manusbuild.py +39 -42
  93. novelwriter/tools/manuscript.py +101 -114
  94. novelwriter/tools/manussettings.py +78 -83
  95. novelwriter/tools/noveldetails.py +51 -64
  96. novelwriter/tools/welcome.py +56 -75
  97. novelwriter/tools/writingstats.py +44 -57
  98. novelwriter/types.py +5 -7
  99. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info}/METADATA +6 -6
  100. novelwriter-2.7b1.dist-info/RECORD +159 -0
  101. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info}/WHEEL +1 -1
  102. novelWriter-2.6.3.dist-info/RECORD +0 -363
  103. novelwriter/assets/icons/typicons_dark/README.md +0 -29
  104. novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
  105. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
  106. novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
  107. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
  108. novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
  109. novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
  110. novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
  111. novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
  112. novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
  113. novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
  114. novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
  115. novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
  116. novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
  117. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
  118. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
  119. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
  120. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
  121. novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
  122. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
  123. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
  124. novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
  125. novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
  126. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
  127. novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
  128. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
  129. novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
  130. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
  131. novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
  132. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
  133. novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
  134. novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
  135. novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
  136. novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
  137. novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
  138. novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
  139. novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
  140. novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
  141. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
  142. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
  143. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
  144. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
  145. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
  146. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
  147. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
  148. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
  149. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
  150. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
  151. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
  152. novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
  153. novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
  154. novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
  155. novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
  156. novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
  157. novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
  158. novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
  159. novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
  160. novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
  161. novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
  162. novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
  163. novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
  164. novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
  165. novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
  166. novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
  167. novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
  168. novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
  169. novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
  170. novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
  171. novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
  172. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
  173. novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
  174. novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
  175. novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
  176. novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
  177. novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
  178. novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
  179. novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
  180. novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
  181. novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
  182. novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
  183. novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
  184. novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
  185. novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
  186. novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
  187. novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
  188. novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
  189. novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
  190. novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
  191. novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
  192. novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
  193. novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
  194. novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
  195. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
  196. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
  197. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
  198. novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
  199. novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
  200. novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
  201. novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
  202. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
  203. novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
  204. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
  205. novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
  206. novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
  207. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
  208. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
  209. novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
  210. novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
  211. novelwriter/assets/icons/typicons_light/README.md +0 -29
  212. novelwriter/assets/icons/typicons_light/icons.conf +0 -134
  213. novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
  214. novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
  215. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
  216. novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
  217. novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
  218. novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
  219. novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
  220. novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
  221. novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
  222. novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
  223. novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
  224. novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
  225. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
  226. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
  227. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
  228. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
  229. novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
  230. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
  231. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
  232. novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
  233. novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
  234. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
  235. novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
  236. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
  237. novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
  238. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
  239. novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
  240. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
  241. novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
  242. novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
  243. novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
  244. novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
  245. novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
  246. novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
  247. novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
  248. novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
  249. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
  250. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
  251. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
  252. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
  253. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
  254. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
  255. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
  256. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
  257. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
  258. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
  259. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
  260. novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
  261. novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
  262. novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
  263. novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
  264. novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
  265. novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
  266. novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
  267. novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
  268. novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
  269. novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
  270. novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
  271. novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
  272. novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
  273. novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
  274. novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
  275. novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
  276. novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
  277. novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
  278. novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
  279. novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
  280. novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
  281. novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
  282. novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
  283. novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
  284. novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
  285. novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
  286. novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
  287. novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
  288. novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
  289. novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
  290. novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
  291. novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
  292. novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
  293. novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
  294. novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
  295. novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
  296. novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
  297. novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
  298. novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
  299. novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
  300. novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
  301. novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
  302. novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
  303. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
  304. novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
  305. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
  306. novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
  307. novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
  308. novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
  309. novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
  310. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
  311. novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
  312. novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
  313. novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
  314. novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
  315. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
  316. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
  317. novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
  318. novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
  319. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info}/entry_points.txt +0 -0
  320. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info/licenses}/LICENSE.md +0 -0
  321. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.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,25 @@ 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
+
115
134
  ##
116
135
  # Public Methods
117
136
  ##
@@ -134,6 +153,8 @@ class NWIndex:
134
153
  self.scanText(nwItem.itemHandle, text, blockSignal=True)
135
154
  self._indexBroken = False
136
155
  SHARED.emitIndexAvailable(self._project)
156
+ for tHandle in self._novelModels:
157
+ self.refreshNovelModel(tHandle)
137
158
  return
138
159
 
139
160
  def deleteHandle(self, tHandle: str) -> None:
@@ -168,6 +189,30 @@ class NWIndex:
168
189
  return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime)
169
190
  return False
170
191
 
192
+ def refreshNovelModel(self, tHandle: str | None) -> None:
193
+ """Refresh a novel model."""
194
+ if tHandle and (model := self.getNovelModel(tHandle)):
195
+ logger.info("Refreshing novel model '%s'", tHandle)
196
+ model.beginResetModel()
197
+ model.clear()
198
+ model.setExtraColumn(self._novelExtra)
199
+ self._appendSubTreeToModel(tHandle, model)
200
+ model.endResetModel()
201
+ return
202
+
203
+ def updateNovelModelData(self, nwItem: NWItem) -> bool:
204
+ """Refresh a novel model."""
205
+ if (
206
+ (rHandle := nwItem.itemRoot)
207
+ and (model := self._novelModels.get(rHandle))
208
+ and (node := self._itemIndex[nwItem.itemHandle])
209
+ and node.item.isDocumentLayout()
210
+ and node.item.isActive
211
+ ):
212
+ logger.info("Updating novel model data '%s'", nwItem.itemHandle)
213
+ return model.refresh(node)
214
+ return False
215
+
171
216
  ##
172
217
  # Load and Save Index to/from File
173
218
  ##
@@ -288,6 +333,10 @@ class NWIndex:
288
333
  else:
289
334
  self._scanActive(tHandle, tItem, text, itemTags)
290
335
 
336
+ if tItem.itemClass == nwItemClass.NOVEL and not blockSignal:
337
+ if not self.updateNovelModelData(tItem):
338
+ self.refreshNovelModel(tItem.itemRoot)
339
+
291
340
  # Update timestamps for index changes
292
341
  nowTime = time()
293
342
  self._indexChange = nowTime
@@ -338,10 +387,10 @@ class NWIndex:
338
387
 
339
388
  elif line.startswith("%"):
340
389
  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:
390
+ if cStyle == nwComment.FOOTNOTE:
344
391
  self._itemIndex.addNoteKey(tHandle, "footnotes", cKey)
392
+ else:
393
+ self._itemIndex.setHeadingComment(tHandle, cTitle, cStyle, cKey, cText)
345
394
 
346
395
  # Count words for remaining text after last heading
347
396
  if pTitle != TT_NONE:
@@ -402,8 +451,9 @@ class NWIndex:
402
451
  self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
403
452
  return
404
453
 
405
- def _indexKeyword(self, tHandle: str, line: str, sTitle: str,
406
- itemClass: nwItemClass, tags: dict[str, bool]) -> None:
454
+ def _indexKeyword(
455
+ self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict[str, bool]
456
+ ) -> None:
407
457
  """Validate and save the information about a reference to a tag
408
458
  in another file, or the setting of a tag in the file. A record
409
459
  of active tags is updated so that no longer used tags can be
@@ -428,6 +478,26 @@ class NWIndex:
428
478
 
429
479
  return
430
480
 
481
+ def _generateNovelModel(self, tHandle: str) -> None:
482
+ """Generate a novel model for a specific handle."""
483
+ if (item := self._project.tree[tHandle]) and item.isRootType() and item.isNovelLike():
484
+ model = NovelModel()
485
+ model.setExtraColumn(self._novelExtra)
486
+ self._appendSubTreeToModel(tHandle, model)
487
+ self._novelModels[tHandle] = model
488
+ return
489
+
490
+ def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None:
491
+ """Append all active novel documents to a novel model."""
492
+ for handle in self._project.tree.subTree(tHandle):
493
+ if (
494
+ (node := self._itemIndex[handle])
495
+ and node.item.isDocumentLayout()
496
+ and node.item.isActive
497
+ ):
498
+ model.append(node)
499
+ return
500
+
431
501
  ##
432
502
  # Check @ Lines
433
503
  ##
@@ -522,7 +592,7 @@ class NWIndex:
522
592
  # Extract Data
523
593
  ##
524
594
 
525
- def getItemData(self, tHandle: str) -> IndexItem | None:
595
+ def getItemData(self, tHandle: str) -> IndexNode | None:
526
596
  """Get the index data for a given item."""
527
597
  return self._itemIndex[tHandle]
528
598
 
@@ -536,7 +606,11 @@ class NWIndex:
536
606
  """Get all headings for a specific item."""
537
607
  if tItem := self._itemIndex[tHandle]:
538
608
  yield from tItem.items()
539
- return []
609
+ return
610
+
611
+ def getStoryKeys(self) -> set[str]:
612
+ """Return all story structure keys."""
613
+ return self._itemIndex.allStoryKeys()
540
614
 
541
615
  def novelStructure(
542
616
  self, rootHandle: str | None = None, activeOnly: bool = True
@@ -568,13 +642,6 @@ class NWIndex:
568
642
  hCount[iLevel] += 1
569
643
  return hCount
570
644
 
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
645
  def getTableOfContents(
579
646
  self, rHandle: str | None, maxDepth: int, activeOnly: bool = True
580
647
  ) -> list[tuple[str, int, str, int]]:
@@ -683,7 +750,7 @@ class NWIndex:
683
750
 
684
751
  def getTagsData(
685
752
  self, activeOnly: bool = True
686
- ) -> Iterable[tuple[str, str, str, IndexItem | None, IndexHeading | None]]:
753
+ ) -> Iterable[tuple[str, str, str, IndexNode | None, IndexHeading | None]]:
687
754
  """Return all known tags."""
688
755
  for tag, data in self._tagsIndex.items():
689
756
  iItem = self._itemIndex[data.get("handle")]
@@ -692,7 +759,7 @@ class NWIndex:
692
759
  yield tag, data.get("name", ""), data.get("class", ""), iItem, hItem
693
760
  return
694
761
 
695
- def getSingleTag(self, tagKey: str) -> tuple[str, str, IndexItem | None, IndexHeading | None]:
762
+ def getSingleTag(self, tagKey: str) -> tuple[str, str, IndexNode | None, IndexHeading | None]:
696
763
  """Return tag data for a specific tag."""
697
764
  tName = self._tagsIndex.tagName(tagKey)
698
765
  tClass = self._tagsIndex.tagClass(tagKey)
@@ -715,7 +782,7 @@ class TagsIndex:
715
782
  control of the keys.
716
783
  """
717
784
 
718
- __slots__ = ("_tags")
785
+ __slots__ = ("_tags",)
719
786
 
720
787
  def __init__(self) -> None:
721
788
  self._tags: dict[str, dict[str, str]] = {}
@@ -756,13 +823,13 @@ class TagsIndex:
756
823
  }
757
824
  return
758
825
 
759
- def tagName(self, tagKey: str) -> str:
826
+ def tagName(self, tagKey: str, default: str = "") -> str:
760
827
  """Get the name of a given tag."""
761
- return self._tags.get(tagKey.lower(), {}).get("name", "")
828
+ return self._tags.get(tagKey.lower(), {}).get("name", default)
762
829
 
763
- def tagDisplay(self, tagKey: str) -> str:
830
+ def tagDisplay(self, tagKey: str, default: str = "") -> str:
764
831
  """Get the display name of a given tag."""
765
- return self._tags.get(tagKey.lower(), {}).get("display", "")
832
+ return self._tags.get(tagKey.lower(), {}).get("display", default)
766
833
 
767
834
  def tagHandle(self, tagKey: str) -> str | None:
768
835
  """Get the handle of a given tag."""
@@ -831,6 +898,21 @@ class TagsIndex:
831
898
  return
832
899
 
833
900
 
901
+ class IndexCache:
902
+ """Core: Item Index Lookup Data Class
903
+
904
+ A small data class passed between all objects of the Item Index
905
+ which provides lookup capabilities and caching for shared data.
906
+ """
907
+
908
+ __slots__ = ("story", "tags")
909
+
910
+ def __init__(self, tagsIndex: TagsIndex) -> None:
911
+ self.tags: TagsIndex = tagsIndex
912
+ self.story: set[str] = set()
913
+ return
914
+
915
+
834
916
  # The Item Index Objects
835
917
  # ======================
836
918
 
@@ -840,15 +922,16 @@ class ItemIndex:
840
922
  A wrapper object holding the indexed items. This is a wrapper
841
923
  class around a single storage dictionary with a set of utility
842
924
  functions for setting and accessing the index data. Each indexed
843
- item is stored in an IndexItem object, which again holds an
925
+ item is stored in an IndexNode object, which again holds an
844
926
  IndexHeading object for each heading of the text.
845
927
  """
846
928
 
847
- __slots__ = ("_project", "_items")
929
+ __slots__ = ("_cache", "_items", "_project")
848
930
 
849
- def __init__(self, project: NWProject) -> None:
931
+ def __init__(self, project: NWProject, tagsIndex: TagsIndex) -> None:
850
932
  self._project = project
851
- self._items: dict[str, IndexItem] = {}
933
+ self._cache = IndexCache(tagsIndex)
934
+ self._items: dict[str, IndexNode] = {}
852
935
  return
853
936
 
854
937
  def __contains__(self, tHandle: str) -> bool:
@@ -858,7 +941,7 @@ class ItemIndex:
858
941
  self._items.pop(tHandle, None)
859
942
  return
860
943
 
861
- def __getitem__(self, tHandle: str) -> IndexItem | None:
944
+ def __getitem__(self, tHandle: str) -> IndexNode | None:
862
945
  return self._items.get(tHandle, None)
863
946
 
864
947
  ##
@@ -874,9 +957,13 @@ class ItemIndex:
874
957
  """Add a new item to the index. This will overwrite the item if
875
958
  it already exists.
876
959
  """
877
- self._items[tHandle] = IndexItem(tHandle, nwItem)
960
+ self._items[tHandle] = IndexNode(self._cache, tHandle, nwItem)
878
961
  return
879
962
 
963
+ def allStoryKeys(self) -> set[str]:
964
+ """Return all story structure keys."""
965
+ return self._cache.story.copy()
966
+
880
967
  def allItemTags(self, tHandle: str) -> list[str]:
881
968
  """Get all tags set for headings of an item."""
882
969
  if tHandle in self._items:
@@ -932,7 +1019,7 @@ class ItemIndex:
932
1019
  if tHandle in self._items:
933
1020
  tItem = self._items[tHandle]
934
1021
  sTitle = tItem.nextHeading()
935
- tItem.addHeading(IndexHeading(sTitle, lineNo, level, text))
1022
+ tItem.addHeading(IndexHeading(self._cache, sTitle, lineNo, level, text))
936
1023
  return sTitle
937
1024
  return TT_NONE
938
1025
 
@@ -944,10 +1031,13 @@ class ItemIndex:
944
1031
  self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
945
1032
  return
946
1033
 
947
- def setHeadingSynopsis(self, tHandle: str, sTitle: str, text: str) -> None:
948
- """Set the synopsis text for a heading on a given item."""
1034
+ def setHeadingComment(
1035
+ self, tHandle: str, sTitle: str,
1036
+ comment: nwComment, key: str, text: str,
1037
+ ) -> None:
1038
+ """Set a story comment for a heading on a given item."""
949
1039
  if tHandle in self._items:
950
- self._items[tHandle].setHeadingSynopsis(sTitle, text)
1040
+ self._items[tHandle].setHeadingComment(sTitle, comment, key, text)
951
1041
  return
952
1042
 
953
1043
  def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None:
@@ -991,7 +1081,7 @@ class ItemIndex:
991
1081
 
992
1082
  def unpackData(self, data: dict) -> None:
993
1083
  """Iterate through the itemIndex loaded from cache and check
994
- that it's valid. This will raise errors if there is a problem.
1084
+ that it's valid. This will raise errors if there are problems.
995
1085
  """
996
1086
  self._items = {}
997
1087
  if not isinstance(data, dict):
@@ -1003,388 +1093,8 @@ class ItemIndex:
1003
1093
 
1004
1094
  nwItem = self._project.tree[tHandle]
1005
1095
  if nwItem is not None:
1006
- tItem = IndexItem(tHandle, nwItem)
1096
+ tItem = IndexNode(self._cache, tHandle, nwItem)
1007
1097
  tItem.unpackData(tData)
1008
1098
  self._items[tHandle] = tItem
1009
1099
 
1010
1100
  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