Implement alternative tag display name format
This commit is contained in:
+18
-14
@@ -420,8 +420,7 @@ class NWIndex:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if tBits[0] == nwKeyWords.TAG_KEY:
|
if tBits[0] == nwKeyWords.TAG_KEY:
|
||||||
tagKey = tBits[1]
|
tagKey, displayName = self.parseValue(tBits[1])
|
||||||
displayName = tBits[2] if len(tBits) > 2 else tagKey
|
|
||||||
self._tagsIndex.add(tagKey, displayName, tHandle, sTitle, itemClass.name)
|
self._tagsIndex.add(tagKey, displayName, tHandle, sTitle, itemClass.name)
|
||||||
self._itemIndex.setHeadingTag(tHandle, sTitle, tagKey)
|
self._itemIndex.setHeadingTag(tHandle, sTitle, tagKey)
|
||||||
tags[tagKey.lower()] = True
|
tags[tagKey.lower()] = True
|
||||||
@@ -472,10 +471,8 @@ class NWIndex:
|
|||||||
|
|
||||||
return True, tBits, tPos
|
return True, tBits, tPos
|
||||||
|
|
||||||
def checkThese(self, tBits: list[str], nwItem: NWItem) -> list[bool]:
|
def checkThese(self, tBits: list[str], tHandle: str) -> list[bool]:
|
||||||
"""Check the tags against the index to see if they are valid
|
"""Check tags against the index to see if they are valid."""
|
||||||
tags. This is needed for syntax highlighting.
|
|
||||||
"""
|
|
||||||
nBits = len(tBits)
|
nBits = len(tBits)
|
||||||
isGood = [False]*nBits
|
isGood = [False]*nBits
|
||||||
if nBits == 0:
|
if nBits == 0:
|
||||||
@@ -489,19 +486,25 @@ class NWIndex:
|
|||||||
# For a tag, only the first value is accepted, the rest are ignored
|
# For a tag, only the first value is accepted, the rest are ignored
|
||||||
if tBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
if tBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
||||||
if tBits[1] in self._tagsIndex:
|
if tBits[1] in self._tagsIndex:
|
||||||
isGood[1] = self._tagsIndex.tagHandle(tBits[1]) == nwItem.itemHandle
|
isGood[1] = self._tagsIndex.tagHandle(tBits[1]) == tHandle
|
||||||
else:
|
else:
|
||||||
isGood[1] = True
|
isGood[1] = True
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
# If we're still here, we check that the references exist
|
# If we're still here, we check that the references exist
|
||||||
|
# Class references cannot have the | symbol in them
|
||||||
refKey = nwKeyWords.KEY_CLASS[tBits[0]].name
|
refKey = nwKeyWords.KEY_CLASS[tBits[0]].name
|
||||||
for n in range(1, nBits):
|
for n in range(1, nBits):
|
||||||
if tBits[n] in self._tagsIndex:
|
if (aBit := tBits[n]) in self._tagsIndex:
|
||||||
isGood[n] = self._tagsIndex.tagClass(tBits[n]) == refKey
|
isGood[n] = self._tagsIndex.tagClass(aBit) == refKey and "|" not in aBit
|
||||||
|
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
|
def parseValue(self, text: str) -> tuple[str, str]:
|
||||||
|
"""Parse a single value into a name and display part."""
|
||||||
|
name, _, display = text.partition("|")
|
||||||
|
return name.rstrip(), display.lstrip()
|
||||||
|
|
||||||
##
|
##
|
||||||
# Extract Data
|
# Extract Data
|
||||||
##
|
##
|
||||||
@@ -725,11 +728,12 @@ class TagsIndex:
|
|||||||
"""Return a dictionary view of all tags."""
|
"""Return a dictionary view of all tags."""
|
||||||
return self._tags.items()
|
return self._tags.items()
|
||||||
|
|
||||||
def add(self, tagKey: str, display: str, tHandle: str, sTitle: str, className: str) -> None:
|
def add(self, tagKey: str, displayName: str, tHandle: str,
|
||||||
|
sTitle: str, className: str) -> None:
|
||||||
"""Add a key to the index and set all values."""
|
"""Add a key to the index and set all values."""
|
||||||
self._tags[tagKey.lower()] = {
|
self._tags[tagKey.lower()] = {
|
||||||
"name": tagKey,
|
"name": tagKey,
|
||||||
"display": display,
|
"display": displayName or tagKey,
|
||||||
"handle": tHandle,
|
"handle": tHandle,
|
||||||
"heading": sTitle,
|
"heading": sTitle,
|
||||||
"class": className,
|
"class": className,
|
||||||
@@ -786,9 +790,9 @@ class TagsIndex:
|
|||||||
|
|
||||||
name = entry.get("name")
|
name = entry.get("name")
|
||||||
display = entry.get("display")
|
display = entry.get("display")
|
||||||
handle = entry.get("handle", "")
|
handle = entry.get("handle")
|
||||||
heading = entry.get("heading", "")
|
heading = entry.get("heading")
|
||||||
className = entry.get("class", "")
|
className = entry.get("class")
|
||||||
|
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise ValueError("tagsIndex name is not a string")
|
raise ValueError("tagsIndex name is not a string")
|
||||||
|
|||||||
@@ -484,7 +484,10 @@ class ToHtml(Tokenizer):
|
|||||||
result = f"<span class='tags'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
|
result = f"<span class='tags'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
|
||||||
if len(bits) > 1:
|
if len(bits) > 1:
|
||||||
if bits[0] == nwKeyWords.TAG_KEY:
|
if bits[0] == nwKeyWords.TAG_KEY:
|
||||||
result += f"<a name='tag_{bits[1]}'>{bits[1]}</a>"
|
one, two = self._project.index.parseValue(bits[1])
|
||||||
|
result += f"<a name='tag_{one}'>{one}</a>"
|
||||||
|
if two:
|
||||||
|
result += f" | <span class='optional'>{two}</a>"
|
||||||
else:
|
else:
|
||||||
if self._genMode == self.M_PREVIEW:
|
if self._genMode == self.M_PREVIEW:
|
||||||
result += ", ".join(f"<a href='#{bits[0][1:]}={t}'>{t}</a>" for t in bits[1:])
|
result += ", ".join(f"<a href='#{bits[0][1:]}={t}'>{t}</a>" for t in bits[1:])
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ from novelwriter import CONFIG, SHARED
|
|||||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
|
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, nwLabels, nwShortcode, nwUnicode, trConst
|
from novelwriter.constants import nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst
|
||||||
from novelwriter.core.item import NWItem
|
|
||||||
from novelwriter.core.index import countWords
|
from novelwriter.core.index import countWords
|
||||||
from novelwriter.tools.lipsum import GuiLipsum
|
from novelwriter.tools.lipsum import GuiLipsum
|
||||||
from novelwriter.core.document import NWDocument
|
from novelwriter.core.document import NWDocument
|
||||||
@@ -1835,16 +1834,16 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
if len(text) == 0:
|
if len(text) == 0:
|
||||||
return nwTrinary.NEUTRAL
|
return nwTrinary.NEUTRAL
|
||||||
|
|
||||||
if text.startswith("@") and isinstance(self._nwItem, NWItem):
|
if text.startswith("@") and self._docHandle:
|
||||||
|
|
||||||
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
||||||
if not isGood:
|
if not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY:
|
||||||
return nwTrinary.NEUTRAL
|
return nwTrinary.NEUTRAL
|
||||||
|
|
||||||
tag = ""
|
tag = ""
|
||||||
exist = False
|
exist = False
|
||||||
cPos = cursor.selectionStart() - block.position()
|
cPos = cursor.selectionStart() - block.position()
|
||||||
tExist = SHARED.project.index.checkThese(tBits, self._nwItem)
|
tExist = SHARED.project.index.checkThese(tBits, self._docHandle)
|
||||||
for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)):
|
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
|
||||||
|
|||||||
@@ -279,23 +279,23 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
if text.startswith("@"): # Keywords and commands
|
if text.startswith("@"): # Keywords and commands
|
||||||
self.setCurrentBlockState(self.BLOCK_META)
|
self.setCurrentBlockState(self.BLOCK_META)
|
||||||
if self._tItem:
|
index = SHARED.project.index
|
||||||
pIndex = SHARED.project.index
|
isValid, bits, pos = index.scanThis(text)
|
||||||
isValid, bits, pos = pIndex.scanThis(text)
|
isGood = index.checkThese(bits, self._tHandle)
|
||||||
isGood = pIndex.checkThese(bits, self._tItem)
|
if isValid:
|
||||||
if isValid:
|
for n, bit in enumerate(bits):
|
||||||
for n, bit in enumerate(bits):
|
xPos = pos[n]
|
||||||
xPos = pos[n]
|
xLen = len(bit)
|
||||||
xLen = len(bit)
|
if not isGood[n]:
|
||||||
if isGood[n]:
|
self.setFormat(xPos, xLen, self._hStyles["codeinval"])
|
||||||
if n == 0:
|
elif n == 0:
|
||||||
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
||||||
else:
|
else:
|
||||||
self.setFormat(xPos, xLen, self._hStyles["value"])
|
one, two = index.parseValue(bit)
|
||||||
else:
|
self.setFormat(xPos, len(one), self._hStyles["value"])
|
||||||
kwFmt = self.format(xPos)
|
if two:
|
||||||
kwFmt.merge(self._hStyles["codeinval"])
|
yPos = xPos + len(bit) - len(two)
|
||||||
self.setFormat(xPos, xLen, kwFmt)
|
self.setFormat(yPos, len(two), self._hStyles["optional"])
|
||||||
|
|
||||||
# We never want to run the spell checker on keyword/values,
|
# We never want to run the spell checker on keyword/values,
|
||||||
# so we force a return here
|
# so we force a return here
|
||||||
|
|||||||
@@ -461,15 +461,16 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
|
|
||||||
def _makeStyleSheet(self) -> None:
|
def _makeStyleSheet(self) -> None:
|
||||||
"""Generate an appropriate style sheet for the document viewer,
|
"""Generate an appropriate style sheet for the document viewer,
|
||||||
based on the current syntax highlighter theme,
|
based on the current syntax highlighter theme.
|
||||||
"""
|
"""
|
||||||
colText = SHARED.theme.colText
|
colText = SHARED.theme.colText
|
||||||
colHead = SHARED.theme.colHead
|
colHead = SHARED.theme.colHead
|
||||||
colVal = SHARED.theme.colVal
|
colVals = SHARED.theme.colVal
|
||||||
colEmph = SHARED.theme.colEmph
|
colEmph = SHARED.theme.colEmph
|
||||||
colKey = SHARED.theme.colKey
|
colKeys = SHARED.theme.colKey
|
||||||
colHidden = SHARED.theme.colHidden
|
colHide = SHARED.theme.colHidden
|
||||||
colMod = SHARED.theme.colMod
|
colMods = SHARED.theme.colMod
|
||||||
|
colOpts = SHARED.theme.colOpt
|
||||||
styleSheet = (
|
styleSheet = (
|
||||||
"body {{"
|
"body {{"
|
||||||
" color: rgb({tColR}, {tColG}, {tColB});"
|
" color: rgb({tColR}, {tColG}, {tColB});"
|
||||||
@@ -486,6 +487,9 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
".tags {{"
|
".tags {{"
|
||||||
" color: rgb({kColR}, {kColG}, {kColB});"
|
" color: rgb({kColR}, {kColG}, {kColB});"
|
||||||
"}}\n"
|
"}}\n"
|
||||||
|
".optional {{"
|
||||||
|
" color: rgb({oColR}, {oColG}, {oColB});"
|
||||||
|
"}}\n"
|
||||||
".comment {{"
|
".comment {{"
|
||||||
" color: rgb({cColR}, {cColG}, {cColB});"
|
" color: rgb({cColR}, {cColG}, {cColB});"
|
||||||
"}}\n"
|
"}}\n"
|
||||||
@@ -496,27 +500,14 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
" text-align: center;"
|
" text-align: center;"
|
||||||
"}}\n"
|
"}}\n"
|
||||||
).format(
|
).format(
|
||||||
tColR=colText.red(),
|
tColR=colText.red(), tColG=colText.green(), tColB=colText.blue(),
|
||||||
tColG=colText.green(),
|
hColR=colHead.red(), hColG=colHead.green(), hColB=colHead.blue(),
|
||||||
tColB=colText.blue(),
|
aColR=colVals.red(), aColG=colVals.green(), aColB=colVals.blue(),
|
||||||
hColR=colHead.red(),
|
eColR=colEmph.red(), eColG=colEmph.green(), eColB=colEmph.blue(),
|
||||||
hColG=colHead.green(),
|
kColR=colKeys.red(), kColG=colKeys.green(), kColB=colKeys.blue(),
|
||||||
hColB=colHead.blue(),
|
cColR=colHide.red(), cColG=colHide.green(), cColB=colHide.blue(),
|
||||||
aColR=colVal.red(),
|
mColR=colMods.red(), mColG=colMods.green(), mColB=colMods.blue(),
|
||||||
aColG=colVal.green(),
|
oColR=colOpts.red(), oColG=colOpts.green(), oColB=colOpts.blue(),
|
||||||
aColB=colVal.blue(),
|
|
||||||
eColR=colEmph.red(),
|
|
||||||
eColG=colEmph.green(),
|
|
||||||
eColB=colEmph.blue(),
|
|
||||||
kColR=colKey.red(),
|
|
||||||
kColG=colKey.green(),
|
|
||||||
kColB=colKey.blue(),
|
|
||||||
cColR=colHidden.red(),
|
|
||||||
cColG=colHidden.green(),
|
|
||||||
cColB=colHidden.blue(),
|
|
||||||
mColR=colMod.red(),
|
|
||||||
mColG=colMod.green(),
|
|
||||||
mColB=colMod.blue(),
|
|
||||||
)
|
)
|
||||||
self.document().setDefaultStyleSheet(styleSheet)
|
self.document().setDefaultStyleSheet(styleSheet)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
%%~name: John Smith
|
%%~name: John Smith
|
||||||
%%~path: f7e2d9f330615/14298de4d9524
|
%%~path: f7e2d9f330615/14298de4d9524
|
||||||
%%~kind: CHARACTER/NOTE
|
%%~kind: CHARACTER/NOTE
|
||||||
%%~hash: 0f40182de0bb7935bb40fc4c7fd0d75d55421299
|
%%~hash: f85b4da6ba4a557db0c4d5f1b59f33ba01542606
|
||||||
%%~date: Unknown/2024-01-29 12:19:42
|
%%~date: Unknown/2024-01-30 10:58:18
|
||||||
# John Smith
|
# John Smith
|
||||||
|
|
||||||
@tag: John, John Smith
|
@tag: John | John Smith
|
||||||
|
|
||||||
% Short: The sidekick
|
% Short: The sidekick
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
%%~name: Jane Smith
|
%%~name: Jane Smith
|
||||||
%%~path: f7e2d9f330615/bb2c23b3c42cc
|
%%~path: f7e2d9f330615/bb2c23b3c42cc
|
||||||
%%~kind: CHARACTER/NOTE
|
%%~kind: CHARACTER/NOTE
|
||||||
%%~hash: 0faba71c86841552090a9e82dd9b7acd5b0bf56f
|
%%~hash: 10f072a4bf7389b0c178a4181e50833d39b945af
|
||||||
%%~date: Unknown/2024-01-29 12:31:34
|
%%~date: Unknown/2024-01-30 11:05:37
|
||||||
# Jane Smith
|
# Jane Smith
|
||||||
|
|
||||||
@tag: Jane, Jane Smith
|
@tag: Jane | Jane Smith
|
||||||
|
|
||||||
% Short: The heroine
|
% Short: The heroine
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user