|
27 | 27 | from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QThreadPool, QUrl |
28 | 28 | from PyQt6.QtGui import ( |
29 | 29 | QAction, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent, |
30 | | - QDropEvent, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption |
| 30 | + QDropEvent, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextDocument, |
| 31 | + QTextOption |
31 | 32 | ) |
32 | 33 | from PyQt6.QtWidgets import QApplication, QMenu, QPlainTextEdit |
33 | 34 |
|
|
36 | 37 | from novelwriter.constants import nwKeyWords, nwUnicode |
37 | 38 | from novelwriter.dialogs.editlabel import GuiEditLabel |
38 | 39 | from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout |
39 | | -from novelwriter.gui.doceditor import GuiDocEditor, _TagAction |
| 40 | +from novelwriter.gui.doceditor import GuiDocEditor, TextAutoReplace, _TagAction |
40 | 41 | from novelwriter.gui.dochighlight import TextBlockData |
41 | 42 | from novelwriter.text.counting import standardCounter |
42 | 43 | from novelwriter.types import ( |
@@ -2342,3 +2343,142 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): |
2342 | 2343 | assert docEditor.textCursor().selectedText() == "" |
2343 | 2344 |
|
2344 | 2345 | # qtbot.stop() |
| 2346 | + |
| 2347 | + |
| 2348 | +@pytest.mark.gui |
| 2349 | +def testGuiEditor_TextAutoReplaceSymbols(): |
| 2350 | + """Test the editor auto-replace functionality.""" |
| 2351 | + CONFIG.fmtSQuoteOpen = nwUnicode.U_LSQUO |
| 2352 | + CONFIG.fmtSQuoteClose = nwUnicode.U_RSQUO |
| 2353 | + CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO |
| 2354 | + CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO |
| 2355 | + |
| 2356 | + CONFIG.doReplaceSQuote = True |
| 2357 | + CONFIG.doReplaceDQuote = True |
| 2358 | + CONFIG.doReplaceDash = True |
| 2359 | + CONFIG.doReplaceDots = True |
| 2360 | + |
| 2361 | + ar = TextAutoReplace() |
| 2362 | + |
| 2363 | + def prep(text: str) -> tuple[str, int]: |
| 2364 | + return text, len(text) |
| 2365 | + |
| 2366 | + # Double Quote Open |
| 2367 | + assert ar._determine(*prep('"')) == (1, nwUnicode.U_LDQUO) |
| 2368 | + assert ar._determine(*prep('Stuff "')) == (1, nwUnicode.U_LDQUO) |
| 2369 | + assert ar._determine(*prep('>"')) == (1, nwUnicode.U_LDQUO) |
| 2370 | + assert ar._determine(*prep('>>"')) == (1, nwUnicode.U_LDQUO) |
| 2371 | + assert ar._determine(*prep('_"')) == (1, nwUnicode.U_LDQUO) |
| 2372 | + assert ar._determine(*prep(' _"')) == (1, nwUnicode.U_LDQUO) |
| 2373 | + assert ar._determine(*prep('\u00a0_"')) == (1, nwUnicode.U_LDQUO) |
| 2374 | + assert ar._determine(*prep('**"')) == (1, nwUnicode.U_LDQUO) |
| 2375 | + assert ar._determine(*prep(' **"')) == (1, nwUnicode.U_LDQUO) |
| 2376 | + assert ar._determine(*prep('\u00a0**"')) == (1, nwUnicode.U_LDQUO) |
| 2377 | + assert ar._determine(*prep('=="')) == (1, nwUnicode.U_LDQUO) |
| 2378 | + assert ar._determine(*prep(' =="')) == (1, nwUnicode.U_LDQUO) |
| 2379 | + assert ar._determine(*prep('\u00a0=="')) == (1, nwUnicode.U_LDQUO) |
| 2380 | + assert ar._determine(*prep('~~"')) == (1, nwUnicode.U_LDQUO) |
| 2381 | + assert ar._determine(*prep(' ~~"')) == (1, nwUnicode.U_LDQUO) |
| 2382 | + assert ar._determine(*prep('\u00a0~~"')) == (1, nwUnicode.U_LDQUO) |
| 2383 | + |
| 2384 | + # Double Quote Close |
| 2385 | + assert ar._determine(*prep('Stuff"')) == (1, nwUnicode.U_RDQUO) |
| 2386 | + |
| 2387 | + # Single Quote Open |
| 2388 | + assert ar._determine(*prep("'")) == (1, nwUnicode.U_LSQUO) |
| 2389 | + assert ar._determine(*prep("Stuff '")) == (1, nwUnicode.U_LSQUO) |
| 2390 | + assert ar._determine(*prep(">'")) == (1, nwUnicode.U_LSQUO) |
| 2391 | + assert ar._determine(*prep(">>'")) == (1, nwUnicode.U_LSQUO) |
| 2392 | + assert ar._determine(*prep("_'")) == (1, nwUnicode.U_LSQUO) |
| 2393 | + assert ar._determine(*prep(" _'")) == (1, nwUnicode.U_LSQUO) |
| 2394 | + assert ar._determine(*prep("\u00a0_'")) == (1, nwUnicode.U_LSQUO) |
| 2395 | + assert ar._determine(*prep("**'")) == (1, nwUnicode.U_LSQUO) |
| 2396 | + assert ar._determine(*prep(" **'")) == (1, nwUnicode.U_LSQUO) |
| 2397 | + assert ar._determine(*prep("\u00a0**'")) == (1, nwUnicode.U_LSQUO) |
| 2398 | + assert ar._determine(*prep("=='")) == (1, nwUnicode.U_LSQUO) |
| 2399 | + assert ar._determine(*prep(" =='")) == (1, nwUnicode.U_LSQUO) |
| 2400 | + assert ar._determine(*prep("\u00a0=='")) == (1, nwUnicode.U_LSQUO) |
| 2401 | + assert ar._determine(*prep("~~'")) == (1, nwUnicode.U_LSQUO) |
| 2402 | + assert ar._determine(*prep(" ~~'")) == (1, nwUnicode.U_LSQUO) |
| 2403 | + assert ar._determine(*prep("\u00a0~~'")) == (1, nwUnicode.U_LSQUO) |
| 2404 | + |
| 2405 | + # Single Quote Close |
| 2406 | + assert ar._determine(*prep("Stuff'")) == (1, nwUnicode.U_RSQUO) |
| 2407 | + |
| 2408 | + # Dashes |
| 2409 | + assert ar._determine(*prep("-")) == (0, "-") |
| 2410 | + assert ar._determine(*prep("--")) == (2, nwUnicode.U_ENDASH) |
| 2411 | + assert ar._determine(*prep("---")) == (3, nwUnicode.U_EMDASH) |
| 2412 | + assert ar._determine(*prep("----")) == (4, nwUnicode.U_HBAR) |
| 2413 | + assert ar._determine(*prep("\u2013-")) == (2, nwUnicode.U_EMDASH) |
| 2414 | + assert ar._determine(*prep("\u2014-")) == (2, nwUnicode.U_HBAR) |
| 2415 | + |
| 2416 | + # Ellipsis |
| 2417 | + assert ar._determine(*prep(".")) == (0, ".") |
| 2418 | + assert ar._determine(*prep("..")) == (0, ".") |
| 2419 | + assert ar._determine(*prep("...")) == (3, nwUnicode.U_HELLIP) |
| 2420 | + |
| 2421 | + # Block Typed Line Separator (#1150) |
| 2422 | + assert ar._determine(*prep("Text\u2028")) == (1, nwUnicode.U_PSEP) |
| 2423 | + |
| 2424 | + |
| 2425 | +@pytest.mark.gui |
| 2426 | +def testGuiEditor_TextAutoReplaceProcess(): |
| 2427 | + """Test the editor auto-replace functionality.""" |
| 2428 | + CONFIG.fmtDQuoteOpen = nwUnicode.U_LAQUO |
| 2429 | + CONFIG.fmtDQuoteClose = nwUnicode.U_RAQUO |
| 2430 | + |
| 2431 | + CONFIG.doReplaceDQuote = True |
| 2432 | + CONFIG.doReplaceDots = True |
| 2433 | + |
| 2434 | + ar = TextAutoReplace() |
| 2435 | + doc = QTextDocument() |
| 2436 | + |
| 2437 | + def prep(text: str) -> tuple[str, QTextCursor]: |
| 2438 | + doc.setPlainText(text) |
| 2439 | + cursor = QTextCursor(doc) |
| 2440 | + cursor.setPosition(len(text)) |
| 2441 | + return text, cursor |
| 2442 | + |
| 2443 | + # Nothing to Process |
| 2444 | + assert ar.process(*prep("")) is False |
| 2445 | + |
| 2446 | + # Standard Auto-Replace |
| 2447 | + assert ar.process(*prep("Text ...")) is True |
| 2448 | + assert doc.toRawText() == "Text \u2026" |
| 2449 | + |
| 2450 | + # Pad Before, Normal |
| 2451 | + CONFIG.fmtPadBefore = ":\u00bb" |
| 2452 | + CONFIG.fmtPadThin = False |
| 2453 | + ar.initSettings() |
| 2454 | + assert ar.process(*prep("Text:")) is True |
| 2455 | + assert doc.toRawText() == "Text\u00a0:" |
| 2456 | + assert ar.process(*prep("Text :")) is True # See #1061 |
| 2457 | + assert doc.toRawText() == "Text\u00a0:" |
| 2458 | + assert ar.process(*prep('Text"')) is True |
| 2459 | + assert doc.toRawText() == "Text\u00a0»" |
| 2460 | + assert ar.process(*prep("@Synopsis:")) is False |
| 2461 | + assert doc.toRawText() == "@Synopsis:" |
| 2462 | + |
| 2463 | + # Pad Before, Thin |
| 2464 | + CONFIG.fmtPadBefore = ":\u00bb" |
| 2465 | + CONFIG.fmtPadThin = True |
| 2466 | + ar.initSettings() |
| 2467 | + assert ar.process(*prep("Text:")) is True |
| 2468 | + assert doc.toRawText() == "Text\u202f:" |
| 2469 | + assert ar.process(*prep("Text :")) is True # See #1061 |
| 2470 | + assert doc.toRawText() == "Text\u202f:" |
| 2471 | + |
| 2472 | + # Pad After, Normal |
| 2473 | + CONFIG.fmtPadAfter = "\u00ab" |
| 2474 | + CONFIG.fmtPadThin = False |
| 2475 | + ar.initSettings() |
| 2476 | + assert ar.process(*prep('Text "')) is True |
| 2477 | + assert doc.toRawText() == "Text «\u00a0" |
| 2478 | + |
| 2479 | + # Pad After, Thin |
| 2480 | + CONFIG.fmtPadAfter = "\u00ab" |
| 2481 | + CONFIG.fmtPadThin = True |
| 2482 | + ar.initSettings() |
| 2483 | + assert ar.process(*prep('Text "')) is True |
| 2484 | + assert doc.toRawText() == "Text «\u202f" |
0 commit comments