All checks were successful
Run Test Suite / test (push) Successful in 56s
Not sure if this is perfect, but it seems to be working
989 lines
32 KiB
Go
989 lines
32 KiB
Go
package editor
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
)
|
|
|
|
// ============================================================================
|
|
// Word Text Object Tests (iw/aw)
|
|
// ============================================================================
|
|
|
|
func TestTextObjectInnerWord(t *testing.T) {
|
|
t.Run("test 'viw' selects inner word", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "v", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if !m.Mode().IsVisualMode() {
|
|
t.Errorf("Expected visual mode")
|
|
}
|
|
// Should select "hello" (cols 0-4)
|
|
if m.ActiveWindow().Anchor.Col != 0 || m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=0, cursor=4",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'diw' deletes inner word", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "d", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != " world" {
|
|
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ciw' changes inner word", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "c", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != " world" {
|
|
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
if m.Mode() != core.InsertMode {
|
|
t.Errorf("Expected insert mode after ciw")
|
|
}
|
|
})
|
|
|
|
t.Run("test 'yiw' yanks inner word", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "y", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
reg, ok := m.GetRegister('"')
|
|
if !ok {
|
|
t.Fatalf("Default register not found")
|
|
}
|
|
if len(reg.Content) != 1 || reg.Content[0] != "hello" {
|
|
t.Errorf("register content = %v, want ['hello']", reg.Content)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'viw' at start of word", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "v", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 0 || m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=0, cursor=4",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'viw' at end of word", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
|
sendKeys(tm, "v", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 0 || m.ActiveWindow().Cursor.Col != 4 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=0, cursor=4",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'viw' on underscore word", func(t *testing.T) {
|
|
lines := []string{"hello_world test"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0})
|
|
sendKeys(tm, "v", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select "hello_world" (cols 0-10)
|
|
if m.ActiveWindow().Anchor.Col != 0 || m.ActiveWindow().Cursor.Col != 10 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=0, cursor=10",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'viw' on punctuation", func(t *testing.T) {
|
|
lines := []string{"foo-bar baz"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select just "-" (col 3)
|
|
if m.ActiveWindow().Anchor.Col != 3 || m.ActiveWindow().Cursor.Col != 3 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=3, cursor=3",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectAroundWord(t *testing.T) {
|
|
t.Run("test 'vaw' selects around word with trailing space", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "v", "a", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select "hello " (cols 0-5, includes trailing space)
|
|
if m.ActiveWindow().Anchor.Col != 0 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=0, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'daw' deletes around word", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "d", "a", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "world" {
|
|
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'daw' on last word (no trailing space)", func(t *testing.T) {
|
|
lines := []string{"hello world"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0})
|
|
sendKeys(tm, "d", "a", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
|
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// WORD Text Object Tests (iW/aW)
|
|
// ============================================================================
|
|
|
|
func TestTextObjectInnerWORD(t *testing.T) {
|
|
t.Run("test 'viW' selects inner WORD", func(t *testing.T) {
|
|
lines := []string{"foo-bar baz"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "v", "i", "W")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select "foo-bar" (cols 0-6)
|
|
if m.ActiveWindow().Anchor.Col != 0 || m.ActiveWindow().Cursor.Col != 6 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=0, cursor=6",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'diW' deletes inner WORD", func(t *testing.T) {
|
|
lines := []string{"foo-bar.baz test"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "d", "i", "W")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != " test" {
|
|
t.Errorf("lines[0] = %q, want ' test'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectAroundWORD(t *testing.T) {
|
|
t.Run("test 'vaW' selects around WORD with trailing space", func(t *testing.T) {
|
|
lines := []string{"foo-bar baz"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "v", "a", "W")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select "foo-bar " (cols 0-7, includes trailing space)
|
|
if m.ActiveWindow().Anchor.Col != 0 || m.ActiveWindow().Cursor.Col != 7 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=0, cursor=7",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'daW' deletes around WORD", func(t *testing.T) {
|
|
lines := []string{"foo-bar baz"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "d", "a", "W")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "baz" {
|
|
t.Errorf("lines[0] = %q, want 'baz'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Delimiter Text Object Tests (i</a<, i(/a(, etc.)
|
|
// ============================================================================
|
|
|
|
func TestTextObjectAngleBrackets(t *testing.T) {
|
|
t.Run("test 'vi<' selects inner angle brackets", func(t *testing.T) {
|
|
lines := []string{"<hello>"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", "<")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select "hello" (cols 1-5)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vi>' works same as 'vi<'", func(t *testing.T) {
|
|
lines := []string{"<hello>"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", ">")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select "hello" (cols 1-5)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di<' deletes inner angle brackets", func(t *testing.T) {
|
|
lines := []string{"<hello>"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "i", "<")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "<>" {
|
|
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da<' deletes around angle brackets", func(t *testing.T) {
|
|
lines := []string{"<hello>"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "a", "<")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vi<' on empty brackets does nothing", func(t *testing.T) {
|
|
lines := []string{"<>"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", "<")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should remain unchanged
|
|
if m.ActiveBuffer().Lines[0].String() != "<>" {
|
|
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vi<' in nested brackets", func(t *testing.T) {
|
|
lines := []string{"<foo<bar>>"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "v", "i", "<")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select "bar" (cols 5-7, the innermost pair)
|
|
if m.ActiveWindow().Anchor.Col != 5 || m.ActiveWindow().Cursor.Col != 7 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=5, cursor=7",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectParentheses(t *testing.T) {
|
|
t.Run("test 'vi(' selects inner parentheses", func(t *testing.T) {
|
|
lines := []string{"(hello)"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vi)' works same as 'vi('", func(t *testing.T) {
|
|
lines := []string{"(hello)"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", ")")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di(' deletes inner parentheses", func(t *testing.T) {
|
|
lines := []string{"func(hello)"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "func()" {
|
|
t.Errorf("lines[0] = %q, want 'func()'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da(' deletes around parentheses", func(t *testing.T) {
|
|
lines := []string{"func(hello)"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0})
|
|
sendKeys(tm, "d", "a", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "func" {
|
|
t.Errorf("lines[0] = %q, want 'func'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vi(' on empty parens does nothing", func(t *testing.T) {
|
|
lines := []string{"()"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "()" {
|
|
t.Errorf("lines[0] = %q, want '()'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectBraces(t *testing.T) {
|
|
t.Run("test 'vi{' selects inner braces", func(t *testing.T) {
|
|
lines := []string{"{hello}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di{' deletes inner braces", func(t *testing.T) {
|
|
lines := []string{"{hello}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "{}" {
|
|
t.Errorf("lines[0] = %q, want '{}'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da{' deletes around braces", func(t *testing.T) {
|
|
lines := []string{"{hello}"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "a", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectBrackets(t *testing.T) {
|
|
t.Run("test 'vi[' selects inner brackets", func(t *testing.T) {
|
|
lines := []string{"[hello]"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", "[")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di[' deletes inner brackets", func(t *testing.T) {
|
|
lines := []string{"[hello]"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "i", "[")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "[]" {
|
|
t.Errorf("lines[0] = %q, want '[]'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da[' deletes around brackets", func(t *testing.T) {
|
|
lines := []string{"[hello]"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "a", "[")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectDoubleQuotes(t *testing.T) {
|
|
t.Run("test 'vi\"' selects inner double quotes", func(t *testing.T) {
|
|
lines := []string{`"hello"`}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", `"`)
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di\"' deletes inner double quotes", func(t *testing.T) {
|
|
lines := []string{`"hello"`}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "i", `"`)
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != `""` {
|
|
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da\"' deletes around double quotes", func(t *testing.T) {
|
|
lines := []string{`"hello"`}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "a", `"`)
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vi\"' on empty quotes does nothing", func(t *testing.T) {
|
|
lines := []string{`""`}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", `"`)
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != `""` {
|
|
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectSingleQuotes(t *testing.T) {
|
|
t.Run("test 'vi'' selects inner single quotes", func(t *testing.T) {
|
|
lines := []string{"'hello'"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", "'")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di'' deletes inner single quotes", func(t *testing.T) {
|
|
lines := []string{"'hello'"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "i", "'")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "''" {
|
|
t.Errorf("lines[0] = %q, want \"''\"", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da'' deletes around single quotes", func(t *testing.T) {
|
|
lines := []string{"'hello'"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "a", "'")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTextObjectBackticks(t *testing.T) {
|
|
t.Run("test 'vi`' selects inner backticks", func(t *testing.T) {
|
|
lines := []string{"`hello`"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "v", "i", "`")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveWindow().Anchor.Col != 1 || m.ActiveWindow().Cursor.Col != 5 {
|
|
t.Errorf("anchor=%d, cursor=%d, want anchor=1, cursor=5",
|
|
m.ActiveWindow().Anchor.Col, m.ActiveWindow().Cursor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di`' deletes inner backticks", func(t *testing.T) {
|
|
lines := []string{"`hello`"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "i", "`")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "``" {
|
|
t.Errorf("lines[0] = %q, want '``'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da`' deletes around backticks", func(t *testing.T) {
|
|
lines := []string{"`hello`"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "d", "a", "`")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
|
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Edge Cases and Complex Scenarios
|
|
// ============================================================================
|
|
|
|
func TestTextObjectEdgeCases(t *testing.T) {
|
|
t.Run("test 'diw' on single character word", func(t *testing.T) {
|
|
lines := []string{"a b c"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", "w")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != " b c" {
|
|
t.Errorf("lines[0] = %q, want ' b c'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'ci<' then type replacement", func(t *testing.T) {
|
|
lines := []string{"<hello>"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
|
sendKeys(tm, "c", "i", "<")
|
|
sendKeyString(tm, "world")
|
|
sendKeys(tm, "esc")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "<world>" {
|
|
t.Errorf("lines[0] = %q, want '<world>'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test 'yi(' then paste", func(t *testing.T) {
|
|
lines := []string{"func(arg)", "test"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
|
sendKeys(tm, "y", "i", "(")
|
|
sendKeys(tm, "j", "p")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// 'p' pastes after cursor, so "arg" is pasted after 't' -> "testarg"
|
|
if m.ActiveBuffer().Lines[1].String() != "testarg" {
|
|
t.Errorf("lines[1] = %q, want 'testarg'", m.ActiveBuffer().Lines[1].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test text object cursor after delimiters does nothing", func(t *testing.T) {
|
|
lines := []string{"before (hello) after"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should remain unchanged since cursor is not inside parens
|
|
if m.ActiveBuffer().Lines[0].String() != "before (hello) after" {
|
|
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test text object cursor before delimiters selects inside", func(t *testing.T) {
|
|
lines := []string{"before (hello) after"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
if m.ActiveBuffer().Lines[0].String() != "before () after" {
|
|
t.Errorf("lines[0] = %q, want 'before () after'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test text object cursor before delimiters with 'a' modifier", func(t *testing.T) {
|
|
lines := []string{"before (hello) after"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "a", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// 'a' should delete including the delimiters
|
|
if m.ActiveBuffer().Lines[0].String() != "before after" {
|
|
t.Errorf("lines[0] = %q, want 'before after'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test text object cursor on opening delimiter", func(t *testing.T) {
|
|
lines := []string{"text (hello) more"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Cursor on '(' at position 5, should still select inside
|
|
if m.ActiveBuffer().Lines[0].String() != "text () more" {
|
|
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test text object cursor on closing delimiter", func(t *testing.T) {
|
|
lines := []string{"text (hello) more"}
|
|
// "text (hello) more"
|
|
// 01234567891011 <- ')' is at position 11
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 11, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Cursor on ')', should still select inside
|
|
if m.ActiveBuffer().Lines[0].String() != "text () more" {
|
|
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test multiple delimiter pairs - cursor before first", func(t *testing.T) {
|
|
lines := []string{"(foo) bar (baz)"}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select the first pair it finds
|
|
if m.ActiveBuffer().Lines[0].String() != "() bar (baz)" {
|
|
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test multiple delimiter pairs - cursor between pairs", func(t *testing.T) {
|
|
lines := []string{"(foo) bar (baz)"}
|
|
// Cursor on 'b' in "bar"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should search forward and find the second pair
|
|
if m.ActiveBuffer().Lines[0].String() != "(foo) bar ()" {
|
|
t.Errorf("lines[0] = %q, want '(foo) bar ()'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test multiple delimiter pairs - cursor inside first", func(t *testing.T) {
|
|
lines := []string{"(foo) bar (baz)"}
|
|
// Cursor on 'o' in "foo"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select the first pair since cursor is inside it
|
|
if m.ActiveBuffer().Lines[0].String() != "() bar (baz)" {
|
|
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test multiple quoted strings - cursor before first", func(t *testing.T) {
|
|
lines := []string{`foo "bar" baz "qux"`}
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", `"`)
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should find and select first quoted string
|
|
if m.ActiveBuffer().Lines[0].String() != `foo "" baz "qux"` {
|
|
t.Errorf("lines[0] = %q, want 'foo \"\" baz \"qux\"'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
|
|
t.Run("test multiple quoted strings - cursor between pairs", func(t *testing.T) {
|
|
lines := []string{`"foo" bar "baz"`}
|
|
// Cursor on 'b' in "bar"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
|
sendKeys(tm, "d", "i", `"`)
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should search forward and find second string
|
|
if m.ActiveBuffer().Lines[0].String() != `"foo" bar ""` {
|
|
t.Errorf("lines[0] = %q, want '\"foo\" bar \"\"'", m.ActiveBuffer().Lines[0].String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Multi-line Delimiter Tests
|
|
// ============================================================================
|
|
|
|
func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
|
t.Run("test 'di{' on multi-line braces", func(t *testing.T) {
|
|
lines := []string{
|
|
"func test() {",
|
|
" body",
|
|
"}",
|
|
}
|
|
// Cursor on "body"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"func test() {",
|
|
"}",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'da{' on multi-line braces", func(t *testing.T) {
|
|
lines := []string{
|
|
"func test() {",
|
|
" body",
|
|
"}",
|
|
}
|
|
// Cursor on "body"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
|
sendKeys(tm, "d", "a", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"func test() ",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'vi(' on multi-line parentheses", func(t *testing.T) {
|
|
lines := []string{
|
|
"function(",
|
|
" arg1,",
|
|
" arg2",
|
|
")",
|
|
}
|
|
// Cursor on "arg1"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
|
sendKeys(tm, "v", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
// Should select from after '(' to before ')'
|
|
// Line 0, col 9 (after '(') to line 3, col -1 (before ')')
|
|
// But since we're in visual mode, check the anchor and cursor
|
|
if m.ActiveWindow().Anchor.Line != 0 || m.ActiveWindow().Cursor.Line != 2 {
|
|
t.Errorf("anchor.Line=%d, cursor.Line=%d, want anchor.Line=0, cursor.Line=2",
|
|
m.ActiveWindow().Anchor.Line, m.ActiveWindow().Cursor.Line)
|
|
}
|
|
// Anchor should be at col 9 (after '('), cursor at end of line 2
|
|
if m.ActiveWindow().Anchor.Col != 9 {
|
|
t.Errorf("anchor.Col=%d, want 9", m.ActiveWindow().Anchor.Col)
|
|
}
|
|
})
|
|
|
|
t.Run("test 'di(' on multi-line parentheses", func(t *testing.T) {
|
|
lines := []string{
|
|
"function(",
|
|
" arg1,",
|
|
" arg2",
|
|
")",
|
|
}
|
|
// Cursor on "arg1"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"function(",
|
|
")",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test nested multi-line braces - cursor in outer", func(t *testing.T) {
|
|
lines := []string{
|
|
"outer {",
|
|
" inner {",
|
|
" content",
|
|
" }",
|
|
" more",
|
|
"}",
|
|
}
|
|
// Cursor on "more" (inside outer, outside inner)
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 4})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"outer {",
|
|
"}",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test nested multi-line braces - cursor in inner", func(t *testing.T) {
|
|
lines := []string{
|
|
"outer {",
|
|
" inner {",
|
|
" content",
|
|
" }",
|
|
" more",
|
|
"}",
|
|
}
|
|
// Cursor on "content" (inside inner block)
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 8, Line: 2})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"outer {",
|
|
" inner {",
|
|
" }",
|
|
" more",
|
|
"}",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test nested multi-line braces with multiple nesting levels", func(t *testing.T) {
|
|
lines := []string{
|
|
"level1 {",
|
|
" level2 {",
|
|
" level3 {",
|
|
" target",
|
|
" }",
|
|
" }",
|
|
"}",
|
|
}
|
|
// Cursor on "target"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 3})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"level1 {",
|
|
" level2 {",
|
|
" level3 {",
|
|
" }",
|
|
" }",
|
|
"}",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test multi-line delimiters - cursor on opening line", func(t *testing.T) {
|
|
lines := []string{
|
|
"function(arg) {",
|
|
" body",
|
|
"}",
|
|
}
|
|
// Cursor on opening line, after '{'
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 14, Line: 0})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"function(arg) {",
|
|
"}",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test multi-line delimiters - cursor on closing line", func(t *testing.T) {
|
|
lines := []string{
|
|
"function(arg) {",
|
|
" body",
|
|
"}",
|
|
}
|
|
// Cursor on closing line, before '}'
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"function(arg) {",
|
|
"}",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test multi-line delimiters - cursor before delimiters searches forward", func(t *testing.T) {
|
|
lines := []string{
|
|
"before",
|
|
"function(arg) {",
|
|
" body",
|
|
"}",
|
|
"after",
|
|
}
|
|
// Cursor on "before"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
|
sendKeys(tm, "d", "i", "{")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"before",
|
|
"function(arg) {",
|
|
"}",
|
|
"after",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("test nested parentheses across lines", func(t *testing.T) {
|
|
lines := []string{
|
|
"outer(",
|
|
" inner(",
|
|
" content",
|
|
" ),",
|
|
" more",
|
|
")",
|
|
}
|
|
// Cursor on "content"
|
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 8, Line: 2})
|
|
sendKeys(tm, "d", "i", "(")
|
|
|
|
m := getFinalModel(t, tm)
|
|
expected := []string{
|
|
"outer(",
|
|
" inner(",
|
|
" ),",
|
|
" more",
|
|
")",
|
|
}
|
|
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
|
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Helper function to get buffer lines as strings
|
|
func bufferLinesToStrings(buf *core.Buffer) []string {
|
|
result := make([]string, buf.LineCount())
|
|
for i := 0; i < buf.LineCount(); i++ {
|
|
result[i] = buf.Line(i)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Helper function to compare slices
|
|
func slicesEqual(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|