1103 lines
36 KiB
Go
1103 lines
36 KiB
Go
package editor
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
)
|
|
|
|
// --- G and gg Tests ---
|
|
|
|
func TestMoveToBottom(t *testing.T) {
|
|
t.Run("test 'G' from top", func(t *testing.T) {
|
|
tm := newTestModel(t)
|
|
sendKeys(tm, "G")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 5 {
|
|
t.Errorf("CursorY() = %d, want 5", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'G' from middle", func(t *testing.T) {
|
|
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 2})
|
|
sendKeys(tm, "G")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 5 {
|
|
t.Errorf("CursorY() = %d, want 5", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'G' already at bottom", func(t *testing.T) {
|
|
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 5})
|
|
sendKeys(tm, "G")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 5 {
|
|
t.Errorf("CursorY() = %d, want 5", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'G' clamps CursorX()", func(t *testing.T) {
|
|
lines := []string{"long line here", "short"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
|
sendKeys(tm, "G")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 1 {
|
|
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
want := len(lines[1])
|
|
if m.ActiveWindow().Cursor.Col != want {
|
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'G' on single line file", func(t *testing.T) {
|
|
lines := []string{"only line"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "G")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToTop(t *testing.T) {
|
|
t.Run("test 'gg' from bottom", func(t *testing.T) {
|
|
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 5})
|
|
sendKeys(tm, "g", "g")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'gg' from middle", func(t *testing.T) {
|
|
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 3})
|
|
sendKeys(tm, "g", "g")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'gg' already at top", func(t *testing.T) {
|
|
tm := newTestModel(t)
|
|
sendKeys(tm, "g", "g")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'gg' clamps CursorX()", func(t *testing.T) {
|
|
lines := []string{"short", "long line here"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 1})
|
|
sendKeys(tm, "g", "g")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
want := len(lines[0])
|
|
if m.ActiveWindow().Cursor.Col != want {
|
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- 0, $ and _ Tests ---
|
|
|
|
func TestMoveToLineStart(t *testing.T) {
|
|
t.Run("test '0' from middle of line", func(t *testing.T) {
|
|
tm := newTestModelWithCursorPos(t, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "0")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '0' from end of line", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: len(lines[0]), Line: 0})
|
|
sendKeys(tm, "0")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '0' already at start", func(t *testing.T) {
|
|
tm := newTestModel(t)
|
|
sendKeys(tm, "0")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '0' on empty line", func(t *testing.T) {
|
|
lines := []string{""}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "0")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '0' preserves line", func(t *testing.T) {
|
|
tm := newTestModelWithCursorPos(t, core.Position{Col: 3, Line: 2})
|
|
sendKeys(tm, "0")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 2 {
|
|
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToLineEnd(t *testing.T) {
|
|
t.Run("test '$' from start of line", func(t *testing.T) {
|
|
lines := []string{"hello"}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "$")
|
|
|
|
m := getFinalModel(t, tm)
|
|
want := len(lines[0]) - 1
|
|
if m.ActiveWindow().Cursor.Col != want {
|
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
|
}
|
|
})
|
|
|
|
t.Run("test '$' from middle of line", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "$")
|
|
|
|
m := getFinalModel(t, tm)
|
|
want := len(lines[0]) - 1
|
|
if m.ActiveWindow().Cursor.Col != want {
|
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
|
}
|
|
})
|
|
|
|
t.Run("test '$' already at end", func(t *testing.T) {
|
|
lines := []string{"hello"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: len(lines[0]), Line: 0})
|
|
sendKeys(tm, "$")
|
|
|
|
m := getFinalModel(t, tm)
|
|
want := len(lines[0]) - 1
|
|
if m.ActiveWindow().Cursor.Col != want {
|
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
|
}
|
|
})
|
|
|
|
t.Run("test '$' on empty line", func(t *testing.T) {
|
|
lines := []string{""}
|
|
tm := newTestModelWithLines(t, lines)
|
|
sendKeys(tm, "$")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '$' preserves line", func(t *testing.T) {
|
|
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 2})
|
|
sendKeys(tm, "$")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 2 {
|
|
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToLineContentStart(t *testing.T) {
|
|
t.Run("test '_' from middle of line with no leading whitespace", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
|
sendKeys(tm, "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '_' from middle of line with leading whitespace", func(t *testing.T) {
|
|
lines := []string{" hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
|
sendKeys(tm, "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '_' from start of line with leading whitespace", func(t *testing.T) {
|
|
lines := []string{" hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '_' from start of line with no leading whitespace", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '_' from middle of line with only whitespace", func(t *testing.T) {
|
|
lines := []string{" "}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '_' from end of line with only whitespace", func(t *testing.T) {
|
|
lines := []string{" "}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
|
sendKeys(tm, "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '_' on empty line", func(t *testing.T) {
|
|
lines := []string{""}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "_")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToLineContentStartAlias(t *testing.T) {
|
|
t.Run("test '^' from middle of line with no leading whitespace", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
|
sendKeys(tm, "^")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '^' from middle of line with leading whitespace", func(t *testing.T) {
|
|
lines := []string{" hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
|
sendKeys(tm, "^")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '^' from start of line with leading whitespace", func(t *testing.T) {
|
|
lines := []string{" hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "^")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '^' from start of line with no leading whitespace", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "^")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '^' from middle of line with only whitespace", func(t *testing.T) {
|
|
lines := []string{" "}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "^")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '^' from end of line with only whitespace", func(t *testing.T) {
|
|
lines := []string{" "}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
|
sendKeys(tm, "^")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '^' on empty line", func(t *testing.T) {
|
|
lines := []string{""}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "^")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
// --- | (Pipe / Move to Column) Tests ---
|
|
// In Vim, | moves to column N (1-indexed). So:
|
|
// - | = 1| = column 1 = index 0
|
|
// - 5| = column 5 = index 4
|
|
// - 10| = column 10 = index 9
|
|
|
|
func TestMoveToColumn(t *testing.T) {
|
|
t.Run("test '|' alone goes to column 1 (index 0)", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// | with no count = 1| = column 1 = index 0
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '1|' goes to column 1 (index 0)", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "1", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '5|' goes to column 5 (index 4)", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 5 = index 4 (the 'o' in hello)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '10|' goes to column 10 (index 9)", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "1", "0", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 10 = index 9 (the 'l' in world)
|
|
if m.ActiveWindow().Cursor.Col != 9 {
|
|
t.Errorf("CursorX() = %d, want 9", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '|' already at column 1", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '5|' already at column 5", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
|
sendKeys(tm, "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToColumnClamp(t *testing.T) {
|
|
t.Run("test '20|' clamps to end of short line", func(t *testing.T) {
|
|
lines := []string{"hello"} // 5 chars, max index 4
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "2", "0", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 20 exceeds line length, should clamp to last char (index 4)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '100|' clamps to end of line", func(t *testing.T) {
|
|
lines := []string{"hello world"} // 11 chars, max index 10
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "1", "0", "0", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should clamp to last char (index 10)
|
|
if m.ActiveWindow().Cursor.Col != 10 {
|
|
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '6|' clamps on 5-char line", func(t *testing.T) {
|
|
lines := []string{"hello"} // 5 chars
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "6", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 6 = index 5, but line only has 5 chars (max index 4)
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '|' on empty line stays at 0", func(t *testing.T) {
|
|
lines := []string{""}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '5|' on empty line stays at 0", func(t *testing.T) {
|
|
lines := []string{""}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '3|' on 2-char line clamps", func(t *testing.T) {
|
|
lines := []string{"ab"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "3", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 3 = index 2, but line only has 2 chars (max index 1)
|
|
if m.ActiveWindow().Cursor.Col != 1 {
|
|
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToColumnPreservesLine(t *testing.T) {
|
|
t.Run("test '|' preserves Y position", func(t *testing.T) {
|
|
lines := []string{"line one", "line two", "line three"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 1})
|
|
sendKeys(tm, "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 1 {
|
|
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test '5|' preserves Y position", func(t *testing.T) {
|
|
lines := []string{"line one", "line two", "line three"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
|
sendKeys(tm, "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 2 {
|
|
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test '|' on different lines", func(t *testing.T) {
|
|
lines := []string{"short", "longer line here"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 1})
|
|
sendKeys(tm, "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != 1 {
|
|
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToColumnWithWhitespace(t *testing.T) {
|
|
t.Run("test '5|' with leading whitespace", func(t *testing.T) {
|
|
lines := []string{" hello"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 5 = index 4 = 'h' in " hello"
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '3|' lands on whitespace", func(t *testing.T) {
|
|
lines := []string{" hello"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "3", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 3 = index 2 = third space
|
|
if m.ActiveWindow().Cursor.Col != 2 {
|
|
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '|' with tabs", func(t *testing.T) {
|
|
lines := []string{"\thello"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// | goes to column 1 = index 0 = the tab
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test '2|' with tabs", func(t *testing.T) {
|
|
lines := []string{"\thello"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "2", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Column 2 = index 1 = 'h' in "\thello"
|
|
if m.ActiveWindow().Cursor.Col != 1 {
|
|
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToColumnWithOperator(t *testing.T) {
|
|
t.Run("test 'd|' deletes to column 1", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "d", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Deletes from column 1 to current position (exclusive), so "hello" deleted
|
|
// Result depends on inclusive/exclusive behavior
|
|
// In Vim: d| from col 5 deletes chars 0-4, leaving " world"
|
|
if m.ActiveBuffer().Lines[0].String() != " world" {
|
|
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'd5|' deletes to column 5", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Deletes from cursor (0) to column 5 (index 4), so "hell" deleted
|
|
// Result: "o world"
|
|
if m.ActiveBuffer().Lines[0].String() != "o world" {
|
|
t.Errorf("Line(0) = %q, want 'o world'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'y5|' yanks to column 5", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "y", "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
reg, ok := m.GetRegister('"')
|
|
if !ok {
|
|
t.Fatal("unnamed register not found")
|
|
}
|
|
// Should yank "hell" (indices 0-3, up to but not including col 5)
|
|
if len(reg.Content) != 1 || reg.Content[0] != "hell" {
|
|
t.Errorf("register content = %q, want 'hell'", reg.Content)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'y|' yanks to column 1 (nothing if at start)", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "y", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
reg, ok := m.GetRegister('"')
|
|
if !ok {
|
|
t.Fatal("unnamed register not found")
|
|
}
|
|
// At col 0, y| should yank nothing (empty string)
|
|
if len(reg.Content) != 1 || reg.Content[0] != "" {
|
|
t.Errorf("register content = %q, want ''", reg.Content)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMoveToColumnInVisualMode(t *testing.T) {
|
|
t.Run("test 'v5|' selects to column 5", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "v", "5", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 0 {
|
|
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v|' selects backward to column 1", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "v", "|")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 5 {
|
|
t.Errorf("AnchorX() = %d, want 5", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v5|d' deletes selection to column 5", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "v", "5", "|", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Visual selection from 0 to 4 inclusive, delete "hello"
|
|
if m.ActiveBuffer().Lines[0].String() != " world" {
|
|
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestJumpToMatchingDelimiter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
lines []string
|
|
start core.Position
|
|
expected core.Position
|
|
}{
|
|
{
|
|
name: "opening paren jumps forward to matching paren",
|
|
lines: []string{"a(b(c)d)e"},
|
|
start: core.Position{Line: 0, Col: 1},
|
|
expected: core.Position{Line: 0, Col: 7},
|
|
},
|
|
{
|
|
name: "closing paren jumps backward to matching opening paren",
|
|
lines: []string{"a(b(c)d)e"},
|
|
start: core.Position{Line: 0, Col: 7},
|
|
expected: core.Position{Line: 0, Col: 1},
|
|
},
|
|
{
|
|
name: "opening square bracket jumps forward with nesting",
|
|
lines: []string{"x[ab[c]d]y"},
|
|
start: core.Position{Line: 0, Col: 1},
|
|
expected: core.Position{Line: 0, Col: 8},
|
|
},
|
|
{
|
|
name: "closing square bracket jumps backward with nesting",
|
|
lines: []string{"x[ab[c]d]y"},
|
|
start: core.Position{Line: 0, Col: 8},
|
|
expected: core.Position{Line: 0, Col: 1},
|
|
},
|
|
{
|
|
name: "opening brace jumps forward across lines",
|
|
lines: []string{"if (ok) {", " call()", "}"},
|
|
start: core.Position{Line: 0, Col: 8},
|
|
expected: core.Position{Line: 2, Col: 0},
|
|
},
|
|
{
|
|
name: "closing brace jumps backward across lines",
|
|
lines: []string{"if (ok) {", " call()", "}"},
|
|
start: core.Position{Line: 2, Col: 0},
|
|
expected: core.Position{Line: 0, Col: 8},
|
|
},
|
|
{
|
|
name: "searches forward on current line when not on delimiter",
|
|
lines: []string{"xx (a(b)c) yy"},
|
|
start: core.Position{Line: 0, Col: 0},
|
|
expected: core.Position{Line: 0, Col: 9},
|
|
},
|
|
{
|
|
name: "no delimiter at or after cursor does nothing",
|
|
lines: []string{"xx (a(b)c) yy"},
|
|
start: core.Position{Line: 0, Col: 10},
|
|
expected: core.Position{Line: 0, Col: 10},
|
|
},
|
|
{
|
|
name: "unmatched opening delimiter does nothing",
|
|
lines: []string{"x (abc"},
|
|
start: core.Position{Line: 0, Col: 2},
|
|
expected: core.Position{Line: 0, Col: 2},
|
|
},
|
|
{
|
|
name: "unmatched closing delimiter does nothing",
|
|
lines: []string{"abc)"},
|
|
start: core.Position{Line: 0, Col: 3},
|
|
expected: core.Position{Line: 0, Col: 3},
|
|
},
|
|
{
|
|
name: "backward matching across lines handles nested delimiters",
|
|
lines: []string{"if (a +", " (b * c)", ")"},
|
|
start: core.Position{Line: 2, Col: 0},
|
|
expected: core.Position{Line: 0, Col: 3},
|
|
},
|
|
{
|
|
name: "forward matching across lines handles nested delimiters",
|
|
lines: []string{"if (a +", " (b * c)", ")"},
|
|
start: core.Position{Line: 0, Col: 3},
|
|
expected: core.Position{Line: 2, Col: 0},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tm := newTestModelWithLinesAndCursorPos(t, tt.lines, tt.start)
|
|
sendKeys(tm, "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Line != tt.expected.Line {
|
|
t.Errorf("CursorY() = %d, want %d", m.ActiveWindow().Cursor.Line, tt.expected.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != tt.expected.Col {
|
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, tt.expected.Col)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJumpToMatchingDelimiterInVisualMode(t *testing.T) {
|
|
t.Run("test 'v%' selects to matching delimiter", func(t *testing.T) {
|
|
lines := []string{"foo(bar)baz"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 3})
|
|
sendKeys(tm, "v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualMode {
|
|
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 3 {
|
|
t.Errorf("AnchorX() = %d, want 3", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 7 {
|
|
t.Errorf("CursorX() = %d, want 7", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v%' from closing delimiter selects backward", func(t *testing.T) {
|
|
lines := []string{"foo(bar)baz"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 7})
|
|
sendKeys(tm, "v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualMode {
|
|
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 7 {
|
|
t.Errorf("AnchorX() = %d, want 7", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 3 {
|
|
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v%' searches forward on current line before selecting", func(t *testing.T) {
|
|
lines := []string{"xx (a(b)c) yy"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 0})
|
|
sendKeys(tm, "v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualMode {
|
|
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 0 {
|
|
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 9 {
|
|
t.Errorf("CursorX() = %d, want 9", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v%' with no delimiter after cursor keeps selection in place", func(t *testing.T) {
|
|
lines := []string{"xx (a(b)c) yy"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 10})
|
|
sendKeys(tm, "v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualMode {
|
|
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 10 {
|
|
t.Errorf("AnchorX() = %d, want 10", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 10 {
|
|
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v%' on unmatched opening delimiter keeps selection in place", func(t *testing.T) {
|
|
lines := []string{"x (abc"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 2})
|
|
sendKeys(tm, "v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualMode {
|
|
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 2 {
|
|
t.Errorf("AnchorX() = %d, want 2", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 2 {
|
|
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v%d' deletes the matched delimiter range", func(t *testing.T) {
|
|
lines := []string{"foo(bar)baz"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 3})
|
|
sendKeys(tm, "v", "%", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.NormalMode {
|
|
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "foobaz" {
|
|
t.Errorf("Line(0) = %q, want 'foobaz'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'V%' spans matching lines", func(t *testing.T) {
|
|
lines := []string{"if (ok) {", " call()", "}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 8})
|
|
sendKeys(tm, "V", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualLineMode {
|
|
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 0 {
|
|
t.Errorf("AnchorY() = %d, want 0", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 2 {
|
|
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'V%' from closing delimiter selects backward across lines", func(t *testing.T) {
|
|
lines := []string{"if (ok) {", " call()", "}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 2, Col: 0})
|
|
sendKeys(tm, "V", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualLineMode {
|
|
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 2 {
|
|
t.Errorf("AnchorY() = %d, want 2", m.ActiveWindow().Anchor.Line)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 {
|
|
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'V%' with no delimiter after cursor keeps selection in place", func(t *testing.T) {
|
|
lines := []string{"xx (a(b)c) yy"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 10})
|
|
sendKeys(tm, "V", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualLineMode {
|
|
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 0 || m.ActiveWindow().Anchor.Col != 10 {
|
|
t.Errorf("anchor = (%d,%d), want (0,10)", m.ActiveWindow().Anchor.Line, m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 || m.ActiveWindow().Cursor.Col != 10 {
|
|
t.Errorf("cursor = (%d,%d), want (0,10)", m.ActiveWindow().Cursor.Line, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'V%' on unmatched opening delimiter keeps selection in place", func(t *testing.T) {
|
|
lines := []string{"if (abc"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 3})
|
|
sendKeys(tm, "V", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualLineMode {
|
|
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 0 || m.ActiveWindow().Anchor.Col != 3 {
|
|
t.Errorf("anchor = (%d,%d), want (0,3)", m.ActiveWindow().Anchor.Line, m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 || m.ActiveWindow().Cursor.Col != 3 {
|
|
t.Errorf("cursor = (%d,%d), want (0,3)", m.ActiveWindow().Cursor.Line, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'V%d' deletes linewise matched range", func(t *testing.T) {
|
|
lines := []string{"if (ok) {", " call()", "}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 8})
|
|
sendKeys(tm, "V", "%", "d")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.NormalMode {
|
|
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
|
}
|
|
if m.ActiveBuffer().LineCount() != 1 {
|
|
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
|
}
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ctrl+v%' updates block selection to matching delimiter", func(t *testing.T) {
|
|
lines := []string{"if (ok) {", " call()", "}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 8})
|
|
sendKeys(tm, "ctrl+v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualBlockMode {
|
|
t.Errorf("Mode() = %v, want VisualBlockMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 0 || m.ActiveWindow().Anchor.Col != 8 {
|
|
t.Errorf("anchor = (%d,%d), want (0,8)", m.ActiveWindow().Anchor.Line, m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 2 || m.ActiveWindow().Cursor.Col != 0 {
|
|
t.Errorf("cursor = (%d,%d), want (2,0)", m.ActiveWindow().Cursor.Line, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ctrl+v%' from closing delimiter selects backward", func(t *testing.T) {
|
|
lines := []string{"if (ok) {", " call()", "}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 2, Col: 0})
|
|
sendKeys(tm, "ctrl+v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualBlockMode {
|
|
t.Errorf("Mode() = %v, want VisualBlockMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 2 || m.ActiveWindow().Anchor.Col != 0 {
|
|
t.Errorf("anchor = (%d,%d), want (2,0)", m.ActiveWindow().Anchor.Line, m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 || m.ActiveWindow().Cursor.Col != 8 {
|
|
t.Errorf("cursor = (%d,%d), want (0,8)", m.ActiveWindow().Cursor.Line, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ctrl+v%' with no delimiter after cursor keeps block selection in place", func(t *testing.T) {
|
|
lines := []string{"xx (a(b)c) yy"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 10})
|
|
sendKeys(tm, "ctrl+v", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualBlockMode {
|
|
t.Errorf("Mode() = %v, want VisualBlockMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Line != 0 || m.ActiveWindow().Anchor.Col != 10 {
|
|
t.Errorf("anchor = (%d,%d), want (0,10)", m.ActiveWindow().Anchor.Line, m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Line != 0 || m.ActiveWindow().Cursor.Col != 10 {
|
|
t.Errorf("cursor = (%d,%d), want (0,10)", m.ActiveWindow().Cursor.Line, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ctrl+v%y' yanks block and exits visual mode", func(t *testing.T) {
|
|
lines := []string{"if (ok) {", " call()", "}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 8})
|
|
sendKeys(tm, "ctrl+v", "%", "y")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.NormalMode {
|
|
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestJumpToMatchingDelimiterIgnoresCount(t *testing.T) {
|
|
t.Run("test '5%' in normal mode still performs delimiter matching", func(t *testing.T) {
|
|
lines := []string{"a(b)c"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 1})
|
|
sendKeys(tm, "5", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Cursor.Col != 3 {
|
|
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'v5%' in visual mode still performs delimiter matching", func(t *testing.T) {
|
|
lines := []string{"a(b)c"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 0, Col: 1})
|
|
sendKeys(tm, "v", "5", "%")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.Mode() != core.VisualMode {
|
|
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
|
}
|
|
if m.ActiveWindow().Anchor.Col != 1 {
|
|
t.Errorf("AnchorX() = %d, want 1", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
if m.ActiveWindow().Cursor.Col != 3 {
|
|
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|