diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py
index dc4914e3..d8e8bfb2 100644
--- a/novelwriter/core/index.py
+++ b/novelwriter/core/index.py
@@ -43,6 +43,7 @@ from novelwriter.common import (
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
+from novelwriter.text.comments import processComment
from novelwriter.text.counting import standardCounter
if TYPE_CHECKING: # pragma: no cover
@@ -1343,48 +1344,3 @@ class IndexHeading:
else:
raise ValueError("The itemIndex contains an invalid reference type")
return
-
-
-# Text Processing Functions
-# =========================
-
-MODIFIERS = {
- "synopsis": nwComment.SYNOPSIS,
- "short": nwComment.SHORT,
- "note": nwComment.NOTE,
- "footnote": nwComment.FOOTNOTE,
-}
-KEY_REQ = {
- "synopsis": 0, # Key not allowed
- "short": 0, # Key not allowed
- "note": 1, # Key optional
- "footnote": 2, # Key required
-}
-
-
-def _checkModKey(modifier: str, key: str) -> bool:
- """Check if a modifier and key set are ok."""
- if modifier in MODIFIERS:
- if key == "":
- return KEY_REQ[modifier] < 2
- elif key.replace("_", "").isalnum():
- return KEY_REQ[modifier] > 0
- return False
-
-
-def processComment(text: str) -> tuple[nwComment, str, str, int, int]:
- """Extract comment style, key and text. Should only be called on
- text starting with a %.
- """
- if text[:2] == "%~":
- return nwComment.IGNORE, "", text[2:].lstrip(), 0, 0
-
- check = text[1:].strip()
- start, _, content = check.partition(":")
- modifier, _, key = start.rstrip().partition(".")
- if content and (clean := modifier.lower()) and _checkModKey(clean, key):
- col = text.find(":") + 1
- dot = text.find(".", 0, col) + 1
- return MODIFIERS[clean], key, content.lstrip(), dot, col
-
- return nwComment.PLAIN, "", check, 0, 0
diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py
index cd390f16..49ce6cbf 100644
--- a/novelwriter/formats/tokenizer.py
+++ b/novelwriter/formats/tokenizer.py
@@ -40,12 +40,12 @@ from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode,
trConst
)
-from novelwriter.core.index import processComment
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment, nwItemLayout
from novelwriter.formats.shared import (
BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextDocumentTheme, TextFmt
)
+from novelwriter.text.comments import processComment
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
logger = logging.getLogger(__name__)
diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py
index 5ee1ea7e..0e378390 100644
--- a/novelwriter/gui/dochighlight.py
+++ b/novelwriter/gui/dochighlight.py
@@ -38,8 +38,8 @@ from PyQt6.QtGui import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt
from novelwriter.constants import nwStyles, nwUnicode
-from novelwriter.core.index import processComment
from novelwriter.enum import nwComment
+from novelwriter.text.comments import processComment
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
logger = logging.getLogger(__name__)
diff --git a/novelwriter/text/comments.py b/novelwriter/text/comments.py
new file mode 100644
index 00000000..d3e22454
--- /dev/null
+++ b/novelwriter/text/comments.py
@@ -0,0 +1,68 @@
+"""
+novelWriter – Text Comments
+===========================
+
+File History:
+Created: 2023-11-23 [2.2b1]
+Moved: 2025-02-09 [2.7b1]
+
+This file is a part of novelWriter
+Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+from __future__ import annotations
+
+from novelwriter.enum import nwComment
+
+MODIFIERS = {
+ "synopsis": nwComment.SYNOPSIS,
+ "short": nwComment.SHORT,
+ "note": nwComment.NOTE,
+ "footnote": nwComment.FOOTNOTE,
+}
+KEY_REQ = {
+ "synopsis": 0, # Key not allowed
+ "short": 0, # Key not allowed
+ "note": 1, # Key optional
+ "footnote": 2, # Key required
+}
+
+
+def _checkModKey(modifier: str, key: str) -> bool:
+ """Check if a modifier and key set are ok."""
+ if modifier in MODIFIERS:
+ if key == "":
+ return KEY_REQ[modifier] < 2
+ elif key.replace("_", "").isalnum():
+ return KEY_REQ[modifier] > 0
+ return False
+
+
+def processComment(text: str) -> tuple[nwComment, str, str, int, int]:
+ """Extract comment style, key and text. Should only be called on
+ text starting with a %.
+ """
+ if text[:2] == "%~":
+ return nwComment.IGNORE, "", text[2:].lstrip(), 0, 0
+
+ check = text[1:].strip()
+ start, _, content = check.partition(":")
+ modifier, _, key = start.rstrip().partition(".")
+ if content and (clean := modifier.lower()) and _checkModKey(clean, key):
+ col = text.find(":") + 1
+ dot = text.find(".", 0, col) + 1
+ return MODIFIERS[clean], key, content.lstrip(), dot, col
+
+ return nwComment.PLAIN, "", check, 0, 0
diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py
index a95fb021..baa1870e 100644
--- a/tests/test_core/test_core_index.py
+++ b/tests/test_core/test_core_index.py
@@ -28,7 +28,7 @@ import pytest
from novelwriter import SHARED
from novelwriter.constants import nwFiles
-from novelwriter.core.index import IndexItem, NWIndex, TagsIndex, _checkModKey, processComment
+from novelwriter.core.index import IndexItem, NWIndex, TagsIndex
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout
@@ -1419,87 +1419,3 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
"notes": {"footnotes": ["fkey"], "comments": ["ckey"]},
}
})
-
-
-@pytest.mark.core
-def testCoreIndex_checkModKey():
- """Test the _checkModKey function."""
- # Check Requirements
-
- # Synopsis
- assert _checkModKey("synopsis", "") is True
- assert _checkModKey("synopsis", "a") is False
-
- # Short
- assert _checkModKey("short", "") is True
- assert _checkModKey("short", "a") is False
-
- # Note
- assert _checkModKey("note", "") is True
- assert _checkModKey("note", "a") is True
-
- # Footnote
- assert _checkModKey("footnote", "") is False
- assert _checkModKey("footnote", "a") is True
-
- # Invalid
- assert _checkModKey("stuff", "") is False
- assert _checkModKey("stuff", "a") is False
-
- # Check Keys
- assert _checkModKey("note", "a") is True
- assert _checkModKey("note", "a1") is True
- assert _checkModKey("note", "a1.2") is False
- assert _checkModKey("note", "a1_2") is True
-
-
-@pytest.mark.core
-def testCoreIndex_processComment():
- """Test the comment processing function."""
- # Plain
- assert processComment("%Hi") == (nwComment.PLAIN, "", "Hi", 0, 0)
- assert processComment("% Hi") == (nwComment.PLAIN, "", "Hi", 0, 0)
- assert processComment("% Hi:You") == (nwComment.PLAIN, "", "Hi:You", 0, 0)
- assert processComment("% Hi.You:There") == (nwComment.PLAIN, "", "Hi.You:There", 0, 0)
-
- # Ignore
- assert processComment("%~Hi") == (nwComment.IGNORE, "", "Hi", 0, 0)
- assert processComment("%~ Hi") == (nwComment.IGNORE, "", "Hi", 0, 0)
-
- # Invalid
- assert processComment("") == (nwComment.PLAIN, "", "", 0, 0)
-
- # Short : Term not allowed
- assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7)
- assert processComment("%short.a: Hi") == (nwComment.PLAIN, "", "short.a: Hi", 0, 0)
-
- # Synopsis : Term not allowed
- assert processComment("%synopsis: Hi") == (nwComment.SYNOPSIS, "", "Hi", 0, 10)
- assert processComment("%synopsis.a: Hi") == (nwComment.PLAIN, "", "synopsis.a: Hi", 0, 0)
-
- # Note : Term optional
- assert processComment("%note: Hi") == (nwComment.NOTE, "", "Hi", 0, 6)
- assert processComment("%note.a: Hi") == (nwComment.NOTE, "a", "Hi", 6, 8)
-
- # Footnote : Term required
- assert processComment("%footnote: Hi") == (nwComment.PLAIN, "", "footnote: Hi", 0, 0)
- assert processComment("%footnote.a: Hi") == (nwComment.FOOTNOTE, "a", "Hi", 10, 12)
-
- # Check Case
- assert processComment("%Footnote.a: Hi") == (nwComment.FOOTNOTE, "a", "Hi", 10, 12)
- assert processComment("%FOOTNOTE.A: Hi") == (nwComment.FOOTNOTE, "A", "Hi", 10, 12)
- assert processComment("%FootNote.A_a: Hi") == (nwComment.FOOTNOTE, "A_a", "Hi", 10, 14)
-
- # Padding without term
- assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7)
- assert processComment("% short: Hi") == (nwComment.SHORT, "", "Hi", 0, 8)
- assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 10)
- assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 12)
- assert processComment("% \t short : Hi") == (nwComment.SHORT, "", "Hi", 0, 13)
-
- # Padding with term
- assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11)
- assert processComment("% note.term: Hi") == (nwComment.NOTE, "term", "Hi", 7, 12)
- assert processComment("% note.term : Hi") == (nwComment.NOTE, "term", "Hi", 7, 13)
- assert processComment("% note. term : Hi") == (nwComment.PLAIN, "", "note. term : Hi", 0, 0)
- assert processComment("% note . term : Hi") == (nwComment.PLAIN, "", "note . term : Hi", 0, 0)
diff --git a/tests/test_text/test_text_comments.py b/tests/test_text/test_text_comments.py
new file mode 100644
index 00000000..6161fb46
--- /dev/null
+++ b/tests/test_text/test_text_comments.py
@@ -0,0 +1,110 @@
+"""
+novelWriter – NWIndex Class Tester
+==================================
+
+This file is a part of novelWriter
+Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+from __future__ import annotations
+
+import pytest
+
+from novelwriter.enum import nwComment
+from novelwriter.text.comments import _checkModKey, processComment
+
+
+@pytest.mark.core
+def testTextComments_checkModKey():
+ """Test the _checkModKey function."""
+ # Check Requirements
+
+ # Synopsis
+ assert _checkModKey("synopsis", "") is True
+ assert _checkModKey("synopsis", "a") is False
+
+ # Short
+ assert _checkModKey("short", "") is True
+ assert _checkModKey("short", "a") is False
+
+ # Note
+ assert _checkModKey("note", "") is True
+ assert _checkModKey("note", "a") is True
+
+ # Footnote
+ assert _checkModKey("footnote", "") is False
+ assert _checkModKey("footnote", "a") is True
+
+ # Invalid
+ assert _checkModKey("stuff", "") is False
+ assert _checkModKey("stuff", "a") is False
+
+ # Check Keys
+ assert _checkModKey("note", "a") is True
+ assert _checkModKey("note", "a1") is True
+ assert _checkModKey("note", "a1.2") is False
+ assert _checkModKey("note", "a1_2") is True
+
+
+@pytest.mark.core
+def testTextComments_processComment():
+ """Test the comment processing function."""
+ # Plain
+ assert processComment("%Hi") == (nwComment.PLAIN, "", "Hi", 0, 0)
+ assert processComment("% Hi") == (nwComment.PLAIN, "", "Hi", 0, 0)
+ assert processComment("% Hi:You") == (nwComment.PLAIN, "", "Hi:You", 0, 0)
+ assert processComment("% Hi.You:There") == (nwComment.PLAIN, "", "Hi.You:There", 0, 0)
+
+ # Ignore
+ assert processComment("%~Hi") == (nwComment.IGNORE, "", "Hi", 0, 0)
+ assert processComment("%~ Hi") == (nwComment.IGNORE, "", "Hi", 0, 0)
+
+ # Invalid
+ assert processComment("") == (nwComment.PLAIN, "", "", 0, 0)
+
+ # Short : Term not allowed
+ assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7)
+ assert processComment("%short.a: Hi") == (nwComment.PLAIN, "", "short.a: Hi", 0, 0)
+
+ # Synopsis : Term not allowed
+ assert processComment("%synopsis: Hi") == (nwComment.SYNOPSIS, "", "Hi", 0, 10)
+ assert processComment("%synopsis.a: Hi") == (nwComment.PLAIN, "", "synopsis.a: Hi", 0, 0)
+
+ # Note : Term optional
+ assert processComment("%note: Hi") == (nwComment.NOTE, "", "Hi", 0, 6)
+ assert processComment("%note.a: Hi") == (nwComment.NOTE, "a", "Hi", 6, 8)
+
+ # Footnote : Term required
+ assert processComment("%footnote: Hi") == (nwComment.PLAIN, "", "footnote: Hi", 0, 0)
+ assert processComment("%footnote.a: Hi") == (nwComment.FOOTNOTE, "a", "Hi", 10, 12)
+
+ # Check Case
+ assert processComment("%Footnote.a: Hi") == (nwComment.FOOTNOTE, "a", "Hi", 10, 12)
+ assert processComment("%FOOTNOTE.A: Hi") == (nwComment.FOOTNOTE, "A", "Hi", 10, 12)
+ assert processComment("%FootNote.A_a: Hi") == (nwComment.FOOTNOTE, "A_a", "Hi", 10, 14)
+
+ # Padding without term
+ assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7)
+ assert processComment("% short: Hi") == (nwComment.SHORT, "", "Hi", 0, 8)
+ assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 10)
+ assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 12)
+ assert processComment("% \t short : Hi") == (nwComment.SHORT, "", "Hi", 0, 13)
+
+ # Padding with term
+ assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11)
+ assert processComment("% note.term: Hi") == (nwComment.NOTE, "term", "Hi", 7, 12)
+ assert processComment("% note.term : Hi") == (nwComment.NOTE, "term", "Hi", 7, 13)
+ assert processComment("% note. term : Hi") == (nwComment.PLAIN, "", "note. term : Hi", 0, 0)
+ assert processComment("% note . term : Hi") == (nwComment.PLAIN, "", "note . term : Hi", 0, 0)