Update tests
This commit is contained in:
@@ -61,7 +61,7 @@ from novelwriter.dialogs.editlink import GuiEditLink
|
||||
from novelwriter.editor.editor import GuiDocEditor, _TagAction
|
||||
from novelwriter.editor.editsearch import GuiDocEditSearch
|
||||
from novelwriter.editor.textblock import TextBlockData, formatCheckText
|
||||
from novelwriter.enum import nwComment, nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwState, nwVimMode
|
||||
from novelwriter.enum import nwComment, nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwState
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.shared import _GuiAlert
|
||||
from novelwriter.text.counting import standardCounter
|
||||
@@ -71,7 +71,6 @@ from novelwriter.types import (
|
||||
QtImCurrentSelection,
|
||||
QtImCursorRectangle,
|
||||
QtKeepAnchor,
|
||||
QtKeyEscape,
|
||||
QtKeyReturn,
|
||||
QtModCtrl,
|
||||
QtModNone,
|
||||
@@ -4463,586 +4462,6 @@ def testGuiDocEditor_BigFixes(qtbot: QtBot, nwGUI: GuiMain):
|
||||
assert isinstance(cV, QVariant)
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocEditor_Vim_EnableVimMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test that enabling CONFIG.vimMode activates vim behavior."""
|
||||
inputDelay = 2
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
|
||||
docEditor = nwGUI.docEditor
|
||||
CONFIG.vimMode = True
|
||||
|
||||
def resetText():
|
||||
"""Reset test text."""
|
||||
docEditor.setPlainText("Line1\nLine2\nLine3")
|
||||
return docEditor.getText()
|
||||
|
||||
original_text = resetText()
|
||||
|
||||
# Normal mode: hjkl should NOT change text
|
||||
for key in "hjkl":
|
||||
qtbot.keyClick(docEditor, key, delay=inputDelay)
|
||||
assert docEditor.getText() == original_text
|
||||
|
||||
# Enter insert mode with "i"
|
||||
qtbot.keyClick(docEditor, "i", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "TEST", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
|
||||
# Text must have changed now
|
||||
new_text = docEditor.getText()
|
||||
assert new_text != original_text
|
||||
assert "TEST" in new_text
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocEditor_Vim_StateInsertModeNoOp(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test that the vim state machine ignores command keys while in
|
||||
INSERT mode, which is a state neither the NORMAL nor VISUAL mode
|
||||
key handlers ever pass through.
|
||||
"""
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
docEditor = nwGUI.docEditor
|
||||
|
||||
vim = docEditor._vim
|
||||
vim.setMode(nwVimMode.INSERT)
|
||||
vim.pushCommandKey("d")
|
||||
assert vim.command == ""
|
||||
vim.setCommand("d")
|
||||
assert vim.command == ""
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocEditor_Vim_InsertMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim hjkl movements and insert commands (i, I, A)."""
|
||||
inputDelay = 2
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
|
||||
docEditor = nwGUI.docEditor
|
||||
CONFIG.vimMode = True
|
||||
|
||||
def resetText():
|
||||
"""Reset test text."""
|
||||
docEditor.setPlainText("Line1\nLine2\nLine3")
|
||||
return docEditor.getText()
|
||||
|
||||
# Insert Mode
|
||||
# ===========
|
||||
|
||||
# i: Insert before cursor
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "i", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "X", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
assert "X" in docEditor.getText()
|
||||
|
||||
# I: Insert at beginning of line
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
qtbot.keyClick(docEditor, "h", delay=inputDelay) # Move forward 1
|
||||
qtbot.keyClick(docEditor, "h", delay=inputDelay) # Move forward 1
|
||||
qtbot.keyClick(docEditor, "I", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "START", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert lines[0].startswith("START")
|
||||
|
||||
# A: Append at end of line
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "A", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "END", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert lines[0].endswith("END")
|
||||
|
||||
# o: Open new line below
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "o", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "below", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert "below" in lines[1] # Inserted after Line1
|
||||
|
||||
# O: Open new line above
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "O", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "above", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert "above" in lines[0] # Inserted before Line1
|
||||
|
||||
# INSERT MODE MOTIONS
|
||||
# ===================
|
||||
|
||||
# i: Insert before cursor
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "i", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "TEST", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape, delay=inputDelay)
|
||||
assert "TEST" in docEditor.getText()
|
||||
|
||||
# aX: Append after cursor
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "a", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "X", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape, delay=inputDelay)
|
||||
assert "X" in docEditor.getText()
|
||||
|
||||
# New line below
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "o", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "below", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape, delay=inputDelay)
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert "below" in lines[1]
|
||||
|
||||
# New line above
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "O", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "above", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape, delay=inputDelay)
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert "above" in lines[0]
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocEditor_Vim_DeleteYankPaste(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim delete (dd, x), yank (yy) and paste (p, P) commands."""
|
||||
inputDelay = 2
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
|
||||
docEditor = nwGUI.docEditor
|
||||
CONFIG.vimMode = True
|
||||
|
||||
def resetText():
|
||||
"""Reset test text."""
|
||||
docEditor.setPlainText("Line1\nLine2\nLine3")
|
||||
return docEditor.getText()
|
||||
|
||||
# p/P: Pasting with nothing yanked yet does nothing
|
||||
resetText()
|
||||
assert docEditor._vim.pasteFromInternal() == ""
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "p", delay=inputDelay)
|
||||
assert docEditor.getText() == "Line1\nLine2\nLine3"
|
||||
qtbot.keyClicks(docEditor, "P", delay=inputDelay)
|
||||
assert docEditor.getText() == "Line1\nLine2\nLine3"
|
||||
|
||||
# dd: Delete entire line
|
||||
resetText()
|
||||
docEditor.setCursorPosition(docEditor.getText().find("Line2"))
|
||||
qtbot.keyClicks(docEditor, "dd", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line1", "Line3"]
|
||||
|
||||
# x: Delete single character
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "x", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines[0] == "ine1" # 'L' deleted
|
||||
|
||||
# p: Paste after current line
|
||||
resetText()
|
||||
line2_pos = docEditor.getText().find("Line2")
|
||||
docEditor.setCursorPosition(line2_pos)
|
||||
qtbot.keyClicks(docEditor, "yy", delay=inputDelay) # Yank Line2
|
||||
qtbot.keyClicks(docEditor, "p", delay=inputDelay) # Paste after Line2
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line1", "Line2", "Line2", "Line3"]
|
||||
|
||||
# P: Paste before current line (using Line3)
|
||||
resetText()
|
||||
line2_pos = docEditor.getText().find("Line2")
|
||||
docEditor.setCursorPosition(line2_pos)
|
||||
line3_pos = docEditor.getText().find("Line3")
|
||||
docEditor.setCursorPosition(line3_pos)
|
||||
qtbot.keyClicks(docEditor, "yy", delay=inputDelay) # Yank Line3
|
||||
docEditor.setCursorPosition(line2_pos)
|
||||
qtbot.keyClicks(docEditor, "P", delay=inputDelay) # Paste before Line2
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line1", "Line3", "Line2", "Line3"]
|
||||
|
||||
# dw: Delete word (from Line1 up to next word boundary)
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0) # Start of Line1
|
||||
qtbot.keyClicks(docEditor, "dw", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line2", "Line3"]
|
||||
|
||||
# d$: Delete line
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0) # Start of Line1
|
||||
qtbot.keyClicks(docEditor, "d$", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line2", "Line3"]
|
||||
|
||||
# de: Delete word (from Line1 up to end of word boundary)
|
||||
resetText()
|
||||
line2_pos = docEditor.getText().find("Line2")
|
||||
docEditor.setCursorPosition(line2_pos)
|
||||
qtbot.keyClicks(docEditor, "de", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line1", "Line3"]
|
||||
|
||||
# de: Delete word already at end of word
|
||||
resetText()
|
||||
line2_pos = docEditor.getText().find("Line2")
|
||||
end_pos = docEditor.getText().find("Line2") + len("Line2")
|
||||
docEditor.setCursorPosition(end_pos)
|
||||
qtbot.keyClicks(docEditor, "de", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line1", "Line2"]
|
||||
|
||||
# de: Already at the very end of the document, nowhere further to delete
|
||||
docEditor.setPlainText("Line1")
|
||||
end_pos = len(docEditor.getText())
|
||||
docEditor.setCursorPosition(end_pos)
|
||||
qtbot.keyClicks(docEditor, "de", delay=inputDelay)
|
||||
assert docEditor.getText() == "Line1"
|
||||
|
||||
# db: Delete word back (from Line2 up to end of word boundary)
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0) # Start of Line1
|
||||
qtbot.keyClicks(docEditor, "w", delay=inputDelay) # End of line1
|
||||
qtbot.keyClicks(docEditor, "db", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines == ["Line2", "Line3"]
|
||||
|
||||
# yw: Yank word (yank Line1)
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0) # Start of Line1
|
||||
qtbot.keyClicks(docEditor, "yw", delay=inputDelay)
|
||||
# Move to start of Line2 and paste
|
||||
docEditor.setCursorPosition(docEditor.getText().find("Line2"))
|
||||
qtbot.keyClicks(docEditor, "p", delay=inputDelay)
|
||||
lines = list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
assert lines[0] == "Line1"
|
||||
assert lines[1] == "Line2"
|
||||
assert lines[2] == "Line1"
|
||||
assert lines[3] == "Line3"
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocEditor_Vim_VisualMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim visual mode selection, yank and paste."""
|
||||
inputDelay = 2
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
|
||||
docEditor = nwGUI.docEditor
|
||||
CONFIG.vimMode = True
|
||||
|
||||
def resetText():
|
||||
"""Reset test text."""
|
||||
docEditor.setPlainText("Line1\nLine2\nLine3")
|
||||
return docEditor.getText()
|
||||
|
||||
original_text = resetText()
|
||||
|
||||
# Visual mode with yy
|
||||
resetText()
|
||||
docEditor.setCursorPosition(docEditor.getText().find("Line3") + 1) # Inside Line3
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "j", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "h", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "k", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "l", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "y", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "p", delay=inputDelay)
|
||||
text = docEditor.getText()
|
||||
assert "Line3" in text or text.endswith("n")
|
||||
|
||||
# Line-wise visual mode (V) with yank and paste
|
||||
resetText()
|
||||
line2_pos = docEditor.getText().find("Line2")
|
||||
docEditor.setCursorPosition(line2_pos)
|
||||
qtbot.keyClick(docEditor, "V", delay=inputDelay) # Line-wise visual mode
|
||||
qtbot.keyClick(docEditor, "j", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "h", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "k", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "l", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "y", delay=inputDelay) # Yank Line2
|
||||
qtbot.keyClick(docEditor, "j", delay=inputDelay) # Move to Line3
|
||||
qtbot.keyClick(docEditor, "p", delay=inputDelay) # Paste Line2 after Line3
|
||||
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert lines == ["Line1", "Line2", "Line3", "Line2"]
|
||||
|
||||
# Visual mode with w motion
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0) # start of Line1
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual mode
|
||||
qtbot.keyClicks(docEditor, "w", delay=inputDelay) # Move by word
|
||||
cursor_pos = docEditor.textCursor().position()
|
||||
expected_pos = docEditor.getText().find("Line1") + len("Line1")
|
||||
assert cursor_pos == expected_pos
|
||||
|
||||
# 0: Move to start of line
|
||||
resetText()
|
||||
line2_pos = docEditor.getText().find("Line2") + 3 # Middle of Line2
|
||||
docEditor.setCursorPosition(line2_pos)
|
||||
qtbot.keyClicks(docEditor, "0", delay=inputDelay)
|
||||
cursor_pos = docEditor.textCursor().position()
|
||||
|
||||
# Start of Line2
|
||||
expected_pos = docEditor.getText().find("Line2")
|
||||
assert cursor_pos == expected_pos
|
||||
|
||||
# No-op command 'a': nothing happens
|
||||
resetText()
|
||||
line3_pos = docEditor.getText().find("Line3") + 2
|
||||
docEditor.setCursorPosition(line3_pos)
|
||||
text_before = docEditor.getText()
|
||||
cursor_before = docEditor.textCursor().position()
|
||||
qtbot.keyClicks(docEditor, "a", delay=inputDelay)
|
||||
text_after = docEditor.getText()
|
||||
cursor_after = docEditor.textCursor().position()
|
||||
|
||||
# Nothing should have changed
|
||||
assert text_after == text_before
|
||||
assert cursor_after == cursor_before
|
||||
|
||||
resetText()
|
||||
start_pos = docEditor.getText().find("Line2") + 2 # Inside "Line2"
|
||||
docEditor.setCursorPosition(start_pos)
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual mode
|
||||
qtbot.keyClicks(docEditor, "b", delay=inputDelay)
|
||||
selected = docEditor.textCursor().selectedText()
|
||||
|
||||
# Should have selected from inside "Line2" back to its start
|
||||
assert selected == "Li"
|
||||
|
||||
resetText()
|
||||
start_pos = docEditor.getText().find("Line1") # Start of "Line1"
|
||||
docEditor.setCursorPosition(start_pos)
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual mode
|
||||
qtbot.keyClicks(docEditor, "e", delay=inputDelay)
|
||||
selected = docEditor.textCursor().selectedText()
|
||||
# Should have selected "Line1"
|
||||
assert selected == "Line1"
|
||||
|
||||
docEditor.setPlainText("Line1 lineExtra Line2 Line3")
|
||||
end_pos = docEditor.getText().find("Line1") + len("Line1") - 1
|
||||
docEditor.setCursorPosition(end_pos)
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual mode
|
||||
qtbot.keyClicks(docEditor, "e", delay=inputDelay)
|
||||
selected = docEditor.textCursor().selectedText().strip()
|
||||
# Should have selected whitespace + "lineExtra"
|
||||
assert "lineExtra" in selected
|
||||
|
||||
# At the very end of the document, there's nowhere further to extend to
|
||||
docEditor.setPlainText("Line1")
|
||||
end_pos = len(docEditor.getText())
|
||||
docEditor.setCursorPosition(end_pos)
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual mode
|
||||
qtbot.keyClicks(docEditor, "e", delay=inputDelay)
|
||||
assert docEditor.textCursor().position() == end_pos
|
||||
|
||||
resetText()
|
||||
# Visual select all with ggVG
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay) # Go to start
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual mode
|
||||
qtbot.keyClick(docEditor, "G", delay=inputDelay) # Extend to end of file
|
||||
|
||||
# Delete selection
|
||||
qtbot.keyClick(docEditor, "d", delay=inputDelay)
|
||||
assert docEditor.getText().strip() == "" # Everything deleted
|
||||
|
||||
# Undo
|
||||
qtbot.keyClick(docEditor, "u", delay=inputDelay)
|
||||
restored_text = docEditor.getText()
|
||||
assert restored_text == original_text
|
||||
|
||||
# Move back to start
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay)
|
||||
cursor_pos = docEditor.textCursor().position()
|
||||
assert restored_text[cursor_pos] == "L" # First char restored
|
||||
|
||||
# Visual select all with Gvgg
|
||||
qtbot.keyClick(docEditor, "G", delay=inputDelay) # End of file
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual mode
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay) # Extend select to start
|
||||
|
||||
# Delete selection
|
||||
qtbot.keyClick(docEditor, "d", delay=inputDelay)
|
||||
assert docEditor.getText().strip() == "" # Everything deleted
|
||||
|
||||
# Undo
|
||||
qtbot.keyClick(docEditor, "u", delay=inputDelay)
|
||||
restored_text = docEditor.getText()
|
||||
assert restored_text == original_text
|
||||
|
||||
# Move back to start
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay)
|
||||
qtbot.keyClicks(docEditor, "g", delay=inputDelay)
|
||||
cursor_pos = docEditor.textCursor().position()
|
||||
assert restored_text[cursor_pos] == "L" # First char restored
|
||||
|
||||
# VISUAL MODE MOTIONS
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay) # Enter visual
|
||||
qtbot.keyClick(docEditor, "l", delay=inputDelay) # Extend selection
|
||||
qtbot.keyClick(docEditor, "y", delay=inputDelay) # Yank selection
|
||||
qtbot.keyClick(docEditor, "p", delay=inputDelay) # Paste it
|
||||
assert "Li" in docEditor.getText()
|
||||
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "v", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "$", delay=inputDelay) # Extend to EOL
|
||||
qtbot.keyClick(docEditor, "d", delay=inputDelay) # Delete
|
||||
line0 = docEditor.getText().splitlines()[0]
|
||||
assert line0.strip() == "" # first line emptied
|
||||
|
||||
# VISUAL LINE MODE MOTIONS
|
||||
resetText()
|
||||
line2_pos = docEditor.getText().find("Line2")
|
||||
docEditor.setCursorPosition(line2_pos)
|
||||
qtbot.keyClick(docEditor, "V", delay=inputDelay) # Vline mode
|
||||
qtbot.keyClick(docEditor, "j", delay=inputDelay) # Extend
|
||||
qtbot.keyClick(docEditor, "y", delay=inputDelay) # Yank
|
||||
qtbot.keyClick(docEditor, "p", delay=inputDelay) # Paste
|
||||
lines = docEditor.getText().splitlines()
|
||||
assert lines.count("Line2") > 1
|
||||
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "V", delay=inputDelay) # Vline select
|
||||
qtbot.keyClick(docEditor, "G", delay=inputDelay) # Extend to EOF
|
||||
qtbot.keyClick(docEditor, "d", delay=inputDelay) # Delete all
|
||||
assert docEditor.getText().strip() == ""
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocEditor_Vim_NormalMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim NORMAL mode commands."""
|
||||
inputDelay = 2
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
|
||||
docEditor = nwGUI.docEditor
|
||||
CONFIG.vimMode = True
|
||||
|
||||
def resetText():
|
||||
"""Reset test text."""
|
||||
docEditor.setPlainText("Line1\nLine2\nLine3")
|
||||
return docEditor.getText()
|
||||
|
||||
originalText = resetText()
|
||||
|
||||
# NORMAL MODE: hjkl should NOT modify text
|
||||
for key in "hjkl":
|
||||
qtbot.keyClick(docEditor, key)
|
||||
assert docEditor.getText() == originalText
|
||||
|
||||
# b: Move to beginning of word
|
||||
resetText()
|
||||
start_pos = docEditor.getText().find("Line2") + 2 # Inside the word
|
||||
docEditor.setCursorPosition(start_pos)
|
||||
qtbot.keyClicks(docEditor, "b", delay=inputDelay)
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
expectedPos = docEditor.getText().find("Line2") # Beginning of Line2
|
||||
assert cursorPos == expectedPos
|
||||
|
||||
# e: Move to end of current word
|
||||
resetText()
|
||||
start_pos = docEditor.getText().find("Line1") # Start of Line1
|
||||
docEditor.setCursorPosition(start_pos)
|
||||
qtbot.keyClicks(docEditor, "e", delay=inputDelay)
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
expectedPos = docEditor.getText().find("Line1") + len("Line1")
|
||||
assert cursorPos == expectedPos
|
||||
|
||||
# e: Move to end of next word when already at end
|
||||
docEditor.setPlainText("Line1 lineExtra Line2 Line3")
|
||||
end_pos = docEditor.getText().find("Line1") + len("Line1")
|
||||
docEditor.setCursorPosition(end_pos)
|
||||
qtbot.keyClicks(docEditor, "e", delay=inputDelay)
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
expectedPos = docEditor.getText().find("lineExtra") + len("lineExtra")
|
||||
assert cursorPos == expectedPos
|
||||
|
||||
# e: At the very end of the document, there's nowhere further to move
|
||||
docEditor.setPlainText("Line1")
|
||||
end_pos = len(docEditor.getText())
|
||||
docEditor.setCursorPosition(end_pos)
|
||||
qtbot.keyClicks(docEditor, "e", delay=inputDelay)
|
||||
assert docEditor.textCursor().position() == end_pos
|
||||
|
||||
# $: Move to end of first line
|
||||
docEditor.setCursorPosition(0) # Start of Line1
|
||||
qtbot.keyClick(docEditor, "$", delay=inputDelay)
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
text = docEditor.getText().splitlines()[0]
|
||||
assert cursorPos == len(text) # Cursor at end of first line
|
||||
|
||||
# a: Move right and enter insert mode
|
||||
docEditor.setCursorPosition(0) # Start of Line1 again
|
||||
qtbot.keyClick(docEditor, "a", delay=inputDelay)
|
||||
# Cursor should have moved one character right
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
assert cursorPos == 1
|
||||
# Insert some text
|
||||
qtbot.keyClicks(docEditor, "TEST", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape, delay=inputDelay)
|
||||
newText = docEditor.getText()
|
||||
assert newText.startswith(("LTESSTine1", "LTESTine1"))
|
||||
|
||||
# w: Move forward by word
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0) # Start of "Line1"
|
||||
qtbot.keyClicks(docEditor, "w", delay=inputDelay)
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
# Cursor should now be at end of "Line1"
|
||||
expectedPos = docEditor.getText().find("Line1") + len("Line1")
|
||||
assert cursorPos == expectedPos
|
||||
|
||||
# NORMAL MODE MOTIONS
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "l", delay=inputDelay) # Move right
|
||||
assert docEditor.textCursor().position() == 1
|
||||
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "0", delay=inputDelay) # Beginning of line
|
||||
assert docEditor.textCursor().position() == 0
|
||||
|
||||
resetText()
|
||||
docEditor.setCursorPosition(0) # Start of Line1
|
||||
qtbot.keyClick(docEditor, "h", delay=inputDelay)
|
||||
|
||||
# Move otherwise "$" is somehow not captured always
|
||||
qtbot.keyClick(docEditor, "l", delay=inputDelay)
|
||||
|
||||
qtbot.keyClick(docEditor, "$", delay=inputDelay) # End of line
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
assert cursorPos == len("Line1")
|
||||
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "g", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "g", delay=inputDelay) # Top of buffer
|
||||
assert docEditor.textCursor().position() == 0
|
||||
|
||||
qtbot.keyClick(docEditor, "z", delay=inputDelay)
|
||||
qtbot.keyClick(docEditor, "z", delay=inputDelay) # Center view
|
||||
|
||||
resetText()
|
||||
qtbot.keyClick(docEditor, "G", delay=inputDelay) # Bottom of buffer
|
||||
cursorPos = docEditor.textCursor().position()
|
||||
assert cursorPos == len(docEditor.getText())
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocEditor_BigDocLifecycle(
|
||||
qtbot: QtBot,
|
||||
|
||||
496
tests/editor/test_vim.py
Normal file
496
tests/editor/test_vim.py
Normal file
@@ -0,0 +1,496 @@
|
||||
"""
|
||||
novelWriter - Editor Vim Mode Tests
|
||||
===================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright (C) 2026 Veronica Berglyd Olsen and novelWriter contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
""" # noqa
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.editor.editor import GuiDocEditor
|
||||
from novelwriter.editor.vim import VimState
|
||||
from novelwriter.enum import nwVimMode
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.types import QtKeyEscape
|
||||
|
||||
from tests.helpers import C, MockRnd, buildTestProject
|
||||
|
||||
KEY_DELAY = 2
|
||||
|
||||
|
||||
def openVimEditor(nwGUI: GuiMain, projPath: Path) -> GuiDocEditor:
|
||||
"""Open the scene document in the editor with vim mode enabled."""
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
CONFIG.vimMode = True
|
||||
nwGUI.docEditor.initSettings(updateVimMode=True)
|
||||
return nwGUI.docEditor
|
||||
|
||||
|
||||
def resetText(docEditor: GuiDocEditor) -> str:
|
||||
"""Reset the editor to the standard test text."""
|
||||
docEditor.setPlainText("Line1\nLine2\nLine3")
|
||||
return docEditor.getText()
|
||||
|
||||
|
||||
def testVimState_InsertModeNoOp():
|
||||
"""Test that the vim state ignores command keys in INSERT mode."""
|
||||
vim = VimState()
|
||||
vim.setMode(nwVimMode.INSERT)
|
||||
vim.pushCommandKey("d")
|
||||
assert vim.command == ""
|
||||
vim.setCommand("d")
|
||||
assert vim.command == ""
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testEditorVimMode_Init(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test enabling, refreshing and disabling vim mode in the editor."""
|
||||
buildTestProject(NWProject(), projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
docEditor = nwGUI.docEditor
|
||||
docFooter = docEditor.docFooter
|
||||
|
||||
# Disabled by default
|
||||
assert docEditor._vimMode is None
|
||||
assert docFooter.vimStatus.isHidden() is True
|
||||
|
||||
# Enable from settings
|
||||
CONFIG.vimMode = True
|
||||
docEditor.initSettings(updateVimMode=True)
|
||||
vimMode = docEditor._vimMode
|
||||
assert vimMode is not None
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
assert docFooter._vimMode == nwVimMode.NORMAL
|
||||
assert docFooter.vimStatus.isHidden() is False
|
||||
|
||||
# Mode changes are reported to the footer
|
||||
qtbot.keyClick(docEditor, "i", delay=KEY_DELAY)
|
||||
assert vimMode._vim.mode == nwVimMode.INSERT
|
||||
assert docFooter._vimMode == nwVimMode.INSERT
|
||||
|
||||
# Escape returns to NORMAL mode
|
||||
qtbot.keyClick(docEditor, QtKeyEscape, delay=KEY_DELAY)
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
assert docFooter._vimMode == nwVimMode.NORMAL
|
||||
|
||||
# Unrelated settings changes don't reset the mode
|
||||
vimMode.setVimMode(nwVimMode.INSERT)
|
||||
docEditor.initSettings()
|
||||
assert vimMode._vim.mode == nwVimMode.INSERT
|
||||
|
||||
# Re-initialising the editor keeps the instance, but resets the mode
|
||||
vimMode._vim.yankToInternal("Text")
|
||||
docEditor.initEditor()
|
||||
assert docEditor._vimMode is vimMode
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
assert vimMode._vim.pasteFromInternal() == "Text"
|
||||
|
||||
# Loading a document resets the mode
|
||||
vimMode.setVimMode(nwVimMode.INSERT)
|
||||
assert nwGUI.docPanes.openDocument(C.hChapterDoc)
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
|
||||
# Disable from settings
|
||||
CONFIG.vimMode = False
|
||||
docEditor.initSettings(updateVimMode=True)
|
||||
assert docEditor._vimMode is None
|
||||
assert docFooter._vimMode is None
|
||||
assert docFooter.vimStatus.isHidden() is True
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testEditorVimMode_InsertMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim commands entering INSERT mode."""
|
||||
docEditor = openVimEditor(nwGUI, projPath)
|
||||
|
||||
# NORMAL mode doesn't insert text
|
||||
originalText = resetText(docEditor)
|
||||
for key in "hjkl":
|
||||
qtbot.keyClick(docEditor, key, delay=KEY_DELAY)
|
||||
assert docEditor.getText() == originalText
|
||||
|
||||
# i: Insert before cursor
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "i", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "X", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
assert "X" in docEditor.getText()
|
||||
|
||||
# I: Insert at beginning of line
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "h", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "h", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "I", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "START", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
assert docEditor.getText().splitlines()[0].startswith("START")
|
||||
|
||||
# a: Append after cursor
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "a", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "X", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
assert "X" in docEditor.getText()
|
||||
|
||||
# A: Append at end of line
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "A", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "END", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
assert docEditor.getText().splitlines()[0].endswith("END")
|
||||
|
||||
# o: Open new line below
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "o", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "below", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
assert "below" in docEditor.getText().splitlines()[1]
|
||||
|
||||
# O: Open new line above
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "O", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "above", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape)
|
||||
assert "above" in docEditor.getText().splitlines()[0]
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testEditorVimMode_NormalMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim NORMAL mode motions."""
|
||||
docEditor = openVimEditor(nwGUI, projPath)
|
||||
|
||||
# b: Move to beginning of word
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2") + 2)
|
||||
qtbot.keyClicks(docEditor, "b", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == text.find("Line2")
|
||||
|
||||
# e: Move to end of current word
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "e", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == len("Line1")
|
||||
|
||||
# e: Move to end of next word when already at end
|
||||
docEditor.setPlainText("Line1 lineExtra Line2 Line3")
|
||||
text = docEditor.getText()
|
||||
docEditor.setCursorPosition(len("Line1"))
|
||||
qtbot.keyClicks(docEditor, "e", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == text.find("lineExtra") + len("lineExtra")
|
||||
|
||||
# e: At the end of the document, there's nowhere further to move
|
||||
docEditor.setPlainText("Line1")
|
||||
docEditor.setCursorPosition(5)
|
||||
qtbot.keyClicks(docEditor, "e", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 5
|
||||
|
||||
# $: Move to end of line
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClick(docEditor, "$", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == len("Line1")
|
||||
|
||||
# a: Move right and enter INSERT mode
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClick(docEditor, "a", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 1
|
||||
qtbot.keyClicks(docEditor, "TEST", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, QtKeyEscape, delay=KEY_DELAY)
|
||||
assert docEditor.getText().startswith(("LTESSTine1", "LTESTine1"))
|
||||
|
||||
# w: Move forward by word
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "w", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == len("Line1")
|
||||
|
||||
# l: Move right
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "l", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 1
|
||||
|
||||
# 0: Move to beginning of line
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "0", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 0
|
||||
|
||||
# $: Move to end of line after a motion
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClick(docEditor, "h", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "l", delay=KEY_DELAY) # Otherwise "$" is not always captured
|
||||
qtbot.keyClick(docEditor, "$", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == len("Line1")
|
||||
|
||||
# gg: Move to start of document
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "g", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 0
|
||||
|
||||
# zz: Centre view
|
||||
qtbot.keyClick(docEditor, "z", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "z", delay=KEY_DELAY)
|
||||
|
||||
# G: Move to end of document
|
||||
text = resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "G", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == len(text)
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testEditorVimMode_DeleteYankPaste(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim delete, yank and paste commands."""
|
||||
docEditor = openVimEditor(nwGUI, projPath)
|
||||
vimMode = docEditor._vimMode
|
||||
assert vimMode is not None
|
||||
|
||||
def getLines() -> list[str]:
|
||||
return list(filter(str.strip, docEditor.getText().splitlines()))
|
||||
|
||||
# p/P: Pasting with nothing yanked does nothing
|
||||
resetText(docEditor)
|
||||
assert vimMode._vim.pasteFromInternal() == ""
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "p", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == "Line1\nLine2\nLine3"
|
||||
qtbot.keyClicks(docEditor, "P", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == "Line1\nLine2\nLine3"
|
||||
|
||||
# dd: Delete line
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2"))
|
||||
qtbot.keyClicks(docEditor, "dd", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line1", "Line3"]
|
||||
|
||||
# x: Delete character
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "x", delay=KEY_DELAY)
|
||||
assert getLines()[0] == "ine1"
|
||||
|
||||
# yy + p: Paste after current line
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2"))
|
||||
qtbot.keyClicks(docEditor, "yy", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "p", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line1", "Line2", "Line2", "Line3"]
|
||||
|
||||
# yy + P: Paste before current line
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line3"))
|
||||
qtbot.keyClicks(docEditor, "yy", delay=KEY_DELAY)
|
||||
docEditor.setCursorPosition(text.find("Line2"))
|
||||
qtbot.keyClicks(docEditor, "P", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line1", "Line3", "Line2", "Line3"]
|
||||
|
||||
# dw: Delete word
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "dw", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line2", "Line3"]
|
||||
|
||||
# d$: Delete to end of line
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "d$", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line2", "Line3"]
|
||||
|
||||
# de: Delete to end of word
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2"))
|
||||
qtbot.keyClicks(docEditor, "de", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line1", "Line3"]
|
||||
|
||||
# de: Delete next word when already at end of word
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2") + len("Line2"))
|
||||
qtbot.keyClicks(docEditor, "de", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line1", "Line2"]
|
||||
|
||||
# de: At the end of the document, there's nothing to delete
|
||||
docEditor.setPlainText("Line1")
|
||||
docEditor.setCursorPosition(5)
|
||||
qtbot.keyClicks(docEditor, "de", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == "Line1"
|
||||
|
||||
# db: Delete word backwards
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "w", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "db", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line2", "Line3"]
|
||||
|
||||
# yw: Yank word
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClicks(docEditor, "yw", delay=KEY_DELAY)
|
||||
docEditor.setCursorPosition(text.find("Line2"))
|
||||
qtbot.keyClicks(docEditor, "p", delay=KEY_DELAY)
|
||||
assert getLines() == ["Line1", "Line2", "Line1", "Line3"]
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testEditorVimMode_VisualMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd: MockRnd):
|
||||
"""Test vim VISUAL and V-LINE mode selection, yank and paste."""
|
||||
docEditor = openVimEditor(nwGUI, projPath)
|
||||
originalText = resetText(docEditor)
|
||||
|
||||
# v: Select, yank and paste
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line3") + 1)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "j", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "h", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "k", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "l", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "y", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "p", delay=KEY_DELAY)
|
||||
text = docEditor.getText()
|
||||
assert "Line3" in text or text.endswith("n")
|
||||
|
||||
# V: Select line, yank and paste
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2"))
|
||||
qtbot.keyClick(docEditor, "V", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "j", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "h", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "k", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "l", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "y", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "j", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "p", delay=KEY_DELAY)
|
||||
assert docEditor.getText().splitlines() == ["Line1", "Line2", "Line3", "Line2"]
|
||||
|
||||
# w: Extend selection by word
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "w", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == len("Line1")
|
||||
|
||||
# 0: Extend selection to start of line
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2") + 3)
|
||||
qtbot.keyClicks(docEditor, "0", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == text.find("Line2")
|
||||
|
||||
# a: Not a visual command, so nothing happens
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line3") + 2)
|
||||
position = docEditor.textCursor().position()
|
||||
qtbot.keyClicks(docEditor, "a", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == text
|
||||
assert docEditor.textCursor().position() == position
|
||||
|
||||
# b: Extend selection to start of word
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2") + 2)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "b", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().selectedText() == "Li"
|
||||
|
||||
# e: Extend selection to end of word
|
||||
resetText(docEditor)
|
||||
docEditor.setCursorPosition(0)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "e", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().selectedText() == "Line1"
|
||||
|
||||
# e: Extend selection to end of next word
|
||||
docEditor.setPlainText("Line1 lineExtra Line2 Line3")
|
||||
docEditor.setCursorPosition(len("Line1") - 1)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "e", delay=KEY_DELAY)
|
||||
assert "lineExtra" in docEditor.textCursor().selectedText()
|
||||
|
||||
# e: At the end of the document, there's nowhere further to extend
|
||||
docEditor.setPlainText("Line1")
|
||||
docEditor.setCursorPosition(5)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "e", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 5
|
||||
|
||||
# ggvG: Select all, delete and undo
|
||||
resetText(docEditor)
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "G", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "d", delay=KEY_DELAY)
|
||||
assert docEditor.getText().strip() == ""
|
||||
qtbot.keyClick(docEditor, "u", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == originalText
|
||||
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 0
|
||||
|
||||
# Gvgg: Select all backwards, delete and undo
|
||||
qtbot.keyClick(docEditor, "G", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "d", delay=KEY_DELAY)
|
||||
assert docEditor.getText().strip() == ""
|
||||
qtbot.keyClick(docEditor, "u", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == originalText
|
||||
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 0
|
||||
|
||||
# vly: Yank selection and paste
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "l", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "y", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "p", delay=KEY_DELAY)
|
||||
assert "Li" in docEditor.getText()
|
||||
|
||||
# v$d: Delete to end of line
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "v", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "$", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "d", delay=KEY_DELAY)
|
||||
assert docEditor.getText().splitlines()[0].strip() == ""
|
||||
|
||||
# Vjy: Yank lines and paste
|
||||
text = resetText(docEditor)
|
||||
docEditor.setCursorPosition(text.find("Line2"))
|
||||
qtbot.keyClick(docEditor, "V", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "j", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "y", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "p", delay=KEY_DELAY)
|
||||
assert docEditor.getText().splitlines().count("Line2") > 1
|
||||
|
||||
# VGd: Delete all lines
|
||||
resetText(docEditor)
|
||||
qtbot.keyClick(docEditor, "V", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "G", delay=KEY_DELAY)
|
||||
qtbot.keyClick(docEditor, "d", delay=KEY_DELAY)
|
||||
assert docEditor.getText().strip() == ""
|
||||
@@ -455,7 +455,10 @@ def testGuiMain_ProcessConfigChanges_LightweightSettings(
|
||||
docViewer = nwGUI.docViewer
|
||||
|
||||
CONFIG.vimMode = True
|
||||
docEditor._vim.setMode(nwVimMode.INSERT)
|
||||
docEditor.initSettings(updateVimMode=True)
|
||||
vimMode = docEditor._vimMode
|
||||
assert vimMode is not None
|
||||
vimMode._vim.setMode(nwVimMode.INSERT)
|
||||
|
||||
# Clear the headers so we can tell if they were refreshed
|
||||
docEditor.docHeader._docHandle = None
|
||||
@@ -477,7 +480,7 @@ def testGuiMain_ProcessConfigChanges_LightweightSettings(
|
||||
nwGUI._processConfigChanges(noChange)
|
||||
assert docEditor.docHeader._docHandle == C.hSceneDoc
|
||||
assert docViewer.docHeader._docHandle == C.hSceneDoc
|
||||
assert docEditor._vim.mode == nwVimMode.INSERT
|
||||
assert vimMode._vim.mode == nwVimMode.INSERT
|
||||
|
||||
# A save where the Vim setting changed must reset the mode to Normal
|
||||
vimChanged = GuiNeedsUpdate(
|
||||
@@ -492,11 +495,11 @@ def testGuiMain_ProcessConfigChanges_LightweightSettings(
|
||||
viewport=False,
|
||||
)
|
||||
nwGUI._processConfigChanges(vimChanged)
|
||||
assert docEditor._vim.mode == nwVimMode.NORMAL
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
|
||||
# A save that fully reinitialises the editor and viewer must not
|
||||
# additionally run the lightweight path
|
||||
docEditor._vim.setMode(nwVimMode.INSERT)
|
||||
vimMode._vim.setMode(nwVimMode.INSERT)
|
||||
fullReinit = GuiNeedsUpdate(
|
||||
restart=False,
|
||||
tree=False,
|
||||
@@ -509,7 +512,7 @@ def testGuiMain_ProcessConfigChanges_LightweightSettings(
|
||||
viewport=False,
|
||||
)
|
||||
nwGUI._processConfigChanges(fullReinit)
|
||||
assert docEditor._vim.mode == nwVimMode.NORMAL
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
|
||||
# A viewport-only change must reinitialise the viewport on both the
|
||||
# editor and viewer even though neither is otherwise reinitialised
|
||||
|
||||
Reference in New Issue
Block a user