diff --git a/novelwriter/constants.py b/novelwriter/constants.py index ee2f3347..6ed952bf 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -173,6 +173,10 @@ class nwKeyWords: 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 = [ + 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) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 52e0180a..4e2c5029 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -34,7 +34,7 @@ from __future__ import annotations import bisect import logging -from enum import Enum +from enum import Enum, IntFlag from time import time from PyQt5.QtCore import ( @@ -57,7 +57,7 @@ from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.core.document import NWDocument from novelwriter.enum import ( nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, - nwItemType, nwTrinary + nwItemType ) from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter @@ -84,6 +84,15 @@ class _SelectAction(Enum): MOVE_AFTER = 3 +class _TagAction(IntFlag): + + NONE = 0b0000 + GOOD = 0b0001 + BAD = 0b0010 + FOLLOW = 0b0100 + CREATE = 0b1000 + + class GuiDocEditor(QPlainTextEdit): """Gui Widget: Main Document Editor""" @@ -1158,11 +1167,12 @@ class GuiDocEditor(QPlainTextEdit): # Follow status = self._processTag(cursor=pCursor, follow=False) - if status == nwTrinary.POSITIVE: + print(status) + if status & _TagAction.FOLLOW: action = ctxMenu.addAction(self.tr("Follow Tag")) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True)) ctxMenu.addSeparator() - elif status == nwTrinary.NEGATIVE: + elif status & _TagAction.CREATE: action = ctxMenu.addAction(self.tr("Create Note for Tag")) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True)) ctxMenu.addSeparator() @@ -1925,8 +1935,9 @@ class GuiDocEditor(QPlainTextEdit): self._qDocument.syntaxHighlighter.rehighlightBlock(block) return - def _processTag(self, cursor: QTextCursor | None = None, - follow: bool = True, create: bool = False) -> nwTrinary: + def _processTag( + self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False + ) -> _TagAction: """Activated by Ctrl+Enter. Checks that we're in a block starting with '@'. We then find the tag under the cursor and check that it is not the tag itself. If all this is fine, we @@ -1936,19 +1947,22 @@ class GuiDocEditor(QPlainTextEdit): if cursor is None: cursor = self.textCursor() + status = _TagAction.NONE block = cursor.block() text = block.text() if len(text) == 0: - return nwTrinary.NEUTRAL + return status if text.startswith("@") and self._docHandle: isGood, tBits, tPos = SHARED.project.index.scanThis(text) if ( - not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY - or tBits[0] not in nwKeyWords.VALID_KEYS + not isGood + or not tBits + or (key := tBits[0]) == nwKeyWords.TAG_KEY + or key not in nwKeyWords.VALID_KEYS ): - return nwTrinary.NEUTRAL + return status tag = "" exist = False @@ -1965,7 +1979,14 @@ class GuiDocEditor(QPlainTextEdit): if not tag or tag.startswith("@"): # The keyword cannot be looked up, so we ignore that - return nwTrinary.NEUTRAL + return status + + if not exist and key in nwKeyWords.CAN_CREATE: + # Must only be set if we have a tag selected + status |= _TagAction.CREATE + + if exist: + status |= _TagAction.FOLLOW if follow and exist: logger.debug("Attempting to follow tag '%s'", tag) @@ -1977,9 +1998,7 @@ class GuiDocEditor(QPlainTextEdit): itemClass = nwKeyWords.KEY_CLASS.get(tBits[0], nwItemClass.NO_CLASS) self.requestNewNoteCreation.emit(tag, itemClass) - return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE - - return nwTrinary.NEUTRAL + return status def _emitRenameItem(self, block: QTextBlock) -> None: """Emit a signal to request an item be renamed.""" diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index efd4345e..e71b93ab 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -35,8 +35,8 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import decodeMimeHandles from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwTrinary -from novelwriter.gui.doceditor import GuiDocEditor +from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout +from novelwriter.gui.doceditor import GuiDocEditor, _TagAction from novelwriter.text.counting import standardCounter from novelwriter.types import ( QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone, @@ -1693,21 +1693,22 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Empty Block docEditor.setCursorLine(2) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # Not On Tag docEditor.setCursorLine(1) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # On Tag Keyword docEditor.setCursorPosition(15) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # On Known Tag, No Follow docEditor.setCursorPosition(22) - assert docEditor._processTag(follow=False) is nwTrinary.POSITIVE + assert docEditor._processTag(follow=False) == _TagAction.FOLLOW assert nwGUI.docViewer._docHandle is None + # qtbot.stop() # On Known Tag, Follow docEditor.setCursorPosition(22) position = docEditor.cursorRect().center() @@ -1723,13 +1724,13 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd): # On Unknown Tag, Create It assert "0000000000011" not in SHARED.project.tree docEditor.setCursorPosition(28) - assert docEditor._processTag(create=True) is nwTrinary.NEGATIVE + assert docEditor._processTag(create=True) == _TagAction.CREATE assert "0000000000011" in SHARED.project.tree # On Unknown Tag, Missing Root assert "0000000000012" not in SHARED.project.tree docEditor.setCursorPosition(42) - assert docEditor._processTag(create=True) is nwTrinary.NEGATIVE + assert docEditor._processTag(create=True) == _TagAction.CREATE oHandle = SHARED.project.tree.findRoot(nwItemClass.OBJECT) assert oHandle == "0000000000012" @@ -1738,7 +1739,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert oItem.itemParent == "0000000000012" docEditor.setCursorPosition(47) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # qtbot.stop()