novelWriter 2.7.4__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 +207 -245
 - 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 +25 -13
 - {novelwriter-2.7.4.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.4.dist-info/RECORD +0 -163
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
 
    
        novelwriter/gui/dochighlight.py
    CHANGED
    
    | 
         @@ -21,7 +21,7 @@ General Public License for more details. 
     | 
|
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            You should have received a copy of the GNU General Public License
         
     | 
| 
       23 
23 
     | 
    
         
             
            along with this program. If not, see <https://www.gnu.org/licenses/>.
         
     | 
| 
       24 
     | 
    
         
            -
            """
         
     | 
| 
      
 24 
     | 
    
         
            +
            """  # noqa
         
     | 
| 
       25 
25 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
            import logging
         
     | 
| 
         @@ -36,11 +36,12 @@ from PyQt6.QtGui import ( 
     | 
|
| 
       36 
36 
     | 
    
         
             
            )
         
     | 
| 
       37 
37 
     | 
    
         | 
| 
       38 
38 
     | 
    
         
             
            from novelwriter import CONFIG, SHARED
         
     | 
| 
       39 
     | 
    
         
            -
            from novelwriter.common import checkInt
         
     | 
| 
      
 39 
     | 
    
         
            +
            from novelwriter.common import checkInt, utf16CharMap
         
     | 
| 
       40 
40 
     | 
    
         
             
            from novelwriter.constants import nwStyles, nwUnicode
         
     | 
| 
       41 
41 
     | 
    
         
             
            from novelwriter.enum import nwComment
         
     | 
| 
       42 
42 
     | 
    
         
             
            from novelwriter.text.comments import processComment
         
     | 
| 
       43 
43 
     | 
    
         
             
            from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
         
     | 
| 
      
 44 
     | 
    
         
            +
            from novelwriter.types import QtTextUserProperty
         
     | 
| 
       44 
45 
     | 
    
         | 
| 
       45 
46 
     | 
    
         
             
            logger = logging.getLogger(__name__)
         
     | 
| 
       46 
47 
     | 
    
         | 
| 
         @@ -56,6 +57,7 @@ BLOCK_TITLE = 4 
     | 
|
| 
       56 
57 
     | 
    
         | 
| 
       57 
58 
     | 
    
         | 
| 
       58 
59 
     | 
    
         
             
            class GuiDocHighlighter(QSyntaxHighlighter):
         
     | 
| 
      
 60 
     | 
    
         
            +
                """GUI: Editor Syntax Highlighter."""
         
     | 
| 
       59 
61 
     | 
    
         | 
| 
       60 
62 
     | 
    
         
             
                __slots__ = (
         
     | 
| 
       61 
63 
     | 
    
         
             
                    "_cmnRules", "_dialogParser", "_hStyles", "_isInactive", "_isNovel",
         
     | 
| 
         @@ -84,8 +86,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       84 
86 
     | 
    
         | 
| 
       85 
87 
     | 
    
         
             
                    logger.debug("Ready: GuiDocHighlighter")
         
     | 
| 
       86 
88 
     | 
    
         | 
| 
       87 
     | 
    
         
            -
                    return
         
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
89 
     | 
    
         
             
                def initHighlighter(self) -> None:
         
     | 
| 
       90 
90 
     | 
    
         
             
                    """Initialise the syntax highlighter, setting all the colour
         
     | 
| 
       91 
91 
     | 
    
         
             
                    rules and building the RegExes.
         
     | 
| 
         @@ -94,8 +94,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       94 
94 
     | 
    
         
             
                    syntax = SHARED.theme.syntaxTheme
         
     | 
| 
       95 
95 
     | 
    
         | 
| 
       96 
96 
     | 
    
         
             
                    colEmph = syntax.emph if CONFIG.highlightEmph else None
         
     | 
| 
       97 
     | 
    
         
            -
                    colBreak = QColor(syntax.emph)
         
     | 
| 
       98 
     | 
    
         
            -
                    colBreak.setAlpha(64)
         
     | 
| 
       99 
97 
     | 
    
         | 
| 
       100 
98 
     | 
    
         
             
                    # Create Character Formats
         
     | 
| 
       101 
99 
     | 
    
         
             
                    self._addCharFormat("text",      syntax.text)
         
     | 
| 
         @@ -110,8 +108,9 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       110 
108 
     | 
    
         
             
                    self._addCharFormat("bold",      colEmph, "b")
         
     | 
| 
       111 
109 
     | 
    
         
             
                    self._addCharFormat("italic",    colEmph, "i")
         
     | 
| 
       112 
110 
     | 
    
         
             
                    self._addCharFormat("strike",    syntax.hidden, "s")
         
     | 
| 
      
 111 
     | 
    
         
            +
                    self._addCharFormat("mark",      syntax.mark, "bg")
         
     | 
| 
       113 
112 
     | 
    
         
             
                    self._addCharFormat("mspaces",   syntax.error, "err")
         
     | 
| 
       114 
     | 
    
         
            -
                    self._addCharFormat("nobreak",    
     | 
| 
      
 113 
     | 
    
         
            +
                    self._addCharFormat("nobreak",   syntax.space, "bg")
         
     | 
| 
       115 
114 
     | 
    
         
             
                    self._addCharFormat("altdialog", syntax.dialA)
         
     | 
| 
       116 
115 
     | 
    
         
             
                    self._addCharFormat("dialog",    syntax.dialN)
         
     | 
| 
       117 
116 
     | 
    
         
             
                    self._addCharFormat("replace",   syntax.repTag)
         
     | 
| 
         @@ -139,7 +138,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       139 
138 
     | 
    
         | 
| 
       140 
139 
     | 
    
         
             
                    # Multiple or Trailing Spaces
         
     | 
| 
       141 
140 
     | 
    
         
             
                    if CONFIG.showMultiSpaces:
         
     | 
| 
       142 
     | 
    
         
            -
                        rxRule = re.compile(r" 
     | 
| 
      
 141 
     | 
    
         
            +
                        rxRule = re.compile(r"\s{2,}")
         
     | 
| 
       143 
142 
     | 
    
         
             
                        hlRule = {
         
     | 
| 
       144 
143 
     | 
    
         
             
                            0: self._hStyles["mspaces"],
         
     | 
| 
       145 
144 
     | 
    
         
             
                        }
         
     | 
| 
         @@ -148,7 +147,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       148 
147 
     | 
    
         
             
                        self._cmnRules.append((rxRule, hlRule))
         
     | 
| 
       149 
148 
     | 
    
         | 
| 
       150 
149 
     | 
    
         
             
                    # Non-Breaking Spaces
         
     | 
| 
       151 
     | 
    
         
            -
                    rxRule = re.compile(f"[{nwUnicode.U_NBSP}{nwUnicode.U_THNBSP}]+" 
     | 
| 
      
 150 
     | 
    
         
            +
                    rxRule = re.compile(f"[{nwUnicode.U_NBSP}{nwUnicode.U_THNBSP}]+")
         
     | 
| 
       152 
151 
     | 
    
         
             
                    hlRule = {
         
     | 
| 
       153 
152 
     | 
    
         
             
                        0: self._hStyles["nobreak"],
         
     | 
| 
       154 
153 
     | 
    
         
             
                    }
         
     | 
| 
         @@ -196,6 +195,17 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       196 
195 
     | 
    
         
             
                    self._txtRules.append((rxRule, hlRule))
         
     | 
| 
       197 
196 
     | 
    
         
             
                    self._cmnRules.append((rxRule, hlRule))
         
     | 
| 
       198 
197 
     | 
    
         | 
| 
      
 198 
     | 
    
         
            +
                    # Markdown Highlight
         
     | 
| 
      
 199 
     | 
    
         
            +
                    rxRule = REGEX_PATTERNS.markdownMark
         
     | 
| 
      
 200 
     | 
    
         
            +
                    hlRule = {
         
     | 
| 
      
 201 
     | 
    
         
            +
                        1: self._hStyles["markup"],
         
     | 
| 
      
 202 
     | 
    
         
            +
                        2: self._hStyles["mark"],
         
     | 
| 
      
 203 
     | 
    
         
            +
                        3: self._hStyles["markup"],
         
     | 
| 
      
 204 
     | 
    
         
            +
                    }
         
     | 
| 
      
 205 
     | 
    
         
            +
                    self._minRules.append((rxRule, hlRule))
         
     | 
| 
      
 206 
     | 
    
         
            +
                    self._txtRules.append((rxRule, hlRule))
         
     | 
| 
      
 207 
     | 
    
         
            +
                    self._cmnRules.append((rxRule, hlRule))
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
       199 
209 
     | 
    
         
             
                    # Shortcodes
         
     | 
| 
       200 
210 
     | 
    
         
             
                    rxRule = REGEX_PATTERNS.shortcodePlain
         
     | 
| 
       201 
211 
     | 
    
         
             
                    hlRule = {
         
     | 
| 
         @@ -226,7 +236,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       226 
236 
     | 
    
         
             
                    self._cmnRules.append((rxRule, hlRule))
         
     | 
| 
       227 
237 
     | 
    
         | 
| 
       228 
238 
     | 
    
         
             
                    # Alignment Tags
         
     | 
| 
       229 
     | 
    
         
            -
                    rxRule = re.compile(r"(^>{1,2}|<{1,2}$)" 
     | 
| 
      
 239 
     | 
    
         
            +
                    rxRule = re.compile(r"(^>{1,2}|<{1,2}$)")
         
     | 
| 
       230 
240 
     | 
    
         
             
                    hlRule = {
         
     | 
| 
       231 
241 
     | 
    
         
             
                        1: self._hStyles["markup"],
         
     | 
| 
       232 
242 
     | 
    
         
             
                    }
         
     | 
| 
         @@ -234,7 +244,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       234 
244 
     | 
    
         
             
                    self._txtRules.append((rxRule, hlRule))
         
     | 
| 
       235 
245 
     | 
    
         | 
| 
       236 
246 
     | 
    
         
             
                    # Auto-Replace Tags
         
     | 
| 
       237 
     | 
    
         
            -
                    rxRule = re.compile(r"<(\S+?)>" 
     | 
| 
      
 247 
     | 
    
         
            +
                    rxRule = re.compile(r"<(\S+?)>")
         
     | 
| 
       238 
248 
     | 
    
         
             
                    hlRule = {
         
     | 
| 
       239 
249 
     | 
    
         
             
                        0: self._hStyles["replace"],
         
     | 
| 
       240 
250 
     | 
    
         
             
                    }
         
     | 
| 
         @@ -242,8 +252,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       242 
252 
     | 
    
         
             
                    self._txtRules.append((rxRule, hlRule))
         
     | 
| 
       243 
253 
     | 
    
         
             
                    self._cmnRules.append((rxRule, hlRule))
         
     | 
| 
       244 
254 
     | 
    
         | 
| 
       245 
     | 
    
         
            -
                    return
         
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
       247 
255 
     | 
    
         
             
                ##
         
     | 
| 
       248 
256 
     | 
    
         
             
                #  Setters
         
     | 
| 
       249 
257 
     | 
    
         
             
                ##
         
     | 
| 
         @@ -251,7 +259,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       251 
259 
     | 
    
         
             
                def setSpellCheck(self, state: bool) -> None:
         
     | 
| 
       252 
260 
     | 
    
         
             
                    """Enable/disable the real time spell checker."""
         
     | 
| 
       253 
261 
     | 
    
         
             
                    self._spellCheck = state
         
     | 
| 
       254 
     | 
    
         
            -
                    return
         
     | 
| 
       255 
262 
     | 
    
         | 
| 
       256 
263 
     | 
    
         
             
                def setHandle(self, tHandle: str) -> None:
         
     | 
| 
       257 
264 
     | 
    
         
             
                    """Set the handle of the currently highlighted document."""
         
     | 
| 
         @@ -262,7 +269,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       262 
269 
     | 
    
         
             
                        self._isNovel = item.isDocumentLayout()
         
     | 
| 
       263 
270 
     | 
    
         
             
                        self._isInactive = item.isInactiveClass()
         
     | 
| 
       264 
271 
     | 
    
         
             
                    logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
         
     | 
| 
       265 
     | 
    
         
            -
                    return
         
     | 
| 
       266 
272 
     | 
    
         | 
| 
       267 
273 
     | 
    
         
             
                ##
         
     | 
| 
       268 
274 
     | 
    
         
             
                #  Methods
         
     | 
| 
         @@ -280,7 +286,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       280 
286 
     | 
    
         
             
                            if block.userState() & cType > 0:
         
     | 
| 
       281 
287 
     | 
    
         
             
                                self.rehighlightBlock(block)
         
     | 
| 
       282 
288 
     | 
    
         
             
                        logger.debug("Document highlighted in %.3f ms" % (1000*(time() - tStart)))
         
     | 
| 
       283 
     | 
    
         
            -
                    return
         
     | 
| 
       284 
289 
     | 
    
         | 
| 
       285 
290 
     | 
    
         
             
                ##
         
     | 
| 
       286 
291 
     | 
    
         
             
                #  Highlight Block
         
     | 
| 
         @@ -296,27 +301,37 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       296 
301 
     | 
    
         
             
                    if self._tHandle is None or not text:
         
     | 
| 
       297 
302 
     | 
    
         
             
                        return
         
     | 
| 
       298 
303 
     | 
    
         | 
| 
       299 
     | 
    
         
            -
                     
     | 
| 
       300 
     | 
    
         
            -
                     
     | 
| 
      
 304 
     | 
    
         
            +
                    blockLen = self.currentBlock().length()
         
     | 
| 
      
 305 
     | 
    
         
            +
                    utf16Map = None
         
     | 
| 
      
 306 
     | 
    
         
            +
                    if blockLen > len(text) + 1:
         
     | 
| 
      
 307 
     | 
    
         
            +
                        # If the lengths are different, the line contains 4 byte
         
     | 
| 
      
 308 
     | 
    
         
            +
                        # Unicode characters, and we must use a map between Python
         
     | 
| 
      
 309 
     | 
    
         
            +
                        # string indices and the UTF-16 indices used by Qt, where a
         
     | 
| 
      
 310 
     | 
    
         
            +
                        # 4 byte character occupies two slots. See #2449.
         
     | 
| 
      
 311 
     | 
    
         
            +
                        utf16Map = utf16CharMap(text)
         
     | 
| 
      
 312 
     | 
    
         
            +
             
     | 
| 
      
 313 
     | 
    
         
            +
                    offset = 0
         
     | 
| 
      
 314 
     | 
    
         
            +
                    rules = None
         
     | 
| 
       301 
315 
     | 
    
         
             
                    if text.startswith("@"):  # Keywords and commands
         
     | 
| 
       302 
316 
     | 
    
         
             
                        self.setCurrentBlockState(BLOCK_META)
         
     | 
| 
       303 
317 
     | 
    
         
             
                        index = SHARED.project.index
         
     | 
| 
       304 
     | 
    
         
            -
                        isValid, bits,  
     | 
| 
      
 318 
     | 
    
         
            +
                        isValid, bits, loc = index.scanThis(text)
         
     | 
| 
       305 
319 
     | 
    
         
             
                        isGood = index.checkThese(bits, self._tHandle)
         
     | 
| 
       306 
320 
     | 
    
         
             
                        if isValid:
         
     | 
| 
       307 
321 
     | 
    
         
             
                            for n, bit in enumerate(bits):
         
     | 
| 
       308 
     | 
    
         
            -
                                 
     | 
| 
       309 
     | 
    
         
            -
                                 
     | 
| 
      
 322 
     | 
    
         
            +
                                pos = utf16Map[loc[n]] if utf16Map else loc[n]
         
     | 
| 
      
 323 
     | 
    
         
            +
                                length = utf16Map[loc[n] + len(bit)] - pos if utf16Map else len(bit)
         
     | 
| 
       310 
324 
     | 
    
         
             
                                if n == 0 and isGood[n]:
         
     | 
| 
       311 
     | 
    
         
            -
                                    self.setFormat( 
     | 
| 
      
 325 
     | 
    
         
            +
                                    self.setFormat(pos, length, self._hStyles["keyword"])
         
     | 
| 
       312 
326 
     | 
    
         
             
                                elif isGood[n] and not self._isInactive:
         
     | 
| 
       313 
     | 
    
         
            -
                                     
     | 
| 
       314 
     | 
    
         
            -
                                     
     | 
| 
       315 
     | 
    
         
            -
                                     
     | 
| 
       316 
     | 
    
         
            -
             
     | 
| 
       317 
     | 
    
         
            -
                                         
     | 
| 
      
 327 
     | 
    
         
            +
                                    a, b = index.parseValue(bit)
         
     | 
| 
      
 328 
     | 
    
         
            +
                                    aLen = utf16Map[loc[n] + len(a)] - pos if utf16Map else len(a)
         
     | 
| 
      
 329 
     | 
    
         
            +
                                    self.setFormat(pos, aLen, self._hStyles["tag"])
         
     | 
| 
      
 330 
     | 
    
         
            +
                                    if b:
         
     | 
| 
      
 331 
     | 
    
         
            +
                                        bLen = utf16Map[loc[n] + len(b)] - pos if utf16Map else len(b)
         
     | 
| 
      
 332 
     | 
    
         
            +
                                        self.setFormat(pos + length - bLen, bLen, self._hStyles["optional"])
         
     | 
| 
       318 
333 
     | 
    
         
             
                                elif not self._isInactive:
         
     | 
| 
       319 
     | 
    
         
            -
                                    self.setFormat( 
     | 
| 
      
 334 
     | 
    
         
            +
                                    self.setFormat(pos, length, self._hStyles["invalid"])
         
     | 
| 
       320 
335 
     | 
    
         | 
| 
       321 
336 
     | 
    
         
             
                        # We never want to run the spell checker on keyword/values,
         
     | 
| 
       322 
337 
     | 
    
         
             
                        # so we force a return here
         
     | 
| 
         @@ -327,98 +342,118 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       327 
342 
     | 
    
         | 
| 
       328 
343 
     | 
    
         
             
                        if text.startswith("# "):  # Heading 1
         
     | 
| 
       329 
344 
     | 
    
         
             
                            self.setFormat(0, 1, self._hStyles["head1h"])
         
     | 
| 
       330 
     | 
    
         
            -
                            self.setFormat(1,  
     | 
| 
      
 345 
     | 
    
         
            +
                            self.setFormat(1, blockLen, self._hStyles["header1"])
         
     | 
| 
       331 
346 
     | 
    
         | 
| 
       332 
347 
     | 
    
         
             
                        elif text.startswith("## "):  # Heading 2
         
     | 
| 
       333 
348 
     | 
    
         
             
                            self.setFormat(0, 2, self._hStyles["head2h"])
         
     | 
| 
       334 
     | 
    
         
            -
                            self.setFormat(2,  
     | 
| 
      
 349 
     | 
    
         
            +
                            self.setFormat(2, blockLen, self._hStyles["header2"])
         
     | 
| 
       335 
350 
     | 
    
         | 
| 
       336 
351 
     | 
    
         
             
                        elif text.startswith("### "):  # Heading 3
         
     | 
| 
       337 
352 
     | 
    
         
             
                            self.setFormat(0, 3, self._hStyles["head3h"])
         
     | 
| 
       338 
     | 
    
         
            -
                            self.setFormat(3,  
     | 
| 
      
 353 
     | 
    
         
            +
                            self.setFormat(3, blockLen, self._hStyles["header3"])
         
     | 
| 
       339 
354 
     | 
    
         | 
| 
       340 
355 
     | 
    
         
             
                        elif text.startswith("#### "):  # Heading 4
         
     | 
| 
       341 
356 
     | 
    
         
             
                            self.setFormat(0, 4, self._hStyles["head4h"])
         
     | 
| 
       342 
     | 
    
         
            -
                            self.setFormat(4,  
     | 
| 
      
 357 
     | 
    
         
            +
                            self.setFormat(4, blockLen, self._hStyles["header4"])
         
     | 
| 
       343 
358 
     | 
    
         | 
| 
       344 
359 
     | 
    
         
             
                        elif text.startswith("#! "):  # Title
         
     | 
| 
       345 
360 
     | 
    
         
             
                            self.setFormat(0, 2, self._hStyles["head1h"])
         
     | 
| 
       346 
     | 
    
         
            -
                            self.setFormat(2,  
     | 
| 
      
 361 
     | 
    
         
            +
                            self.setFormat(2, blockLen, self._hStyles["header1"])
         
     | 
| 
       347 
362 
     | 
    
         | 
| 
       348 
363 
     | 
    
         
             
                        elif text.startswith("##! "):  # Unnumbered
         
     | 
| 
       349 
364 
     | 
    
         
             
                            self.setFormat(0, 3, self._hStyles["head2h"])
         
     | 
| 
       350 
     | 
    
         
            -
                            self.setFormat(3,  
     | 
| 
      
 365 
     | 
    
         
            +
                            self.setFormat(3, blockLen, self._hStyles["header2"])
         
     | 
| 
       351 
366 
     | 
    
         | 
| 
       352 
367 
     | 
    
         
             
                        elif text.startswith("###! "):  # Alternative Scene
         
     | 
| 
       353 
368 
     | 
    
         
             
                            self.setFormat(0, 4, self._hStyles["head3h"])
         
     | 
| 
       354 
     | 
    
         
            -
                            self.setFormat(4,  
     | 
| 
      
 369 
     | 
    
         
            +
                            self.setFormat(4, blockLen, self._hStyles["header3"])
         
     | 
| 
       355 
370 
     | 
    
         | 
| 
       356 
371 
     | 
    
         
             
                    elif text.startswith("%"):  # Comments
         
     | 
| 
       357 
372 
     | 
    
         
             
                        self.setCurrentBlockState(BLOCK_TEXT)
         
     | 
| 
       358 
     | 
    
         
            -
                         
     | 
| 
       359 
     | 
    
         
            -
             
     | 
| 
       360 
     | 
    
         
            -
                         
     | 
| 
       361 
     | 
    
         
            -
                         
     | 
| 
       362 
     | 
    
         
            -
                         
     | 
| 
       363 
     | 
    
         
            -
             
     | 
| 
       364 
     | 
    
         
            -
                             
     | 
| 
       365 
     | 
    
         
            -
                         
     | 
| 
       366 
     | 
    
         
            -
             
     | 
| 
      
 373 
     | 
    
         
            +
                        rules = self._cmnRules
         
     | 
| 
      
 374 
     | 
    
         
            +
             
     | 
| 
      
 375 
     | 
    
         
            +
                        style, mod, _, dot, pos = processComment(text)
         
     | 
| 
      
 376 
     | 
    
         
            +
                        offset = pos
         
     | 
| 
      
 377 
     | 
    
         
            +
                        if utf16Map:
         
     | 
| 
      
 378 
     | 
    
         
            +
                            dot = utf16Map[dot]
         
     | 
| 
      
 379 
     | 
    
         
            +
                            pos = utf16Map[pos]
         
     | 
| 
      
 380 
     | 
    
         
            +
                        length = blockLen - pos
         
     | 
| 
      
 381 
     | 
    
         
            +
                        if style == nwComment.PLAIN:
         
     | 
| 
      
 382 
     | 
    
         
            +
                            self.setFormat(0, length, self._hStyles["hidden"])
         
     | 
| 
      
 383 
     | 
    
         
            +
                        elif style == nwComment.IGNORE:
         
     | 
| 
      
 384 
     | 
    
         
            +
                            self.setFormat(0, length, self._hStyles["strike"])
         
     | 
| 
       367 
385 
     | 
    
         
             
                            return  # No more processing for these
         
     | 
| 
       368 
     | 
    
         
            -
                        elif  
     | 
| 
       369 
     | 
    
         
            -
                            self.setFormat(0,  
     | 
| 
       370 
     | 
    
         
            -
                            self.setFormat( 
     | 
| 
       371 
     | 
    
         
            -
                            self.setFormat( 
     | 
| 
      
 386 
     | 
    
         
            +
                        elif mod:
         
     | 
| 
      
 387 
     | 
    
         
            +
                            self.setFormat(0, dot, self._hStyles["modifier"])
         
     | 
| 
      
 388 
     | 
    
         
            +
                            self.setFormat(dot, pos - dot, self._hStyles["value"])
         
     | 
| 
      
 389 
     | 
    
         
            +
                            self.setFormat(pos, length, self._hStyles["note"])
         
     | 
| 
       372 
390 
     | 
    
         
             
                        else:
         
     | 
| 
       373 
     | 
    
         
            -
                            self.setFormat(0,  
     | 
| 
       374 
     | 
    
         
            -
                            self.setFormat( 
     | 
| 
      
 391 
     | 
    
         
            +
                            self.setFormat(0, pos, self._hStyles["modifier"])
         
     | 
| 
      
 392 
     | 
    
         
            +
                            self.setFormat(pos, length, self._hStyles["note"])
         
     | 
| 
       375 
393 
     | 
    
         | 
| 
       376 
394 
     | 
    
         
             
                    elif text.startswith("["):  # Special Command
         
     | 
| 
       377 
395 
     | 
    
         
             
                        self.setCurrentBlockState(BLOCK_TEXT)
         
     | 
| 
       378 
     | 
    
         
            -
                         
     | 
| 
      
 396 
     | 
    
         
            +
                        rules = self._txtRules if self._isNovel else self._minRules
         
     | 
| 
       379 
397 
     | 
    
         | 
| 
       380 
     | 
    
         
            -
                         
     | 
| 
       381 
     | 
    
         
            -
                        if  
     | 
| 
       382 
     | 
    
         
            -
                            self.setFormat(0,  
     | 
| 
      
 398 
     | 
    
         
            +
                        check = text.rstrip().lower()
         
     | 
| 
      
 399 
     | 
    
         
            +
                        if check in ("[newpage]", "[new page]", "[vspace]"):
         
     | 
| 
      
 400 
     | 
    
         
            +
                            self.setFormat(0, blockLen, self._hStyles["code"])
         
     | 
| 
       383 
401 
     | 
    
         
             
                            return
         
     | 
| 
       384 
     | 
    
         
            -
                        elif  
     | 
| 
       385 
     | 
    
         
            -
                             
     | 
| 
       386 
     | 
    
         
            -
                             
     | 
| 
       387 
     | 
    
         
            -
                            cVal = "value" if tVal > 0 else "invalid"
         
     | 
| 
      
 402 
     | 
    
         
            +
                        elif check.startswith("[vspace:") and check.endswith("]"):
         
     | 
| 
      
 403 
     | 
    
         
            +
                            value = checkInt(check[8:-1], 0)
         
     | 
| 
      
 404 
     | 
    
         
            +
                            style = "value" if value > 0 else "invalid"
         
     | 
| 
       388 
405 
     | 
    
         
             
                            self.setFormat(0, 8, self._hStyles["code"])
         
     | 
| 
       389 
     | 
    
         
            -
                            self.setFormat(8,  
     | 
| 
       390 
     | 
    
         
            -
                            self.setFormat( 
     | 
| 
      
 406 
     | 
    
         
            +
                            self.setFormat(8, blockLen-10, self._hStyles[style])
         
     | 
| 
      
 407 
     | 
    
         
            +
                            self.setFormat(blockLen-2, blockLen, self._hStyles["code"])
         
     | 
| 
       391 
408 
     | 
    
         
             
                            return
         
     | 
| 
       392 
409 
     | 
    
         | 
| 
       393 
410 
     | 
    
         
             
                    else:  # Text Paragraph
         
     | 
| 
       394 
411 
     | 
    
         
             
                        self.setCurrentBlockState(BLOCK_TEXT)
         
     | 
| 
       395 
     | 
    
         
            -
                         
     | 
| 
      
 412 
     | 
    
         
            +
                        rules = self._txtRules if self._isNovel else self._minRules
         
     | 
| 
       396 
413 
     | 
    
         
             
                        if self._isNovel and self._dialogParser.enabled:
         
     | 
| 
       397 
     | 
    
         
            -
                             
     | 
| 
       398 
     | 
    
         
            -
                                 
     | 
| 
       399 
     | 
    
         
            -
             
     | 
| 
       400 
     | 
    
         
            -
             
     | 
| 
       401 
     | 
    
         
            -
             
     | 
| 
       402 
     | 
    
         
            -
             
     | 
| 
       403 
     | 
    
         
            -
             
     | 
| 
       404 
     | 
    
         
            -
             
     | 
| 
       405 
     | 
    
         
            -
             
     | 
| 
       406 
     | 
    
         
            -
             
     | 
| 
       407 
     | 
    
         
            -
             
     | 
| 
       408 
     | 
    
         
            -
             
     | 
| 
       409 
     | 
    
         
            -
             
     | 
| 
       410 
     | 
    
         
            -
             
     | 
| 
       411 
     | 
    
         
            -
             
     | 
| 
      
 414 
     | 
    
         
            +
                            if utf16Map:
         
     | 
| 
      
 415 
     | 
    
         
            +
                                for pos, end in self._dialogParser(text):
         
     | 
| 
      
 416 
     | 
    
         
            +
                                    pos = utf16Map[pos]
         
     | 
| 
      
 417 
     | 
    
         
            +
                                    end = utf16Map[end]
         
     | 
| 
      
 418 
     | 
    
         
            +
                                    self.setFormat(pos, end - pos, self._hStyles["dialog"])
         
     | 
| 
      
 419 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 420 
     | 
    
         
            +
                                for pos, end in self._dialogParser(text):
         
     | 
| 
      
 421 
     | 
    
         
            +
                                    self.setFormat(pos, end - pos, self._hStyles["dialog"])
         
     | 
| 
      
 422 
     | 
    
         
            +
             
     | 
| 
      
 423 
     | 
    
         
            +
                    if rules:
         
     | 
| 
      
 424 
     | 
    
         
            +
                        if utf16Map:
         
     | 
| 
      
 425 
     | 
    
         
            +
                            for rX, hRule in rules:
         
     | 
| 
      
 426 
     | 
    
         
            +
                                for res in re.finditer(rX, text[offset:]):
         
     | 
| 
      
 427 
     | 
    
         
            +
                                    for x, hFmt in hRule.items():
         
     | 
| 
      
 428 
     | 
    
         
            +
                                        pos = res.start(x) + offset
         
     | 
| 
      
 429 
     | 
    
         
            +
                                        end = res.end(x) + offset
         
     | 
| 
      
 430 
     | 
    
         
            +
                                        for x in range(pos, end):
         
     | 
| 
      
 431 
     | 
    
         
            +
                                            m = utf16Map[x]
         
     | 
| 
      
 432 
     | 
    
         
            +
                                            cFmt = self.format(m)
         
     | 
| 
      
 433 
     | 
    
         
            +
                                            if not cFmt.property(QtTextUserProperty):
         
     | 
| 
      
 434 
     | 
    
         
            +
                                                cFmt.merge(hFmt)
         
     | 
| 
      
 435 
     | 
    
         
            +
                                                self.setFormat(m, utf16Map[x+1] - m, cFmt)
         
     | 
| 
      
 436 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 437 
     | 
    
         
            +
                            for rX, hRule in rules:
         
     | 
| 
      
 438 
     | 
    
         
            +
                                for res in re.finditer(rX, text[offset:]):
         
     | 
| 
      
 439 
     | 
    
         
            +
                                    for x, hFmt in hRule.items():
         
     | 
| 
      
 440 
     | 
    
         
            +
                                        pos = res.start(x) + offset
         
     | 
| 
      
 441 
     | 
    
         
            +
                                        end = res.end(x) + offset
         
     | 
| 
      
 442 
     | 
    
         
            +
                                        for x in range(pos, end):
         
     | 
| 
      
 443 
     | 
    
         
            +
                                            cFmt = self.format(x)
         
     | 
| 
      
 444 
     | 
    
         
            +
                                            if not cFmt.property(QtTextUserProperty):
         
     | 
| 
      
 445 
     | 
    
         
            +
                                                cFmt.merge(hFmt)
         
     | 
| 
      
 446 
     | 
    
         
            +
                                                self.setFormat(x, 1, cFmt)
         
     | 
| 
       412 
447 
     | 
    
         | 
| 
       413 
448 
     | 
    
         
             
                    data = self.currentBlockUserData()
         
     | 
| 
       414 
449 
     | 
    
         
             
                    if not isinstance(data, TextBlockData):
         
     | 
| 
       415 
450 
     | 
    
         
             
                        data = TextBlockData()
         
     | 
| 
       416 
451 
     | 
    
         
             
                        self.setCurrentBlockUserData(data)
         
     | 
| 
       417 
452 
     | 
    
         | 
| 
       418 
     | 
    
         
            -
                    data.processText(text,  
     | 
| 
      
 453 
     | 
    
         
            +
                    data.processText(text, offset)
         
     | 
| 
       419 
454 
     | 
    
         
             
                    if self._spellCheck:
         
     | 
| 
       420 
     | 
    
         
            -
                        for  
     | 
| 
       421 
     | 
    
         
            -
                            for x in range( 
     | 
| 
      
 455 
     | 
    
         
            +
                        for pos, end, _ in data.spellCheck(utf16Map):
         
     | 
| 
      
 456 
     | 
    
         
            +
                            for x in range(pos, end):
         
     | 
| 
       422 
457 
     | 
    
         
             
                                cFmt = self.format(x)
         
     | 
| 
       423 
458 
     | 
    
         
             
                                cFmt.merge(self._spellErr)
         
     | 
| 
       424 
459 
     | 
    
         
             
                                self.setFormat(x, 1, cFmt)
         
     | 
| 
         @@ -435,10 +470,8 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       435 
470 
     | 
    
         
             
                ) -> None:
         
     | 
| 
       436 
471 
     | 
    
         
             
                    """Generate a highlighter character format."""
         
     | 
| 
       437 
472 
     | 
    
         
             
                    charFormat = QTextCharFormat()
         
     | 
| 
       438 
     | 
    
         
            -
                     
     | 
| 
       439 
     | 
    
         
            -
             
     | 
| 
       440 
     | 
    
         
            -
                    if color:
         
     | 
| 
       441 
     | 
    
         
            -
                        charFormat.setForeground(color)
         
     | 
| 
      
 473 
     | 
    
         
            +
                    blockMerge = name == "markup"
         
     | 
| 
      
 474 
     | 
    
         
            +
                    charFormat.setProperty(QtTextUserProperty, blockMerge)
         
     | 
| 
       442 
475 
     | 
    
         | 
| 
       443 
476 
     | 
    
         
             
                    if style:
         
     | 
| 
       444 
477 
     | 
    
         
             
                        styles = style.split(",")
         
     | 
| 
         @@ -455,16 +488,23 @@ class GuiDocHighlighter(QSyntaxHighlighter): 
     | 
|
| 
       455 
488 
     | 
    
         
             
                            charFormat.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
         
     | 
| 
       456 
489 
     | 
    
         
             
                        if "bg" in styles and color is not None:
         
     | 
| 
       457 
490 
     | 
    
         
             
                            charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern))
         
     | 
| 
      
 491 
     | 
    
         
            +
                            color = None
         
     | 
| 
      
 492 
     | 
    
         
            +
             
     | 
| 
      
 493 
     | 
    
         
            +
                    if color:
         
     | 
| 
      
 494 
     | 
    
         
            +
                        charFormat.setForeground(color)
         
     | 
| 
       458 
495 
     | 
    
         | 
| 
       459 
496 
     | 
    
         
             
                    if size:
         
     | 
| 
       460 
497 
     | 
    
         
             
                        charFormat.setFontPointSize(round(size*CONFIG.textFont.pointSize()))
         
     | 
| 
       461 
498 
     | 
    
         | 
| 
       462 
499 
     | 
    
         
             
                    self._hStyles[name] = charFormat
         
     | 
| 
       463 
500 
     | 
    
         | 
| 
       464 
     | 
    
         
            -
                    return
         
     | 
| 
       465 
     | 
    
         
            -
             
     | 
| 
       466 
501 
     | 
    
         | 
| 
       467 
502 
     | 
    
         
             
            class TextBlockData(QTextBlockUserData):
         
     | 
| 
      
 503 
     | 
    
         
            +
                """Custom QTextBlock Data.
         
     | 
| 
      
 504 
     | 
    
         
            +
             
     | 
| 
      
 505 
     | 
    
         
            +
                Custom data stored in a single text block. The spell check state is
         
     | 
| 
      
 506 
     | 
    
         
            +
                cached here and used when correcting misspelled text.
         
     | 
| 
      
 507 
     | 
    
         
            +
                """
         
     | 
| 
       468 
508 
     | 
    
         | 
| 
       469 
509 
     | 
    
         
             
                __slots__ = ("_metaData", "_offset", "_spellErrors", "_text")
         
     | 
| 
       470 
510 
     | 
    
         | 
| 
         @@ -473,8 +513,7 @@ class TextBlockData(QTextBlockUserData): 
     | 
|
| 
       473 
513 
     | 
    
         
             
                    self._text = ""
         
     | 
| 
       474 
514 
     | 
    
         
             
                    self._offset = 0
         
     | 
| 
       475 
515 
     | 
    
         
             
                    self._metaData: list[tuple[int, int, str, str]] = []
         
     | 
| 
       476 
     | 
    
         
            -
                    self._spellErrors: list[tuple[int, int]] = []
         
     | 
| 
       477 
     | 
    
         
            -
                    return
         
     | 
| 
      
 516 
     | 
    
         
            +
                    self._spellErrors: list[tuple[int, int, str]] = []
         
     | 
| 
       478 
517 
     | 
    
         | 
| 
       479 
518 
     | 
    
         
             
                @property
         
     | 
| 
       480 
519 
     | 
    
         
             
                def metaData(self) -> list[tuple[int, int, str, str]]:
         
     | 
| 
         @@ -482,7 +521,7 @@ class TextBlockData(QTextBlockUserData): 
     | 
|
| 
       482 
521 
     | 
    
         
             
                    return self._metaData
         
     | 
| 
       483 
522 
     | 
    
         | 
| 
       484 
523 
     | 
    
         
             
                @property
         
     | 
| 
       485 
     | 
    
         
            -
                def spellErrors(self) -> list[tuple[int, int]]:
         
     | 
| 
      
 524 
     | 
    
         
            +
                def spellErrors(self) -> list[tuple[int, int, str]]:
         
     | 
| 
       486 
525 
     | 
    
         
             
                    """Return spell error data from last check."""
         
     | 
| 
       487 
526 
     | 
    
         
             
                    return self._spellErrors
         
     | 
| 
       488 
527 
     | 
    
         | 
| 
         @@ -505,22 +544,24 @@ class TextBlockData(QTextBlockUserData): 
     | 
|
| 
       505 
544 
     | 
    
         
             
                                text = f"{text[:s]}{pad}{text[e:]}"
         
     | 
| 
       506 
545 
     | 
    
         
             
                                self._metaData.append((s, e, res.group(0), "url"))
         
     | 
| 
       507 
546 
     | 
    
         | 
| 
       508 
     | 
    
         
            -
                    self._text = text.replace("\u02bc", "'")
         
     | 
| 
      
 547 
     | 
    
         
            +
                    self._text = text.replace("\u02bc", "'").replace("_", " ")
         
     | 
| 
       509 
548 
     | 
    
         
             
                    self._offset = offset
         
     | 
| 
       510 
549 
     | 
    
         | 
| 
       511 
     | 
    
         
            -
             
     | 
| 
       512 
     | 
    
         
            -
             
     | 
| 
       513 
     | 
    
         
            -
                def spellCheck(self) -> list[tuple[int, int]]:
         
     | 
| 
      
 550 
     | 
    
         
            +
                def spellCheck(self, utf16Map: list[int] | None) -> list[tuple[int, int, str]]:
         
     | 
| 
       514 
551 
     | 
    
         
             
                    """Run the spell checker and cache the result, and return the
         
     | 
| 
       515 
552 
     | 
    
         
             
                    list of spell check errors.
         
     | 
| 
       516 
553 
     | 
    
         
             
                    """
         
     | 
| 
       517 
     | 
    
         
            -
                     
     | 
| 
       518 
     | 
    
         
            -
                     
     | 
| 
       519 
     | 
    
         
            -
             
     | 
| 
       520 
     | 
    
         
            -
             
     | 
| 
       521 
     | 
    
         
            -
                             
     | 
| 
       522 
     | 
    
         
            -
                            and not ( 
     | 
| 
       523 
     | 
    
         
            -
                         
     | 
| 
       524 
     | 
    
         
            -
             
     | 
| 
       525 
     | 
    
         
            -
             
     | 
| 
      
 554 
     | 
    
         
            +
                    spell = SHARED.spelling
         
     | 
| 
      
 555 
     | 
    
         
            +
                    if utf16Map:
         
     | 
| 
      
 556 
     | 
    
         
            +
                        self._spellErrors = [
         
     | 
| 
      
 557 
     | 
    
         
            +
                            (utf16Map[r.start(0)], utf16Map[r.end(0)], w)
         
     | 
| 
      
 558 
     | 
    
         
            +
                            for r in RX_WORDS.finditer(self._text, self._offset)
         
     | 
| 
      
 559 
     | 
    
         
            +
                            if (w := r.group(0)) and not (w.isnumeric() or w.isupper() or spell.checkWord(w))
         
     | 
| 
      
 560 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 561 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 562 
     | 
    
         
            +
                        self._spellErrors = [
         
     | 
| 
      
 563 
     | 
    
         
            +
                            (r.start(0), r.end(0), w)
         
     | 
| 
      
 564 
     | 
    
         
            +
                            for r in RX_WORDS.finditer(self._text, self._offset)
         
     | 
| 
      
 565 
     | 
    
         
            +
                            if (w := r.group(0)) and not (w.isnumeric() or w.isupper() or spell.checkWord(w))
         
     | 
| 
      
 566 
     | 
    
         
            +
                        ]
         
     | 
| 
       526 
567 
     | 
    
         
             
                    return self._spellErrors
         
     |