From 53784b310eb8d993dcf01e59d914d53341034695 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 6 Jun 2025 18:39:21 +0200 Subject: [PATCH 1/3] Close instead of hide auto-complete menu when inactive (#2386) --- novelwriter/gui/doceditor.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 0892bb71..8d1c42c1 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -1090,11 +1090,14 @@ class GuiDocEditor(QPlainTextEdit): show = self._completer.updateMetaText(text, bPos) else: show = self._completer.updateCommentText(text, bPos) - point = self.cursorRect().bottomRight() - self._completer.move(viewport.mapToGlobal(point)) - self._completer.setVisible(show) + if show: + point = self.cursorRect().bottomRight() + self._completer.move(viewport.mapToGlobal(point)) + self._completer.show() + else: + self._completer.close() else: - self._completer.setVisible(False) + self._completer.close() if self._doReplace and added == 1: cursor = self.textCursor() From bf1067a346314cd7d6b900b0fe4f1dce62772b2c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 6 Jun 2025 19:07:32 +0200 Subject: [PATCH 2/3] Make sure project items in inactive classes are removed from the index (#2387) --- novelwriter/core/index.py | 16 ++++++++++------ novelwriter/core/item.py | 2 +- tests/test_core/test_core_index.py | 29 ++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index fb357842..ee108ad3 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -131,12 +131,6 @@ class Index: self._novelExtra = extra return - def setItemClass(self, tHandle: str, itemClass: nwItemClass) -> None: - """Update the class for all tags of a handle.""" - logger.info("Updating class for '%s'", tHandle) - self._tagsIndex.updateClass(tHandle, itemClass.name) - return - ## # Public Methods ## @@ -183,6 +177,16 @@ class Index: self.scanText(tHandle, self._project.storage.getDocumentText(tHandle)) return + def refreshHandle(self, tHandle: str) -> None: + """Update the class for all tags of a handle.""" + if item := self._project.tree[tHandle]: + logger.info("Updating class for '%s'", tHandle) + if item.isInactiveClass(): + self.deleteHandle(tHandle) + else: + self._tagsIndex.updateClass(tHandle, item.itemClass.name) + return + def indexChangedSince(self, checkTime: int | float) -> bool: """Check if the index has changed since a given time.""" return self._indexChange > float(checkTime) diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index a8f2fbbd..a6b92734 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -439,7 +439,7 @@ class NWItem: self.setClass(itemClass) if self._type == nwItemType.FILE: # Notify the index of the class change - self._project.index.setItemClass(self._handle, itemClass) + self._project.index.refreshHandle(self._handle) if self._layout == nwItemLayout.NO_LAYOUT: # If no layout is set, pick one diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 990a2206..bc9bef2c 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -95,12 +95,35 @@ def testCoreIndex_LoadSave(qtbot, monkeypatch, prjLipsum, nwGUI, tstPaths): tagIndex = str(index._tagsIndex.packData()) itemsIndex = str(index._itemIndex.packData()) + # Update item class + bHandle = "4c4f28287af27" + bItem = project.tree[bHandle] + assert bItem is not None + + tagsBod = index._tagsIndex["Bod"] + assert tagsBod is not None + assert tagsBod["handle"] == bHandle + assert tagsBod["class"] == "CHARACTER" + + bItem.setClass(nwItemClass.CUSTOM) + index.refreshHandle(bHandle) + assert tagsBod is not None + assert tagsBod["handle"] == bHandle + assert tagsBod["class"] == "CUSTOM" + + # Update item class to inactive + bItem.setClass(nwItemClass.TRASH) + index.refreshHandle(bHandle) + assert "Bod" not in index._tagsIndex + bItem.setClass(nwItemClass.CHARACTER) + index.reIndexHandle(bHandle) + # Delete a handle assert index._tagsIndex["Bod"] is not None - assert index._itemIndex["4c4f28287af27"] is not None - index.deleteHandle("4c4f28287af27") + assert index._itemIndex[bHandle] is not None + index.deleteHandle(bHandle) assert index._tagsIndex["Bod"] is None - assert index._itemIndex["4c4f28287af27"] is None + assert index._itemIndex[bHandle] is None # Clear the index index.clear() From 53355dbcc64480ca4280b7484a99b8cf32d42323 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 6 Jun 2025 19:16:59 +0200 Subject: [PATCH 3/3] Don't remove tags for the tag keyword (#2386) --- novelwriter/constants.py | 4 ++++ novelwriter/core/index.py | 10 ++++++---- novelwriter/gui/doceditor.py | 4 +--- tests/test_core/test_core_index.py | 11 ++++++----- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/novelwriter/constants.py b/novelwriter/constants.py index c20b9c47..4fde6f38 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -184,6 +184,10 @@ class nwKeyWords: POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, ] + CAN_LOOKUP: Final[list[str]] = [ + POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY, + OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY, + ] # Set of Valid Keys VALID_KEYS: Final[set[str]] = set(ALL_KEYS) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index ee108ad3..d8e9c8e4 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -757,10 +757,12 @@ class Index: """Return all tags used by a specific document.""" return self._itemIndex.allItemTags(tHandle) if tHandle else [] - def getClassTags(self, itemClass: nwItemClass | None) -> list[str]: - """Return all tags based on itemClass.""" - name = None if itemClass is None else itemClass.name - return self._tagsIndex.filterTagNames(name) + def getKeyWordTags(self, keyWord: str) -> list[str]: + """Return all tags usable for a specific keyword.""" + if keyWord in nwKeyWords.CAN_LOOKUP: + itemClass = nwKeyWords.KEY_CLASS.get(keyWord) + return self._tagsIndex.filterTagNames(itemClass.name if itemClass else None) + return [] def getTagsData( self, activeOnly: bool = True diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 8d1c42c1..846b6317 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2116,9 +2116,7 @@ class CommandCompleter(QMenu): length = len(lookup) suffix = "" options = sorted(filter( - lambda x: lookup in x.lower(), SHARED.project.index.getClassTags( - nwKeyWords.KEY_CLASS.get(kw.strip()) - ) + lambda x: lookup in x.lower(), SHARED.project.index.getKeyWordTags(kw.strip()) ))[:15] if not options: diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index bc9bef2c..dca4575a 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -789,11 +789,12 @@ def testCoreIndex_ExtractData(nwGUI, fncPath, mockRnd): assert index.getDocumentTags(cHandle) == ["jane"] assert index.getDocumentTags(None) == [] - # getClassTags - # ============ - assert index.getClassTags(None) == ["Jane", "John"] - assert index.getClassTags(nwItemClass.CHARACTER) == ["Jane", "John"] - assert index.getClassTags(nwItemClass.PLOT) == [] + # getKeyWordTags + # ============== + assert index.getKeyWordTags("@mention") == ["Jane", "John"] + assert index.getKeyWordTags("@char") == ["Jane", "John"] + assert index.getKeyWordTags("@plot") == [] + assert index.getKeyWordTags("@tag") == [] # getTagsData # ===========