Fix spell checking when 4 byte are in use
This commit is contained in:
@@ -1202,13 +1202,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
|
|
||||||
# Spell Checking
|
# Spell Checking
|
||||||
if SHARED.project.data.spellCheck:
|
if SHARED.project.data.spellCheck:
|
||||||
word, cPos, cLen, suggest = self._qDocument.spellErrorAtPos(pCursor.position())
|
word, offset, suggest = self._qDocument.spellErrorAtPos(pCursor.position())
|
||||||
if word and cPos >= 0 and cLen > 0:
|
if word and offset >= 0:
|
||||||
logger.debug("Word '%s' is misspelled", word)
|
logger.debug("Word '%s' is misspelled", word)
|
||||||
block = pCursor.block()
|
block = pCursor.block()
|
||||||
sCursor = self.textCursor()
|
sCursor = self.textCursor()
|
||||||
sCursor.setPosition(block.position() + cPos)
|
sCursor.setPosition(block.position() + offset)
|
||||||
sCursor.movePosition(QtMoveRight, QtKeepAnchor, cLen)
|
sCursor.movePosition(QtMoveRight, QtKeepAnchor, len(word))
|
||||||
if suggest:
|
if suggest:
|
||||||
ctxMenu.addSeparator()
|
ctxMenu.addSeparator()
|
||||||
qtAddAction(ctxMenu, self.tr("Spelling Suggestion(s)"))
|
qtAddAction(ctxMenu, self.tr("Spelling Suggestion(s)"))
|
||||||
|
|||||||
@@ -323,24 +323,23 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
if text.startswith("@"): # Keywords and commands
|
if text.startswith("@"): # Keywords and commands
|
||||||
self.setCurrentBlockState(BLOCK_META)
|
self.setCurrentBlockState(BLOCK_META)
|
||||||
index = SHARED.project.index
|
index = SHARED.project.index
|
||||||
isValid, bits, pos = index.scanThis(text)
|
isValid, bits, loc = index.scanThis(text)
|
||||||
isGood = index.checkThese(bits, self._tHandle)
|
isGood = index.checkThese(bits, self._tHandle)
|
||||||
if isValid:
|
if isValid:
|
||||||
for n, bit in enumerate(bits):
|
for n, bit in enumerate(bits):
|
||||||
xPos = utf16Map[pos[n]] if utf16Map else pos[n]
|
pos = utf16Map[loc[n]] if utf16Map else loc[n]
|
||||||
xLen = utf16Map[pos[n] + len(bit)] - xPos if utf16Map else len(bit)
|
length = utf16Map[loc[n] + len(bit)] - pos if utf16Map else len(bit)
|
||||||
if n == 0 and isGood[n]:
|
if n == 0 and isGood[n]:
|
||||||
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
self.setFormat(pos, length, self._hStyles["keyword"])
|
||||||
elif isGood[n] and not self._isInactive:
|
elif isGood[n] and not self._isInactive:
|
||||||
a, b = index.parseValue(bit)
|
a, b = index.parseValue(bit)
|
||||||
aLen = utf16Map[pos[n] + len(a)] - xPos if utf16Map else len(a)
|
aLen = utf16Map[loc[n] + len(a)] - pos if utf16Map else len(a)
|
||||||
self.setFormat(xPos, aLen, self._hStyles["tag"])
|
self.setFormat(pos, aLen, self._hStyles["tag"])
|
||||||
if b:
|
if b:
|
||||||
blockLen = utf16Map[pos[n] + len(b)] - xPos if utf16Map else len(b)
|
bLen = utf16Map[loc[n] + len(b)] - pos if utf16Map else len(b)
|
||||||
bPos = xPos + xLen - blockLen
|
self.setFormat(pos + length - bLen, bLen, self._hStyles["optional"])
|
||||||
self.setFormat(bPos, blockLen, self._hStyles["optional"])
|
|
||||||
elif not self._isInactive:
|
elif not self._isInactive:
|
||||||
self.setFormat(xPos, xLen, self._hStyles["invalid"])
|
self.setFormat(pos, length, self._hStyles["invalid"])
|
||||||
|
|
||||||
# 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
|
||||||
@@ -386,19 +385,19 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
if utf16Map:
|
if utf16Map:
|
||||||
dot = utf16Map[dot]
|
dot = utf16Map[dot]
|
||||||
pos = utf16Map[pos]
|
pos = utf16Map[pos]
|
||||||
cLen = blockLen - pos
|
length = blockLen - pos
|
||||||
if style == nwComment.PLAIN:
|
if style == nwComment.PLAIN:
|
||||||
self.setFormat(0, cLen, self._hStyles["hidden"])
|
self.setFormat(0, length, self._hStyles["hidden"])
|
||||||
elif style == nwComment.IGNORE:
|
elif style == nwComment.IGNORE:
|
||||||
self.setFormat(0, cLen, self._hStyles["strike"])
|
self.setFormat(0, length, self._hStyles["strike"])
|
||||||
return # No more processing for these
|
return # No more processing for these
|
||||||
elif mod:
|
elif mod:
|
||||||
self.setFormat(0, dot, self._hStyles["modifier"])
|
self.setFormat(0, dot, self._hStyles["modifier"])
|
||||||
self.setFormat(dot, pos - dot, self._hStyles["value"])
|
self.setFormat(dot, pos - dot, self._hStyles["value"])
|
||||||
self.setFormat(pos, cLen, self._hStyles["note"])
|
self.setFormat(pos, length, self._hStyles["note"])
|
||||||
else:
|
else:
|
||||||
self.setFormat(0, pos, self._hStyles["modifier"])
|
self.setFormat(0, pos, self._hStyles["modifier"])
|
||||||
self.setFormat(pos, cLen, self._hStyles["note"])
|
self.setFormat(pos, length, self._hStyles["note"])
|
||||||
|
|
||||||
elif text.startswith("["): # Special Command
|
elif text.startswith("["): # Special Command
|
||||||
self.setCurrentBlockState(BLOCK_TEXT)
|
self.setCurrentBlockState(BLOCK_TEXT)
|
||||||
@@ -409,12 +408,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.setFormat(0, blockLen, self._hStyles["code"])
|
self.setFormat(0, blockLen, self._hStyles["code"])
|
||||||
return
|
return
|
||||||
elif check.startswith("[vspace:") and check.endswith("]"):
|
elif check.startswith("[vspace:") and check.endswith("]"):
|
||||||
tLen = len(check)
|
length = len(check)
|
||||||
tVal = checkInt(check[8:-1], 0)
|
value = checkInt(check[8:-1], 0)
|
||||||
cVal = "value" if tVal > 0 else "invalid"
|
style = "value" if value > 0 else "invalid"
|
||||||
self.setFormat(0, 8, self._hStyles["code"])
|
self.setFormat(0, 8, self._hStyles["code"])
|
||||||
self.setFormat(8, tLen-9, self._hStyles[cVal])
|
self.setFormat(8, length-9, self._hStyles[style])
|
||||||
self.setFormat(tLen-1, tLen, self._hStyles["code"])
|
self.setFormat(length-1, length, self._hStyles["code"])
|
||||||
return
|
return
|
||||||
|
|
||||||
else: # Text Paragraph
|
else: # Text Paragraph
|
||||||
@@ -462,19 +461,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
data.processText(text, offset)
|
data.processText(text, offset)
|
||||||
if self._spellCheck:
|
if self._spellCheck:
|
||||||
if utf16Map:
|
for pos, end, _ in data.spellCheck(utf16Map):
|
||||||
for pos, end in data.spellCheck():
|
for x in range(pos, end):
|
||||||
for x in range(pos, end):
|
cFmt = self.format(x)
|
||||||
m = utf16Map[x]
|
cFmt.merge(self._spellErr)
|
||||||
cFmt = self.format(m)
|
self.setFormat(x, 1, cFmt)
|
||||||
cFmt.merge(self._spellErr)
|
|
||||||
self.setFormat(m, utf16Map[x+1] - m, cFmt)
|
|
||||||
else:
|
|
||||||
for pos, end in data.spellCheck():
|
|
||||||
for x in range(pos, end):
|
|
||||||
cFmt = self.format(x)
|
|
||||||
cFmt.merge(self._spellErr)
|
|
||||||
self.setFormat(x, 1, cFmt)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -528,7 +519,7 @@ class TextBlockData(QTextBlockUserData):
|
|||||||
self._text = ""
|
self._text = ""
|
||||||
self._offset = 0
|
self._offset = 0
|
||||||
self._metaData: list[tuple[int, int, str, str]] = []
|
self._metaData: list[tuple[int, int, str, str]] = []
|
||||||
self._spellErrors: list[tuple[int, int]] = []
|
self._spellErrors: list[tuple[int, int, str]] = []
|
||||||
return
|
return
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -537,7 +528,7 @@ class TextBlockData(QTextBlockUserData):
|
|||||||
return self._metaData
|
return self._metaData
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spellErrors(self) -> list[tuple[int, int]]:
|
def spellErrors(self) -> list[tuple[int, int, str]]:
|
||||||
"""Return spell error data from last check."""
|
"""Return spell error data from last check."""
|
||||||
return self._spellErrors
|
return self._spellErrors
|
||||||
|
|
||||||
@@ -565,13 +556,21 @@ class TextBlockData(QTextBlockUserData):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def spellCheck(self) -> list[tuple[int, int]]:
|
def spellCheck(self, utf16Map: list[int] | None) -> list[tuple[int, int, str]]:
|
||||||
"""Run the spell checker and cache the result, and return the
|
"""Run the spell checker and cache the result, and return the
|
||||||
list of spell check errors.
|
list of spell check errors.
|
||||||
"""
|
"""
|
||||||
spell = SHARED.spelling
|
spell = SHARED.spelling
|
||||||
self._spellErrors = [
|
if utf16Map:
|
||||||
(r.start(0), r.end(0)) for r in RX_WORDS.finditer(self._text, self._offset)
|
self._spellErrors = [
|
||||||
if (w := r.group(0)) and not (w.isnumeric() or w.isupper() or spell.checkWord(w))
|
(utf16Map[r.start(0)], utf16Map[r.end(0)], w)
|
||||||
]
|
for r in RX_WORDS.finditer(self._text, self._offset)
|
||||||
|
if (w := r.group(0)) and not (w.isnumeric() or w.isupper() or spell.checkWord(w))
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self._spellErrors = [
|
||||||
|
(r.start(0), r.end(0), w)
|
||||||
|
for r in RX_WORDS.finditer(self._text, self._offset)
|
||||||
|
if (w := r.group(0)) and not (w.isnumeric() or w.isupper() or spell.checkWord(w))
|
||||||
|
]
|
||||||
return self._spellErrors
|
return self._spellErrors
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class GuiTextDocument(QTextDocument):
|
|||||||
return cData, cType
|
return cData, cType
|
||||||
return "", ""
|
return "", ""
|
||||||
|
|
||||||
def spellErrorAtPos(self, pos: int) -> tuple[str, int, int, list[str]]:
|
def spellErrorAtPos(self, pos: int) -> tuple[str, int, list[str]]:
|
||||||
"""Check if there is a misspelled word at a given position in
|
"""Check if there is a misspelled word at a given position in
|
||||||
the document, and if so, return it.
|
the document, and if so, return it.
|
||||||
"""
|
"""
|
||||||
@@ -122,15 +122,11 @@ class GuiTextDocument(QTextDocument):
|
|||||||
block = cursor.block()
|
block = cursor.block()
|
||||||
data = block.userData()
|
data = block.userData()
|
||||||
if block.isValid() and isinstance(data, TextBlockData):
|
if block.isValid() and isinstance(data, TextBlockData):
|
||||||
text = block.text()
|
if (check := pos - block.position()) >= 0:
|
||||||
check = pos - block.position()
|
for start, end, word in data.spellErrors:
|
||||||
if check >= 0:
|
if start <= check <= end:
|
||||||
for cPos, cEnd in data.spellErrors:
|
return word, start, SHARED.spelling.suggestWords(word)
|
||||||
cLen = cEnd - cPos
|
return "", -1, []
|
||||||
if cPos <= check <= cEnd:
|
|
||||||
word = text[cPos:cEnd]
|
|
||||||
return word, cPos, cLen, SHARED.spelling.suggestWords(word)
|
|
||||||
return "", -1, -1, []
|
|
||||||
|
|
||||||
def iterBlockByType(self, cType: int, maxCount: int = 1000) -> Iterable[QTextBlock]:
|
def iterBlockByType(self, cType: int, maxCount: int = 1000) -> Iterable[QTextBlock]:
|
||||||
"""Iterate over all text blocks of a given type."""
|
"""Iterate over all text blocks of a given type."""
|
||||||
|
|||||||
@@ -520,10 +520,10 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText,
|
|||||||
data = cursor.block().userData()
|
data = cursor.block().userData()
|
||||||
assert cursor.block().text().startswith("Lorem")
|
assert cursor.block().text().startswith("Lorem")
|
||||||
assert isinstance(data, TextBlockData)
|
assert isinstance(data, TextBlockData)
|
||||||
data._spellErrors = [(0, 5)]
|
data._spellErrors = [(0, 5, "Lorem")]
|
||||||
|
|
||||||
# No known position
|
# No known position
|
||||||
assert docEditor._qDocument.spellErrorAtPos(-1) == ("", -1, -1, [])
|
assert docEditor._qDocument.spellErrorAtPos(-1) == ("", -1, [])
|
||||||
|
|
||||||
# With Suggestion
|
# With Suggestion
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
@@ -540,6 +540,9 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText,
|
|||||||
ctxMenu.setObjectName("")
|
ctxMenu.setObjectName("")
|
||||||
ctxMenu.deleteLater()
|
ctxMenu.deleteLater()
|
||||||
|
|
||||||
|
# Update Entry
|
||||||
|
data._spellErrors = [(0, 5, "Lorax")]
|
||||||
|
|
||||||
# Without Suggestion
|
# Without Suggestion
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(SHARED.spelling, "suggestWords", lambda *a: [])
|
mp.setattr(SHARED.spelling, "suggestWords", lambda *a: [])
|
||||||
|
|||||||
Reference in New Issue
Block a user