novelWriter 2.6.3__py3-none-any.whl → 2.7rc1__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 +98 -75
- novelwriter/assets/i18n/project_en_GB.json +2 -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 +109 -0
- novelwriter/assets/icons/remix_outline.icons +109 -0
- novelwriter/assets/images/splash.png +0 -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/syntax/snazzy.conf +3 -3
- 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/snazzy.conf +48 -0
- novelwriter/assets/themes/solarized_dark.conf +24 -8
- novelwriter/assets/themes/solarized_light.conf +22 -6
- novelwriter/common.py +43 -27
- novelwriter/config.py +201 -139
- novelwriter/constants.py +67 -36
- novelwriter/core/buildsettings.py +25 -16
- novelwriter/core/coretools.py +52 -41
- novelwriter/core/docbuild.py +20 -13
- novelwriter/core/document.py +2 -2
- novelwriter/core/index.py +166 -432
- novelwriter/core/indexdata.py +406 -0
- novelwriter/core/item.py +50 -32
- novelwriter/core/itemmodel.py +43 -38
- novelwriter/core/novelmodel.py +225 -0
- novelwriter/core/options.py +1 -1
- novelwriter/core/project.py +24 -25
- novelwriter/core/projectdata.py +47 -29
- novelwriter/core/projectxml.py +18 -8
- novelwriter/core/sessions.py +32 -15
- novelwriter/core/spellcheck.py +4 -3
- novelwriter/core/status.py +12 -15
- novelwriter/core/storage.py +1 -1
- novelwriter/core/tree.py +55 -13
- novelwriter/dialogs/about.py +19 -22
- novelwriter/dialogs/docmerge.py +23 -24
- novelwriter/dialogs/docsplit.py +26 -26
- novelwriter/dialogs/editlabel.py +19 -20
- novelwriter/dialogs/preferences.py +143 -69
- novelwriter/dialogs/projectsettings.py +51 -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 +24 -20
- 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 +31 -41
- novelwriter/extensions/switchbox.py +8 -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 +37 -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 +314 -305
- 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 +11 -14
- novelwriter/gui/mainmenu.py +146 -145
- novelwriter/gui/noveltree.py +246 -406
- novelwriter/gui/outline.py +141 -72
- novelwriter/gui/projtree.py +139 -132
- novelwriter/gui/search.py +34 -31
- novelwriter/gui/sidebar.py +13 -18
- novelwriter/gui/statusbar.py +27 -21
- novelwriter/gui/theme.py +554 -436
- novelwriter/guimain.py +56 -38
- novelwriter/shared.py +32 -23
- novelwriter/splash.py +74 -0
- novelwriter/text/comments.py +70 -0
- novelwriter/text/patterns.py +4 -4
- novelwriter/tools/dictionaries.py +20 -29
- novelwriter/tools/lipsum.py +18 -18
- novelwriter/tools/manusbuild.py +39 -42
- novelwriter/tools/manuscript.py +102 -115
- novelwriter/tools/manussettings.py +116 -96
- novelwriter/tools/noveldetails.py +60 -72
- novelwriter/tools/welcome.py +57 -75
- novelwriter/tools/writingstats.py +112 -102
- novelwriter/types.py +5 -7
- {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/METADATA +6 -6
- novelwriter-2.7rc1.dist-info/RECORD +162 -0
- {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/WHEEL +1 -1
- novelWriter-2.6.3.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.3.dist-info → novelwriter-2.7rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info/licenses}/LICENSE.md +0 -0
- {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.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,31 @@ class NWIndex:
|
|
112
112
|
def indexBroken(self) -> bool:
|
113
113
|
return self._indexBroken
|
114
114
|
|
115
|
+
##
|
116
|
+
# Getters
|
117
|
+
##
|
118
|
+
|
119
|
+
def getNovelModel(self, tHandle: str) -> NovelModel | None:
|
120
|
+
"""Get the model for a specific novel root."""
|
121
|
+
if tHandle not in self._novelModels:
|
122
|
+
self._generateNovelModel(tHandle)
|
123
|
+
return self._novelModels.get(tHandle)
|
124
|
+
|
125
|
+
##
|
126
|
+
# Setters
|
127
|
+
##
|
128
|
+
|
129
|
+
def setNovelModelExtraColumn(self, extra: nwNovelExtra) -> None:
|
130
|
+
"""Set the data content type of the novel model extra column."""
|
131
|
+
self._novelExtra = extra
|
132
|
+
return
|
133
|
+
|
134
|
+
def setItemClass(self, tHandle: str, itemClass: nwItemClass) -> None:
|
135
|
+
"""Update the class for all tags of a handle."""
|
136
|
+
logger.info("Updating class for '%s'", tHandle)
|
137
|
+
self._tagsIndex.updateClass(tHandle, itemClass.name)
|
138
|
+
return
|
139
|
+
|
115
140
|
##
|
116
141
|
# Public Methods
|
117
142
|
##
|
@@ -134,6 +159,8 @@ class NWIndex:
|
|
134
159
|
self.scanText(nwItem.itemHandle, text, blockSignal=True)
|
135
160
|
self._indexBroken = False
|
136
161
|
SHARED.emitIndexAvailable(self._project)
|
162
|
+
for tHandle in self._novelModels:
|
163
|
+
self.refreshNovelModel(tHandle)
|
137
164
|
return
|
138
165
|
|
139
166
|
def deleteHandle(self, tHandle: str) -> None:
|
@@ -168,6 +195,30 @@ class NWIndex:
|
|
168
195
|
return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime)
|
169
196
|
return False
|
170
197
|
|
198
|
+
def refreshNovelModel(self, tHandle: str | None) -> None:
|
199
|
+
"""Refresh a novel model."""
|
200
|
+
if tHandle and (model := self.getNovelModel(tHandle)):
|
201
|
+
logger.info("Refreshing novel model '%s'", tHandle)
|
202
|
+
model.beginResetModel()
|
203
|
+
model.clear()
|
204
|
+
model.setExtraColumn(self._novelExtra)
|
205
|
+
self._appendSubTreeToModel(tHandle, model)
|
206
|
+
model.endResetModel()
|
207
|
+
return
|
208
|
+
|
209
|
+
def updateNovelModelData(self, nwItem: NWItem) -> bool:
|
210
|
+
"""Refresh a novel model."""
|
211
|
+
if (
|
212
|
+
(rHandle := nwItem.itemRoot)
|
213
|
+
and (model := self._novelModels.get(rHandle))
|
214
|
+
and (node := self._itemIndex[nwItem.itemHandle])
|
215
|
+
and node.item.isDocumentLayout()
|
216
|
+
and node.item.isActive
|
217
|
+
):
|
218
|
+
logger.info("Updating novel model data '%s'", nwItem.itemHandle)
|
219
|
+
return model.refresh(node)
|
220
|
+
return False
|
221
|
+
|
171
222
|
##
|
172
223
|
# Load and Save Index to/from File
|
173
224
|
##
|
@@ -288,6 +339,10 @@ class NWIndex:
|
|
288
339
|
else:
|
289
340
|
self._scanActive(tHandle, tItem, text, itemTags)
|
290
341
|
|
342
|
+
if tItem.itemClass == nwItemClass.NOVEL and not blockSignal:
|
343
|
+
if not self.updateNovelModelData(tItem):
|
344
|
+
self.refreshNovelModel(tItem.itemRoot)
|
345
|
+
|
291
346
|
# Update timestamps for index changes
|
292
347
|
nowTime = time()
|
293
348
|
self._indexChange = nowTime
|
@@ -338,10 +393,10 @@ class NWIndex:
|
|
338
393
|
|
339
394
|
elif line.startswith("%"):
|
340
395
|
cStyle, cKey, cText, _, _ = processComment(line)
|
341
|
-
if cStyle
|
342
|
-
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText)
|
343
|
-
elif cStyle == nwComment.FOOTNOTE:
|
396
|
+
if cStyle == nwComment.FOOTNOTE:
|
344
397
|
self._itemIndex.addNoteKey(tHandle, "footnotes", cKey)
|
398
|
+
else:
|
399
|
+
self._itemIndex.setHeadingComment(tHandle, cTitle, cStyle, cKey, cText)
|
345
400
|
|
346
401
|
# Count words for remaining text after last heading
|
347
402
|
if pTitle != TT_NONE:
|
@@ -402,8 +457,9 @@ class NWIndex:
|
|
402
457
|
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
|
403
458
|
return
|
404
459
|
|
405
|
-
def _indexKeyword(
|
406
|
-
|
460
|
+
def _indexKeyword(
|
461
|
+
self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict[str, bool]
|
462
|
+
) -> None:
|
407
463
|
"""Validate and save the information about a reference to a tag
|
408
464
|
in another file, or the setting of a tag in the file. A record
|
409
465
|
of active tags is updated so that no longer used tags can be
|
@@ -428,6 +484,26 @@ class NWIndex:
|
|
428
484
|
|
429
485
|
return
|
430
486
|
|
487
|
+
def _generateNovelModel(self, tHandle: str) -> None:
|
488
|
+
"""Generate a novel model for a specific handle."""
|
489
|
+
if (item := self._project.tree[tHandle]) and item.isRootType() and item.isNovelLike():
|
490
|
+
model = NovelModel()
|
491
|
+
model.setExtraColumn(self._novelExtra)
|
492
|
+
self._appendSubTreeToModel(tHandle, model)
|
493
|
+
self._novelModels[tHandle] = model
|
494
|
+
return
|
495
|
+
|
496
|
+
def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None:
|
497
|
+
"""Append all active novel documents to a novel model."""
|
498
|
+
for handle in self._project.tree.subTree(tHandle):
|
499
|
+
if (
|
500
|
+
(node := self._itemIndex[handle])
|
501
|
+
and node.item.isDocumentLayout()
|
502
|
+
and node.item.isActive
|
503
|
+
):
|
504
|
+
model.append(node)
|
505
|
+
return
|
506
|
+
|
431
507
|
##
|
432
508
|
# Check @ Lines
|
433
509
|
##
|
@@ -522,7 +598,7 @@ class NWIndex:
|
|
522
598
|
# Extract Data
|
523
599
|
##
|
524
600
|
|
525
|
-
def getItemData(self, tHandle: str) ->
|
601
|
+
def getItemData(self, tHandle: str) -> IndexNode | None:
|
526
602
|
"""Get the index data for a given item."""
|
527
603
|
return self._itemIndex[tHandle]
|
528
604
|
|
@@ -536,7 +612,15 @@ class NWIndex:
|
|
536
612
|
"""Get all headings for a specific item."""
|
537
613
|
if tItem := self._itemIndex[tHandle]:
|
538
614
|
yield from tItem.items()
|
539
|
-
return
|
615
|
+
return
|
616
|
+
|
617
|
+
def getStoryKeys(self) -> set[str]:
|
618
|
+
"""Return all story structure keys."""
|
619
|
+
return self._itemIndex.allStoryKeys()
|
620
|
+
|
621
|
+
def getNoteKeys(self) -> set[str]:
|
622
|
+
"""Return all note comment keys."""
|
623
|
+
return self._itemIndex.allNoteKeys()
|
540
624
|
|
541
625
|
def novelStructure(
|
542
626
|
self, rootHandle: str | None = None, activeOnly: bool = True
|
@@ -568,13 +652,6 @@ class NWIndex:
|
|
568
652
|
hCount[iLevel] += 1
|
569
653
|
return hCount
|
570
654
|
|
571
|
-
def getHandleHeaderCount(self, tHandle: str) -> int:
|
572
|
-
"""Get the number of headers in an item."""
|
573
|
-
tItem = self._itemIndex[tHandle]
|
574
|
-
if isinstance(tItem, IndexItem):
|
575
|
-
return len(tItem)
|
576
|
-
return 0
|
577
|
-
|
578
655
|
def getTableOfContents(
|
579
656
|
self, rHandle: str | None, maxDepth: int, activeOnly: bool = True
|
580
657
|
) -> list[tuple[str, int, str, int]]:
|
@@ -683,7 +760,7 @@ class NWIndex:
|
|
683
760
|
|
684
761
|
def getTagsData(
|
685
762
|
self, activeOnly: bool = True
|
686
|
-
) -> Iterable[tuple[str, str, str,
|
763
|
+
) -> Iterable[tuple[str, str, str, IndexNode | None, IndexHeading | None]]:
|
687
764
|
"""Return all known tags."""
|
688
765
|
for tag, data in self._tagsIndex.items():
|
689
766
|
iItem = self._itemIndex[data.get("handle")]
|
@@ -692,7 +769,7 @@ class NWIndex:
|
|
692
769
|
yield tag, data.get("name", ""), data.get("class", ""), iItem, hItem
|
693
770
|
return
|
694
771
|
|
695
|
-
def getSingleTag(self, tagKey: str) -> tuple[str, str,
|
772
|
+
def getSingleTag(self, tagKey: str) -> tuple[str, str, IndexNode | None, IndexHeading | None]:
|
696
773
|
"""Return tag data for a specific tag."""
|
697
774
|
tName = self._tagsIndex.tagName(tagKey)
|
698
775
|
tClass = self._tagsIndex.tagClass(tagKey)
|
@@ -715,7 +792,7 @@ class TagsIndex:
|
|
715
792
|
control of the keys.
|
716
793
|
"""
|
717
794
|
|
718
|
-
__slots__ = ("_tags")
|
795
|
+
__slots__ = ("_tags",)
|
719
796
|
|
720
797
|
def __init__(self) -> None:
|
721
798
|
self._tags: dict[str, dict[str, str]] = {}
|
@@ -756,13 +833,13 @@ class TagsIndex:
|
|
756
833
|
}
|
757
834
|
return
|
758
835
|
|
759
|
-
def tagName(self, tagKey: str) -> str:
|
836
|
+
def tagName(self, tagKey: str, default: str = "") -> str:
|
760
837
|
"""Get the name of a given tag."""
|
761
|
-
return self._tags.get(tagKey.lower(), {}).get("name",
|
838
|
+
return self._tags.get(tagKey.lower(), {}).get("name", default)
|
762
839
|
|
763
|
-
def tagDisplay(self, tagKey: str) -> str:
|
840
|
+
def tagDisplay(self, tagKey: str, default: str = "") -> str:
|
764
841
|
"""Get the display name of a given tag."""
|
765
|
-
return self._tags.get(tagKey.lower(), {}).get("display",
|
842
|
+
return self._tags.get(tagKey.lower(), {}).get("display", default)
|
766
843
|
|
767
844
|
def tagHandle(self, tagKey: str) -> str | None:
|
768
845
|
"""Get the handle of a given tag."""
|
@@ -787,6 +864,15 @@ class TagsIndex:
|
|
787
864
|
x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
|
788
865
|
]
|
789
866
|
|
867
|
+
def updateClass(self, tHandle: str, className: str) -> None:
|
868
|
+
"""Update the class name of an item. This must be called when a
|
869
|
+
document moves to another class.
|
870
|
+
"""
|
871
|
+
for entry in self._tags.values():
|
872
|
+
if entry.get("handle") == tHandle:
|
873
|
+
entry["class"] = className
|
874
|
+
return
|
875
|
+
|
790
876
|
##
|
791
877
|
# Pack/Unpack
|
792
878
|
##
|
@@ -831,6 +917,22 @@ class TagsIndex:
|
|
831
917
|
return
|
832
918
|
|
833
919
|
|
920
|
+
class IndexCache:
|
921
|
+
"""Core: Item Index Lookup Data Class
|
922
|
+
|
923
|
+
A small data class passed between all objects of the Item Index
|
924
|
+
which provides lookup capabilities and caching for shared data.
|
925
|
+
"""
|
926
|
+
|
927
|
+
__slots__ = ("note", "story", "tags")
|
928
|
+
|
929
|
+
def __init__(self, tagsIndex: TagsIndex) -> None:
|
930
|
+
self.tags: TagsIndex = tagsIndex
|
931
|
+
self.story: set[str] = set()
|
932
|
+
self.note: set[str] = set()
|
933
|
+
return
|
934
|
+
|
935
|
+
|
834
936
|
# The Item Index Objects
|
835
937
|
# ======================
|
836
938
|
|
@@ -840,15 +942,16 @@ class ItemIndex:
|
|
840
942
|
A wrapper object holding the indexed items. This is a wrapper
|
841
943
|
class around a single storage dictionary with a set of utility
|
842
944
|
functions for setting and accessing the index data. Each indexed
|
843
|
-
item is stored in an
|
945
|
+
item is stored in an IndexNode object, which again holds an
|
844
946
|
IndexHeading object for each heading of the text.
|
845
947
|
"""
|
846
948
|
|
847
|
-
__slots__ = ("
|
949
|
+
__slots__ = ("_cache", "_items", "_project")
|
848
950
|
|
849
|
-
def __init__(self, project: NWProject) -> None:
|
951
|
+
def __init__(self, project: NWProject, tagsIndex: TagsIndex) -> None:
|
850
952
|
self._project = project
|
851
|
-
self.
|
953
|
+
self._cache = IndexCache(tagsIndex)
|
954
|
+
self._items: dict[str, IndexNode] = {}
|
852
955
|
return
|
853
956
|
|
854
957
|
def __contains__(self, tHandle: str) -> bool:
|
@@ -858,7 +961,7 @@ class ItemIndex:
|
|
858
961
|
self._items.pop(tHandle, None)
|
859
962
|
return
|
860
963
|
|
861
|
-
def __getitem__(self, tHandle: str) ->
|
964
|
+
def __getitem__(self, tHandle: str) -> IndexNode | None:
|
862
965
|
return self._items.get(tHandle, None)
|
863
966
|
|
864
967
|
##
|
@@ -874,9 +977,17 @@ class ItemIndex:
|
|
874
977
|
"""Add a new item to the index. This will overwrite the item if
|
875
978
|
it already exists.
|
876
979
|
"""
|
877
|
-
self._items[tHandle] =
|
980
|
+
self._items[tHandle] = IndexNode(self._cache, tHandle, nwItem)
|
878
981
|
return
|
879
982
|
|
983
|
+
def allStoryKeys(self) -> set[str]:
|
984
|
+
"""Return all story structure keys."""
|
985
|
+
return self._cache.story.copy()
|
986
|
+
|
987
|
+
def allNoteKeys(self) -> set[str]:
|
988
|
+
"""Return all note comment keys."""
|
989
|
+
return self._cache.note.copy()
|
990
|
+
|
880
991
|
def allItemTags(self, tHandle: str) -> list[str]:
|
881
992
|
"""Get all tags set for headings of an item."""
|
882
993
|
if tHandle in self._items:
|
@@ -932,7 +1043,7 @@ class ItemIndex:
|
|
932
1043
|
if tHandle in self._items:
|
933
1044
|
tItem = self._items[tHandle]
|
934
1045
|
sTitle = tItem.nextHeading()
|
935
|
-
tItem.addHeading(IndexHeading(sTitle, lineNo, level, text))
|
1046
|
+
tItem.addHeading(IndexHeading(self._cache, sTitle, lineNo, level, text))
|
936
1047
|
return sTitle
|
937
1048
|
return TT_NONE
|
938
1049
|
|
@@ -944,10 +1055,13 @@ class ItemIndex:
|
|
944
1055
|
self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
|
945
1056
|
return
|
946
1057
|
|
947
|
-
def
|
948
|
-
|
1058
|
+
def setHeadingComment(
|
1059
|
+
self, tHandle: str, sTitle: str,
|
1060
|
+
comment: nwComment, key: str, text: str,
|
1061
|
+
) -> None:
|
1062
|
+
"""Set a story comment for a heading on a given item."""
|
949
1063
|
if tHandle in self._items:
|
950
|
-
self._items[tHandle].
|
1064
|
+
self._items[tHandle].setHeadingComment(sTitle, comment, key, text)
|
951
1065
|
return
|
952
1066
|
|
953
1067
|
def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None:
|
@@ -991,7 +1105,7 @@ class ItemIndex:
|
|
991
1105
|
|
992
1106
|
def unpackData(self, data: dict) -> None:
|
993
1107
|
"""Iterate through the itemIndex loaded from cache and check
|
994
|
-
that it's valid. This will raise errors if there
|
1108
|
+
that it's valid. This will raise errors if there are problems.
|
995
1109
|
"""
|
996
1110
|
self._items = {}
|
997
1111
|
if not isinstance(data, dict):
|
@@ -1003,388 +1117,8 @@ class ItemIndex:
|
|
1003
1117
|
|
1004
1118
|
nwItem = self._project.tree[tHandle]
|
1005
1119
|
if nwItem is not None:
|
1006
|
-
tItem =
|
1120
|
+
tItem = IndexNode(self._cache, tHandle, nwItem)
|
1007
1121
|
tItem.unpackData(tData)
|
1008
1122
|
self._items[tHandle] = tItem
|
1009
1123
|
|
1010
1124
|
return
|
1011
|
-
|
1012
|
-
|
1013
|
-
class IndexItem:
|
1014
|
-
"""Core: Single Index Item Class
|
1015
|
-
|
1016
|
-
This object represents the index data of a project item (NWItem).
|
1017
|
-
It holds a record of all the headings in the text, and the meta data
|
1018
|
-
associated with each heading. It also holds a pointer to the project
|
1019
|
-
item. The main heading level of the item is also held here since it
|
1020
|
-
must be reset each time the item is re-indexed.
|
1021
|
-
"""
|
1022
|
-
|
1023
|
-
__slots__ = ("_handle", "_item", "_headings", "_count", "_notes")
|
1024
|
-
|
1025
|
-
def __init__(self, tHandle: str, nwItem: NWItem) -> None:
|
1026
|
-
self._handle = tHandle
|
1027
|
-
self._item = nwItem
|
1028
|
-
self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(TT_NONE)}
|
1029
|
-
self._notes: dict[str, set[str]] = {}
|
1030
|
-
self._count = 0
|
1031
|
-
return
|
1032
|
-
|
1033
|
-
def __repr__(self) -> str:
|
1034
|
-
return f"<IndexItem handle='{self._handle}'>"
|
1035
|
-
|
1036
|
-
def __len__(self) -> int:
|
1037
|
-
return len(self._headings)
|
1038
|
-
|
1039
|
-
def __getitem__(self, sTitle: str) -> IndexHeading | None:
|
1040
|
-
return self._headings.get(sTitle, None)
|
1041
|
-
|
1042
|
-
def __contains__(self, sTitle: str) -> bool:
|
1043
|
-
return sTitle in self._headings
|
1044
|
-
|
1045
|
-
##
|
1046
|
-
# Properties
|
1047
|
-
##
|
1048
|
-
|
1049
|
-
@property
|
1050
|
-
def handle(self) -> str:
|
1051
|
-
"""Return the item handle of the index item."""
|
1052
|
-
return self._handle
|
1053
|
-
|
1054
|
-
@property
|
1055
|
-
def item(self) -> NWItem:
|
1056
|
-
"""Return the project item of the index item."""
|
1057
|
-
return self._item
|
1058
|
-
|
1059
|
-
##
|
1060
|
-
# Setters
|
1061
|
-
##
|
1062
|
-
|
1063
|
-
def addHeading(self, tHeading: IndexHeading) -> None:
|
1064
|
-
"""Add a heading to the item. Also remove the placeholder entry
|
1065
|
-
if it exists.
|
1066
|
-
"""
|
1067
|
-
if TT_NONE in self._headings:
|
1068
|
-
self._headings.pop(TT_NONE)
|
1069
|
-
self._headings[tHeading.key] = tHeading
|
1070
|
-
return
|
1071
|
-
|
1072
|
-
def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None:
|
1073
|
-
"""Set the character, word and paragraph count of a heading."""
|
1074
|
-
if sTitle in self._headings:
|
1075
|
-
self._headings[sTitle].setCounts(cCount, wCount, pCount)
|
1076
|
-
return
|
1077
|
-
|
1078
|
-
def setHeadingSynopsis(self, sTitle: str, text: str) -> None:
|
1079
|
-
"""Set the synopsis text of a heading."""
|
1080
|
-
if sTitle in self._headings:
|
1081
|
-
self._headings[sTitle].setSynopsis(text)
|
1082
|
-
return
|
1083
|
-
|
1084
|
-
def setHeadingTag(self, sTitle: str, tagKey: str) -> None:
|
1085
|
-
"""Set the tag of a heading."""
|
1086
|
-
if sTitle in self._headings:
|
1087
|
-
self._headings[sTitle].setTag(tagKey)
|
1088
|
-
return
|
1089
|
-
|
1090
|
-
def addHeadingRef(self, sTitle: str, tagKeys: list[str], refType: str) -> None:
|
1091
|
-
"""Add a reference key and all its types to a heading."""
|
1092
|
-
if sTitle in self._headings:
|
1093
|
-
for tagKey in tagKeys:
|
1094
|
-
self._headings[sTitle].addReference(tagKey, refType)
|
1095
|
-
return
|
1096
|
-
|
1097
|
-
def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
|
1098
|
-
"""Add a note key to the index."""
|
1099
|
-
if style not in self._notes:
|
1100
|
-
self._notes[style] = set()
|
1101
|
-
self._notes[style].add(key)
|
1102
|
-
return
|
1103
|
-
|
1104
|
-
##
|
1105
|
-
# Data Methods
|
1106
|
-
##
|
1107
|
-
|
1108
|
-
def items(self) -> ItemsView[str, IndexHeading]:
|
1109
|
-
"""Return IndexHeading items."""
|
1110
|
-
return self._headings.items()
|
1111
|
-
|
1112
|
-
def headings(self) -> list[str]:
|
1113
|
-
"""Return heading keys in sorted order."""
|
1114
|
-
return sorted(self._headings.keys())
|
1115
|
-
|
1116
|
-
def allTags(self) -> list[str]:
|
1117
|
-
"""Return a list of all tags in the current item."""
|
1118
|
-
return [h.tag for h in self._headings.values() if h.tag]
|
1119
|
-
|
1120
|
-
def nextHeading(self) -> str:
|
1121
|
-
"""Return the next heading key to be used."""
|
1122
|
-
self._count += 1
|
1123
|
-
return f"T{self._count:04d}"
|
1124
|
-
|
1125
|
-
def noteKeys(self, style: T_NoteTypes) -> set[str]:
|
1126
|
-
"""Return a set of all note keys."""
|
1127
|
-
return self._notes.get(style, set())
|
1128
|
-
|
1129
|
-
##
|
1130
|
-
# Pack/Unpack
|
1131
|
-
##
|
1132
|
-
|
1133
|
-
def packData(self) -> dict:
|
1134
|
-
"""Pack the indexed item's data into a dictionary."""
|
1135
|
-
heads = {}
|
1136
|
-
refs = {}
|
1137
|
-
for sTitle, hItem in self._headings.items():
|
1138
|
-
heads[sTitle] = hItem.packData()
|
1139
|
-
hRefs = hItem.packReferences()
|
1140
|
-
if hRefs:
|
1141
|
-
refs[sTitle] = hRefs
|
1142
|
-
|
1143
|
-
data = {}
|
1144
|
-
data["headings"] = heads
|
1145
|
-
if refs:
|
1146
|
-
data["references"] = refs
|
1147
|
-
if self._notes:
|
1148
|
-
data["notes"] = {style: list(keys) for style, keys in self._notes.items()}
|
1149
|
-
|
1150
|
-
return data
|
1151
|
-
|
1152
|
-
def unpackData(self, data: dict) -> None:
|
1153
|
-
"""Unpack an item entry from the data."""
|
1154
|
-
references = data.get("references", {})
|
1155
|
-
for sTitle, hData in data.get("headings", {}).items():
|
1156
|
-
if not isTitleTag(sTitle):
|
1157
|
-
raise ValueError("The itemIndex contains an invalid title key")
|
1158
|
-
tHeading = IndexHeading(sTitle)
|
1159
|
-
tHeading.unpackData(hData)
|
1160
|
-
tHeading.unpackReferences(references.get(sTitle, {}))
|
1161
|
-
self.addHeading(tHeading)
|
1162
|
-
|
1163
|
-
for style, keys in data.get("notes", {}).items():
|
1164
|
-
if style not in NOTE_TYPES:
|
1165
|
-
raise ValueError("The notes style is invalid")
|
1166
|
-
if not isListInstance(keys, str):
|
1167
|
-
raise ValueError("The notes keys must be a list of strings")
|
1168
|
-
self._notes[style] = set(keys)
|
1169
|
-
|
1170
|
-
return
|
1171
|
-
|
1172
|
-
|
1173
|
-
class IndexHeading:
|
1174
|
-
"""Core: Single Index Heading Class
|
1175
|
-
|
1176
|
-
This object represents a section of text in a project item
|
1177
|
-
associated with a single (valid) heading. It holds a separate record
|
1178
|
-
of all references made under the heading.
|
1179
|
-
"""
|
1180
|
-
|
1181
|
-
__slots__ = (
|
1182
|
-
"_key", "_line", "_level", "_title", "_charCount", "_wordCount",
|
1183
|
-
"_paraCount", "_synopsis", "_tag", "_refs",
|
1184
|
-
)
|
1185
|
-
|
1186
|
-
def __init__(self, key: str, line: int = 0, level: str = "H0", title: str = "") -> None:
|
1187
|
-
self._key = key
|
1188
|
-
self._line = line
|
1189
|
-
self._level = level
|
1190
|
-
self._title = title
|
1191
|
-
|
1192
|
-
self._charCount = 0
|
1193
|
-
self._wordCount = 0
|
1194
|
-
self._paraCount = 0
|
1195
|
-
self._synopsis = ""
|
1196
|
-
|
1197
|
-
self._tag = ""
|
1198
|
-
self._refs: dict[str, set[str]] = {}
|
1199
|
-
|
1200
|
-
return
|
1201
|
-
|
1202
|
-
def __repr__(self) -> str:
|
1203
|
-
return f"<IndexHeading key='{self._key}'>"
|
1204
|
-
|
1205
|
-
##
|
1206
|
-
# Properties
|
1207
|
-
##
|
1208
|
-
|
1209
|
-
@property
|
1210
|
-
def key(self) -> str:
|
1211
|
-
return self._key
|
1212
|
-
|
1213
|
-
@property
|
1214
|
-
def line(self) -> int:
|
1215
|
-
return self._line
|
1216
|
-
|
1217
|
-
@property
|
1218
|
-
def level(self) -> str:
|
1219
|
-
return self._level
|
1220
|
-
|
1221
|
-
@property
|
1222
|
-
def title(self) -> str:
|
1223
|
-
return self._title
|
1224
|
-
|
1225
|
-
@property
|
1226
|
-
def charCount(self) -> int:
|
1227
|
-
return self._charCount
|
1228
|
-
|
1229
|
-
@property
|
1230
|
-
def wordCount(self) -> int:
|
1231
|
-
return self._wordCount
|
1232
|
-
|
1233
|
-
@property
|
1234
|
-
def paraCount(self) -> int:
|
1235
|
-
return self._paraCount
|
1236
|
-
|
1237
|
-
@property
|
1238
|
-
def synopsis(self) -> str:
|
1239
|
-
return self._synopsis
|
1240
|
-
|
1241
|
-
@property
|
1242
|
-
def tag(self) -> str:
|
1243
|
-
return self._tag
|
1244
|
-
|
1245
|
-
@property
|
1246
|
-
def references(self) -> dict[str, set[str]]:
|
1247
|
-
return self._refs
|
1248
|
-
|
1249
|
-
##
|
1250
|
-
# Setters
|
1251
|
-
##
|
1252
|
-
|
1253
|
-
def setLevel(self, level: str) -> None:
|
1254
|
-
"""Set the level of the heading if it's a valid value."""
|
1255
|
-
if level in nwStyles.H_VALID:
|
1256
|
-
self._level = level
|
1257
|
-
return
|
1258
|
-
|
1259
|
-
def setLine(self, line: int) -> None:
|
1260
|
-
"""Set the line number of a heading."""
|
1261
|
-
self._line = max(0, checkInt(line, 0))
|
1262
|
-
return
|
1263
|
-
|
1264
|
-
def setCounts(self, charCount: int, wordCount: int, paraCount: int) -> None:
|
1265
|
-
"""Set the character, word and paragraph count. Make sure the
|
1266
|
-
value is an integer and is not smaller than 0.
|
1267
|
-
"""
|
1268
|
-
self._charCount = max(0, checkInt(charCount, 0))
|
1269
|
-
self._wordCount = max(0, checkInt(wordCount, 0))
|
1270
|
-
self._paraCount = max(0, checkInt(paraCount, 0))
|
1271
|
-
return
|
1272
|
-
|
1273
|
-
def setSynopsis(self, text: str) -> None:
|
1274
|
-
"""Set the synopsis text and make sure it is a string."""
|
1275
|
-
self._synopsis = str(text)
|
1276
|
-
return
|
1277
|
-
|
1278
|
-
def setTag(self, tagKey: str) -> None:
|
1279
|
-
"""Set the tag for references, and make sure it is a string."""
|
1280
|
-
self._tag = str(tagKey).lower()
|
1281
|
-
return
|
1282
|
-
|
1283
|
-
def addReference(self, tagKey: str, refType: str) -> None:
|
1284
|
-
"""Add a record of a reference tag, and what keyword types it is
|
1285
|
-
associated with.
|
1286
|
-
"""
|
1287
|
-
if refType in nwKeyWords.VALID_KEYS:
|
1288
|
-
tagKey = tagKey.lower()
|
1289
|
-
if tagKey not in self._refs:
|
1290
|
-
self._refs[tagKey] = set()
|
1291
|
-
self._refs[tagKey].add(refType)
|
1292
|
-
return
|
1293
|
-
|
1294
|
-
##
|
1295
|
-
# Data Methods
|
1296
|
-
##
|
1297
|
-
|
1298
|
-
def packData(self) -> dict:
|
1299
|
-
"""Pack the values into a dictionary for saving to cache."""
|
1300
|
-
return {
|
1301
|
-
"level": self._level,
|
1302
|
-
"title": self._title,
|
1303
|
-
"line": self._line,
|
1304
|
-
"tag": self._tag,
|
1305
|
-
"cCount": self._charCount,
|
1306
|
-
"wCount": self._wordCount,
|
1307
|
-
"pCount": self._paraCount,
|
1308
|
-
"synopsis": self._synopsis,
|
1309
|
-
}
|
1310
|
-
|
1311
|
-
def packReferences(self) -> dict[str, str]:
|
1312
|
-
"""Pack references into a dictionary for saving to cache.
|
1313
|
-
Multiple types are packed into a sorted, comma separated string.
|
1314
|
-
It is sorted to prevent creating unnecessary diffs as the order
|
1315
|
-
of a set is not guaranteed.
|
1316
|
-
"""
|
1317
|
-
return {key: ",".join(sorted(list(value))) for key, value in self._refs.items()}
|
1318
|
-
|
1319
|
-
def unpackData(self, data: dict) -> None:
|
1320
|
-
"""Unpack a heading entry from a dictionary."""
|
1321
|
-
self.setLevel(data.get("level", "H0"))
|
1322
|
-
self._title = str(data.get("title", ""))
|
1323
|
-
self._tag = str(data.get("tag", ""))
|
1324
|
-
self.setLine(data.get("line", 0))
|
1325
|
-
self.setCounts(
|
1326
|
-
data.get("cCount", 0),
|
1327
|
-
data.get("wCount", 0),
|
1328
|
-
data.get("pCount", 0),
|
1329
|
-
)
|
1330
|
-
self._synopsis = str(data.get("synopsis", ""))
|
1331
|
-
return
|
1332
|
-
|
1333
|
-
def unpackReferences(self, data: dict) -> None:
|
1334
|
-
"""Unpack a set of references from a dictionary."""
|
1335
|
-
for tagKey, refTypes in data.items():
|
1336
|
-
if not isinstance(tagKey, str):
|
1337
|
-
raise ValueError("itemIndex reference key must be a string")
|
1338
|
-
if not isinstance(refTypes, str):
|
1339
|
-
raise ValueError("itemIndex reference type must be a string")
|
1340
|
-
for refType in refTypes.split(","):
|
1341
|
-
if refType in nwKeyWords.VALID_KEYS:
|
1342
|
-
self.addReference(tagKey, refType)
|
1343
|
-
else:
|
1344
|
-
raise ValueError("The itemIndex contains an invalid reference type")
|
1345
|
-
return
|
1346
|
-
|
1347
|
-
|
1348
|
-
# Text Processing Functions
|
1349
|
-
# =========================
|
1350
|
-
|
1351
|
-
MODIFIERS = {
|
1352
|
-
"synopsis": nwComment.SYNOPSIS,
|
1353
|
-
"short": nwComment.SHORT,
|
1354
|
-
"note": nwComment.NOTE,
|
1355
|
-
"footnote": nwComment.FOOTNOTE,
|
1356
|
-
}
|
1357
|
-
KEY_REQ = {
|
1358
|
-
"synopsis": 0, # Key not allowed
|
1359
|
-
"short": 0, # Key not allowed
|
1360
|
-
"note": 1, # Key optional
|
1361
|
-
"footnote": 2, # Key required
|
1362
|
-
}
|
1363
|
-
|
1364
|
-
|
1365
|
-
def _checkModKey(modifier: str, key: str) -> bool:
|
1366
|
-
"""Check if a modifier and key set are ok."""
|
1367
|
-
if modifier in MODIFIERS:
|
1368
|
-
if key == "":
|
1369
|
-
return KEY_REQ[modifier] < 2
|
1370
|
-
elif key.replace("_", "").isalnum():
|
1371
|
-
return KEY_REQ[modifier] > 0
|
1372
|
-
return False
|
1373
|
-
|
1374
|
-
|
1375
|
-
def processComment(text: str) -> tuple[nwComment, str, str, int, int]:
|
1376
|
-
"""Extract comment style, key and text. Should only be called on
|
1377
|
-
text starting with a %.
|
1378
|
-
"""
|
1379
|
-
if text[:2] == "%~":
|
1380
|
-
return nwComment.IGNORE, "", text[2:].lstrip(), 0, 0
|
1381
|
-
|
1382
|
-
check = text[1:].strip()
|
1383
|
-
start, _, content = check.partition(":")
|
1384
|
-
modifier, _, key = start.rstrip().partition(".")
|
1385
|
-
if content and (clean := modifier.lower()) and _checkModKey(clean, key):
|
1386
|
-
col = text.find(":") + 1
|
1387
|
-
dot = text.find(".", 0, col) + 1
|
1388
|
-
return MODIFIERS[clean], key, content.lstrip(), dot, col
|
1389
|
-
|
1390
|
-
return nwComment.PLAIN, "", check, 0, 0
|