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