diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml
index a1bf648d..e66f3104 100644
--- a/.github/workflows/syntax.yml
+++ b/.github/workflows/syntax.yml
@@ -23,11 +23,15 @@ jobs:
uses: actions/checkout@v4
- name: Install Dependencies
run: pip install -r requirements.txt -r requirements-dev.txt
- - name: Run Ruff
+ - name: Ruff Check
run: |
ruff --version
ruff check
- - name: Run Pyright
+ - name: Pyright Check
run: |
pyright --version
pyright
+ - name: Isort Check
+ run: |
+ isort --version
+ isort --check .
diff --git a/README.md b/README.md
index 929dda5e..bb2cd3e9 100644
--- a/README.md
+++ b/README.md
@@ -40,9 +40,9 @@ documentation.
## Implementation
-novelWriter is written with Python 3 (3.10+) using Qt6 and PyQt6 (6.4+), and is released on
-Linux, Windows and macOS. It can in principle run on any Operating System that also supports Qt,
-PyQt and Python.
+novelWriter is written with Python and Qt6 with PyQt6 Python binding. It is released on Linux,
+Windows and MacOS. It can in principle run on any Operating System that also supports Qt, PyQt and
+Python.
@@ -51,9 +51,9 @@ PyQt and Python.
## Project Contributions
Please don't make feature pull requests without first having discussed them with the maintainer.
-You can make a feature request in the issue tracker, or if the idea isn't fully formed, start a
-discussion on the discussion page. Please also don't make pull requests to reformat or rewrite
-existing code unless there is a very good reason for doing so.
+You can make a feature request in the [issues tracker](https://github.com/vkbo/novelWriter/issues),
+or if the idea isn't fully formed, start a [discussion](https://github.com/vkbo/novelWriter/discussions).
+Please also don't make pull requests to reformat or rewrite existing code unless there is a very good reason for doing so.
Fixes and patches are welcome. Contributions related to packaging and installing novelWriter will
also be appreciated, but please make an issue or a discussion topic first. Before contributing any
@@ -62,9 +62,9 @@ code, please also read the full
Project credits are available in [CREDITS.md](https://github.com/vkbo/novelWriter/blob/main/CREDITS.md).
-**Note:** As of April 2024 only pre-releases are made from the `main` branch. Full releases are
-made from the `release` branch. So if you're submitting a fix to a current release, **including
-changes to documentation**, they must be made to the `release` branch.
+**Note:** New features and pre-releases are made on the `main` branch. Full releases are made from
+the `release` branch. So if you're submitting a fix to a current release, **including changes to
+documentation**, they must be made to the `release` branch.
### Translations
@@ -73,9 +73,6 @@ can contribute translations at the [Crowdin project page](https://crowdin.com/pr
If you have any questions, feel free to post them to the
[Translations of novelWriter](https://github.com/vkbo/novelWriter/issues/93) issue thread.
-For more details, and how to use Qt Linguist for translations, see the
-[i18n instructions](https://github.com/vkbo/novelWriter/blob/main/i18n/README.md).
-
## Licence
This is Open Source software, and novelWriter is licenced under GPLv3. See the
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 8b507e58..4791c0e4 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -24,7 +24,7 @@ initFile = os.path.join(
os.path.dirname(__file__), os.pardir, os.pardir,
"novelwriter", "__init__.py"
)
-with open(initFile) as inFile:
+with open(initFile, encoding="utf-8") as inFile:
for aLine in inFile:
if aLine.startswith("__version__"):
release = aLine.split('"')[1].strip()
diff --git a/novelWriter.py b/novelWriter.py
index 285cb5dd..63d437e1 100755
--- a/novelWriter.py
+++ b/novelWriter.py
@@ -7,8 +7,8 @@ import os
import sys
try:
- import PyQt6.QtCore # noqa: F401
- import PyQt6.QtGui # noqa: F401
+ import PyQt6.QtCore
+ import PyQt6.QtGui
import PyQt6.QtWidgets # noqa: F401
except Exception:
print("ERROR: Failed to load dependency PyQt6")
diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index efa6a177..8f5214dc 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -36,7 +36,7 @@ from novelwriter.config import Config
from novelwriter.error import exceptionHandler
from novelwriter.shared import SharedData
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
# Package Meta
@@ -148,7 +148,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
inOpts, inRemain = getopt.getopt(sysArgs, shortOpt, longOpt)
except getopt.GetoptError as exc:
print(helpMsg)
- print(f"ERROR: {str(exc)}")
+ print(f"ERROR: {exc!s}")
sys.exit(2)
if len(inRemain) > 0:
diff --git a/novelwriter/common.py b/novelwriter/common.py
index 435efd4c..7cb6c5e0 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -29,11 +29,10 @@ import unicodedata
import uuid
import xml.etree.ElementTree as ET
-from collections.abc import Callable
from configparser import ConfigParser
from datetime import datetime
from pathlib import Path
-from typing import Any, Literal, TypeGuard, TypeVar
+from typing import TYPE_CHECKING, Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin
from urllib.request import pathname2url
@@ -45,6 +44,9 @@ from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
logger = logging.getLogger(__name__)
_Type = TypeVar("_Type")
@@ -347,7 +349,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 3300: # 55 minutes
return QCoreApplication.translate(
"Common", "{0} minutes ago"
- ).format(int(round(seconds/60)))
+ ).format(round(seconds/60))
elif seconds < 5400: # 90 minutes
return QCoreApplication.translate(
"Common", "an hour ago"
@@ -355,7 +357,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 84600: # 23.5 hours
return QCoreApplication.translate(
"Common", "{0} hours ago"
- ).format(int(round(seconds/3600)))
+ ).format(round(seconds/3600))
elif seconds < 129600: # 1.5 days
return QCoreApplication.translate(
"Common", "a day ago"
@@ -363,7 +365,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 561600: # 6.5 days
return QCoreApplication.translate(
"Common", "{0} days ago"
- ).format(int(round(seconds/86400)))
+ ).format(round(seconds/86400))
elif seconds < 907200: # 10.5 days
return QCoreApplication.translate(
"Common", "a week ago"
@@ -371,7 +373,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 2419200: # 28 days
return QCoreApplication.translate(
"Common", "{0} weeks ago"
- ).format(int(round(seconds/604800)))
+ ).format(round(seconds/604800))
elif seconds < 3888000: # 45 days
return QCoreApplication.translate(
"Common", "a month ago"
@@ -379,7 +381,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 29808000: # 345 days
return QCoreApplication.translate(
"Common", "{0} months ago"
- ).format(int(round(seconds/2592000)))
+ ).format(round(seconds/2592000))
elif seconds < 47336400: # 1.5 years
return QCoreApplication.translate(
"Common", "a year ago"
@@ -387,7 +389,7 @@ def fuzzyTime(seconds: int) -> str:
else:
return QCoreApplication.translate(
"Common", "{0} years ago"
- ).format(int(round(seconds/31557600)))
+ ).format(round(seconds/31557600))
def numberToRoman(value: int, toLower: bool = False) -> str:
@@ -423,7 +425,7 @@ def describeFont(font: QFont) -> str:
info = QFontInfo(font)
family = info.family()
styles = [v for v in info.styleName().split() if v not in family]
- return " ".join([f"{info.pointSize()} pt", family] + styles)
+ return " ".join([f"{info.pointSize()} pt", family, *styles])
return "Error"
diff --git a/novelwriter/config.py b/novelwriter/config.py
index c5d1f166..51d6e164 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -29,10 +29,9 @@ import json
import logging
import sys
-from datetime import datetime
from pathlib import Path
from time import time
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Final
from PyQt6.QtCore import (
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
@@ -48,7 +47,9 @@ from novelwriter.common import (
from novelwriter.constants import nwFiles, nwUnicode
from novelwriter.error import formatException, logException
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from datetime import datetime
+
from novelwriter.core.projectdata import NWProjectData
logger = logging.getLogger(__name__)
@@ -62,29 +63,27 @@ DEF_TREECOL = "theme"
class Config:
__slots__ = (
- "_confPath", "_dataPath", "_homePath", "_backPath", "_appPath", "_appRoot", "_hasError",
- "_errData", "_nwLangPath", "_qtLangPath", "_qLocale", "_dLocale", "_dShortDate",
- "_dShortDateTime", "_qtTrans", "_manuals", "_recentProjects", "_recentPaths",
- "_backupPath",
-
- "appName", "appHandle", "guiLocale", "guiTheme", "guiSyntax", "guiFont", "hideVScroll",
- "hideHScroll", "lastNotes", "nativeFont", "useCharCount", "iconTheme", "iconColTree",
- "iconColDocs", "mainWinSize", "welcomeWinSize", "prefsWinSize", "mainPanePos",
- "viewPanePos", "outlinePanePos", "autoSaveProj", "autoSaveDoc", "emphLabels",
- "backupOnClose", "askBeforeBackup", "askBeforeExit", "textFont", "textWidth", "textMargin",
- "tabWidth", "cursorWidth", "focusWidth", "hideFocusFooter", "showFullPath", "autoSelect",
- "doJustify", "showTabsNSpaces", "showLineEndings", "showMultiSpaces", "doReplace",
- "doReplaceSQuote", "doReplaceDQuote", "doReplaceDash", "doReplaceDots", "autoScroll",
- "autoScrollPos", "scrollPastEnd", "dialogStyle", "allowOpenDial", "dialogLine",
- "narratorBreak", "narratorDialog", "altDialogOpen", "altDialogClose", "highlightEmph",
- "stopWhenIdle", "userIdleTime", "incNotesWCount", "fmtApostrophe", "fmtSQuoteOpen",
- "fmtSQuoteClose", "fmtDQuoteOpen", "fmtDQuoteClose", "fmtPadBefore", "fmtPadAfter",
- "fmtPadThin", "spellLanguage", "showViewerPanel", "showEditToolBar", "showSessionTime",
- "viewComments", "viewSynopsis", "searchCase", "searchWord", "searchRegEx", "searchLoop",
- "searchNextFile", "searchMatchCap", "searchProjCase", "searchProjWord", "searchProjRegEx",
- "verQtString", "verQtValue", "verPyQtString", "verPyQtValue", "verPyString", "osType",
- "osLinux", "osWindows", "osDarwin", "osUnknown", "hostName", "kernelVer", "isDebug",
- "memInfo", "hasEnchant",
+ "_appPath", "_appRoot", "_backPath", "_backupPath", "_confPath", "_dLocale", "_dShortDate",
+ "_dShortDateTime", "_dataPath", "_errData", "_hasError", "_homePath", "_manuals",
+ "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths", "_recentProjects",
+ "allowOpenDial", "altDialogClose", "altDialogOpen", "appHandle", "appName",
+ "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj", "autoScroll",
+ "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine", "dialogStyle",
+ "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash", "doReplaceDots",
+ "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose", "fmtDQuoteOpen",
+ "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose", "fmtSQuoteOpen",
+ "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme", "hasEnchant",
+ "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName",
+ "iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer",
+ "lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog",
+ "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
+ "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap",
+ "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx",
+ "searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces",
+ "showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle",
+ "tabWidth", "textFont", "textMargin", "textWidth", "useCharCount", "userIdleTime",
+ "verPyQtString", "verPyQtValue", "verPyString", "verQtString", "verQtValue",
+ "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
)
LANG_NW = 1
@@ -306,6 +305,10 @@ class Config:
def pdfDocs(self) -> Path | None:
return self._manuals.get(f"manual_{self.locale.name()}", self._manuals.get("manual"))
+ @property
+ def nwLangPath(self) -> Path:
+ return self._nwLangPath
+
@property
def locale(self) -> QLocale:
return self._dLocale
@@ -918,7 +921,7 @@ class RecentProjects:
class RecentPaths:
- KEYS = ["default", "project", "import", "outline", "stats"]
+ KEYS: Final[list[str]] = ["default", "project", "import", "outline", "stats"]
def __init__(self, config: Config) -> None:
self._conf = config
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 9f4c0e1e..9928ece4 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -23,6 +23,8 @@ along with this program. If not, see .
"""
from __future__ import annotations
+from typing import Final
+
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import (
@@ -99,7 +101,7 @@ class nwShortcode:
FOOTNOTE_B = "[footnote:"
FIELD_B = "[field:"
- COMMENT_STYLES = {
+ COMMENT_STYLES: Final[dict[nwComment, str]] = {
nwComment.FOOTNOTE: "[footnote:{0}]",
nwComment.COMMENT: "[comment:{0}]",
}
@@ -110,13 +112,13 @@ class nwShortcode:
class nwStyles:
H_VALID = ("H0", "H1", "H2", "H3", "H4")
- H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
- H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
+ H_LEVEL: Final[dict[str, int]] = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
+ H_SIZES: Final[dict[int, float]] = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
T_NORMAL = 1.0
T_SMALL = 0.8
- T_LABEL = {
+ T_LABEL: Final[dict[str, str]] = {
"H0": QT_TRANSLATE_NOOP("Constant", "Title"),
"H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"),
"H2": QT_TRANSLATE_NOOP("Constant", "Heading 2 (Chapter)"),
@@ -125,7 +127,7 @@ class nwStyles:
"TT": QT_TRANSLATE_NOOP("Constant", "Text Paragraph"),
"SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"),
}
- T_MARGIN = {
+ T_MARGIN: Final[dict[str, tuple[float, float]]] = {
"H0": (1.50, 0.60), # Title margins (top, bottom)
"H1": (1.50, 0.60), # Heading 1 margins (top, bottom)
"H2": (1.50, 0.60), # Heading 2 margins (top, bottom)
@@ -174,20 +176,20 @@ class nwKeyWords:
MENTION_KEY = "@mention"
# Note: The order here affects the order of menu entries
- ALL_KEYS = [
+ ALL_KEYS: Final[list[str]] = [
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
]
- CAN_CREATE = [
+ CAN_CREATE: Final[list[str]] = [
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY,
]
# Set of Valid Keys
- VALID_KEYS = set(ALL_KEYS)
+ VALID_KEYS: Final[set[str]] = set(ALL_KEYS)
# Map from Keys to Item Class
- KEY_CLASS = {
+ KEY_CLASS: Final[dict[str, nwItemClass]] = {
POV_KEY: nwItemClass.CHARACTER,
FOCUS_KEY: nwItemClass.CHARACTER,
CHAR_KEY: nwItemClass.CHARACTER,
@@ -203,7 +205,7 @@ class nwKeyWords:
class nwLists:
- USER_CLASSES = [
+ USER_CLASSES: Final[list[nwItemClass]] = [
nwItemClass.CHARACTER,
nwItemClass.PLOT,
nwItemClass.WORLD,
@@ -229,7 +231,7 @@ class nwStats:
WORDS_TITLE = "titleWords"
# Note: The order here affects the order of menu entries
- ALL_FIELDS = [
+ ALL_FIELDS: Final[list[str]] = [
WORDS, WORDS_TEXT, WORDS_TITLE,
CHARS, CHARS_TEXT, CHARS_TITLE,
WCHARS_ALL, WCHARS_TEXT, WCHARS_TITLE,
@@ -239,7 +241,7 @@ class nwStats:
class nwLabels:
- CLASS_NAME = {
+ CLASS_NAME: Final[dict[nwItemClass, str]] = {
nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"),
nwItemClass.NOVEL: QT_TRANSLATE_NOOP("Constant", "Novel"),
nwItemClass.PLOT: QT_TRANSLATE_NOOP("Constant", "Plot"),
@@ -253,7 +255,7 @@ class nwLabels:
nwItemClass.TEMPLATE: QT_TRANSLATE_NOOP("Constant", "Templates"),
nwItemClass.TRASH: QT_TRANSLATE_NOOP("Constant", "Trash"),
}
- CLASS_ICON = {
+ CLASS_ICON: Final[dict[nwItemClass, str]] = {
nwItemClass.NO_CLASS: "cls_none",
nwItemClass.NOVEL: "cls_novel",
nwItemClass.PLOT: "cls_plot",
@@ -267,12 +269,12 @@ class nwLabels:
nwItemClass.TEMPLATE: "cls_template",
nwItemClass.TRASH: "cls_trash",
}
- LAYOUT_NAME = {
+ LAYOUT_NAME: Final[dict[nwItemLayout, str]] = {
nwItemLayout.NO_LAYOUT: QT_TRANSLATE_NOOP("Constant", "None"),
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
}
- ITEM_DESCRIPTION = {
+ ITEM_DESCRIPTION: Final[dict[str, str]] = {
"none": QT_TRANSLATE_NOOP("Constant", "None"),
"root": QT_TRANSLATE_NOOP("Constant", "Root Folder"),
"folder": QT_TRANSLATE_NOOP("Constant", "Folder"),
@@ -283,11 +285,11 @@ class nwLabels:
"doc_h4": QT_TRANSLATE_NOOP("Constant", "Novel Section"),
"note": QT_TRANSLATE_NOOP("Constant", "Project Note"),
}
- ACTIVE_NAME = {
+ ACTIVE_NAME: Final[dict[str, str]] = {
"checked": QT_TRANSLATE_NOOP("Constant", "Active"),
"unchecked": QT_TRANSLATE_NOOP("Constant", "Inactive"),
}
- KEY_NAME = {
+ KEY_NAME: Final[dict[str, str]] = {
nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"),
nwKeyWords.FOCUS_KEY: QT_TRANSLATE_NOOP("Constant", "Focus"),
@@ -301,7 +303,7 @@ class nwLabels:
nwKeyWords.STORY_KEY: QT_TRANSLATE_NOOP("Constant", "Story"),
nwKeyWords.MENTION_KEY: QT_TRANSLATE_NOOP("Constant", "Mentions"),
}
- KEY_SHORTCUT = {
+ KEY_SHORTCUT: Final[dict[str, str]] = {
nwKeyWords.TAG_KEY: "Ctrl+K, G",
nwKeyWords.POV_KEY: "Ctrl+K, V",
nwKeyWords.FOCUS_KEY: "Ctrl+K, F",
@@ -315,7 +317,7 @@ class nwLabels:
nwKeyWords.STORY_KEY: "Ctrl+K, N",
nwKeyWords.MENTION_KEY: "Ctrl+K, M",
}
- OUTLINE_COLS = {
+ OUTLINE_COLS: Final[dict[nwOutline, str]] = {
nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"),
nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"),
nwOutline.LABEL: QT_TRANSLATE_NOOP("Constant", "Document"),
@@ -337,7 +339,7 @@ class nwLabels:
nwOutline.MENTION: KEY_NAME[nwKeyWords.MENTION_KEY],
nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
}
- STATS_NAME = {
+ STATS_NAME: Final[dict[str, str]] = {
nwStats.CHARS: QT_TRANSLATE_NOOP("Stats", "Characters"),
nwStats.CHARS_TEXT: QT_TRANSLATE_NOOP("Stats", "Characters in Text"),
nwStats.CHARS_TITLE: QT_TRANSLATE_NOOP("Stats", "Characters in Headings"),
@@ -350,7 +352,7 @@ class nwLabels:
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"),
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"),
}
- BUILD_FMT = {
+ BUILD_FMT: Final[dict[nwBuildFmt, str]] = {
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
nwBuildFmt.DOCX: QT_TRANSLATE_NOOP("Constant", "Microsoft Word Document (.docx)"),
@@ -362,7 +364,7 @@ class nwLabels:
nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + HTML 5 (.json)"),
nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"),
}
- BUILD_EXT = {
+ BUILD_EXT: Final[dict[nwBuildFmt, str]] = {
nwBuildFmt.ODT: ".odt",
nwBuildFmt.FODT: ".fodt",
nwBuildFmt.DOCX: ".docx",
@@ -374,7 +376,7 @@ class nwLabels:
nwBuildFmt.J_HTML: ".json",
nwBuildFmt.J_NWD: ".json",
}
- SHAPES_PLAIN = {
+ SHAPES_PLAIN: Final[dict[nwStatusShape, str]] = {
nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"),
@@ -384,42 +386,42 @@ class nwLabels:
nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
}
- SHAPES_CIRCLE = {
+ SHAPES_CIRCLE: Final[dict[nwStatusShape, str]] = {
nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"),
nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"),
nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"),
nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"),
}
- SHAPES_BARS = {
+ SHAPES_BARS: Final[dict[nwStatusShape, str]] = {
nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"),
nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"),
nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"),
nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
}
- SHAPES_BLOCKS = {
+ SHAPES_BLOCKS: Final[dict[nwStatusShape, str]] = {
nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
}
- FILE_FILTERS = {
+ FILE_FILTERS: Final[dict[str, str]] = {
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
"*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
"*.nwd": QT_TRANSLATE_NOOP("Constant", "novelWriter files"),
"*.csv": QT_TRANSLATE_NOOP("Constant", "CSV files"),
"*": QT_TRANSLATE_NOOP("Constant", "All files"),
}
- UNIT_NAME = {
+ UNIT_NAME: Final[dict[str, str]] = {
"mm": QT_TRANSLATE_NOOP("Constant", "Millimetres"),
"cm": QT_TRANSLATE_NOOP("Constant", "Centimetres"),
"in": QT_TRANSLATE_NOOP("Constant", "Inches"),
}
- UNIT_SCALE = {
+ UNIT_SCALE: Final[dict[str, float]] = {
"mm": 1.0,
"cm": 10.0,
"in": 25.4,
}
- PAPER_NAME = {
+ PAPER_NAME: Final[dict[str, str]] = {
"A4": QT_TRANSLATE_NOOP("Constant", "A4"),
"A5": QT_TRANSLATE_NOOP("Constant", "A5"),
"A6": QT_TRANSLATE_NOOP("Constant", "A6"),
@@ -427,7 +429,7 @@ class nwLabels:
"Letter": QT_TRANSLATE_NOOP("Constant", "US Letter"),
"Custom": QT_TRANSLATE_NOOP("Constant", "Custom"),
}
- PAPER_SIZE = {
+ PAPER_SIZE: Final[dict[str, tuple[float, float]]] = {
"A4": (210.0, 297.0),
"A5": (148.0, 210.0),
"A6": (105.0, 148.0),
@@ -435,7 +437,7 @@ class nwLabels:
"Letter": (215.9, 279.4),
"Custom": (-1.0, -1.0),
}
- THEME_COLORS = {
+ THEME_COLORS: Final[dict[str, str]] = {
"theme": QT_TRANSLATE_NOOP("Constant", "Theme Colours"),
"default": QT_TRANSLATE_NOOP("Constant", "Foreground Colour"),
"faded": QT_TRANSLATE_NOOP("Constant", "Faded Colour"),
@@ -462,7 +464,7 @@ class nwHeadFmt:
CHAR_POV = "{Char:POV}"
CHAR_FOCUS = "{Char:Focus}"
- PAGE_HEADERS = [
+ PAGE_HEADERS: Final[list[str]] = [
TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS,
CHAR_POV, CHAR_FOCUS
]
@@ -478,7 +480,7 @@ class nwQuotes:
"""Allowed quotation marks.
Source: https://en.wikipedia.org/wiki/Quotation_mark
"""
- SYMBOLS = {
+ SYMBOLS: Final[dict[str, str]] = {
"\u0027": QT_TRANSLATE_NOOP("Constant", "Straight single quotation mark"),
"\u0022": QT_TRANSLATE_NOOP("Constant", "Straight double quotation mark"),
@@ -645,7 +647,7 @@ class nwUnicode:
class nwHtmlUnicode:
- U_TO_H = {
+ U_TO_H: Final[dict[str, str]] = {
# Quotes
nwUnicode.U_QUOT: nwUnicode.H_QUOT,
nwUnicode.U_APOS: nwUnicode.H_APOS,
diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py
index 6c6420b6..4032eb0b 100644
--- a/novelwriter/core/buildsettings.py
+++ b/novelwriter/core/buildsettings.py
@@ -28,9 +28,9 @@ import json
import logging
import uuid
-from collections.abc import Iterable
from enum import Enum
from pathlib import Path
+from typing import TYPE_CHECKING
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
@@ -41,6 +41,9 @@ from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt
from novelwriter.error import logException
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+
logger = logging.getLogger(__name__)
T_BuildValue = str | int | float | bool
@@ -227,9 +230,9 @@ class BuildSettings:
@classmethod
def fromDict(cls, data: dict) -> BuildSettings:
"""Create a build settings object from a dict."""
- cls = BuildSettings()
- cls.unpack(data)
- return cls
+ new = cls()
+ new.unpack(data)
+ return new
##
# Properties
@@ -514,11 +517,11 @@ class BuildSettings:
@classmethod
def duplicate(cls, source: BuildSettings) -> BuildSettings:
"""Make a copy of another build."""
- cls = BuildSettings()
- cls.unpack(source.pack())
- cls._uuid = str(uuid.uuid4())
- cls._name = f"{source.name} 2"
- return cls
+ new = cls()
+ new.unpack(source.pack())
+ new._uuid = str(uuid.uuid4())
+ new._name = f"{source.name} 2"
+ return new
class BuildCollection:
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index d691835e..815504e2 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -30,9 +30,9 @@ import logging
import re
import shutil
-from collections.abc import Iterable
from functools import partial
from pathlib import Path
+from typing import TYPE_CHECKING
from zipfile import ZipFile, is_zipfile
from PyQt6.QtCore import QCoreApplication
@@ -40,10 +40,14 @@ from PyQt6.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED
from novelwriter.common import isHandle, minmax, simplified
from novelwriter.constants import nwConst, nwFiles, nwItemClass, nwStats
-from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.core.storage import NWStorageCreate
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+
+ from novelwriter.core.item import NWItem
+
logger = logging.getLogger(__name__)
@@ -284,7 +288,7 @@ class DocDuplicator:
class DocSearch:
def __init__(self) -> None:
- self._regEx = re.compile("")
+ self._regEx = re.compile(r"")
self._opts = re.UNICODE | re.IGNORECASE
self._words = False
self._escape = True
@@ -381,7 +385,7 @@ class ProjectBuilder:
path = data.get("path", None) or None
if isinstance(path, str | Path):
self._path = Path(path).resolve()
- if data.get("sample", False):
+ if data.get("sample"):
return self._extractSampleProject(self._path)
elif data.get("template"):
return self._copyProject(self._path, data)
diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index 0b10bfe4..ce3f4930 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -25,26 +25,30 @@ from __future__ import annotations
import logging
-from collections.abc import Iterable
-from pathlib import Path
+from typing import TYPE_CHECKING
from PyQt6.QtGui import QFont
from novelwriter import CONFIG
from novelwriter.constants import nwLabels
-from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.item import NWItem
-from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt
from novelwriter.error import formatException, logException
from novelwriter.formats.todocx import ToDocX
from novelwriter.formats.tohtml import ToHtml
-from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.formats.tomarkdown import ToMarkdown
from novelwriter.formats.toodt import ToOdt
from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.formats.toraw import ToRaw
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+ from pathlib import Path
+
+ from novelwriter.core.buildsettings import BuildSettings
+ from novelwriter.core.project import NWProject
+ from novelwriter.formats.tokenizer import Tokenizer
+
logger = logging.getLogger(__name__)
@@ -56,8 +60,8 @@ class NWBuildDocument:
"""
__slots__ = (
- "_project", "_build", "_queue", "_error", "_cache", "_count",
- "_outline",
+ "_build", "_cache", "_count", "_error", "_outline", "_project",
+ "_queue",
)
def __init__(self, project: NWProject, build: BuildSettings) -> None:
diff --git a/novelwriter/core/document.py b/novelwriter/core/document.py
index 9a512753..e0f469e9 100644
--- a/novelwriter/core/document.py
+++ b/novelwriter/core/document.py
@@ -31,11 +31,11 @@ from time import time
from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp, isHandle
-from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.error import formatException, logException
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py
index f320c076..967861bf 100644
--- a/novelwriter/core/index.py
+++ b/novelwriter/core/index.py
@@ -29,7 +29,6 @@ import json
import logging
import random
-from collections.abc import ItemsView, Iterable
from pathlib import Path
from time import time
from typing import TYPE_CHECKING
@@ -44,7 +43,9 @@ from novelwriter.error import logException
from novelwriter.text.comments import processComment
from novelwriter.text.counting import standardCounter
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from collections.abc import ItemsView, Iterable
+
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
@@ -900,7 +901,7 @@ class IndexCache:
which provides lookup capabilities and caching for shared data.
"""
- __slots__ = ("tags", "story")
+ __slots__ = ("story", "tags")
def __init__(self, tagsIndex: TagsIndex) -> None:
self.tags: TagsIndex = tagsIndex
@@ -921,7 +922,7 @@ class ItemIndex:
IndexHeading object for each heading of the text.
"""
- __slots__ = ("_project", "_cache", "_items")
+ __slots__ = ("_cache", "_items", "_project")
def __init__(self, project: NWProject, tagsIndex: TagsIndex) -> None:
self._project = project
diff --git a/novelwriter/core/indexdata.py b/novelwriter/core/indexdata.py
index 30b373ba..9a9ce74c 100644
--- a/novelwriter/core/indexdata.py
+++ b/novelwriter/core/indexdata.py
@@ -28,17 +28,18 @@ from __future__ import annotations
import logging
-from collections.abc import ItemsView, Sequence
from typing import TYPE_CHECKING, Literal
from novelwriter import CONFIG
from novelwriter.common import checkInt, compact, isListInstance, isTitleTag
from novelwriter.constants import nwKeyWords, nwStyles
-from novelwriter.enum import nwComment
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from collections.abc import ItemsView, Sequence
+
from novelwriter.core.index import IndexCache
from novelwriter.core.item import NWItem
+ from novelwriter.enum import nwComment
logger = logging.getLogger(__name__)
@@ -58,7 +59,7 @@ class IndexNode:
must be reset each time the item is re-indexed.
"""
- __slots__ = ("_cache", "_handle", "_item", "_headings", "_notes", "_count")
+ __slots__ = ("_cache", "_count", "_handle", "_headings", "_item", "_notes")
def __init__(self, cache: IndexCache, tHandle: str, nwItem: NWItem) -> None:
self._cache = cache
@@ -206,8 +207,8 @@ class IndexHeading:
"""
__slots__ = (
- "_cache", "_key", "_line", "_level", "_title",
- "_counts", "_tag", "_refs", "_comments",
+ "_cache", "_comments", "_counts", "_key", "_level", "_line", "_refs",
+ "_tag", "_title",
)
def __init__(
diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py
index 3bbb8cec..ca3ce498 100644
--- a/novelwriter/core/item.py
+++ b/novelwriter/core/item.py
@@ -27,8 +27,6 @@ import logging
from typing import TYPE_CHECKING, Any
-from PyQt6.QtGui import QFont, QIcon
-
from novelwriter import CONFIG, SHARED
from novelwriter.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified,
@@ -37,7 +35,9 @@ from novelwriter.common import (
from novelwriter.constants import nwLabels, nwStyles, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from PyQt6.QtGui import QFont, QIcon
+
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
@@ -53,10 +53,10 @@ class NWItem:
"""
__slots__ = (
- "_project", "_name", "_handle", "_parent", "_root", "_order",
- "_type", "_class", "_layout", "_status", "_import", "_active",
- "_expanded", "_heading", "_charCount", "_wordCount",
- "_paraCount", "_cursorPos", "_initCount",
+ "_active", "_charCount", "_class", "_cursorPos", "_expanded",
+ "_handle", "_heading", "_import", "_initCount", "_layout", "_name",
+ "_order", "_paraCount", "_parent", "_project", "_root", "_status",
+ "_type", "_wordCount",
)
def __init__(self, project: NWProject, handle: str) -> None:
@@ -264,25 +264,25 @@ class NWItem:
@classmethod
def duplicate(cls, source: NWItem, handle: str) -> NWItem:
"""Make a copy of an item."""
- cls = NWItem(source._project, handle)
- cls._name = source._name
- cls._parent = source._parent
- cls._root = source._root
- cls._order = source._order
- cls._type = source._type
- cls._class = source._class
- cls._layout = source._layout
- cls._status = source._status
- cls._import = source._import
- cls._active = source._active
- cls._expanded = source._expanded
- cls._heading = source._heading
- cls._charCount = source._charCount
- cls._wordCount = source._wordCount
- cls._paraCount = source._paraCount
- cls._cursorPos = source._cursorPos
- cls._initCount = source._initCount
- return cls
+ new = cls(source._project, handle)
+ new._name = source._name
+ new._parent = source._parent
+ new._root = source._root
+ new._order = source._order
+ new._type = source._type
+ new._class = source._class
+ new._layout = source._layout
+ new._status = source._status
+ new._import = source._import
+ new._active = source._active
+ new._expanded = source._expanded
+ new._heading = source._heading
+ new._charCount = source._charCount
+ new._wordCount = source._wordCount
+ new._paraCount = source._paraCount
+ new._cursorPos = source._cursorPos
+ new._initCount = source._initCount
+ return new
##
# Action Methods
diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py
index 505ee542..6fb2580a 100644
--- a/novelwriter/core/itemmodel.py
+++ b/novelwriter/core/itemmodel.py
@@ -37,7 +37,7 @@ from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass
from novelwriter.types import QtAlignRight
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.core.tree import NWTree
logger = logging.getLogger(__name__)
@@ -89,7 +89,7 @@ class ProjectNode:
C_ACTIVE = 2
C_STATUS = 3
- __slots__ = ("_item", "_children", "_parent", "_row", "_cache", "_flags", "_count")
+ __slots__ = ("_cache", "_children", "_count", "_flags", "_item", "_parent", "_row")
def __init__(self, item: NWItem) -> None:
self._item = item
@@ -162,7 +162,7 @@ class ProjectNode:
def updateCount(self, propagate: bool = True) -> None:
"""Update counts, and propagate upwards in the tree."""
- self._count = self._item.wordCount + sum(c._count for c in self._children)
+ self._count = self._item.wordCount + sum(c._count for c in self._children) # noqa: SLF001
self._cache[C_COUNT_TEXT] = f"{self._count:n}"
if propagate and (parent := self._parent):
parent.updateCount()
@@ -257,13 +257,13 @@ class ProjectNode:
"""Recursively add all nodes to a list."""
for node in self._children:
children.append(node)
- node._recursiveAppendChildren(children)
+ node._recursiveAppendChildren(children) # noqa: SLF001
return
def _refreshChildrenPos(self) -> None:
"""Update the row value on all children."""
for n, child in enumerate(self._children):
- child._row = n
+ child._row = n # noqa: SLF001
child.item.setOrder(n)
return
@@ -290,12 +290,12 @@ class ProjectModel(QAbstractItemModel):
methods needed primarily by the project tree GUI component.
"""
- __slots__ = ("_tree", "_root")
+ __slots__ = ("_root", "_tree")
def __init__(self, tree: NWTree) -> None:
super().__init__()
self._tree = tree
- self._root = ProjectNode(NWItem(tree._project, INV_ROOT))
+ self._root = ProjectNode(NWItem(tree.project, INV_ROOT))
self._root.item.setName("Invisible Root")
logger.debug("Ready: ProjectModel")
return
@@ -391,10 +391,10 @@ class ProjectModel(QAbstractItemModel):
) -> bool:
"""Process mime data drop."""
if self.canDropMimeData(data, action, row, column, parent):
- items = []
- for handle in decodeMimeHandles(data):
- if (index := self.indexFromHandle(handle)).isValid():
- items.append(index)
+ items = [
+ index for handle in decodeMimeHandles(data)
+ if (index := self.indexFromHandle(handle)).isValid()
+ ]
self.multiMove(items, parent, row)
return True
return False
@@ -490,7 +490,7 @@ class ProjectModel(QAbstractItemModel):
if temp := self.removeChild(index.parent(), index.row()):
self.insertChild(temp, target, pos)
for child in reversed(node.allChildren()):
- node._updateRelationships(child)
+ node._updateRelationships(child) # noqa: SLF001
child.item.notifyToRefresh()
node.item.notifyToRefresh()
return
@@ -501,16 +501,15 @@ class ProjectModel(QAbstractItemModel):
def clear(self) -> None:
"""Clear the project model."""
- self._root._children.clear()
+ self._root.children.clear()
return
def allExpanded(self) -> list[QModelIndex]:
"""Return a list of all expanded items."""
- expanded = []
- for node in self._root.allChildren():
- if node._item.isExpanded:
- expanded.append(self.createIndex(node.row(), 0, node))
- return expanded
+ return [
+ self.createIndex(node.row(), 0, node) for node in self._root.allChildren()
+ if node.item.isExpanded
+ ]
def trashSelection(self, indices: list[QModelIndex]) -> bool:
"""Check if a selection of indices are all in trash or not."""
diff --git a/novelwriter/core/novelmodel.py b/novelwriter/core/novelmodel.py
index b55fa615..e5b49b15 100644
--- a/novelwriter/core/novelmodel.py
+++ b/novelwriter/core/novelmodel.py
@@ -25,15 +25,19 @@ from __future__ import annotations
import logging
+from typing import TYPE_CHECKING
+
from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt6.QtGui import QIcon, QPixmap
from novelwriter import SHARED
from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst
-from novelwriter.core.indexdata import IndexHeading, IndexNode
from novelwriter.enum import nwNovelExtra
from novelwriter.types import QtAlignRight
+if TYPE_CHECKING:
+ from novelwriter.core.indexdata import IndexHeading, IndexNode
+
logger = logging.getLogger(__name__)
C_FACTOR = 0x0100
@@ -50,7 +54,7 @@ T_NodeData = str | QIcon | QPixmap | Qt.AlignmentFlag | None
class NovelModel(QAbstractTableModel):
- __slots__ = ("_rows", "_more", "_columns", "_extraKey", "_extraLabel")
+ __slots__ = ("_columns", "_extraKey", "_extraLabel", "_more", "_rows")
def __init__(self) -> None:
super().__init__()
diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py
index 40f8567d..2753c086 100644
--- a/novelwriter/core/options.py
+++ b/novelwriter/core/options.py
@@ -35,7 +35,7 @@ from novelwriter.common import checkBool, checkFloat, checkInt, checkString, jso
from novelwriter.constants import nwFiles
from novelwriter.error import logException
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 040f12f1..9ad46682 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -30,6 +30,7 @@ from enum import Enum
from functools import partial
from pathlib import Path
from time import time
+from typing import TYPE_CHECKING
from PyQt6.QtCore import QCoreApplication
@@ -44,12 +45,14 @@ from novelwriter.core.options import OptionState
from novelwriter.core.projectdata import NWProjectData
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.sessions import NWSessionLog
-from novelwriter.core.status import T_StatusKind, T_UpdateEntry
from novelwriter.core.storage import NWStorage, NWStorageOpen
from novelwriter.core.tree import NWTree
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
+if TYPE_CHECKING:
+ from novelwriter.core.status import T_StatusKind, T_UpdateEntry
+
logger = logging.getLogger(__name__)
@@ -64,8 +67,8 @@ class NWProjectState(Enum):
class NWProject:
__slots__ = (
- "_options", "_storage", "_data", "_tree", "_index", "_session",
- "_langData", "_changed", "_valid", "_state", "tr",
+ "_changed", "_data", "_index", "_langData", "_options", "_session",
+ "_state", "_storage", "_tree", "_valid", "tr",
)
def __init__(self) -> None:
@@ -557,13 +560,13 @@ class NWProject:
def _loadProjectLocalisation(self) -> bool:
"""Load the language data for the current project language."""
- if self._data.language is None or CONFIG._nwLangPath is None:
+ if self._data.language is None or CONFIG.nwLangPath is None:
self._langData = {}
return False
- langFile = Path(CONFIG._nwLangPath) / f"project_{self._data.language}.json"
+ langFile = Path(CONFIG.nwLangPath) / f"project_{self._data.language}.json"
if not langFile.is_file():
- langFile = Path(CONFIG._nwLangPath) / "project_en_GB.json"
+ langFile = Path(CONFIG.nwLangPath) / "project_en_GB.json"
try:
with open(langFile, mode="r", encoding="utf-8") as inFile:
diff --git a/novelwriter/core/projectdata.py b/novelwriter/core/projectdata.py
index ff608874..1c2c1f42 100644
--- a/novelwriter/core/projectdata.py
+++ b/novelwriter/core/projectdata.py
@@ -34,7 +34,7 @@ from novelwriter.common import (
)
from novelwriter.core.status import NWStatus
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py
index 033bd463..461d9a40 100644
--- a/novelwriter/core/projectxml.py
+++ b/novelwriter/core/projectxml.py
@@ -39,7 +39,7 @@ from novelwriter.common import (
hexToInt, simplified, xmlIndent, yesNo
)
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.core.projectdata import NWProjectData
from novelwriter.core.status import NWStatus
diff --git a/novelwriter/core/sessions.py b/novelwriter/core/sessions.py
index 4be8689d..6f7fcef4 100644
--- a/novelwriter/core/sessions.py
+++ b/novelwriter/core/sessions.py
@@ -26,7 +26,6 @@ from __future__ import annotations
import json
import logging
-from collections.abc import Iterable
from pathlib import Path
from time import time
from typing import TYPE_CHECKING
@@ -35,7 +34,9 @@ from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwFiles
from novelwriter.error import logException
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
diff --git a/novelwriter/core/spellcheck.py b/novelwriter/core/spellcheck.py
index 87b701ba..a9e8b443 100644
--- a/novelwriter/core/spellcheck.py
+++ b/novelwriter/core/spellcheck.py
@@ -27,7 +27,6 @@ from __future__ import annotations
import json
import logging
-from collections.abc import Iterator
from pathlib import Path
from typing import TYPE_CHECKING
@@ -36,7 +35,9 @@ from PyQt6.QtCore import QLocale
from novelwriter.constants import nwFiles
from novelwriter.error import logException
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 92c78d50..f7d9b584 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -28,8 +28,7 @@ import dataclasses
import logging
import random
-from collections.abc import Iterable
-from typing import Literal, TypeGuard
+from typing import TYPE_CHECKING, Literal, TypeGuard
from PyQt6.QtCore import QPointF, Qt
from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF
@@ -39,6 +38,9 @@ from novelwriter.common import simplified
from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAntiAlias, QtTransparent
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+
logger = logging.getLogger(__name__)
@@ -54,10 +56,10 @@ class StatusEntry:
@classmethod
def duplicate(cls, source: StatusEntry) -> StatusEntry:
"""Create a deep copy of the source object."""
- cls = dataclasses.replace(source)
- cls.color = QColor(source.color)
- cls.icon = QIcon(source.icon)
- return cls
+ status = dataclasses.replace(source)
+ status.color = QColor(source.color)
+ status.icon = QIcon(source.icon)
+ return status
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
@@ -71,7 +73,7 @@ class NWStatus:
STATUS = "s"
IMPORT = "i"
- __slots__ = ("_store", "_default", "_prefix", "_height")
+ __slots__ = ("_default", "_height", "_prefix", "_store")
def __init__(self, prefix: T_StatusKind) -> None:
self._store: dict[str, StatusEntry] = {}
diff --git a/novelwriter/core/storage.py b/novelwriter/core/storage.py
index 23e37909..b8c21948 100644
--- a/novelwriter/core/storage.py
+++ b/novelwriter/core/storage.py
@@ -40,7 +40,7 @@ from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
from novelwriter.core.spellcheck import UserDictionary
from novelwriter.error import logException
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py
index be582526..2ff74a53 100644
--- a/novelwriter/core/tree.py
+++ b/novelwriter/core/tree.py
@@ -27,7 +27,6 @@ from __future__ import annotations
import logging
import random
-from collections.abc import Iterable, Iterator
from pathlib import Path
from typing import TYPE_CHECKING, Literal, overload
@@ -40,7 +39,9 @@ from novelwriter.core.itemmodel import ProjectModel, ProjectNode
from novelwriter.enum import nwChange, nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from collections.abc import Iterable, Iterator
+
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
@@ -60,7 +61,7 @@ class NWTree:
also used for file names.
"""
- __slots__ = ("_project", "_model", "_items", "_nodes", "_trash", "_ready")
+ __slots__ = ("_items", "_model", "_nodes", "_project", "_ready", "_trash")
def __init__(self, project: NWProject) -> None:
self._project = project
@@ -107,6 +108,11 @@ class NWTree:
# Properties
##
+ @property
+ def project(self) -> NWProject:
+ """Return the parent project."""
+ return self._project
+
@property
def trash(self) -> ProjectNode | None:
"""Return trash node, if it exists."""
@@ -396,7 +402,7 @@ class NWTree:
def checkType(self, tHandle: str, itemType: nwItemType) -> bool:
"""Check if item exists and is of the specified item type."""
- if tItem := self.__getitem__(tHandle):
+ if tItem := self[tHandle]:
return tItem.itemType == itemType
return False
@@ -414,8 +420,7 @@ class NWTree:
node = parent
else:
return path
- else:
- logger.error("Max project tree depth reached")
+ logger.error("Max project tree depth reached")
return path
def subTree(self, tHandle: str) -> list[str]:
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index e122fd19..3c7070df 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -25,7 +25,8 @@ from __future__ import annotations
import logging
-from PyQt6.QtGui import QCloseEvent
+from typing import TYPE_CHECKING
+
from PyQt6.QtWidgets import (
QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout, QWidget
)
@@ -37,6 +38,9 @@ from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.versioninfo import VersionInfoWidget
from novelwriter.types import QtAlignRightTop, QtDialogClose, QtHexArgb
+if TYPE_CHECKING:
+ from PyQt6.QtGui import QCloseEvent
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py
index 14e6553a..97717dc6 100644
--- a/novelwriter/dialogs/docmerge.py
+++ b/novelwriter/dialogs/docmerge.py
@@ -131,11 +131,11 @@ class GuiDocMerge(NDialog):
@classmethod
def getData(cls, parent: QWidget, handle: str, items: list[str]) -> tuple[dict, bool]:
"""Pop the dialog and return the result."""
- cls = GuiDocMerge(parent, handle, items)
- cls.exec()
- data = cls.data()
- accepted = cls.result() == QtAccepted
- cls.softDelete()
+ dialog = cls(parent, handle, items)
+ dialog.exec()
+ data = dialog.data()
+ accepted = dialog.result() == QtAccepted
+ dialog.softDelete()
return data, accepted
##
diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py
index 447d1696..3f1f34ad 100644
--- a/novelwriter/dialogs/docsplit.py
+++ b/novelwriter/dialogs/docsplit.py
@@ -178,11 +178,11 @@ class GuiDocSplit(NDialog):
@classmethod
def getData(cls, parent: QWidget, handle: str) -> tuple[dict, list[str], bool]:
"""Pop the dialog and return the result."""
- cls = GuiDocSplit(parent, handle)
- cls.exec()
- data, text = cls.data()
- accepted = cls.result() == QtAccepted
- cls.softDelete()
+ dialog = cls(parent, handle)
+ dialog.exec()
+ data, text = dialog.data()
+ accepted = dialog.result() == QtAccepted
+ dialog.softDelete()
return data, text, accepted
##
diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py
index caf97335..5386acb4 100644
--- a/novelwriter/dialogs/editlabel.py
+++ b/novelwriter/dialogs/editlabel.py
@@ -82,9 +82,9 @@ class GuiEditLabel(NDialog):
@classmethod
def getLabel(cls, parent: QWidget, text: str) -> tuple[str, bool]:
"""Pop the dialog and return the result."""
- cls = GuiEditLabel(parent, text=text)
- cls.exec()
- label = cls.itemLabel
- accepted = cls.result() == QtAccepted
- cls.softDelete()
+ dialog = cls(parent, text=text)
+ dialog.exec()
+ label = dialog.itemLabel
+ accepted = dialog.result() == QtAccepted
+ dialog.softDelete()
return label, accepted
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index a1166c04..aa4f9cd1 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -432,7 +432,7 @@ class GuiPreferences(NDialog):
self.textWidth.setSingleStep(10)
self.textWidth.setValue(CONFIG.textWidth)
self.mainForm.addRow(
- self.tr("Maximum text width in \"Normal Mode\""), self.textWidth,
+ self.tr('Maximum text width in "Normal Mode"'), self.textWidth,
self.tr("Set to 0 to disable this feature."), unit=self.tr("px")
)
@@ -443,7 +443,7 @@ class GuiPreferences(NDialog):
self.focusWidth.setSingleStep(10)
self.focusWidth.setValue(CONFIG.focusWidth)
self.mainForm.addRow(
- self.tr("Maximum text width in \"Focus Mode\""), self.focusWidth,
+ self.tr('Maximum text width in "Focus Mode"'), self.focusWidth,
self.tr("The maximum width cannot be disabled."), unit=self.tr("px")
)
@@ -451,7 +451,7 @@ class GuiPreferences(NDialog):
self.hideFocusFooter = NSwitch(self)
self.hideFocusFooter.setChecked(CONFIG.hideFocusFooter)
self.mainForm.addRow(
- self.tr("Hide document footer in \"Focus Mode\""), self.hideFocusFooter,
+ self.tr('Hide document footer in "Focus Mode"'), self.hideFocusFooter,
self.tr("Hide the information bar in the document editor.")
)
diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py
index 2f7501f6..931558a2 100644
--- a/novelwriter/dialogs/quotes.py
+++ b/novelwriter/dialogs/quotes.py
@@ -122,11 +122,11 @@ class GuiQuoteSelect(NDialog):
@classmethod
def getQuote(cls, parent: QWidget, current: str = "") -> tuple[str, bool]:
"""Pop the dialog and return the result."""
- cls = GuiQuoteSelect(parent, current=current)
- cls.exec()
- quote = cls._selected
- accepted = cls.result() == QtAccepted
- cls.softDelete()
+ dialog = cls(parent, current=current)
+ dialog.exec()
+ quote = dialog._selected
+ accepted = dialog.result() == QtAccepted
+ dialog.softDelete()
return quote, accepted
##
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index 65d19193..a667738a 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging
from pathlib import Path
+from typing import TYPE_CHECKING
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt6.QtGui import QCloseEvent
from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
@@ -41,6 +41,9 @@ from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog, NIconToolButton
from novelwriter.types import QtDialogClose, QtDialogSave
+if TYPE_CHECKING:
+ from PyQt6.QtGui import QCloseEvent
+
logger = logging.getLogger(__name__)
@@ -242,8 +245,7 @@ class GuiWordList(NDialog):
def _listWords(self) -> list[str]:
"""List all words in the list box."""
- result = []
- for i in range(self.listBox.count()):
- if (item := self.listBox.item(i)) and (word := item.text().strip()):
- result.append(word)
- return result
+ return [
+ word for i in range(self.listBox.count())
+ if (item := self.listBox.item(i)) and (word := item.text().strip())
+ ]
diff --git a/novelwriter/error.py b/novelwriter/error.py
index 53ac8846..03f5d07a 100644
--- a/novelwriter/error.py
+++ b/novelwriter/error.py
@@ -36,7 +36,7 @@ from PyQt6.QtWidgets import (
QPlainTextEdit, QStyle, QWidget
)
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from types import TracebackType
logger = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ def logException() -> None:
"""Log the content of an exception message."""
exType, exValue, _ = sys.exc_info()
if exType is not None:
- logger.error(f"{exType.__name__}: {str(exValue)}", stacklevel=2)
+ logger.error(f"{exType.__name__}: {exValue!s}", stacklevel=2)
return
@@ -54,7 +54,7 @@ def formatException(exc: BaseException) -> str:
"""Format an exception as a string the same way the default
exception handler does.
"""
- return f"{type(exc).__name__}: {str(exc)}"
+ return f"{type(exc).__name__}: {exc!s}"
class NWErrorMessage(QDialog):
@@ -151,7 +151,7 @@ class NWErrorMessage(QDialog):
f"Python: {sys.version.split()[0]} ({sys.hexversion:#x})\n"
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
f"enchant: {enchantVersion}\n\n"
- f"{exType.__name__}:\n{str(exValue)}\n\n"
+ f"{exType.__name__}:\n{exValue!s}\n\n"
f"Traceback:\n{txtTrace}\n"
)
except Exception:
diff --git a/novelwriter/extensions/eventfilters.py b/novelwriter/extensions/eventfilters.py
index 7e0939b6..09366590 100644
--- a/novelwriter/extensions/eventfilters.py
+++ b/novelwriter/extensions/eventfilters.py
@@ -24,9 +24,13 @@ along with this program. If not, see .
"""
from __future__ import annotations
+from typing import TYPE_CHECKING
+
from PyQt6.QtCore import QEvent, QObject
from PyQt6.QtGui import QStatusTipEvent, QWheelEvent
-from PyQt6.QtWidgets import QWidget
+
+if TYPE_CHECKING:
+ from PyQt6.QtWidgets import QWidget
class WheelEventFilter(QObject):
@@ -40,7 +44,7 @@ class WheelEventFilter(QObject):
Reference: https://stackoverflow.com/a/17739995/5825851
"""
- __slots__ = ("_parent", "_locked")
+ __slots__ = ("_locked", "_parent")
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py
index 6a095d8e..9bea4c78 100644
--- a/novelwriter/extensions/modified.py
+++ b/novelwriter/extensions/modified.py
@@ -27,11 +27,9 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from enum import Enum
from typing import TYPE_CHECKING
from PyQt6.QtCore import QModelIndex, QSize, Qt, pyqtSignal, pyqtSlot
-from PyQt6.QtGui import QMouseEvent, QWheelEvent
from PyQt6.QtWidgets import (
QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
QToolButton, QTreeView, QWidget
@@ -40,7 +38,11 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.types import QtMouseLeft, QtMouseMiddle
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
+ from enum import Enum
+
+ from PyQt6.QtGui import QMouseEvent, QWheelEvent
+
from novelwriter.guimain import GuiMain
diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py
index b58de63a..b1745816 100644
--- a/novelwriter/extensions/pagedsidebar.py
+++ b/novelwriter/extensions/pagedsidebar.py
@@ -113,7 +113,7 @@ class NPagedSideBar(QToolBar):
class _PagedToolButton(QToolButton):
- __slots__ = ("_bH", "_tM", "_aH")
+ __slots__ = ("_aH", "_bH", "_tM")
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
diff --git a/novelwriter/extensions/progressbars.py b/novelwriter/extensions/progressbars.py
index 456d1550..0c92564e 100644
--- a/novelwriter/extensions/progressbars.py
+++ b/novelwriter/extensions/progressbars.py
@@ -44,8 +44,8 @@ class NProgressCircle(QProgressBar):
"""
__slots__ = (
- "_text", "_point", "_dRect", "_cRect", "_dPen", "_dBrush",
- "_cPen", "_bPen", "_tColor"
+ "_bPen", "_cPen", "_cRect", "_dBrush", "_dPen", "_dRect", "_point",
+ "_tColor", "_text",
)
def __init__(self, parent: QWidget, size: int, point: int) -> None:
diff --git a/novelwriter/extensions/statusled.py b/novelwriter/extensions/statusled.py
index 53f0c65e..5547a664 100644
--- a/novelwriter/extensions/statusled.py
+++ b/novelwriter/extensions/statusled.py
@@ -35,7 +35,7 @@ logger = logging.getLogger(__name__)
class StatusLED(QAbstractButton):
- __slots__ = ("_neutral", "_postitve", "_negative", "_color", "_state")
+ __slots__ = ("_color", "_negative", "_neutral", "_postitve", "_state")
def __init__(self, sW: int, sH: int, parent: QWidget | None = None) -> None:
super().__init__(parent=parent)
diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py
index 5a97edde..421fb712 100644
--- a/novelwriter/extensions/switch.py
+++ b/novelwriter/extensions/switch.py
@@ -33,7 +33,7 @@ from novelwriter.types import QtMouseLeft, QtNoPen, QtPaintAntiAlias, QtSizeFixe
class NSwitch(QAbstractButton):
- __slots__ = ("_xW", "_xH", "_xR", "_rH", "_rR", "_offset")
+ __slots__ = ("_offset", "_rH", "_rR", "_xH", "_xR", "_xW")
def __init__(self, parent: QWidget, height: int = 0) -> None:
super().__init__(parent=parent)
diff --git a/novelwriter/extensions/switchbox.py b/novelwriter/extensions/switchbox.py
index 9acc0cc9..c43def8a 100644
--- a/novelwriter/extensions/switchbox.py
+++ b/novelwriter/extensions/switchbox.py
@@ -23,8 +23,9 @@ along with this program. If not, see .
"""
from __future__ import annotations
+from typing import TYPE_CHECKING
+
from PyQt6.QtCore import pyqtSignal
-from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
from novelwriter.extensions.switch import NSwitch
@@ -33,6 +34,9 @@ from novelwriter.types import (
QtSizeMinimumExpanding
)
+if TYPE_CHECKING:
+ from PyQt6.QtGui import QIcon
+
class NSwitchBox(QScrollArea):
"""Extension: Switch Box Widget
diff --git a/novelwriter/formats/todocx.py b/novelwriter/formats/todocx.py
index 76577826..c173918d 100644
--- a/novelwriter/formats/todocx.py
+++ b/novelwriter/formats/todocx.py
@@ -29,21 +29,25 @@ import re
import xml.etree.ElementTree as ET
from datetime import datetime
-from pathlib import Path
-from typing import NamedTuple
+from typing import TYPE_CHECKING, NamedTuple
from zipfile import ZIP_DEFLATED, ZipFile
from PyQt6.QtCore import QMargins, QSize
-from PyQt6.QtGui import QColor
from novelwriter import __version__
from novelwriter.common import firstFloat, xmlElement, xmlSubElem
from novelwriter.constants import nwHeadFmt, nwStyles
-from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.types import QtHexRgb
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from PyQt6.QtGui import QColor
+
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
# RegEx
@@ -1052,9 +1056,9 @@ class ToDocX(Tokenizer):
class DocXParagraph:
__slots__ = (
- "_content", "_style", "_textAlign",
- "_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin",
- "_indentFirst", "_breakBefore", "_breakAfter", "_footnoteRef",
+ "_bottomMargin", "_breakAfter", "_breakBefore", "_content",
+ "_footnoteRef", "_indentFirst", "_leftMargin", "_rightMargin",
+ "_style", "_textAlign", "_topMargin",
)
def __init__(self) -> None:
diff --git a/novelwriter/formats/tohtml.py b/novelwriter/formats/tohtml.py
index fb2c1ee5..c7bf7b17 100644
--- a/novelwriter/formats/tohtml.py
+++ b/novelwriter/formats/tohtml.py
@@ -26,16 +26,20 @@ from __future__ import annotations
import json
import logging
-from pathlib import Path
from time import time
+from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwHtmlUnicode, nwStyles
-from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
# Each opener tag, with the id of its corresponding closer and tag format
@@ -311,10 +315,8 @@ class ToHtml(Tokenizer):
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " ") -> None:
"""Replace tabs with spaces in the html."""
- pages = []
tabSpace = spaceChar*nSpaces
- for aLine in self._pages:
- pages.append(aLine.replace("\t", tabSpace))
+ pages = [aLine.replace("\t", tabSpace) for aLine in self._pages]
self._pages = pages
return
diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py
index f44139a3..7d1aeec0 100644
--- a/novelwriter/formats/tokenizer.py
+++ b/novelwriter/formats/tokenizer.py
@@ -28,8 +28,7 @@ import logging
import re
from abc import ABC, abstractmethod
-from pathlib import Path
-from typing import NamedTuple
+from typing import TYPE_CHECKING, NamedTuple
from PyQt6.QtCore import QLocale
from PyQt6.QtGui import QColor, QFont
@@ -40,7 +39,6 @@ from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode,
trConst
)
-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
@@ -48,6 +46,11 @@ from novelwriter.formats.shared import (
from novelwriter.text.comments import processComment
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
@@ -72,7 +75,7 @@ HEADINGS = [
BlockTyp.TITLE, BlockTyp.PART, BlockTyp.HEAD1,
BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4,
]
-SKIP_INDENT = HEADINGS + [BlockTyp.SEP, BlockTyp.SKIP]
+SKIP_INDENT = [*HEADINGS, BlockTyp.SEP, BlockTyp.SKIP]
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
@@ -651,7 +654,7 @@ class Tokenizer(ABC):
tText = aLine[2:].strip()
tType = BlockTyp.HEAD1 if isPlain else BlockTyp.TITLE
sHide = self._hidePart if isPlain else False
- if not (isPlain or isNovel and sHide):
+ if not (isPlain or (isNovel and sHide)):
tStyle |= self._titleStyle
if isNovel:
tType = BlockTyp.PART if isPlain else BlockTyp.TITLE
@@ -1124,12 +1127,10 @@ class Tokenizer(ABC):
temp.append((res.end(0), 0, TextFmt.HRF_E, ""))
# Match Shortcodes
- for res in REGEX_PATTERNS.shortcodePlain.finditer(text):
- temp.append((
- res.start(1), res.end(1),
- self._shortCodeFmt.get(res.group(1).lower(), 0),
- "",
- ))
+ temp.extend(
+ (res.start(1), res.end(1), self._shortCodeFmt.get(res.group(1).lower(), 0), "")
+ for res in REGEX_PATTERNS.shortcodePlain.finditer(text)
+ )
# Match Shortcode w/Values
tHandle = self._handle or ""
diff --git a/novelwriter/formats/tomarkdown.py b/novelwriter/formats/tomarkdown.py
index 2f87813b..b87d0325 100644
--- a/novelwriter/formats/tomarkdown.py
+++ b/novelwriter/formats/tomarkdown.py
@@ -25,13 +25,17 @@ from __future__ import annotations
import logging
-from pathlib import Path
+from typing import TYPE_CHECKING
from novelwriter.constants import nwUnicode
-from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import Tokenizer
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py
index fce5eec1..15622845 100644
--- a/novelwriter/formats/toodt.py
+++ b/novelwriter/formats/toodt.py
@@ -29,10 +29,9 @@ from __future__ import annotations
import logging
import xml.etree.ElementTree as ET
-from collections.abc import Sequence
from datetime import datetime
from hashlib import sha256
-from pathlib import Path
+from typing import TYPE_CHECKING, Final
from zipfile import ZIP_DEFLATED, ZipFile
from PyQt6.QtGui import QColor, QFont
@@ -40,11 +39,16 @@ from PyQt6.QtGui import QColor, QFont
from novelwriter import __version__
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
from novelwriter.constants import nwHeadFmt, nwStyles
-from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.types import FONT_STYLE, QtHexRgb
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+ from pathlib import Path
+
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
# Main XML NameSpaces
@@ -1000,11 +1004,11 @@ class ODTParagraphStyle:
exporter. Only the used settings are exposed here to keep the class
minimal and fast.
"""
- VALID_ALIGN = ["start", "center", "end", "justify", "inside", "outside", "left", "right"]
- VALID_BREAK = ["auto", "column", "page", "even-page", "odd-page", "inherit"]
- VALID_LEVEL = ["1", "2", "3", "4"]
- VALID_CLASS = ["text", "chapter", "extra"]
- VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
+ VALID_ALIGN: Final[list[str]] = ["start", "center", "end", "justify", "left", "right"]
+ VALID_BREAK: Final[list[str]] = ["auto", "page", "even-page", "odd-page", "inherit"]
+ VALID_LEVEL: Final[list[str]] = ["1", "2", "3", "4"]
+ VALID_CLASS: Final[list[str]] = ["text", "chapter", "extra"]
+ VALID_WEIGHT: Final[list[str]] = ["normal", "bold", *FONT_WEIGHT_NUM]
def __init__(self, name: str) -> None:
@@ -1207,9 +1211,9 @@ class ODTParagraphStyle:
def getID(self) -> str:
"""Generate a unique ID from the settings."""
return sha256((
- f"Paragraph:Main:{str(self._mAttr)}:"
- f"Paragraph:Para:{str(self._pAttr)}:"
- f"Paragraph:Text:{str(self._tAttr)}:"
+ f"Paragraph:Main:{self._mAttr!s}:"
+ f"Paragraph:Para:{self._pAttr!s}:"
+ f"Paragraph:Text:{self._tAttr!s}:"
).encode()).hexdigest()
def packXML(self, xParent: ET.Element) -> None:
@@ -1237,13 +1241,13 @@ class ODTTextStyle:
Only the used settings are exposed here to keep the class minimal
and fast.
"""
- VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
- VALID_STYLE = ["normal", "italic", "oblique"]
- VALID_POS = ["super", "sub"]
- VALID_LSTYLE = ["none", "solid"]
- VALID_LTYPE = ["single", "double"]
- VALID_LWIDTH = ["auto"]
- VALID_LCOL = ["font-color"]
+ VALID_WEIGHT: Final[list[str]] = ["normal", "bold", *FONT_WEIGHT_NUM]
+ VALID_STYLE: Final[list[str]] = ["normal", "italic", "oblique"]
+ VALID_POS: Final[list[str]] = ["super", "sub"]
+ VALID_LSTYLE: Final[list[str]] = ["none", "solid"]
+ VALID_LTYPE: Final[list[str]] = ["single", "double"]
+ VALID_LWIDTH: Final[list[str]] = ["auto"]
+ VALID_LCOL: Final[list[str]] = ["font-color"]
def __init__(self, name: str) -> None:
self._name = name
diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py
index 0cccfd09..f4bc897b 100644
--- a/novelwriter/formats/toqdoc.py
+++ b/novelwriter/formats/toqdoc.py
@@ -25,7 +25,7 @@ from __future__ import annotations
import logging
-from pathlib import Path
+from typing import TYPE_CHECKING
from PyQt6.QtCore import QMarginsF, QSizeF
from PyQt6.QtGui import (
@@ -36,7 +36,6 @@ from PyQt6.QtPrintSupport import QPrinter
from novelwriter import __version__
from novelwriter.constants import nwStyles, nwUnicode
-from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
from novelwriter.types import (
@@ -45,6 +44,11 @@ from novelwriter.types import (
QtPropLineHeight, QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper
)
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat]
diff --git a/novelwriter/formats/toraw.py b/novelwriter/formats/toraw.py
index 241d7c77..c4ed979b 100644
--- a/novelwriter/formats/toraw.py
+++ b/novelwriter/formats/toraw.py
@@ -26,13 +26,17 @@ from __future__ import annotations
import json
import logging
-from pathlib import Path
from time import time
+from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp
-from novelwriter.core.project import NWProject
from novelwriter.formats.tokenizer import Tokenizer
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index f4d1bff5..208db194 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -97,10 +97,10 @@ class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor"""
__slots__ = (
- "_nwDocument", "_nwItem", "_docChanged", "_docHandle", "_vpMargin",
- "_lastEdit", "_lastActive", "_lastFind", "_doReplace", "_autoReplace",
- "_completer", "_qDocument", "_keyContext", "_followTag1", "_followTag2",
- "_timerDoc", "_wCounterDoc", "_timerSel", "_wCounterSel",
+ "_autoReplace", "_completer", "_doReplace", "_docChanged", "_docHandle", "_followTag1",
+ "_followTag2", "_keyContext", "_lastActive", "_lastEdit", "_lastFind", "_nwDocument",
+ "_nwItem", "_qDocument", "_timerDoc", "_timerSel", "_vpMargin", "_wCounterDoc",
+ "_wCounterSel",
)
MOVE_KEYS = (
@@ -760,7 +760,7 @@ class GuiDocEditor(QPlainTextEdit):
elif action == nwDocAction.REPL_SNG:
self._replaceQuotes("'", CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose)
elif action == nwDocAction.REPL_DBL:
- self._replaceQuotes("\"", CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose)
+ self._replaceQuotes('"', CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose)
elif action == nwDocAction.RM_BREAKS:
self._removeInParLineBreaks()
elif action == nwDocAction.ALIGN_L:
@@ -2003,7 +2003,7 @@ class GuiDocEditor(QPlainTextEdit):
sPos = cPos - i - 1
cOne = str(self._qDocument.characterAt(sPos))
cTwo = str(self._qDocument.characterAt(sPos - 1))
- if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()):
+ if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
sPos += 1
break
@@ -2013,7 +2013,7 @@ class GuiDocEditor(QPlainTextEdit):
ePos = cPos + i
cOne = str(self._qDocument.characterAt(ePos))
cTwo = str(self._qDocument.characterAt(ePos + 1))
- if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()):
+ if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
break
if ePos - sPos <= 0:
@@ -2193,9 +2193,9 @@ class BackgroundWordCounterSignals(QObject):
class TextAutoReplace:
__slots__ = (
- "_quoteSO", "_quoteSC", "_quoteDO", "_quoteDC",
- "_replaceSQuote", "_replaceDQuote", "_replaceDash", "_replaceDots",
- "_padChar", "_padBefore", "_padAfter", "_doPadBefore", "_doPadAfter",
+ "_doPadAfter", "_doPadBefore", "_padAfter", "_padBefore", "_padChar",
+ "_quoteDC", "_quoteDO", "_quoteSC", "_quoteSO", "_replaceDQuote",
+ "_replaceDash", "_replaceDots", "_replaceSQuote",
)
def __init__(self) -> None:
diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py
index 40d6c657..61082fa2 100644
--- a/novelwriter/gui/dochighlight.py
+++ b/novelwriter/gui/dochighlight.py
@@ -58,8 +58,8 @@ BLOCK_TITLE = 4
class GuiDocHighlighter(QSyntaxHighlighter):
__slots__ = (
- "_tHandle", "_isNovel", "_isInactive", "_spellCheck", "_spellErr",
- "_hStyles", "_minRules", "_txtRules", "_cmnRules", "_dialogParser",
+ "_cmnRules", "_dialogParser", "_hStyles", "_isInactive", "_isNovel",
+ "_minRules", "_spellCheck", "_spellErr", "_tHandle", "_txtRules",
)
def __init__(self, document: QTextDocument) -> None:
@@ -466,7 +466,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
class TextBlockData(QTextBlockUserData):
- __slots__ = ("_text", "_offset", "_metaData", "_spellErrors")
+ __slots__ = ("_metaData", "_offset", "_spellErrors", "_text")
def __init__(self) -> None:
super().__init__()
diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py
index d395a4ec..7d996992 100644
--- a/novelwriter/gui/docviewerpanel.py
+++ b/novelwriter/gui/docviewerpanel.py
@@ -26,6 +26,7 @@ from __future__ import annotations
import logging
from enum import Enum
+from typing import TYPE_CHECKING
from PyQt6.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import (
@@ -36,12 +37,14 @@ from PyQt6.QtWidgets import (
from novelwriter import SHARED
from novelwriter.common import checkInt, qtAddAction
from novelwriter.constants import nwLabels, nwLists, nwStyles, trConst
-from novelwriter.core.indexdata import IndexHeading, IndexNode
from novelwriter.enum import nwChange, nwDocMode, nwItemClass
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
from novelwriter.types import QtDecoration, QtHeaderFixed, QtHeaderToContents, QtUserRole
+if TYPE_CHECKING:
+ from novelwriter.core.indexdata import IndexHeading, IndexNode
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/gui/editordocument.py b/novelwriter/gui/editordocument.py
index e2753038..d42ffaf1 100644
--- a/novelwriter/gui/editordocument.py
+++ b/novelwriter/gui/editordocument.py
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging
-from collections.abc import Iterable
from time import time
+from typing import TYPE_CHECKING
from PyQt6.QtCore import QObject, pyqtSlot
from PyQt6.QtGui import QTextBlock, QTextCursor, QTextDocument
@@ -35,6 +35,9 @@ from PyQt6.QtWidgets import QApplication, QPlainTextDocumentLayout
from novelwriter import SHARED
from novelwriter.gui.dochighlight import GuiDocHighlighter, TextBlockData
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py
index d873b8a1..9bece183 100644
--- a/novelwriter/gui/itemdetails.py
+++ b/novelwriter/gui/itemdetails.py
@@ -228,7 +228,7 @@ class GuiItemDetails(QWidget):
return
self._handle = tHandle
- iPx = int(round(0.9*SHARED.theme.baseIconHeight))
+ iPx = round(0.9*SHARED.theme.baseIconHeight)
# Label
# =====
diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py
index 7bc2d196..33f97dde 100644
--- a/novelwriter/gui/mainmenu.py
+++ b/novelwriter/gui/mainmenu.py
@@ -41,7 +41,7 @@ from novelwriter.constants import (
from novelwriter.enum import nwDocAction, nwDocInsert, nwFocus, nwView
from novelwriter.extensions.eventfilters import StatusTipFilter
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__)
@@ -471,7 +471,7 @@ class GuiMainMenu(QMenuBar):
# Insert > Double Prime
self.aInsDPrime = qtAddAction(self.mInsPunct, self.tr("Double Prime"))
- self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"")
+ self.aInsDPrime.setShortcut('Ctrl+K, Ctrl+"')
self.aInsDPrime.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_DPRIME)
)
@@ -680,7 +680,7 @@ class GuiMainMenu(QMenuBar):
# Format > Double Quotes
self.aFmtDQuote = qtAddAction(self.fmtMenu, self.tr("Wrap Double Quotes"))
- self.aFmtDQuote.setShortcut("Ctrl+\"")
+ self.aFmtDQuote.setShortcut('Ctrl+"')
self.aFmtDQuote.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.D_QUOTE)
)
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index a6551466..10a396b4 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -32,6 +32,7 @@ import logging
from enum import Enum
from time import time
+from typing import Final
from PyQt6.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QAction, QIcon
@@ -319,7 +320,7 @@ class GuiOutlineToolBar(QToolBar):
class GuiOutlineTree(QTreeWidget):
- DEF_WIDTH = {
+ DEF_WIDTH: Final[dict[nwOutline, int]] = {
nwOutline.TITLE: 200,
nwOutline.LEVEL: 40,
nwOutline.LABEL: 150,
@@ -342,7 +343,7 @@ class GuiOutlineTree(QTreeWidget):
nwOutline.SYNOP: 200,
}
- DEF_HIDDEN = {
+ DEF_HIDDEN: Final[dict[nwOutline, bool]] = {
nwOutline.TITLE: False,
nwOutline.LEVEL: True,
nwOutline.LABEL: False,
@@ -498,7 +499,7 @@ class GuiOutlineTree(QTreeWidget):
tree.
"""
# If it's the first time, we always build
- if self._firstView or self._firstView and overRide:
+ if self._firstView or (self._firstView and overRide):
self._loadHeaderState()
self._populateTree(rootHandle)
self._firstView = False
@@ -558,7 +559,7 @@ class GuiOutlineTree(QTreeWidget):
logger.info("Writing CSV file: %s", path)
cols = [col for col in self._treeOrder if not self._colHidden[col]]
order = [self._colIdx[col] for col in cols]
- with open(path, mode="w", newline="") as csvFile:
+ with open(path, mode="w", newline="", encoding="utf-8") as csvFile:
writer = csv.writer(csvFile, dialect="excel", quoting=csv.QUOTE_ALL)
writer.writerow([trConst(nwLabels.OUTLINE_COLS[col]) for col in cols])
for i in range(self.topLevelItemCount()):
@@ -795,7 +796,7 @@ class GuiOutlineHeaderMenu(QMenu):
class GuiOutlineDetails(QScrollArea):
- LVL_MAP = {
+ LVL_MAP: Final[dict[str, str]] = {
"H1": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"),
"H2": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Chapter"),
"H3": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Scene"),
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 721324f7..f8b13e9f 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -1099,7 +1099,7 @@ class _UpdatableMenu(QMenu):
class _TreeContextMenu(QMenu):
- __slots__ = ("_tree", "_view", "_node", "_item", "_model", "_handle", "_indices", "_children")
+ __slots__ = ("_children", "_handle", "_indices", "_item", "_model", "_node", "_tree", "_view")
def __init__(
self, projTree: GuiProjectTree, model: ProjectModel,
@@ -1327,7 +1327,7 @@ class _TreeContextMenu(QMenu):
"""Add move to Trash action."""
if (
self._model.trashSelection(self._indices)
- or len(self._indices) == 1 and self._item.isRootType()
+ or (len(self._indices) == 1 and self._item.isRootType())
):
text = self.tr("Delete Permanently")
else:
diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py
index bf403012..323b2200 100644
--- a/novelwriter/gui/search.py
+++ b/novelwriter/gui/search.py
@@ -26,6 +26,7 @@ from __future__ import annotations
import logging
from time import time
+from typing import TYPE_CHECKING
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QAction, QCursor, QKeyEvent, QPalette
@@ -37,12 +38,14 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt, qtAddAction
from novelwriter.core.coretools import DocSearch
-from novelwriter.core.item import NWItem
from novelwriter.types import (
QtAlignMiddle, QtAlignRight, QtHeaderStretch, QtHeaderToContents,
QtHexArgb, QtUserRole
)
+if TYPE_CHECKING:
+ from novelwriter.core.item import NWItem
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py
index e69b1dad..f784623f 100644
--- a/novelwriter/gui/sidebar.py
+++ b/novelwriter/gui/sidebar.py
@@ -37,7 +37,7 @@ from novelwriter.extensions.eventfilters import StatusTipFilter
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__)
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 729db7ee..207bc6fc 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -27,7 +27,7 @@ from __future__ import annotations
import logging
from math import ceil
-from pathlib import Path
+from typing import TYPE_CHECKING, Final
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import (
@@ -44,6 +44,9 @@ from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent
+if TYPE_CHECKING:
+ from pathlib import Path
+
logger = logging.getLogger(__name__)
STYLES_FLAT_TABS = "flatTabWidget"
@@ -93,18 +96,13 @@ class GuiTheme:
"""
__slots__ = (
- # Attributes
- "iconCache", "themeMeta", "isDarkTheme", "helpText", "fadedText", "errorText",
- "syntaxMeta", "syntaxTheme", "guiFont", "guiFontB", "guiFontBU", "guiFontSmall",
- "fontPointSize", "fontPixelSize", "baseIconHeight", "baseButtonHeight", "textNHeight",
- "textNWidth", "baseIconSize", "buttonIconSize", "guiFontFixed",
-
- # Functions
- "getIcon", "getPixmap", "getItemIcon", "getIconColor", "getToggleIcon", "getDecoration",
- "getHeaderDecoration", "getHeaderDecorationNarrow",
-
- # Internal
- "_guiPalette", "_themeList", "_syntaxList", "_availThemes", "_availSyntax", "_styleSheets",
+ "_availSyntax", "_availThemes", "_guiPalette", "_styleSheets", "_syntaxList", "_themeList",
+ "baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText",
+ "fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
+ "getHeaderDecorationNarrow", "getIcon", "getIconColor", "getItemIcon", "getPixmap",
+ "getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall",
+ "helpText", "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight",
+ "textNWidth", "themeMeta",
)
def __init__(self) -> None:
@@ -164,9 +162,9 @@ class GuiTheme:
fHeight = qMetric.height()
fAscent = qMetric.ascent()
self.fontPointSize = self.guiFont.pointSizeF()
- self.fontPixelSize = int(round(fHeight))
- self.baseIconHeight = int(round(fAscent))
- self.baseButtonHeight = int(round(1.35*fAscent))
+ self.fontPixelSize = round(fHeight)
+ self.baseIconHeight = round(fAscent)
+ self.baseButtonHeight = round(1.35*fAscent)
self.textNHeight = qMetric.boundingRect("N").height()
self.textNWidth = qMetric.boundingRect("N").width()
@@ -202,7 +200,7 @@ class GuiTheme:
qMetrics = QFontMetrics(font)
else:
qMetrics = QFontMetrics(self.guiFont)
- return int(ceil(qMetrics.boundingRect(text).width()))
+ return ceil(qMetrics.boundingRect(text).width())
##
# Theme Methods
@@ -567,16 +565,16 @@ class GuiIcons:
"""
__slots__ = (
- "mainTheme", "themeMeta", "_svgData", "_svgColors", "_qColors",
- "_qIcons", "_headerDec", "_headerDecNarrow", "_availThemes",
- "_themeList", "_noIcon",
+ "_availThemes", "_headerDec", "_headerDecNarrow", "_noIcon",
+ "_qColors", "_qIcons", "_svgColors", "_svgData", "_themeList",
+ "mainTheme", "themeMeta",
)
- TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
+ TOGGLE_ICON_KEYS: Final[dict[str, tuple[str, str]]] = {
"bullet": ("bullet-on", "bullet-off"),
"unfold": ("unfold-show", "unfold-hide"),
}
- IMAGE_MAP: dict[str, tuple[str, str]] = {
+ IMAGE_MAP: Final[dict[str, tuple[str, str]]] = {
"welcome": ("welcome-light.jpg", "welcome-dark.jpg"),
"nw-text": ("novelwriter-text-light.svg", "novelwriter-text-dark.svg"),
}
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index ffff4560..fac283f3 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -1009,7 +1009,7 @@ class GuiMain(QMainWindow):
fN = self.novelView.treeHasFocus()
self._changeView(nwView.EDITOR)
- if (vM and (vP and fP or vN and not fN)) or (not vM and vN):
+ if (vM and ((vP and fP) or (vN and not fN))) or (not vM and vN):
self._changeView(nwView.NOVEL)
self.novelView.setTreeFocus()
else:
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index 9fbd5581..04add94c 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -40,7 +40,7 @@ from novelwriter.constants import nwFiles
from novelwriter.core.spellcheck import NWSpellEnchant
from novelwriter.enum import nwChange, nwItemClass
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.core.project import NWProject
from novelwriter.core.status import T_StatusKind
from novelwriter.gui.theme import GuiTheme
@@ -54,8 +54,8 @@ NWWidget = TypeVar("NWWidget", bound=QWidget)
class SharedData(QObject):
__slots__ = (
- "_gui", "_theme", "_project", "_spelling", "_lockedBy", "_lastAlert",
- "_idleTime", "_idleRefTime",
+ "_gui", "_idleRefTime", "_idleTime", "_lastAlert", "_lockedBy",
+ "_project", "_spelling", "_theme",
)
focusModeChanged = pyqtSignal(bool)
@@ -480,7 +480,7 @@ class _GuiAlert(QMessageBox):
def setException(self, exception: Exception) -> None:
"""Add exception details."""
info = self.informativeText()
- text = f"{type(exception).__name__}: {str(exception)}"
+ text = f"{type(exception).__name__}: {exception!s}"
self.setInformativeText(f"{info}
{text}" if info else text)
return
diff --git a/novelwriter/text/patterns.py b/novelwriter/text/patterns.py
index c4e0bf07..d592b70c 100644
--- a/novelwriter/text/patterns.py
+++ b/novelwriter/text/patterns.py
@@ -128,8 +128,8 @@ REGEX_PATTERNS = RegExPatterns()
class DialogParser:
__slots__ = (
- "_quotes", "_dialog", "_alternate", "_enabled",
- "_narrator", "_breakD", "_breakQ", "_mode",
+ "_alternate", "_breakD", "_breakQ", "_dialog", "_enabled", "_mode",
+ "_narrator", "_quotes",
)
def __init__(self) -> None:
diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py
index 48aefa2d..b2028dca 100644
--- a/novelwriter/tools/lipsum.py
+++ b/novelwriter/tools/lipsum.py
@@ -124,10 +124,10 @@ class GuiLipsum(NDialog):
@classmethod
def getLipsum(cls, parent: QWidget) -> str:
"""Pop the dialog and return the lipsum text."""
- cls = GuiLipsum(parent)
- cls.exec()
- text = cls.lipsumText
- cls.softDelete()
+ dialog = cls(parent)
+ dialog.exec()
+ text = dialog.lipsumText
+ dialog.softDelete()
return text
##
diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py
index f45d7a1f..22628f43 100644
--- a/novelwriter/tools/manusbuild.py
+++ b/novelwriter/tools/manusbuild.py
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging
from pathlib import Path
+from typing import TYPE_CHECKING
from PyQt6.QtCore import QTimer, pyqtSlot
-from PyQt6.QtGui import QCloseEvent
from PyQt6.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog,
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
@@ -38,7 +38,6 @@ from PyQt6.QtWidgets import (
from novelwriter import SHARED
from novelwriter.common import makeFileNameSafe, openExternalPath
from novelwriter.constants import nwLabels
-from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.item import NWItem
from novelwriter.enum import nwBuildFmt
@@ -46,6 +45,11 @@ from novelwriter.extensions.modified import NDialog, NIconToolButton
from novelwriter.extensions.progressbars import NProgressSimple
from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
+if TYPE_CHECKING:
+ from PyQt6.QtGui import QCloseEvent
+
+ from novelwriter.core.buildsettings import BuildSettings
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index f06505d9..b6918b18 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -59,7 +59,7 @@ from novelwriter.types import (
QtUserRole
)
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__)
@@ -427,10 +427,10 @@ class GuiManuscript(NToolDialog):
def _saveSettings(self) -> None:
"""Save the user GUI settings."""
- buildOrder = []
- for i in range(self.buildList.count()):
- if item := self.buildList.item(i):
- buildOrder.append(item.data(self.D_KEY))
+ buildOrder = [
+ item.data(self.D_KEY) for i in range(self.buildList.count())
+ if (item := self.buildList.item(i))
+ ]
current = self.buildList.currentItem()
lastBuild = current.data(self.D_KEY) if isinstance(current, QListWidgetItem) else ""
@@ -744,7 +744,7 @@ class _PreviewWidget(QTextBrowser):
document.setDocumentMargin(CONFIG.textMargin)
self.setPlaceholderText(self.tr(
- "Press the \"Preview\" button to generate ..."
+ 'Press the "Preview" button to generate ...'
))
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index f0f6d1c0..cfac8f11 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -55,7 +55,7 @@ from novelwriter.types import (
QtUserRole
)
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__)
diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py
index 9f4bfb26..ec42ccf1 100644
--- a/novelwriter/tools/noveldetails.py
+++ b/novelwriter/tools/noveldetails.py
@@ -26,8 +26,9 @@ from __future__ import annotations
import logging
import math
+from typing import TYPE_CHECKING
+
from PyQt6.QtCore import pyqtSlot
-from PyQt6.QtGui import QCloseEvent
from PyQt6.QtWidgets import (
QAbstractItemView, QDialogButtonBox, QFormLayout, QGridLayout, QHBoxLayout,
QLabel, QSpinBox, QStackedWidget, QTreeWidget, QTreeWidgetItem,
@@ -44,6 +45,9 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtAlignRight, QtDecoration, QtDialogClose
+if TYPE_CHECKING:
+ from PyQt6.QtGui import QCloseEvent
+
logger = logging.getLogger(__name__)
diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py
index 4d2a0679..3638c18d 100644
--- a/novelwriter/tools/welcome.py
+++ b/novelwriter/tools/welcome.py
@@ -385,7 +385,7 @@ class _OpenProjectPage(QWidget):
class _ProjectListItem(QStyledItemDelegate):
- __slots__ = ("_pPx", "_hPx", "_tFont", "_dFont", "_dPen", "_icon")
+ __slots__ = ("_dFont", "_dPen", "_hPx", "_icon", "_pPx", "_tFont")
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index 3a8aab06..f2c14be2 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -47,7 +47,7 @@ from novelwriter.types import (
QtDialogClose, QtRoleAction
)
-if TYPE_CHECKING: # pragma: no cover
+if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__)
@@ -124,7 +124,7 @@ class GuiWritingStats(NToolDialog):
self.listBox.setSortingEnabled(True)
# Word Bar
- self.barHeight = int(round(0.5*SHARED.theme.fontPixelSize))
+ self.barHeight = round(0.5*SHARED.theme.fontPixelSize)
self.barImage = QPixmap(self.barHeight, self.barHeight)
self.barImage.fill(self.palette().highlight().color())
diff --git a/pyproject.toml b/pyproject.toml
index 76bff95a..0804a98c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -60,30 +60,48 @@ line-length = 99
[tool.ruff.lint]
preview = true
+
+# Rules: https://docs.astral.sh/ruff/rules
select = [
- "A", # flake8-builtins (A)
- "B", # flake8-bugbear (B)
- "E", # pycodestyle (E)
- "F", # Pyflakes (F)
- "W", # pycodestyle (W)
- "UP", # pyupgrade (UP)
- "ANN", # flake8-annotations (ANN)
- "PLE", # Pylint Error (PLE)
- "FA", # flake8-future-annotations (FA)
+ "A", # flake8-builtins (A)
+ "ANN", # flake8-annotations (ANN)
+ "B", # flake8-bugbear (B)
+ "E", # pycodestyle (E)
+ "F", # Pyflakes (F)
+ "FA", # flake8-future-annotations (FA)
+ "PERF", # Perflint (PERF)
+ "PLC", # Pylint Convention (PLC)
+ "PLE", # Pylint Error (PLE)
+ "PLW", # Pylint Warning (PLW)
+ "Q", # flake8-quotes (Q)
+ "RUF", # Ruff-specific rules (RUF)
+ "SLF", # flake8-self (SLF)
+ "SLOT", # flake8-slots (SLOT)
+ "TC", # flake8-type-checking (TC)
+ "UP", # pyupgrade (UP)
+ "W", # pycodestyle (W)
]
ignore = [
- "E221", # multiple-spaces-before-operator
- "E226", # missing-whitespace-around-arithmetic-operator
- "E228", # missing-whitespace-around-modulo-operator
- "E241", # multiple-spaces-after-comma
- "E272", # multiple-spaces-before-keyword
- "ANN401", # any-type
- "UP015", # redundant-open-modes
- "UP030", # format-literals
+ "ANN401", # any-type
+ "E221", # multiple-spaces-before-operator
+ "E226", # missing-whitespace-around-arithmetic-operator
+ "E228", # missing-whitespace-around-modulo-operator
+ "E241", # multiple-spaces-after-comma
+ "E272", # multiple-spaces-before-keyword
+ "PLC0415", # import-outside-top-level
+ "PLC1901", # compare-to-empty-string
+ "PLW0108", # unnecessary-lambda
+ "PLW2901", # redefined-loop-name
+ "RUF001", # ambiguous-unicode-character-string
+ "RUF002", # ambiguous-unicode-character-docstring
+ "RUF015", # unnecessary-iterable-allocation-for-first-element
+ "UP015", # redundant-open-modes
+ "UP030", # format-literals
]
[tool.ruff.lint.per-file-ignores]
-"tests/*" = ["ANN"]
+"tests/*" = ["ANN", "SLF", "TC", "PLC2701"]
+"utils/*" = ["ANN", "SLF", "TC"]
[tool.ruff.format]
quote-style = "double"
@@ -111,3 +129,6 @@ branch = false
[tool.coverage.report]
precision = 2
+exclude_also = [
+ "if TYPE_CHECKING:"
+]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index f4e05a41..18c97a30 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,3 +1,3 @@
-ruff
isort
pyright
+ruff
diff --git a/tests/conftest.py b/tests/conftest.py
index 3dffc8c6..33baa870 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -33,10 +33,10 @@ from PyQt6.QtWidgets import QMessageBox
sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
-from novelwriter import CONFIG, SHARED # noqa: E402
+from novelwriter import CONFIG, SHARED
-from tests.mocked import MockGuiMain, MockTheme # noqa: E402
-from tests.tools import cleanProject # noqa: E402
+from tests.mocked import MockGuiMain, MockTheme
+from tests.tools import cleanProject
_TST_ROOT = Path(__file__).parent
_TMP_ROOT = _TST_ROOT / "temp"
@@ -89,7 +89,7 @@ def functionFixture(qtbot):
shutil.rmtree(_TMP_CONF)
_TMP_CONF.mkdir()
- CONFIG.__init__()
+ CONFIG.__init__() # noqa: PLC2801
CONFIG.initConfig(confPath=_TMP_CONF, dataPath=_TMP_CONF)
resetConfigVars()
logging.getLogger("novelwriter").setLevel(logging.INFO)
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 7314a844..39f96e3c 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,4 +1,5 @@
-pytest>=6.0.0
-pytest-timeout
+coverage>=7.2.0
pytest-cov
pytest-qt
+pytest-timeout
+pytest>=6.0.0
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index 7e03b895..bfd2b55c 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -556,7 +556,7 @@ def testBaseCommon_jsonEncode():
# Correct types
assert jsonEncode([1, 2]) == "[\n 1,\n 2\n]"
assert jsonEncode((1, 2)) == "[\n 1,\n 2\n]"
- assert jsonEncode({1: 2}) == "{\n \"1\": 2\n}"
+ assert jsonEncode({1: 2}) == '{\n "1": 2\n}'
tstDict = {
"null": None,
diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py
index b58c1fd2..74468629 100644
--- a/tests/test_base/test_base_config.py
+++ b/tests/test_base/test_base_config.py
@@ -141,8 +141,8 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
assert newConf.guiSyntax == "bar"
# Test Correcting Quote Settings
- tstConf.fmtDQuoteOpen = "\""
- tstConf.fmtDQuoteClose = "\""
+ tstConf.fmtDQuoteOpen = '"'
+ tstConf.fmtDQuoteClose = '"'
tstConf.fmtSQuoteOpen = "'"
tstConf.fmtSQuoteClose = "'"
tstConf.doReplaceDQuote = True
diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py
index 5b9f6c57..0bd4b24c 100644
--- a/tests/test_core/test_core_coretools.py
+++ b/tests/test_core/test_core_coretools.py
@@ -768,7 +768,7 @@ def testCoreTools_ProjectBuilderSample(monkeypatch, mockGUI, fncPath, tstPaths):
assert builder.buildProject(data) is False
# Create and open a defective zip file
- with open(dstSample, mode="w+") as outFile:
+ with open(dstSample, mode="w+", encoding="utf-8") as outFile:
outFile.write("foo")
assert builder.buildProject(data) is False
diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py
index 39fa9297..acd5d19c 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -42,6 +42,8 @@ from tests.tools import cmpFiles, writeFile
class MockProject:
"""Fake project object."""
+ data: NWProjectData
+
def setProjectChanged(self, *a):
"""Fake project method."""
pass
@@ -219,7 +221,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
packedContent = []
mockProject = MockProject()
- mockProject.__setattr__("data", data)
+ mockProject.data = data
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry)
@@ -348,7 +350,7 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = []
mockProject = MockProject()
- mockProject.__setattr__("data", data)
+ mockProject.data = data
status = {}
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -493,7 +495,7 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = []
mockProject = MockProject()
- mockProject.__setattr__("data", data)
+ mockProject.data = data
status = {}
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -638,7 +640,7 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = []
mockProject = MockProject()
- mockProject.__setattr__("data", data)
+ mockProject.data = data
status = {}
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -786,7 +788,7 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = []
mockProject = MockProject()
- mockProject.__setattr__("data", data)
+ mockProject.data = data
status = {}
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -933,7 +935,7 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = []
mockProject = MockProject()
- mockProject.__setattr__("data", data)
+ mockProject.data = data
status = {}
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
diff --git a/tests/test_formats/test_fmt_toodt.py b/tests/test_formats/test_fmt_toodt.py
index 4f72f9df..05fde0e5 100644
--- a/tests/test_formats/test_fmt_toodt.py
+++ b/tests/test_formats/test_fmt_toodt.py
@@ -982,7 +982,7 @@ def testFmtToOdt_SaveFull(mockGUI, fncPath, tstPaths, ipsumText):
def prettifyXml(inFile, outFile):
with open(outFile, mode="wb") as fStream:
xml = ET.parse(inFile)
- xmlIndent(xml)
+ xmlIndent(xml.getroot())
xml.write(fStream, encoding="utf-8", xml_declaration=True)
prettifyXml(maniOut, maniFile)
@@ -1082,10 +1082,6 @@ def testFmtToOdt_ODTParagraphStyle():
assert parStyle._pAttr["text-align"] == ["fo", "end"]
parStyle.setTextAlign("justify")
assert parStyle._pAttr["text-align"] == ["fo", "justify"]
- parStyle.setTextAlign("inside")
- assert parStyle._pAttr["text-align"] == ["fo", "inside"]
- parStyle.setTextAlign("outside")
- assert parStyle._pAttr["text-align"] == ["fo", "outside"]
parStyle.setTextAlign("left")
assert parStyle._pAttr["text-align"] == ["fo", "left"]
parStyle.setTextAlign("right")
@@ -1099,8 +1095,6 @@ def testFmtToOdt_ODTParagraphStyle():
assert parStyle._pAttr["break-before"] == ["fo", None]
parStyle.setBreakBefore("auto")
assert parStyle._pAttr["break-before"] == ["fo", "auto"]
- parStyle.setBreakBefore("column")
- assert parStyle._pAttr["break-before"] == ["fo", "column"]
parStyle.setBreakBefore("page")
assert parStyle._pAttr["break-before"] == ["fo", "page"]
parStyle.setBreakBefore("even-page")
@@ -1118,8 +1112,6 @@ def testFmtToOdt_ODTParagraphStyle():
assert parStyle._pAttr["break-after"] == ["fo", None]
parStyle.setBreakAfter("auto")
assert parStyle._pAttr["break-after"] == ["fo", "auto"]
- parStyle.setBreakAfter("column")
- assert parStyle._pAttr["break-after"] == ["fo", "column"]
parStyle.setBreakAfter("page")
assert parStyle._pAttr["break-after"] == ["fo", "page"]
parStyle.setBreakAfter("even-page")
@@ -1394,9 +1386,9 @@ def testFmtToOdt_XMLParagraph():
# Plain Text
xmlPar.appendText("Hello World")
assert xmlToText(xRoot) == (
- ''
- 'Hello World'
- ''
+ ""
+ "Hello World"
+ ""
)
# Text Span
@@ -1431,9 +1423,9 @@ def testFmtToOdt_XMLParagraph():
# Plain Text w/Line Break
xmlPar.appendText("Hello\nWorld\n!!")
assert xmlToText(xRoot) == (
- ''
- 'HelloWorld!!'
- ''
+ ""
+ "HelloWorld!!"
+ ""
)
# Text Span w/Line Break
@@ -1467,9 +1459,9 @@ def testFmtToOdt_XMLParagraph():
# Plain Text w/Line Break
xmlPar.appendText("Hello\tWorld\t!!")
assert xmlToText(xRoot) == (
- ''
- 'HelloWorld!!'
- ''
+ ""
+ "HelloWorld!!"
+ ""
)
# Text Span w/Line Break
diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py
index be76f526..312c88bb 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -763,7 +763,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
# Replace Double Quotes
- repText = text.replace("consectetur", "\"consectetur\"")
+ repText = text.replace("consectetur", '"consectetur"')
docEditor.replaceText(repText)
assert docEditor.docAction(nwDocAction.SEL_ALL) is True
assert docEditor.docAction(nwDocAction.REPL_DBL) is True
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index da2d3ba0..24694ffe 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -116,7 +116,8 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
# Check that project open dialog launches
nwGUI.postLaunchTasks(None)
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiWelcome) is not None, timeout=1000)
- assert isinstance(welcome := SHARED.findTopLevelWidget(GuiWelcome), GuiWelcome)
+ welcome = SHARED.findTopLevelWidget(GuiWelcome)
+ assert isinstance(welcome, GuiWelcome)
welcome.show()
welcome.close()
@@ -454,7 +455,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
# Dialogue
# ========
- for c in "\"Full line double quoted text.\"":
+ for c in '"Full line double quoted text."':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
@@ -469,7 +470,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
CONFIG.fmtPadAfter = "\u201c"
docEditor.initEditor()
- for c in "Some \"double quoted text with spaces padded\".":
+ for c in 'Some "double quoted text with spaces padded".':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
@@ -533,17 +534,17 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
# ================
nwGUI._switchFocus(nwView.EDITOR)
- for c in "\t\"Tab-indented text\"":
+ for c in '\t"Tab-indented text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
- for c in ">\"Paragraph-indented text\"":
+ for c in '>"Paragraph-indented text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
- for c in ">>\"Right-aligned text\"":
+ for c in '>>"Right-aligned text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index ec29a233..f63f588a 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -279,7 +279,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
docEditor.setPlainText(
"### New Text\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ 'Also text with "double" quotes which are "less tricky".\n\n'
)
mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger)
@@ -287,7 +287,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
assert docEditor.getText() == (
"### New Text\n\n"
"Text with ‘single’ quotes and ‘tricky stuff’s’.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ 'Also text with "double" quotes which are "less tricky".\n\n'
)
mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger)
@@ -347,7 +347,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"### New Text\n\n"
"@tag: Bod\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ 'Also text with "double" quotes which are "less tricky".\n\n'
)
# Cannot Format Tag
@@ -363,7 +363,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"### New Text\n\n"
"@tag: Bod\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ 'Also text with "double" quotes which are "less tricky".\n\n'
)
# qtbot.stop()
diff --git a/tests/test_text/test_text_patterns.py b/tests/test_text/test_text_patterns.py
index ca459292..ee938e10 100644
--- a/tests/test_text/test_text_patterns.py
+++ b/tests/test_text/test_text_patterns.py
@@ -29,15 +29,12 @@ from novelwriter.constants import nwUnicode
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
-def allMatches(regEx: re.Pattern, text: str) -> list[list[str]]:
+def allMatches(regEx: re.Pattern, text: str) -> list[list[tuple[str, int, int]]]:
"""Get all matches for a regex."""
- result = []
- for res in regEx.finditer(text):
- result.append([
- (res.group(n), res.start(n), res.end(n))
- for n in range((res.lastindex or 0) + 1)
- ])
- return result
+ return [
+ [(res.group(n), res.start(n), res.end(n)) for n in range((res.lastindex or 0) + 1)]
+ for res in regEx.finditer(text)
+ ]
@pytest.mark.core
@@ -310,7 +307,7 @@ def testTextPatterns_DialogueStyle():
assert allMatches(regEx, "one 'two' three") == []
# Straight double quotes are ignored
- assert allMatches(regEx, "one \"two\" three") == []
+ assert allMatches(regEx, 'one "two" three') == []
# Check with no whitespace, single quote
assert allMatches(regEx, "one\u2018two\u2019three") == [
@@ -370,19 +367,19 @@ def testTextPatterns_DialoguePlain():
# ======
# One double quoted string
- assert allMatches(regEx, "one \"two\" three") == [
- [("\"two\"", 4, 9)]
+ assert allMatches(regEx, 'one "two" three') == [
+ [('"two"', 4, 9)]
]
# Two double quoted strings
- assert allMatches(regEx, "one \"two\" three \"four\" five") == [
- [("\"two\"", 4, 9)], [("\"four\"", 16, 22)],
+ assert allMatches(regEx, 'one "two" three "four" five') == [
+ [('"two"', 4, 9)], [('"four"', 16, 22)],
]
# No space
- assert allMatches(regEx, "one\"two\" three") == []
- assert allMatches(regEx, "one \"two\"three") == []
- assert allMatches(regEx, "one\"two\"three") == []
+ assert allMatches(regEx, 'one"two" three') == []
+ assert allMatches(regEx, 'one "two"three') == []
+ assert allMatches(regEx, 'one"two"three') == []
# Single
# ======
@@ -595,6 +592,6 @@ def testTextPatterns_DialogParserPolish():
]
assert parser(
- "And so on and so forth. However, \"text in quotation marks\" should not be "
+ 'And so on and so forth. However, "text in quotation marks" should not be '
"highlighted at all, and if so, it should be highlighted differently."
) == []
diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py
index 9a86eff6..50d04391 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -83,8 +83,7 @@ def testToolManuscriptBuild_Main(
if item.data(manus.D_KEY) == fmt:
manus.listFormats.setCurrentItem(item)
return
- else:
- raise ValueError("No such key in format list")
+ raise ValueError("No such key in format list")
# Build documents
lastFmt = None
diff --git a/utils/assets.py b/utils/assets.py
index dc53b8b3..b9291c74 100644
--- a/utils/assets.py
+++ b/utils/assets.py
@@ -180,9 +180,9 @@ def updateTranslationSources(args: argparse.Namespace) -> None:
else: # Create an empty new language file
langCode = item.name[3:-3]
writeFile(item, (
- "\n"
+ '\n'
"\n"
- f"\n"
+ f'\n'
))
translations.append(item)
print(f"Created: {item}")
diff --git a/utils/build_debian.py b/utils/build_debian.py
index b962e620..ea08e705 100644
--- a/utils/build_debian.py
+++ b/utils/build_debian.py
@@ -134,10 +134,10 @@ def makeDebianPackage(
signArgs = [f"-k{signKey}"]
if sourceBuild:
- subprocess.call(["debuild", "-S"] + signArgs, cwd=outDir)
+ subprocess.call(["debuild", "-S", *signArgs], cwd=outDir)
toUpload(bldDir / f"{bldPkg}.tar.xz")
else:
- subprocess.call(["dpkg-buildpackage"] + signArgs, cwd=outDir)
+ subprocess.call(["dpkg-buildpackage", *signArgs], cwd=outDir)
shutil.copyfile(bldDir / f"{bldPkg}.tar.xz", bldDir / f"{bldPkg}.debian.tar.xz")
toUpload(bldDir / f"{bldPkg}.debian.tar.xz")
toUpload(bldDir / f"{bldPkg}_all.deb")
@@ -195,7 +195,7 @@ def launchpad(args: argparse.Namespace) -> None:
signKey = SIGN_KEY if args.sign else None
- print(f"Sign Key: {str(signKey)}")
+ print(f"Sign Key: {signKey!s}")
print("")
dputCmd = []
diff --git a/utils/build_windows.py b/utils/build_windows.py
index 89766be0..a4f0e866 100644
--- a/utils/build_windows.py
+++ b/utils/build_windows.py
@@ -218,9 +218,9 @@ def main(args: argparse.Namespace) -> None:
"import sys\n"
"\n"
"os.curdir = os.path.abspath(os.path.dirname(__file__))\n"
- "sys.path.insert(0, os.path.join(os.curdir, \"lib\"))\n"
+ 'sys.path.insert(0, os.path.join(os.curdir, "lib"))\n'
"\n"
- "if __name__ == \"__main__\":\n"
+ 'if __name__ == "__main__":\n'
" import novelwriter\n"
" novelwriter.main(sys.argv[1:])\n"
))
diff --git a/utils/common.py b/utils/common.py
index c5d88469..6eda3841 100644
--- a/utils/common.py
+++ b/utils/common.py
@@ -137,7 +137,7 @@ def makeCheckSum(sumFile: str, cwd: Path | None = None) -> str:
shaFile = f"{sumFile}.sha256"
else:
shaFile = cwd / f"{sumFile}.sha256"
- with open(shaFile, mode="w") as fOut:
+ with open(shaFile, mode="w", encoding="utf-8") as fOut:
subprocess.call(["shasum", "-a", "256", sumFile], stdout=fOut, cwd=cwd)
print(f"SHA256 Sum: {shaFile}")
except Exception as exc: