Add option to create note from tag, and improve auto-completer a bit

This commit is contained in:
Veronica Berglyd Olsen
2023-11-06 22:25:28 +01:00
parent 1ab35b5b63
commit f17e1f38de
4 changed files with 65 additions and 23 deletions
+9
View File
@@ -61,6 +61,15 @@ class nwItemLayout(Enum):
# END Enum nwItemLayout # END Enum nwItemLayout
class nwTrinary(Enum):
NEGATIVE = -1
UNKNOWN = 0
POSITIVE = 1
# END Enum nwTrinary
class nwDocMode(Enum): class nwDocMode(Enum):
VIEW = 0 VIEW = 0
+40 -22
View File
@@ -51,9 +51,10 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass from novelwriter.core.item import NWItem
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
from novelwriter.common import minmax, transferCase from novelwriter.common import minmax, transferCase
from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.constants import nwKeyWords, nwLabels, nwUnicode, trConst
from novelwriter.core.index import countWords from novelwriter.core.index import countWords
from novelwriter.core.document import NWDocument from novelwriter.core.document import NWDocument
from novelwriter.gui.dochighlight import GuiDocHighlighter from novelwriter.gui.dochighlight import GuiDocHighlighter
@@ -149,7 +150,7 @@ class GuiDocEditor(QPlainTextEdit):
self.keyContext = QShortcut(self) self.keyContext = QShortcut(self)
self.keyContext.setKey("Ctrl+.") self.keyContext.setKey("Ctrl+.")
self.keyContext.setContext(Qt.WidgetShortcut) self.keyContext.setContext(Qt.WidgetShortcut)
self.keyContext.activated.connect(self._openSpellContext) self.keyContext.activated.connect(self._openContextFromCursor)
self.followTag1 = QShortcut(self) self.followTag1 = QShortcut(self)
self.followTag1.setKey(Qt.Key_Return | Qt.ControlModifier) self.followTag1.setKey(Qt.Key_Return | Qt.ControlModifier)
@@ -974,10 +975,9 @@ class GuiDocEditor(QPlainTextEdit):
bPos = cursor.positionInBlock() bPos = cursor.positionInBlock()
if bPos > 0: if bPos > 0:
show = self._completer.updateText(text, bPos) show = self._completer.updateText(text, bPos)
if not self._completer.isVisible() and show: point = self.cursorRect().bottomRight()
point = self.cursorRect().bottomRight() self._completer.move(self.viewport().mapToGlobal(point))
self._completer.move(self.viewport().mapToGlobal(point)) self._completer.setVisible(show)
self._completer.show()
elif self._doReplace and added == 1: elif self._doReplace and added == 1:
self._docAutoReplace(text) self._docAutoReplace(text)
@@ -994,6 +994,7 @@ class GuiDocEditor(QPlainTextEdit):
cursor.setPosition(pos, QTextCursor.MoveMode.MoveAnchor) cursor.setPosition(pos, QTextCursor.MoveMode.MoveAnchor)
cursor.setPosition(pos + length, QTextCursor.MoveMode.KeepAnchor) cursor.setPosition(pos + length, QTextCursor.MoveMode.KeepAnchor)
cursor.insertText(text) cursor.insertText(text)
self._completer.hide()
return return
@pyqtSlot("QPoint") @pyqtSlot("QPoint")
@@ -1007,9 +1008,14 @@ class GuiDocEditor(QPlainTextEdit):
ctxMenu = QMenu(self) ctxMenu = QMenu(self)
# Follow # Follow
if self._followTag(cursor=pCursor, loadTag=False): status = self._followTag(cursor=pCursor, process=False)
if status == nwTrinary.POSITIVE:
aTag = ctxMenu.addAction(self.tr("Follow Tag")) aTag = ctxMenu.addAction(self.tr("Follow Tag"))
aTag.triggered.connect(lambda: self._followTag(cursor=pCursor)) aTag.triggered.connect(lambda: self._followTag(cursor=pCursor, process=True))
ctxMenu.addSeparator()
elif status == nwTrinary.NEGATIVE:
aTag = ctxMenu.addAction(self.tr("Create Note for Tag"))
aTag.triggered.connect(lambda: self._followTag(cursor=pCursor, process=True))
ctxMenu.addSeparator() ctxMenu.addSeparator()
# Cut, Copy and Paste # Cut, Copy and Paste
@@ -1690,7 +1696,7 @@ class GuiDocEditor(QPlainTextEdit):
# Internal Functions # Internal Functions
## ##
def _followTag(self, cursor: QTextCursor | None = None, loadTag: bool = True) -> bool: def _followTag(self, cursor: QTextCursor | None = None, process: bool = True) -> nwTrinary:
"""Activated by Ctrl+Enter. Checks that we're in a block """Activated by Ctrl+Enter. Checks that we're in a block
starting with '@'. We then find the tag under the cursor and 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 check that it is not the tag itself. If all this is fine, we
@@ -1702,41 +1708,53 @@ class GuiDocEditor(QPlainTextEdit):
block = cursor.block() block = cursor.block()
text = block.text() text = block.text()
if len(text) == 0: if len(text) == 0:
return False return nwTrinary.UNKNOWN
if text.startswith("@"): if text.startswith("@") and isinstance(self._nwItem, NWItem):
isGood, tBits, tPos = SHARED.project.index.scanThis(text) isGood, tBits, tPos = SHARED.project.index.scanThis(text)
if not isGood: if not isGood:
return False return nwTrinary.UNKNOWN
tag = "" tag = ""
exist = False
cPos = cursor.selectionStart() - block.position() cPos = cursor.selectionStart() - block.position()
for sTag, sPos in zip(reversed(tBits), reversed(tPos)): tExist = SHARED.project.index.checkThese(tBits, self._nwItem)
for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)):
if cPos >= sPos: if cPos >= sPos:
# The cursor is between the start of two tags # The cursor is between the start of two tags
if cPos <= sPos + len(sTag): if cPos <= sPos + len(sTag):
# The cursor is inside or at the edge of the tag # The cursor is inside or at the edge of the tag
tag = sTag tag = sTag
exist = sExist
break break
if not tag or tag.startswith("@"): if not tag or tag.startswith("@"):
# The keyword cannot be looked up, so we ignore that # The keyword cannot be looked up, so we ignore that
return False return nwTrinary.UNKNOWN
if loadTag: if process and exist:
logger.debug("Attempting to follow tag '%s'", tag) logger.debug("Attempting to follow tag '%s'", tag)
self.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW) self.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
else: elif process and not exist:
logger.debug("Potential tag '%s'", tag) if SHARED.question(self.tr(
"Do you want to create a new project note for the tag '{0}'?"
).format(tag)):
itemClass = nwKeyWords.KEY_CLASS.get(tBits[0], nwItemClass.NO_CLASS)
if SHARED.mainGui.projView.createNewNote(tag, itemClass):
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
else:
SHARED.error(self.tr(
"Could not create note in a root folder for '{0}'. "
"If one doesn't exist, you must create one first."
).format(trConst(nwLabels.CLASS_NAME[itemClass])))
return True return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE
return False return nwTrinary.UNKNOWN
def _openSpellContext(self) -> None: def _openContextFromCursor(self) -> None:
"""Open the spell check context menu at the cursor.""" """Open the spell check context menu at the cursor."""
self._openContextMenu(self.cursorRect().center()) self._openContextMenu(self.cursorRect().center())
return return
+15
View File
@@ -147,6 +147,7 @@ class GuiProjectView(QWidget):
self.getSelectedHandle = self.projTree.getSelectedHandle self.getSelectedHandle = self.projTree.getSelectedHandle
self.setSelectedHandle = self.projTree.setSelectedHandle self.setSelectedHandle = self.projTree.setSelectedHandle
self.changedSince = self.projTree.changedSince self.changedSince = self.projTree.changedSince
self.createNewNote = self.projTree.createNewNote
return return
@@ -570,6 +571,20 @@ class GuiProjectTree(QTreeWidget):
self._timeChanged = 0.0 self._timeChanged = 0.0
return return
def createNewNote(self, tag: str, itemClass: nwItemClass | None) -> bool:
"""Create a new note. This function is used by the document
editor to create note files for unknown tags.
"""
rHandle = SHARED.project.tree.findRoot(itemClass)
if rHandle:
tHandle = SHARED.project.newFile(tag, rHandle)
if tHandle:
text = f"# {tag}\n\n@tag: {tag}\n\n"
SHARED.project.writeNewFile(tHandle, 1, False, text)
self.revealNewTreeItem(tHandle, wordCount=True)
return True
return False
def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None, def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None,
hLevel: int = 1, isNote: bool = False) -> bool: hLevel: int = 1, isNote: bool = False) -> bool:
"""Add new item to the tree, with a given itemType (and """Add new item to the tree, with a given itemType (and
+1 -1
View File
@@ -530,7 +530,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(QMenu, "exec_", lambda *a: None) mp.setattr(QMenu, "exec_", lambda *a: None)
docEditor.setCursorPosition(errPos) docEditor.setCursorPosition(errPos)
docEditor._openSpellContext() docEditor._openContextFromCursor()
# Check Files # Check Files
# =========== # ===========