From b093446020a38d5eb423cbeff0aa47ede2cdab5b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:08:42 +0200 Subject: [PATCH 1/4] Fix Qt side crash when forwarding editor auto-complete keypresses (#2510) --- novelwriter/gui/doceditor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 846b6317..83ff8fe7 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -1080,6 +1080,7 @@ class GuiDocEditor(QPlainTextEdit): if (block := self._qDocument.findBlock(pos)).isValid(): text = block.text() + if text and text[0] in "@%" and added + removed == 1: # Only run on single character changes, or it will trigger # at unwanted times when other changes are made to the document @@ -1094,10 +1095,6 @@ class GuiDocEditor(QPlainTextEdit): point = self.cursorRect().bottomRight() self._completer.move(viewport.mapToGlobal(point)) self._completer.show() - else: - self._completer.close() - else: - self._completer.close() if self._doReplace and added == 1: cursor = self.textCursor() @@ -1121,7 +1118,7 @@ class GuiDocEditor(QPlainTextEdit): cursor.setPosition(check, QtMoveAnchor) cursor.setPosition(check + length, QtKeepAnchor) cursor.insertText(text) - self._completer.hide() + self._completer.close() return @pyqtSlot() @@ -2186,6 +2183,7 @@ class CommandCompleter(QMenu): ): super().keyPressEvent(event) elif isinstance(parent, GuiDocEditor): + self.close() # Close to release the event lock before forwarding the key press (#2510) parent.keyPressEvent(event) return From 33b3a71ecdc832bef47d5e168aeecd6e5a6aaf9a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:11:51 +0200 Subject: [PATCH 2/4] Make a minor performance improvement in the editor completer --- novelwriter/gui/doceditor.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 83ff8fe7..daf99c4c 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2086,10 +2086,13 @@ class CommandCompleter(QMenu): called on every keystroke on a line starting with @ or %. """ + __slots__ = ("_parent",) + complete = pyqtSignal(int, int, str) def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) + self._parent = parent return def updateMetaText(self, text: str, pos: int) -> bool: @@ -2176,15 +2179,14 @@ class CommandCompleter(QMenu): def keyPressEvent(self, event: QKeyEvent) -> None: """Capture keypresses and forward most of them to the editor.""" - parent = self.parent() if event.key() in ( Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Return, Qt.Key.Key_Enter, Qt.Key.Key_Escape ): super().keyPressEvent(event) - elif isinstance(parent, GuiDocEditor): + else: self.close() # Close to release the event lock before forwarding the key press (#2510) - parent.keyPressEvent(event) + self._parent.keyPressEvent(event) return ## From d69d2234c4dc91abab9cadbca17ae82a98976fdf Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:23:33 +0200 Subject: [PATCH 3/4] Rename completer signal --- novelwriter/gui/doceditor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index daf99c4c..0ac4a992 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -151,7 +151,7 @@ class GuiDocEditor(QPlainTextEdit): # Completer self._completer = CommandCompleter(self) - self._completer.complete.connect(self._insertCompletion) + self._completer.insertText.connect(self._insertCompletion) # Create Custom Document self._qDocument = GuiTextDocument(self) @@ -2088,7 +2088,7 @@ class CommandCompleter(QMenu): __slots__ = ("_parent",) - complete = pyqtSignal(int, int, str) + insertText = pyqtSignal(int, int, str) def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) @@ -2195,7 +2195,7 @@ class CommandCompleter(QMenu): def _emitComplete(self, pos: int, length: int, value: str) -> None: """Emit the signal to indicate a selection has been made.""" - self.complete.emit(pos, length, value) + self.insertText.emit(pos, length, value) return From 08d2a5fa958af433e1fbb069c5166f262b614e98 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:27:55 +0200 Subject: [PATCH 4/4] Fix linting errors from updated Ruff version --- tests/test_gui/test_gui_guimain.py | 8 ++++---- tests/test_gui/test_gui_theme.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 6fda9b6a..69c4da26 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -812,13 +812,13 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd) # Handle broken index on project open nwGUI.closeProject() idxPath: Path = projPath / "meta" / nwFiles.INDEX_FILE - assert idxPath.read_text() != "{}" - idxPath.write_text("{}") - assert idxPath.read_text() == "{}" + assert idxPath.read_text(encoding="utf-8") != "{}" + idxPath.write_text("{}", encoding="utf-8") + assert idxPath.read_text(encoding="utf-8") == "{}" nwGUI.openProject(projPath) nwGUI.saveProject() - assert idxPath.read_text() != "{}" + assert idxPath.read_text(encoding="utf-8") != "{}" assert nwGUI.docEditor.docHandle == C.hSceneDoc assert nwGUI.docViewer.docHandle == C.hTitlePage diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 7b0f518d..2deb3464 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -164,14 +164,14 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths): # =============== mockTheme: Path = tstPaths.cnfDir / "themes" / "test.conf" - mockTheme.write_text( + mockTheme.write_text(( "[Main]\n" "name = Test\n" "\n" "[Palette]\n" "window = 0, 0, 0\n" "text = 255, 255, 255\n" - ) + ), encoding="utf-8") mainTheme._availThemes["test"] = mockTheme CONFIG.guiTheme = "test"