Update auto-replace in editor, and update tests

This commit is contained in:
Veronica Berglyd Olsen
2024-11-01 21:17:46 +01:00
parent f355b82bba
commit 1ad6a80682
5 changed files with 139 additions and 105 deletions
+108 -90
View File
@@ -36,6 +36,7 @@ import logging
from enum import Enum
from time import time
from typing import NamedTuple
from PyQt5.QtCore import (
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
@@ -83,6 +84,21 @@ class _SelectAction(Enum):
MOVE_AFTER = 3
class AutoReplaceConfig(NamedTuple):
typPadChar: str
typSQuoteO: str
typSQuoteC: str
typDQuoteO: str
typDQuoteC: str
typRepDQuote: bool
typRepSQuote: bool
typRepDash: bool
typRepDots: bool
typPadBefore: str
typPadAfter: str
class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor"""
@@ -128,17 +144,19 @@ class GuiDocEditor(QPlainTextEdit):
self._doReplace = False # Switch to temporarily disable auto-replace
# Typography Cache
self._typPadChar = " "
self._typDQuoteO = '"'
self._typDQuoteC = '"'
self._typSQuoteO = "'"
self._typSQuoteC = "'"
self._typRepDQuote = False
self._typRepSQuote = False
self._typRepDash = False
self._typRepDots = False
self._typPadBefore = ""
self._typPadAfter = ""
self._typConf = AutoReplaceConfig(
typPadChar=" ",
typSQuoteO="'",
typSQuoteC="'",
typDQuoteO='"',
typDQuoteC='"',
typRepSQuote=False,
typRepDQuote=False,
typRepDash=False,
typRepDots=False,
typPadBefore="",
typPadAfter="",
)
# Completer
self._completer = MetaCompleter(self)
@@ -310,21 +328,19 @@ class GuiDocEditor(QPlainTextEdit):
created, and when the user changes the main editor preferences.
"""
# Typography
if CONFIG.fmtPadThin:
self._typPadChar = nwUnicode.U_THNBSP
else:
self._typPadChar = nwUnicode.U_NBSP
self._typSQuoteO = CONFIG.fmtSQuoteOpen
self._typSQuoteC = CONFIG.fmtSQuoteClose
self._typDQuoteO = CONFIG.fmtDQuoteOpen
self._typDQuoteC = CONFIG.fmtDQuoteClose
self._typRepDQuote = CONFIG.doReplaceDQuote
self._typRepSQuote = CONFIG.doReplaceSQuote
self._typRepDash = CONFIG.doReplaceDash
self._typRepDots = CONFIG.doReplaceDots
self._typPadBefore = CONFIG.fmtPadBefore
self._typPadAfter = CONFIG.fmtPadAfter
self._typConf = AutoReplaceConfig(
typPadChar=nwUnicode.U_THNBSP if CONFIG.fmtPadThin else nwUnicode.U_NBSP,
typSQuoteO=CONFIG.fmtSQuoteOpen,
typSQuoteC=CONFIG.fmtSQuoteClose,
typDQuoteO=CONFIG.fmtDQuoteOpen,
typDQuoteC=CONFIG.fmtDQuoteClose,
typRepSQuote=CONFIG.doReplaceSQuote,
typRepDQuote=CONFIG.doReplaceDQuote,
typRepDash=CONFIG.doReplaceDash,
typRepDots=CONFIG.doReplaceDots,
typPadBefore=CONFIG.fmtPadBefore,
typPadAfter=CONFIG.fmtPadAfter,
)
# Reload spell check and dictionaries
SHARED.updateSpellCheckLanguage()
@@ -737,6 +753,7 @@ class GuiDocEditor(QPlainTextEdit):
logger.debug("Requesting action: %s", action.name)
tConf = self._typConf
self._allowAutoReplace(False)
if action == nwDocAction.UNDO:
self.undo()
@@ -755,9 +772,9 @@ class GuiDocEditor(QPlainTextEdit):
elif action == nwDocAction.MD_STRIKE:
self._toggleFormat(2, "~")
elif action == nwDocAction.S_QUOTE:
self._wrapSelection(self._typSQuoteO, self._typSQuoteC)
self._wrapSelection(tConf.typSQuoteO, tConf.typSQuoteC)
elif action == nwDocAction.D_QUOTE:
self._wrapSelection(self._typDQuoteO, self._typDQuoteC)
self._wrapSelection(tConf.typDQuoteO, tConf.typDQuoteC)
elif action == nwDocAction.SEL_ALL:
self._makeSelection(QTextCursor.SelectionType.Document)
elif action == nwDocAction.SEL_PARA:
@@ -783,9 +800,9 @@ class GuiDocEditor(QPlainTextEdit):
elif action == nwDocAction.BLOCK_HSC:
self._formatBlock(nwDocAction.BLOCK_HSC)
elif action == nwDocAction.REPL_SNG:
self._replaceQuotes("'", self._typSQuoteO, self._typSQuoteC)
self._replaceQuotes("'", tConf.typSQuoteO, tConf.typSQuoteC)
elif action == nwDocAction.REPL_DBL:
self._replaceQuotes("\"", self._typDQuoteO, self._typDQuoteC)
self._replaceQuotes("\"", tConf.typDQuoteO, tConf.typDQuoteC)
elif action == nwDocAction.RM_BREAKS:
self._removeInParLineBreaks()
elif action == nwDocAction.ALIGN_L:
@@ -857,13 +874,13 @@ class GuiDocEditor(QPlainTextEdit):
text = insert
elif isinstance(insert, nwDocInsert):
if insert == nwDocInsert.QUOTE_LS:
text = self._typSQuoteO
text = self._typConf.typSQuoteO
elif insert == nwDocInsert.QUOTE_RS:
text = self._typSQuoteC
text = self._typConf.typSQuoteC
elif insert == nwDocInsert.QUOTE_LD:
text = self._typDQuoteO
text = self._typConf.typDQuoteO
elif insert == nwDocInsert.QUOTE_RD:
text = self._typDQuoteC
text = self._typConf.typDQuoteC
elif insert == nwDocInsert.SYNOPSIS:
text = "%Synopsis: "
block = True
@@ -1986,89 +2003,90 @@ class GuiDocEditor(QPlainTextEdit):
if not t1:
return
nDelete = 0
tInsert = t1
delete = 0
insert = t1
tConf = self._typConf
if self._typRepDQuote and t2[:1].isspace() and t2.endswith('"'):
nDelete = 1
tInsert = self._typDQuoteO
if tConf.typRepDQuote and t2[:1].isspace() and t2.endswith('"'):
delete = 1
insert = tConf.typDQuoteO
elif self._typRepDQuote and t1 == '"':
nDelete = 1
elif tConf.typRepDQuote and t1 == '"':
delete = 1
if tPos == 1:
tInsert = self._typDQuoteO
insert = tConf.typDQuoteO
elif tPos == 2 and t2 == '>"':
tInsert = self._typDQuoteO
insert = tConf.typDQuoteO
elif tPos == 3 and t3 == '>>"':
tInsert = self._typDQuoteO
insert = tConf.typDQuoteO
else:
tInsert = self._typDQuoteC
insert = tConf.typDQuoteC
elif self._typRepSQuote and t2[:1].isspace() and t2.endswith("'"):
nDelete = 1
tInsert = self._typSQuoteO
elif tConf.typRepSQuote and t2[:1].isspace() and t2.endswith("'"):
delete = 1
insert = tConf.typSQuoteO
elif self._typRepSQuote and t1 == "'":
nDelete = 1
elif tConf.typRepSQuote and t1 == "'":
delete = 1
if tPos == 1:
tInsert = self._typSQuoteO
insert = tConf.typSQuoteO
elif tPos == 2 and t2 == ">'":
tInsert = self._typSQuoteO
insert = tConf.typSQuoteO
elif tPos == 3 and t3 == ">>'":
tInsert = self._typSQuoteO
insert = tConf.typSQuoteO
else:
tInsert = self._typSQuoteC
insert = tConf.typSQuoteC
elif self._typRepDash and t4 == "----":
nDelete = 4
tInsert = nwUnicode.U_HBAR
elif tConf.typRepDash and t4 == "----":
delete = 4
insert = nwUnicode.U_HBAR
elif self._typRepDash and t3 == "---":
nDelete = 3
tInsert = nwUnicode.U_EMDASH
elif tConf.typRepDash and t3 == "---":
delete = 3
insert = nwUnicode.U_EMDASH
elif self._typRepDash and t2 == "--":
nDelete = 2
tInsert = nwUnicode.U_ENDASH
elif tConf.typRepDash and t2 == "--":
delete = 2
insert = nwUnicode.U_ENDASH
elif self._typRepDash and t2 == nwUnicode.U_ENDASH + "-":
nDelete = 2
tInsert = nwUnicode.U_EMDASH
elif tConf.typRepDash and t2 == nwUnicode.U_ENDASH + "-":
delete = 2
insert = nwUnicode.U_EMDASH
elif self._typRepDash and t2 == nwUnicode.U_EMDASH + "-":
nDelete = 2
tInsert = nwUnicode.U_HBAR
elif tConf.typRepDash and t2 == nwUnicode.U_EMDASH + "-":
delete = 2
insert = nwUnicode.U_HBAR
elif self._typRepDots and t3 == "...":
nDelete = 3
tInsert = nwUnicode.U_HELLIP
elif tConf.typRepDots and t3 == "...":
delete = 3
insert = nwUnicode.U_HELLIP
elif t1 == nwUnicode.U_LSEP:
# This resolves issue #1150
nDelete = 1
tInsert = nwUnicode.U_PSEP
delete = 1
insert = nwUnicode.U_PSEP
tCheck = tInsert
if self._typPadBefore and tCheck in self._typPadBefore:
if self._allowSpaceBeforeColon(text, tCheck):
nDelete = max(nDelete, 1)
chkPos = tPos - nDelete - 1
check = insert
if tConf.typPadBefore and check in tConf.typPadBefore:
if self._allowSpaceBeforeColon(text, check):
delete = max(delete, 1)
chkPos = tPos - delete - 1
if chkPos >= 0 and text[chkPos].isspace():
# Strip existing space before inserting a new (#1061)
nDelete += 1
tInsert = self._typPadChar + tInsert
delete += 1
insert = tConf.typPadChar + insert
if self._typPadAfter and tCheck in self._typPadAfter:
if self._allowSpaceBeforeColon(text, tCheck):
nDelete = max(nDelete, 1)
tInsert = tInsert + self._typPadChar
if tConf.typPadAfter and check in tConf.typPadAfter:
if self._allowSpaceBeforeColon(text, check):
delete = max(delete, 1)
insert = insert + tConf.typPadChar
if nDelete > 0:
cursor.movePosition(QtMoveLeft, QtKeepAnchor, nDelete)
cursor.insertText(tInsert)
if delete > 0:
cursor.movePosition(QtMoveLeft, QtKeepAnchor, delete)
cursor.insertText(insert)
# Re-highlight, since the auto-replace sometimes interferes with it
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
# Re-highlight, since the auto-replace sometimes interferes with it
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
return
@@ -1,8 +1,8 @@
%%~name: New Scene
%%~path: 000000000000d/000000000000f
%%~kind: NOVEL/DOCUMENT
%%~hash: 89b54ddaec2fddfd220e91dd438c84fb3aef9fc2
%%~date: 2024-10-30 00:07:21/2024-10-30 00:07:26
%%~hash: e4148ea77e78c90c334d5dc46c38a2b7904ac117
%%~date: 2024-11-01 21:15:57/2024-11-01 21:16:01
# Novel
## Chapter
@@ -26,7 +26,7 @@ This is a paragraph of nonsense text.
This is another paragraph
with a line separator in it.
This is another paragraph of much longer nonsense text. It is in fact 1 very very NONSENSICAL nonsense text! We can also try replacing “quotes”, even single quotes are replaced. Isnt that nice? We can hyphen-ate, make dashes and even longer dashes — if we want. Ellipsis? Not a problem either … How about three hyphens — for long dash? It works too.
This is another paragraph of much longer nonsense text. It is in fact 1 very very NONSENSICAL nonsense text! We can also try replacing “quotes”, even single quotes are replaced. Isnt that nice? We can hyphen-ate, make dashes and even longer dashes — if we want. We can even go on to a ― hotizontal bar. Ellipsis? Not a problem either … How about three hyphens — for long dash? It works too. Even four hyphens ― for a horizontal works!
“Full line double quoted text.”
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.6a3" hexVersion="0x020600a3" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-30 00:06:45">
<novelWriterXML appVersion="2.6a3" hexVersion="0x020600a3" fileVersion="1.5" fileRevision="4" timeStamp="2024-11-01 21:15:11">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="5">
<name>New Project</name>
<author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance>
</settings>
<content items="11" novelWords="161" notesWords="27">
<content items="11" novelWords="179" notesWords="27">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" />
<name status="s000000" import="i000004">Novel</name>
@@ -46,7 +46,7 @@
<name status="s000000" import="i000004" active="yes">New Chapter</name>
</item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H1" charCount="918" wordCount="154" paraCount="17" cursorPos="1174" />
<meta expanded="no" heading="H1" charCount="1003" wordCount="172" paraCount="17" cursorPos="1259" />
<name status="s000000" import="i000004" active="yes">New Scene</name>
</item>
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
+2 -2
View File
@@ -80,7 +80,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert qDoc.defaultTextOption().alignment() == QtAlignLeft
assert docEditor.verticalScrollBarPolicy() == QtScrollAsNeeded
assert docEditor.horizontalScrollBarPolicy() == QtScrollAsNeeded
assert docEditor._typPadChar == nwUnicode.U_NBSP
assert docEditor._typConf.typPadChar == nwUnicode.U_NBSP
assert docEditor.docHeader.itemTitle.text() == (
"Novel \u203a New Chapter \u203a New Scene"
)
@@ -105,7 +105,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert qDoc.defaultTextOption().flags() & QTextOption.ShowLineAndParagraphSeparators
assert docEditor.verticalScrollBarPolicy() == QtScrollAlwaysOff
assert docEditor.horizontalScrollBarPolicy() == QtScrollAlwaysOff
assert docEditor._typPadChar == nwUnicode.U_THNBSP
assert docEditor._typConf.typPadChar == nwUnicode.U_THNBSP
assert docEditor.docHeader.itemTitle.text() == "New Scene"
# Header
+23 -7
View File
@@ -421,6 +421,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "We can even go on to a ---- hotizontal bar. ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "Ellipsis? Not a problem either ... ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "How about three hyphens - -":
@@ -428,7 +430,17 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
qtbot.keyClick(docEditor, Qt.Key.Key_Left, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Backspace, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Right, delay=KEY_DELAY)
for c in "- for long dash? It works too.":
for c in "- for long dash? It works too. ":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
for c in "Even four hyphens - - -":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Left, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Backspace, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Left, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Backspace, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Right, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Right, delay=KEY_DELAY)
for c in "- for a horizontal works!":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
@@ -447,16 +459,18 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Insert spaces before and after quotes
docEditor._typPadBefore = "\u201d"
docEditor._typPadAfter = "\u201c"
CONFIG.fmtPadBefore = "\u201d"
CONFIG.fmtPadAfter = "\u201c"
docEditor.initEditor()
for c in "Some \"double quoted text with spaces padded\".":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
docEditor._typPadBefore = ""
docEditor._typPadAfter = ""
CONFIG.fmtPadBefore = ""
CONFIG.fmtPadAfter = ""
docEditor.initEditor()
# Dialogue Line
for c in "-- Hi, I am a character speaking.":
@@ -478,7 +492,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
# ==================
# Insert spaces before colon, but ignore tags
docEditor._typPadBefore = ":"
CONFIG.fmtPadBefore = ":"
docEditor.initEditor()
for c in "@object: NoSpaceAdded":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
@@ -505,7 +520,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
docEditor._typPadBefore = ""
CONFIG.fmtPadBefore = ""
docEditor.initEditor()
# Indent and Align
# ================