Add story notes and comment auto-complete (#2346)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"Footnotes": "Footnotes",
|
||||
"Comment": "Comment",
|
||||
"Story Structure": "Story Structure",
|
||||
"Note": "Note",
|
||||
"Notes": "Notes",
|
||||
"Tag": "Tag",
|
||||
"Point of View": "Point of View",
|
||||
|
||||
@@ -79,6 +79,7 @@ SETTINGS_TEMPLATE: dict[str, tuple[type, T_BuildValue]] = {
|
||||
"text.includeSynopsis": (bool, False),
|
||||
"text.includeComments": (bool, False),
|
||||
"text.includeStory": (bool, False),
|
||||
"text.includeNotes": (bool, False),
|
||||
"text.includeKeywords": (bool, False),
|
||||
"text.includeBodyText": (bool, True),
|
||||
"text.ignoredKeywords": (str, ""),
|
||||
@@ -146,6 +147,7 @@ SETTINGS_LABELS = {
|
||||
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
|
||||
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
|
||||
"text.includeStory": QT_TRANSLATE_NOOP("Builds", "Include Story Structure"),
|
||||
"text.includeNotes": QT_TRANSLATE_NOOP("Builds", "Include Manuscript Notes"),
|
||||
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
|
||||
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
|
||||
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
|
||||
|
||||
@@ -317,6 +317,7 @@ class NWBuildDocument:
|
||||
bldObj.setCommentType(nwComment.SYNOPSIS, self._build.getBool("text.includeSynopsis"))
|
||||
bldObj.setCommentType(nwComment.SHORT, self._build.getBool("text.includeSynopsis"))
|
||||
bldObj.setCommentType(nwComment.STORY, self._build.getBool("text.includeStory"))
|
||||
bldObj.setCommentType(nwComment.NOTE, self._build.getBool("text.includeNotes"))
|
||||
|
||||
if isinstance(bldObj, ToHtml):
|
||||
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
||||
|
||||
@@ -618,6 +618,10 @@ class Index:
|
||||
"""Return all story structure keys."""
|
||||
return self._itemIndex.allStoryKeys()
|
||||
|
||||
def getNoteKeys(self) -> set[str]:
|
||||
"""Return all note comment keys."""
|
||||
return self._itemIndex.allNoteKeys()
|
||||
|
||||
def novelStructure(
|
||||
self, rootHandle: str | None = None, activeOnly: bool = True
|
||||
) -> Iterable[tuple[str, str, str, IndexHeading]]:
|
||||
@@ -920,11 +924,12 @@ class IndexCache:
|
||||
which provides lookup capabilities and caching for shared data.
|
||||
"""
|
||||
|
||||
__slots__ = ("story", "tags")
|
||||
__slots__ = ("note", "story", "tags")
|
||||
|
||||
def __init__(self, tagsIndex: TagsIndex) -> None:
|
||||
self.tags: TagsIndex = tagsIndex
|
||||
self.story: set[str] = set()
|
||||
self.note: set[str] = set()
|
||||
return
|
||||
|
||||
|
||||
@@ -979,6 +984,10 @@ class ItemIndex:
|
||||
"""Return all story structure keys."""
|
||||
return self._cache.story.copy()
|
||||
|
||||
def allNoteKeys(self) -> set[str]:
|
||||
"""Return all note comment keys."""
|
||||
return self._cache.note.copy()
|
||||
|
||||
def allItemTags(self, tHandle: str) -> list[str]:
|
||||
"""Get all tags set for headings of an item."""
|
||||
if tHandle in self._items:
|
||||
|
||||
@@ -316,6 +316,9 @@ class IndexHeading:
|
||||
case "story" if key:
|
||||
self._cache.story.add(key)
|
||||
self._comments[f"story.{key}"] = str(text)
|
||||
case "note" if key:
|
||||
self._cache.note.add(key)
|
||||
self._comments[f"note.{key}"] = str(text)
|
||||
return
|
||||
|
||||
def setTag(self, tag: str) -> None:
|
||||
@@ -395,7 +398,7 @@ class IndexHeading:
|
||||
self.addReference(tag, keyword)
|
||||
else:
|
||||
raise ValueError("Heading reference contains an invalid keyword")
|
||||
elif key == "summary" or key.startswith("story"):
|
||||
elif key == "summary" or key.startswith(("story", "note")):
|
||||
comment, _, kind = str(key).partition(".")
|
||||
self.setComment(comment, compact(kind), str(entry))
|
||||
else:
|
||||
|
||||
@@ -614,7 +614,8 @@ class Tokenizer(ABC):
|
||||
tStyle |= BlockFmt.JUSTIFY
|
||||
|
||||
if cStyle in (
|
||||
nwComment.SYNOPSIS, nwComment.SHORT, nwComment.PLAIN, nwComment.STORY
|
||||
nwComment.SYNOPSIS, nwComment.SHORT, nwComment.PLAIN,
|
||||
nwComment.STORY, nwComment.NOTE,
|
||||
):
|
||||
bStyle = COMMENT_STYLE[cStyle]
|
||||
tLine, tFmt = self._formatComment(bStyle, cKey, cText)
|
||||
|
||||
@@ -3,15 +3,16 @@ novelWriter – GUI Document Editor
|
||||
=================================
|
||||
|
||||
File History:
|
||||
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
||||
Created: 2019-04-22 [0.0.1] BackgroundWordCounter
|
||||
Created: 2019-09-29 [0.2.1] GuiDocEditSearch
|
||||
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
||||
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
|
||||
Created: 2020-06-27 [0.10] GuiDocEditFooter
|
||||
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
|
||||
Created: 2023-11-06 [2.2b1] MetaCompleter
|
||||
Created: 2023-11-07 [2.2b1] GuiDocToolBar
|
||||
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
||||
Created: 2019-04-22 [0.0.1] BackgroundWordCounter
|
||||
Created: 2019-09-29 [0.2.1] GuiDocEditSearch
|
||||
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
||||
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
|
||||
Created: 2020-06-27 [0.10] GuiDocEditFooter
|
||||
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
|
||||
Created: 2023-11-06 [2.2b1] MetaCompleter
|
||||
Created: 2023-11-07 [2.2b1] GuiDocToolBar
|
||||
Extended: 2025-05-18 [2.7rc1] CommandCompleter
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
|
||||
@@ -149,7 +150,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._autoReplace = TextAutoReplace()
|
||||
|
||||
# Completer
|
||||
self._completer = MetaCompleter(self)
|
||||
self._completer = CommandCompleter(self)
|
||||
self._completer.complete.connect(self._insertCompletion)
|
||||
|
||||
# Create Custom Document
|
||||
@@ -1079,13 +1080,16 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
if (block := self._qDocument.findBlock(pos)).isValid():
|
||||
text = block.text()
|
||||
if text.startswith("@") and added + removed == 1:
|
||||
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
|
||||
cursor = self.textCursor()
|
||||
bPos = cursor.positionInBlock()
|
||||
if bPos > 0 and (viewport := self.viewport()):
|
||||
show = self._completer.updateText(text, bPos)
|
||||
if text[0] == "@":
|
||||
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)
|
||||
@@ -2073,13 +2077,13 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
return
|
||||
|
||||
|
||||
class MetaCompleter(QMenu):
|
||||
"""GuiWidget: Meta Completer Menu
|
||||
class CommandCompleter(QMenu):
|
||||
"""GuiWidget: Command Completer Menu
|
||||
|
||||
This is a context menu with options populated from the user's
|
||||
defined tags. It also helps to type the meta data keyword on a new
|
||||
line starting with an @. The updateText function should be called on
|
||||
every keystroke on a line starting with @.
|
||||
defined tags and keys. It also helps to type the meta data keyword
|
||||
on a new line starting with @ or %. The update functions should be
|
||||
called on every keystroke on a line starting with @ or %.
|
||||
"""
|
||||
|
||||
complete = pyqtSignal(int, int, str)
|
||||
@@ -2088,7 +2092,7 @@ class MetaCompleter(QMenu):
|
||||
super().__init__(parent=parent)
|
||||
return
|
||||
|
||||
def updateText(self, text: str, pos: int) -> bool:
|
||||
def updateMetaText(self, text: str, pos: int) -> bool:
|
||||
"""Update the menu options based on the line of text."""
|
||||
self.clear()
|
||||
kw, sep, _ = text.partition(":")
|
||||
@@ -2096,7 +2100,7 @@ class MetaCompleter(QMenu):
|
||||
offset = 0
|
||||
length = len(kw.rstrip())
|
||||
suffix = "" if sep else ":"
|
||||
options = list(filter(
|
||||
options = sorted(filter(
|
||||
lambda x: x.startswith(kw.rstrip()), nwKeyWords.VALID_KEYS
|
||||
))
|
||||
else:
|
||||
@@ -2108,7 +2112,7 @@ class MetaCompleter(QMenu):
|
||||
offset = tPos[index] if lookup else pos
|
||||
length = len(lookup)
|
||||
suffix = ""
|
||||
options = list(filter(
|
||||
options = sorted(filter(
|
||||
lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
|
||||
nwKeyWords.KEY_CLASS.get(kw.strip())
|
||||
)
|
||||
@@ -2117,13 +2121,57 @@ class MetaCompleter(QMenu):
|
||||
if not options:
|
||||
return False
|
||||
|
||||
for value in sorted(options):
|
||||
for value in options:
|
||||
rep = value + suffix
|
||||
action = qtAddAction(self, value)
|
||||
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
||||
|
||||
return True
|
||||
|
||||
def updateCommentText(self, text: str, pos: int) -> bool:
|
||||
"""Update the menu options based on the line of text."""
|
||||
self.clear()
|
||||
cmd, sep, _ = text.partition(":")
|
||||
if pos <= len(cmd):
|
||||
clean = text[1:].lstrip()[:6].lower()
|
||||
if clean[:6] == "story.":
|
||||
pre, _, key = cmd.partition(".")
|
||||
offset = len(pre) + 1
|
||||
length = len(key)
|
||||
suffix = "" if sep else ": "
|
||||
options = sorted(filter(
|
||||
lambda x: x.startswith(key.rstrip()),
|
||||
SHARED.project.index.getStoryKeys(),
|
||||
))
|
||||
elif clean[:5] == "note.":
|
||||
pre, _, key = cmd.partition(".")
|
||||
offset = len(pre) + 1
|
||||
length = len(key)
|
||||
suffix = "" if sep else ": "
|
||||
options = sorted(filter(
|
||||
lambda x: x.startswith(key.rstrip()),
|
||||
SHARED.project.index.getNoteKeys(),
|
||||
))
|
||||
elif pos < 12:
|
||||
offset = 0
|
||||
length = len(cmd.rstrip())
|
||||
suffix = ""
|
||||
options = list(filter(
|
||||
lambda x: x.startswith(cmd.rstrip()),
|
||||
["%Synopsis: ", "%Short: ", "%Story", "%Note"],
|
||||
))
|
||||
else:
|
||||
return False
|
||||
|
||||
if options:
|
||||
for value in options:
|
||||
rep = value + suffix
|
||||
action = qtAddAction(self, rep.rstrip(":. "))
|
||||
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
|
||||
@@ -751,9 +751,13 @@ class GuiOutlineTree(QTreeWidget):
|
||||
def _dumpNovelData(self, rootHandle: str | None) -> list[list[str | int]]:
|
||||
"""Dump all novel data into a table."""
|
||||
sLabel = SHARED.project.localLookup("Story Structure")
|
||||
nLabel = SHARED.project.localLookup("Note")
|
||||
sKeys = sorted(SHARED.project.index.getStoryKeys())
|
||||
nKeys = sorted(SHARED.project.index.getNoteKeys())
|
||||
sMatch = [f"story.{k}" for k in sKeys]
|
||||
nMatch = [f"note.{k}" for k in nKeys]
|
||||
sHeaders = [f"{sLabel} ({k})" for k in sKeys]
|
||||
nHeaders = [f"{nLabel} ({k})" for k in nKeys]
|
||||
|
||||
data: list[list[str | int]] = [[
|
||||
"H",
|
||||
@@ -777,6 +781,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
trConst(nwLabels.OUTLINE_COLS[nwOutline.MENTION]),
|
||||
trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]),
|
||||
*sHeaders,
|
||||
*nHeaders,
|
||||
]]
|
||||
|
||||
for _, tHandle, sTitle, novIdx in SHARED.project.index.novelStructure(
|
||||
@@ -786,6 +791,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
||||
comments = dict(novIdx.comments.items())
|
||||
story = [comments.get(k, "") for k in sMatch]
|
||||
notes = [comments.get(k, "") for k in nMatch]
|
||||
data.append([
|
||||
novIdx.level,
|
||||
novIdx.title,
|
||||
@@ -808,6 +814,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
", ".join(refs[nwKeyWords.MENTION_KEY]),
|
||||
novIdx.synopsis,
|
||||
*story,
|
||||
*notes,
|
||||
])
|
||||
|
||||
return data
|
||||
|
||||
@@ -630,7 +630,7 @@ class _DetailsWidget(QWidget):
|
||||
self.listView.addTopLevelItem(item)
|
||||
for key in [
|
||||
"text.includeSynopsis", "text.includeComments", "text.includeStory",
|
||||
"text.includeKeywords", "text.includeBodyText",
|
||||
"text.includeNotes", "text.includeKeywords", "text.includeBodyText",
|
||||
]:
|
||||
sub = QTreeWidgetItem()
|
||||
sub.setText(0, build.getLabel(key))
|
||||
|
||||
@@ -974,12 +974,14 @@ class _FormattingTab(NScrollableForm):
|
||||
self.incSynopsis = NSwitch(self, height=iPx)
|
||||
self.incComments = NSwitch(self, height=iPx)
|
||||
self.incStory = NSwitch(self, height=iPx)
|
||||
self.incNotes = NSwitch(self, height=iPx)
|
||||
self.incKeywords = NSwitch(self, height=iPx)
|
||||
|
||||
self.addRow(self._build.getLabel("text.includeBodyText"), self.incBodyText)
|
||||
self.addRow(self._build.getLabel("text.includeSynopsis"), self.incSynopsis)
|
||||
self.addRow(self._build.getLabel("text.includeComments"), self.incComments)
|
||||
self.addRow(self._build.getLabel("text.includeStory"), self.incStory)
|
||||
self.addRow(self._build.getLabel("text.includeNotes"), self.incNotes)
|
||||
self.addRow(self._build.getLabel("text.includeKeywords"), self.incKeywords)
|
||||
|
||||
# Ignored Keywords
|
||||
@@ -1288,6 +1290,7 @@ class _FormattingTab(NScrollableForm):
|
||||
self.incSynopsis.setChecked(self._build.getBool("text.includeSynopsis"))
|
||||
self.incComments.setChecked(self._build.getBool("text.includeComments"))
|
||||
self.incStory.setChecked(self._build.getBool("text.includeStory"))
|
||||
self.incNotes.setChecked(self._build.getBool("text.includeNotes"))
|
||||
self.incKeywords.setChecked(self._build.getBool("text.includeKeywords"))
|
||||
self.ignoredKeywords.setText(self._build.getStr("text.ignoredKeywords"))
|
||||
self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings"))
|
||||
@@ -1387,6 +1390,7 @@ class _FormattingTab(NScrollableForm):
|
||||
self._build.setValue("text.includeSynopsis", self.incSynopsis.isChecked())
|
||||
self._build.setValue("text.includeComments", self.incComments.isChecked())
|
||||
self._build.setValue("text.includeStory", self.incStory.isChecked())
|
||||
self._build.setValue("text.includeNotes", self.incNotes.isChecked())
|
||||
self._build.setValue("text.includeKeywords", self.incKeywords.isChecked())
|
||||
self._build.setValue("text.ignoredKeywords", self.ignoredKeywords.text())
|
||||
self._build.setValue("text.addNoteHeadings", self.addNoteHead.isChecked())
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
%%~name: Making a Scene
|
||||
%%~path: 6a2d6d5f4f401/636b6aa9b697b
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: c057a5e9309b0e764c367b0fe9ab0607e3308622
|
||||
%%~date: Unknown/2025-04-08 20:10:33
|
||||
%%~hash: 1f3d98a6a27b4f9a7f2239fcd78f9e2263cd3b97
|
||||
%%~date: Unknown/2025-05-18 22:36:59
|
||||
### Making a Scene
|
||||
|
||||
@pov: Jane
|
||||
@@ -11,6 +11,7 @@
|
||||
@mention: Space
|
||||
|
||||
%Story.Resolution: You can describe the scene structure with story comments.
|
||||
%Note.Consistency: You can also make notes about things like consistency of the story.
|
||||
|
||||
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:07:59">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2179" autoCount="285" editTime="96588">
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-05-18 22:37:05">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2189" autoCount="286" editTime="96864">
|
||||
<name>Sample Project</name>
|
||||
<author>Jane Smith</author>
|
||||
</project>
|
||||
@@ -58,7 +58,7 @@
|
||||
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
|
||||
</item>
|
||||
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="159" />
|
||||
<meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="718" />
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
|
||||
@@ -224,6 +224,14 @@ def testCoreIndexData_IndexHeading():
|
||||
"story.crisis": "It exploded!",
|
||||
}
|
||||
|
||||
# Set Note Comment
|
||||
head.setComment(nwComment.NOTE.name, "consitency", "Only explode once")
|
||||
assert head.comments == {
|
||||
"summary": "In the beginning ...",
|
||||
"story.crisis": "It exploded!",
|
||||
"note.consitency": "Only explode once",
|
||||
}
|
||||
|
||||
# Set Tag
|
||||
head.setTag("Stuff")
|
||||
assert head.tag == "stuff" # Case insensitive
|
||||
@@ -240,6 +248,7 @@ def testCoreIndexData_IndexHeading():
|
||||
"refs": {"stuff": "@object"},
|
||||
"summary": "In the beginning ...",
|
||||
"story.crisis": "It exploded!",
|
||||
"note.consitency": "Only explode once",
|
||||
}
|
||||
|
||||
# Unpack KeyError
|
||||
|
||||
@@ -1877,10 +1877,68 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd):
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Escape, delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
assert docEditor.getText() == (
|
||||
"### Scene One\n\n"
|
||||
"@char: Jane\n"
|
||||
"@focus: John"
|
||||
"@focus: John\n"
|
||||
)
|
||||
|
||||
# Send keypresses to the completer object for a comment
|
||||
qtbot.keyClick(docEditor, "%", delay=KEY_DELAY)
|
||||
assert len(completer.actions()) == 4
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
assert docEditor.getText() == (
|
||||
"### Scene One\n\n"
|
||||
"@char: Jane\n"
|
||||
"@focus: John\n"
|
||||
"%Synopsis: \n"
|
||||
)
|
||||
|
||||
# Auto-complete story comment
|
||||
SHARED.project.index._itemIndex._cache.story.add("Resolution")
|
||||
qtbot.keyClick(docEditor, "%", delay=KEY_DELAY)
|
||||
assert len(completer.actions()) == 4
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, ".", delay=KEY_DELAY)
|
||||
assert len(completer.actions()) == 1
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
assert docEditor.getText() == (
|
||||
"### Scene One\n\n"
|
||||
"@char: Jane\n"
|
||||
"@focus: John\n"
|
||||
"%Synopsis: \n"
|
||||
"%Story.Resolution: \n"
|
||||
)
|
||||
|
||||
# Auto-complete note comment
|
||||
SHARED.project.index._itemIndex._cache.note.add("Consistency")
|
||||
qtbot.keyClick(docEditor, "%", delay=KEY_DELAY)
|
||||
assert len(completer.actions()) == 4
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, ".", delay=KEY_DELAY)
|
||||
assert len(completer.actions()) == 1
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
assert docEditor.getText() == (
|
||||
"### Scene One\n\n"
|
||||
"@char: Jane\n"
|
||||
"@focus: John\n"
|
||||
"%Synopsis: \n"
|
||||
"%Story.Resolution: \n"
|
||||
"%Note.Consistency: \n"
|
||||
)
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
@@ -510,6 +510,8 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
||||
build.setValue("text.includeBodyText", False)
|
||||
build.setValue("text.includeSynopsis", False)
|
||||
build.setValue("text.includeComments", False)
|
||||
build.setValue("text.includeStory", False)
|
||||
build.setValue("text.includeNotes", False)
|
||||
build.setValue("text.includeKeywords", False)
|
||||
build.setValue("text.ignoredKeywords", "")
|
||||
|
||||
@@ -530,6 +532,8 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
||||
assert fmtTab.incBodyText.isChecked() is False
|
||||
assert fmtTab.incSynopsis.isChecked() is False
|
||||
assert fmtTab.incComments.isChecked() is False
|
||||
assert fmtTab.incStory.isChecked() is False
|
||||
assert fmtTab.incNotes.isChecked() is False
|
||||
assert fmtTab.incKeywords.isChecked() is False
|
||||
assert fmtTab.ignoredKeywords.text() == ""
|
||||
|
||||
@@ -539,6 +543,8 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
||||
fmtTab.incBodyText.setChecked(True)
|
||||
fmtTab.incSynopsis.setChecked(True)
|
||||
fmtTab.incComments.setChecked(True)
|
||||
fmtTab.incStory.setChecked(True)
|
||||
fmtTab.incNotes.setChecked(True)
|
||||
fmtTab.incKeywords.setChecked(True)
|
||||
|
||||
fmtTab.addNoteHead.setChecked(True)
|
||||
@@ -554,6 +560,8 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
||||
assert build.getBool("text.includeBodyText") is True
|
||||
assert build.getBool("text.includeSynopsis") is True
|
||||
assert build.getBool("text.includeComments") is True
|
||||
assert build.getBool("text.includeStory") is True
|
||||
assert build.getBool("text.includeNotes") is True
|
||||
assert build.getBool("text.includeKeywords") is True
|
||||
assert build.getStr("text.ignoredKeywords") in ("@custom, @object", "@object, @custom")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user