novelWriter 2.6.2__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.
- novelwriter/__init__.py +84 -74
- novelwriter/assets/i18n/nw_cs_CZ.qm +0 -0
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/font_awesome.icons +109 -0
- novelwriter/assets/icons/material_filled_bold.icons +109 -0
- novelwriter/assets/icons/material_filled_normal.icons +109 -0
- novelwriter/assets/icons/material_filled_thin.icons +109 -0
- novelwriter/assets/icons/material_rounded_bold.icons +109 -0
- novelwriter/assets/icons/material_rounded_normal.icons +109 -0
- novelwriter/assets/icons/material_rounded_thin.icons +109 -0
- novelwriter/assets/icons/remix_filled.icons +108 -0
- novelwriter/assets/icons/remix_outline.icons +108 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
- novelwriter/assets/text/credits_en.htm +6 -6
- novelwriter/assets/themes/cyberpunk_night.conf +23 -7
- novelwriter/assets/themes/default_dark.conf +20 -4
- novelwriter/assets/themes/default_light.conf +21 -5
- novelwriter/assets/themes/dracula.conf +20 -4
- novelwriter/assets/themes/solarized_dark.conf +24 -8
- novelwriter/assets/themes/solarized_light.conf +22 -6
- novelwriter/common.py +33 -26
- novelwriter/config.py +118 -127
- novelwriter/constants.py +75 -56
- novelwriter/core/buildsettings.py +23 -16
- novelwriter/core/coretools.py +11 -7
- novelwriter/core/docbuild.py +19 -13
- novelwriter/core/document.py +2 -2
- novelwriter/core/index.py +142 -432
- novelwriter/core/indexdata.py +403 -0
- novelwriter/core/item.py +35 -28
- novelwriter/core/itemmodel.py +27 -26
- novelwriter/core/novelmodel.py +223 -0
- novelwriter/core/options.py +1 -1
- novelwriter/core/project.py +10 -11
- novelwriter/core/projectdata.py +5 -5
- novelwriter/core/projectxml.py +1 -1
- novelwriter/core/sessions.py +3 -2
- novelwriter/core/spellcheck.py +4 -3
- novelwriter/core/status.py +12 -15
- novelwriter/core/storage.py +1 -1
- novelwriter/core/tree.py +46 -8
- novelwriter/dialogs/about.py +19 -22
- novelwriter/dialogs/docmerge.py +21 -23
- novelwriter/dialogs/docsplit.py +20 -23
- novelwriter/dialogs/editlabel.py +9 -13
- novelwriter/dialogs/preferences.py +111 -48
- novelwriter/dialogs/projectsettings.py +48 -54
- novelwriter/dialogs/quotes.py +14 -19
- novelwriter/dialogs/wordlist.py +25 -30
- novelwriter/enum.py +8 -0
- novelwriter/error.py +16 -16
- novelwriter/extensions/configlayout.py +18 -18
- novelwriter/extensions/eventfilters.py +9 -5
- novelwriter/extensions/modified.py +34 -15
- novelwriter/extensions/novelselector.py +18 -5
- novelwriter/extensions/pagedsidebar.py +39 -49
- novelwriter/extensions/progressbars.py +10 -8
- novelwriter/extensions/statusled.py +6 -13
- novelwriter/extensions/switch.py +19 -30
- novelwriter/extensions/switchbox.py +7 -3
- novelwriter/extensions/versioninfo.py +4 -4
- novelwriter/formats/shared.py +1 -1
- novelwriter/formats/todocx.py +14 -10
- novelwriter/formats/tohtml.py +7 -5
- novelwriter/formats/tokenizer.py +36 -33
- novelwriter/formats/tomarkdown.py +6 -2
- novelwriter/formats/toodt.py +27 -22
- novelwriter/formats/toqdoc.py +19 -14
- novelwriter/formats/toraw.py +6 -2
- novelwriter/gui/doceditor.py +216 -265
- novelwriter/gui/dochighlight.py +46 -45
- novelwriter/gui/docviewer.py +102 -104
- novelwriter/gui/docviewerpanel.py +47 -51
- novelwriter/gui/editordocument.py +8 -5
- novelwriter/gui/itemdetails.py +15 -18
- novelwriter/gui/mainmenu.py +147 -146
- novelwriter/gui/noveltree.py +239 -405
- novelwriter/gui/outline.py +137 -76
- novelwriter/gui/projtree.py +138 -132
- novelwriter/gui/search.py +33 -31
- novelwriter/gui/sidebar.py +13 -18
- novelwriter/gui/statusbar.py +13 -15
- novelwriter/gui/theme.py +533 -430
- novelwriter/guimain.py +27 -29
- novelwriter/shared.py +32 -23
- novelwriter/text/comments.py +70 -0
- novelwriter/text/patterns.py +4 -4
- novelwriter/tools/dictionaries.py +20 -29
- novelwriter/tools/lipsum.py +16 -17
- novelwriter/tools/manusbuild.py +39 -42
- novelwriter/tools/manuscript.py +113 -126
- novelwriter/tools/manussettings.py +78 -83
- novelwriter/tools/noveldetails.py +51 -64
- novelwriter/tools/welcome.py +56 -75
- novelwriter/tools/writingstats.py +44 -57
- novelwriter/types.py +5 -7
- {novelWriter-2.6.2.dist-info → novelwriter-2.7b1.dist-info}/METADATA +6 -6
- novelwriter-2.7b1.dist-info/RECORD +159 -0
- {novelWriter-2.6.2.dist-info → novelwriter-2.7b1.dist-info}/WHEEL +1 -1
- novelWriter-2.6.2.dist-info/RECORD +0 -363
- novelwriter/assets/icons/typicons_dark/README.md +0 -29
- novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
- novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
- novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
- novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
- novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/README.md +0 -29
- novelwriter/assets/icons/typicons_light/icons.conf +0 -134
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
- novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
- novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
- novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
- novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
- {novelWriter-2.6.2.dist-info → novelwriter-2.7b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.6.2.dist-info → novelwriter-2.7b1.dist-info/licenses}/LICENSE.md +0 -0
- {novelWriter-2.6.2.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]
|
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
|
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.
|
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:
|
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
|
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
|
75
|
-
NWItem of the project. Each
|
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"<
|
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
|
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(
|
406
|
-
|
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) ->
|
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,
|
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,
|
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
|
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__ = ("
|
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.
|
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) ->
|
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] =
|
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
|
948
|
-
|
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].
|
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
|
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 =
|
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
|