Compare commits

..

No commits in common. "master" and "feature/dot-op" have entirely different histories.

49 changed files with 1434 additions and 5534 deletions

View File

@ -14,8 +14,8 @@
- [x] `b` - Backward to start of word
- [x] `W` - Forward to start of WORD (whitespace-delimited)
- [x] `E` - Forward to end of WORD
- [x] `B` - Backward to start of WORD
- [x] `ge` - Backward to end of word
- [ ] `B` - Backward to start of WORD
- [ ] `ge` - Backward to end of word
### Line Movement
- [x] `0` - Move to start of line
@ -34,8 +34,8 @@
### Scroll
- [x] `ctrl+u` - Scroll up half page
- [x] `ctrl+d` - Scroll down half page
- [x] `ctrl+b` - Scroll up full page
- [x] `ctrl+f` - Scroll down full page
- [ ] `ctrl+b` - Scroll up full page
- [ ] `ctrl+f` - Scroll down full page
- [ ] `ctrl+y` - Scroll up one line
- [ ] `ctrl+e` - Scroll down one line
- [ ] `zz` - Center cursor on screen
@ -129,12 +129,12 @@
### Undo/Redo
- [x] `u` - Undo
- [x] `ctrl+r` - Redo
- [x] `.` - Repeat last change
- [ ] `.` - Repeat last change
- [ ] `U` - Undo all changes on line
### Other Normal Mode
- [x] `r{char}` - Replace character
- [x] `R` - Replace mode
- [ ] `r{char}` - Replace character
- [ ] `R` - Replace mode
- [ ] `~` - Swap case of character
- [ ] `ctrl+a` - Increment number
- [ ] `ctrl+x` - Decrement number
@ -219,7 +219,7 @@
- [x] `:q!` - Force quit
- [x] `:e {file}` - Edit file
- [x] `:bn` / `:bp` - Next/previous buffer
- [x] `:{range}` - Go to line
- [ ] `:{range}` - Go to line
- [ ] `:%s/old/new/g` - Search and replace
- [ ] `:!{cmd}` - Run shell command
- [ ] `:help` - Show help
@ -408,3 +408,63 @@ Buffers are in-memory representations of files. A buffer exists for each open fi
- [ ] Spell check
---
### Well Tested - Editor Core
#### Command Execution (179 tests)
- [x] Command parsing and validation
- [x] Command lookup and prefix matching
- [x] Force flag handling (!)
- [x] Write commands (`:w`, `:w {file}`, `:w!`)
- [x] Write all commands (`:wa`, `:wall`, `:wa!`)
- [x] Quit commands (`:q`, `:q!`, `:qa`, `:qa!`)
- [x] Write-quit commands (`:wq`, `:wq!`, `:wqa`, `:wqa!`)
- [x] Edit command (`:e {file}`)
- [x] Register display (`:register`, `:reg {name}`)
- [x] Set commands (`:set number`, `:set tabstop=N`, etc.)
- [x] Setting lookup and validation
- [x] Buffer-level readonly protection
- [x] Scratch buffer write protection
- [x] Force write bypassing readonly/scratch checks
- [x] Multiple buffer write operations
- [x] File write error handling (permissions, paths)
- [x] Modified buffer tracking
- [x] Unicode filename and content handling
- [x] Edge cases (empty args, long filenames, special chars)
#### Program Initialization (70 tests)
- [x] Empty program creation
- [x] File program with nonexistent files (new file buffers)
- [x] File program with existing files (content loading)
- [x] Line ending handling (Unix `\n`, Windows `\r\n`, mixed)
- [x] Tab to space conversion based on TabStop
- [x] Unicode content preservation (CJK, emoji)
- [x] File extension and type detection
- [x] Buffer state initialization (flags, metadata)
- [x] Large file handling (10,000+ lines)
- [x] Long line handling (10,000+ chars)
- [x] Empty file handling
- [x] Builder pattern method chaining
- [x] Program option accumulation
- [x] Model state defaults (settings, registers, mode)
- [x] Error handling (permissions, invalid paths)
- [x] Integration workflows (end-to-end)
- [x] Edge cases (empty filenames, relative paths, dot files)
### Moderately Tested
- [x] Basic motions (h, j, k, l)
- [x] Word motions (w, e, b)
- [x] Jump motions (G, gg, 0, $, _, ^, |)
- [x] Scroll actions (ctrl+u, ctrl+d)
- [x] Delete operator (d, dd)
- [x] Yank operator (y, yy)
- [x] Paste actions (p, P)
- [x] Change operator (c, cc, C)
- [x] Substitute action (s, S)
- [x] Insert mode entry (i, a, I, A, o, O)
- [x] Insert mode editing (enter, backspace, delete, tab, ctrl+w)
- [x] Visual modes (v, V, ctrl+v)
- [x] Visual mode with motions
- [x] Delete actions (x, D)
- [x] Register behavior

View File

@ -64,27 +64,12 @@ While the undo tree method that vim uses is powerful, I rarely find myself using
approach is more natural to "non-vim" users and much simpler to implement. Implementing a feature similar
to Vims undo tree would many times longer than a simple stack.
#### Vim-like Replace vs. Custom Replace
The way that vim's replace mode is implemented is quite complex, keeping track of the previous
line backspace can only delete newly replaced characters. This is a complex feature, one that
I rarely use, and even find a bit un-intuitive. Implementing replace mode in a way where all
actions function the same as insert mode (other than the actual character typing) allows for
a much simpler implementation, as well as a more intuitive user experience.
Replace mode implements and replaces (no pun intended) the last inserted keys of insert mode. Due to
the infrequent use of replace mode, and the '.' action for insert mode, this felt like a natural
trade off.
---
## TODO List
- Ops like change, and substitute and such should add to paste reg
- Delete op should also add to paste reg
- Gap buffer implementation (this shouldn't be TOO hard)
- Alternate buffer handling and implementation
- Scroll in X direction
---

39
V0.1.md
View File

@ -1,39 +0,0 @@
# Gim v0.1 MVP
## Must Have (Blocker for Release)
- [x] Modal editing (normal, insert, visual)
- [x] Core motions and operators
- [x] Text objects
- [x] Undo/redo
- [x] File I/O
- [x] Buffer switching
- [ ] Search (/, ?, n, N) with highlighting
- [ ] Syntax highlighting (Chroma + tree-sitter for Go/Python/JS)
- [ ] % (matching bracket)
- [ ] J (join lines)
- [ ] H/M/L (screen movement)
- [ ] Status line (mode, filename, position, modified flag)
## Should Have (Makes it Usable)
- [ ] :substitute (%s/old/new/g) - at least basic version
- [ ] Better command-mode autocomplete/hints
- [ ] Configuration file support (~/.gimrc or similar)
- [ ] Persistent undo history
- [ ] Line wrapping display option
- [ ] Handle large files gracefully (>10k lines)
## Nice to Have (Polish)
- [ ] Incremental search (search as you type)
- [ ] * and # (search word under cursor)
- [ ] Cursor line highlight
- [ ] Scroll cursor to center (zz, zt, zb)
- [ ] Named registers (a-z)
- [ ] Black hole register (_)
## Won't Have in v0.1 (Future)
- LSP integration → v0.2
- Fuzzy finder → v0.3
- Splits/windows → v0.3
- Macros → v0.2
- Git integration → v0.3
- Plugin system → v1.0

View File

@ -23,13 +23,11 @@ func main() {
prog = program.NewProgramBuilder().
EmptyProgram().
WithOpt(tea.WithAltScreen()).
WithOpt(tea.WithMouseCellMotion()).
Build()
} else {
prog = program.NewProgramBuilder().
FileProgram(args[0]).
WithOpt(tea.WithAltScreen()).
WithOpt(tea.WithMouseCellMotion()).
Build()
}

View File

@ -24,8 +24,6 @@
glibc_multi
];
name = "Gim";
# Define the shell that will be executed.
# Here, we explicitly use zsh.
# Note: pkgs.zsh needs to be included in `packages` or `nativeBuildInputs`

View File

@ -16,7 +16,7 @@ func (a ChangeToEndOfLine) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line)
line := buf.Lines[win.Cursor.Line]
// Save deleted text to register
if pos < len(line) {
@ -51,7 +51,7 @@ func (a SubstituteChar) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line)
line := buf.Lines[win.Cursor.Line]
// Calculate how many chars to delete (limited by line length)
count := min(a.Count, len(line)-pos)
@ -97,7 +97,7 @@ func (a SubstituteLine) Execute(m Model) tea.Cmd {
// Collect and delete lines
for range count {
lines = append(lines, buf.Line(y))
lines = append(lines, buf.Lines[y])
buf.DeleteLine(y)
}

View File

@ -1,8 +1,6 @@
package action
import (
"strconv"
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
@ -141,12 +139,6 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
// history = append([]string{cmdLine}, history...)
m.SetCommandHistory(history)
// try to parse entire thing as a number
num, err := strconv.Atoi(cmdLine)
if err == nil {
return cmdGoToLine(m, num)
}
cmd, err := a.Registry.Execute(m, cmdLine)
if err != nil {
out := core.CommandOutput{
@ -160,23 +152,3 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
return cmd
}
// cmdGoToLine: DOES NOT implement command.Command. Instead, is called directly
// by CommandExecture.Execute(). Jumps the cursor to the line provided
func cmdGoToLine(m Model, line int) tea.Cmd {
// number below 0 just goes back that many
win := m.ActiveWindow()
buf := m.ActiveBuffer()
if line <= 0 {
newLine := max(0, win.Cursor.Line+line)
win.SetCursorLine(newLine)
return nil
}
newLine := min(line-1, buf.LineCount())
win.SetCursorLine(newLine)
return nil
}

View File

@ -1,725 +0,0 @@
package action
import (
"testing"
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
// mockCommandRegistry for testing - returns nil for all commands (used for numeric goto)
type mockCommandRegistry struct{}
func (r *mockCommandRegistry) Execute(m Model, cmdLine string) (tea.Cmd, error) {
return nil, nil
}
// TestCommandExecute_GoToLine tests the :<num> command functionality
func TestCommandExecute_GoToLine(t *testing.T) {
t.Run("goto positive line number within bounds", func(t *testing.T) {
// Create buffer with 10 lines
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5", "line6", "line7", "line8", "line9", "line10"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(0)
// Set command to "5"
m.SetCommand("5")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should jump to line 5 (0-indexed: line 4)
if win.Cursor.Line != 4 {
t.Fatalf("expected cursor at line 4, got %d", win.Cursor.Line)
}
// Should exit command mode
if m.Mode() != core.NormalMode {
t.Fatalf("expected normal mode, got %v", m.Mode())
}
})
t.Run("goto line 1", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(3) // Start at line 4
m.SetCommand("1")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should jump to line 1 (0-indexed: line 0)
if win.Cursor.Line != 0 {
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
}
})
t.Run("goto last line", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(0)
m.SetCommand("5")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should jump to line 5 (0-indexed: line 4)
if win.Cursor.Line != 4 {
t.Fatalf("expected cursor at line 4, got %d", win.Cursor.Line)
}
})
t.Run("goto line beyond buffer length clamps to last line", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(0)
m.SetCommand("999")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should clamp to last line (0-indexed: line 2)
// Note: cmdGoToLine uses min(line-1, buf.LineCount())
// LineCount() returns 3, so min(998, 3) = 3
// But SetCursorLine calls ClampCursor() which clamps to valid range [0, LineCount-1]
lastLine := buf.LineCount() - 1
if win.Cursor.Line != lastLine {
t.Fatalf("expected cursor at line %d (last line), got %d", lastLine, win.Cursor.Line)
}
})
t.Run("goto line zero goes to beginning", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(3)
m.SetCommand("0")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Line 0 or below goes relative: max(0, currentLine + 0) = currentLine
// But with the logic: if line <= 0, newLine = max(0, win.Cursor.Line + line)
// So for line=0: max(0, 3+0) = 3 (stays at same line)
if win.Cursor.Line != 3 {
t.Fatalf("expected cursor at line 3, got %d", win.Cursor.Line)
}
})
t.Run("negative number moves relative backwards", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5", "line6", "line7", "line8", "line9", "line10"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(5) // Start at line 6 (0-indexed)
m.SetCommand("-3")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should move back 3 lines: 5 + (-3) = 2
if win.Cursor.Line != 2 {
t.Fatalf("expected cursor at line 2, got %d", win.Cursor.Line)
}
})
t.Run("negative number beyond start clamps to line 0", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(2)
m.SetCommand("-10")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should clamp to line 0: max(0, 2 + (-10)) = max(0, -8) = 0
if win.Cursor.Line != 0 {
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
}
})
t.Run("goto same line", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(2) // At line 3
m.SetCommand("3")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should stay at line 3 (0-indexed: line 2)
if win.Cursor.Line != 2 {
t.Fatalf("expected cursor at line 2, got %d", win.Cursor.Line)
}
})
t.Run("empty buffer", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{""}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
m.SetCommand("1")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should stay at line 0
if win.Cursor.Line != 0 {
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
}
})
t.Run("single line buffer goto line 1", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"only line"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
m.SetCommand("1")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
if win.Cursor.Line != 0 {
t.Fatalf("expected cursor at line 0, got %d", win.Cursor.Line)
}
})
t.Run("single line buffer goto beyond", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"only line"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
m.SetCommand("10")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should clamp to the available lines
if win.Cursor.Line > 0 {
t.Fatalf("expected cursor at line 0 or 1, got %d", win.Cursor.Line)
}
})
t.Run("large line number", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
m.SetCommand("1000000")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should clamp to last available line
lineCount := buf.LineCount()
if win.Cursor.Line > lineCount {
t.Fatalf("expected cursor at or before line %d, got %d", lineCount, win.Cursor.Line)
}
})
t.Run("command with leading/trailing spaces", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
win.SetCursorLine(0)
// strconv.Atoi should handle leading spaces
m.SetCommand(" 3 ")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// strconv.Atoi(" 3 ") will fail, so this won't be treated as a line number
// It should stay at the same position and potentially show an error
// depending on the CommandRegistry behavior
})
t.Run("mixed alphanumeric command not treated as line number", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3"}).
Build()
m := NewMockModelWithBuffer(&buf)
win := m.ActiveWindow()
initialLine := 1
win.SetCursorLine(initialLine)
m.SetCommand("3abc")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// "3abc" is not a valid number, so won't trigger goto line
// Cursor should stay at original position
if win.Cursor.Line != initialLine {
t.Fatalf("expected cursor at line %d, got %d", initialLine, win.Cursor.Line)
}
})
}
// TestCommandExecute_EmptyCommand tests empty command behavior
func TestCommandExecute_EmptyCommand(t *testing.T) {
t.Run("empty command does nothing", func(t *testing.T) {
m := NewMockModel()
win := m.ActiveWindow()
initialLine := 0
win.SetCursorLine(initialLine)
m.SetCommand("")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
// Should exit command mode
if m.Mode() != core.NormalMode {
t.Fatalf("expected normal mode, got %v", m.Mode())
}
// Cursor should not move
if win.Cursor.Line != initialLine {
t.Fatalf("expected cursor at line %d, got %d", initialLine, win.Cursor.Line)
}
})
}
// TestCommandExecute_CommandHistory tests that commands are added to history
func TestCommandExecute_CommandHistory(t *testing.T) {
t.Run("numeric command added to history", func(t *testing.T) {
buf := core.NewBufferBuilder().
WithLines([]string{"line1", "line2", "line3", "line4", "line5"}).
Build()
m := NewMockModelWithBuffer(&buf)
m.SetCommand("3")
m.SetMode(core.CommandMode)
m.SetCommandHistory([]string{})
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
history := m.CommandHistory()
if len(history) != 1 {
t.Fatalf("expected 1 item in history, got %d", len(history))
}
if history[0] != "3" {
t.Fatalf("expected '3' in history, got '%s'", history[0])
}
})
t.Run("commands prepended to existing history", func(t *testing.T) {
m := NewMockModel()
m.SetCommandHistory([]string{"oldcommand1", "oldcommand2"})
m.SetCommand("5")
m.SetMode(core.CommandMode)
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
history := m.CommandHistory()
if len(history) != 3 {
t.Fatalf("expected 3 items in history, got %d", len(history))
}
if history[0] != "5" {
t.Fatalf("expected '5' as first item, got '%s'", history[0])
}
if history[1] != "oldcommand1" {
t.Fatalf("expected 'oldcommand1' as second item, got '%s'", history[1])
}
})
}
// TestCommandExecute_ModeTransition tests command mode exit behavior
func TestCommandExecute_ModeTransition(t *testing.T) {
t.Run("exits command mode after execution", func(t *testing.T) {
m := NewMockModel()
m.SetMode(core.CommandMode)
m.SetCommand("5")
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
if m.Mode() != core.NormalMode {
t.Fatalf("expected normal mode, got %v", m.Mode())
}
})
t.Run("resets command cursor to 0", func(t *testing.T) {
m := NewMockModel()
m.SetCommandCursor(10)
m.SetCommand("5")
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
if m.CommandCursor() != 0 {
t.Fatalf("expected command cursor at 0, got %d", m.CommandCursor())
}
})
t.Run("resets command history cursor to 0", func(t *testing.T) {
m := NewMockModel()
m.SetCommandHistoryCursor(5)
m.SetCommand("5")
action := CommandExecute{Registry: &mockCommandRegistry{}}
action.Execute(m)
if m.CommandHistoryCursor() != 0 {
t.Fatalf("expected command history cursor at 0, got %d", m.CommandHistoryCursor())
}
})
}
// TestExitCommandMode tests the Esc key behavior
func TestExitCommandMode(t *testing.T) {
t.Run("exit command mode clears state", func(t *testing.T) {
m := NewMockModel()
m.SetMode(core.CommandMode)
m.SetCommand("test command")
m.SetCommandCursor(5)
m.SetCommandHistoryCursor(3)
action := ExitCommandMode{}
action.Execute(m)
if m.Mode() != core.NormalMode {
t.Fatalf("expected normal mode, got %v", m.Mode())
}
if m.Command() != "" {
t.Fatalf("expected empty command, got '%s'", m.Command())
}
if m.CommandCursor() != 0 {
t.Fatalf("expected command cursor at 0, got %d", m.CommandCursor())
}
if m.CommandHistoryCursor() != 0 {
t.Fatalf("expected command history cursor at 0, got %d", m.CommandHistoryCursor())
}
})
}
// TestInsertCommandChar tests character insertion in command mode
func TestInsertCommandChar(t *testing.T) {
t.Run("insert character at empty command", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("")
m.SetCommandCursor(0)
action := InsertCommandChar{Char: "5"}
action.Execute(m)
if m.Command() != "5" {
t.Fatalf("expected '5', got '%s'", m.Command())
}
if m.CommandCursor() != 1 {
t.Fatalf("expected cursor at 1, got %d", m.CommandCursor())
}
})
t.Run("insert character at end", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("12")
m.SetCommandCursor(2)
action := InsertCommandChar{Char: "3"}
action.Execute(m)
if m.Command() != "123" {
t.Fatalf("expected '123', got '%s'", m.Command())
}
if m.CommandCursor() != 3 {
t.Fatalf("expected cursor at 3, got %d", m.CommandCursor())
}
})
t.Run("insert character in middle", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("13")
m.SetCommandCursor(1)
action := InsertCommandChar{Char: "2"}
action.Execute(m)
if m.Command() != "123" {
t.Fatalf("expected '123', got '%s'", m.Command())
}
if m.CommandCursor() != 2 {
t.Fatalf("expected cursor at 2, got %d", m.CommandCursor())
}
})
}
// TestCommandBackspace tests backspace in command mode
func TestCommandBackspace(t *testing.T) {
t.Run("backspace at beginning does nothing", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("123")
m.SetCommandCursor(0)
action := CommandBackspace{}
action.Execute(m)
if m.Command() != "123" {
t.Fatalf("expected '123', got '%s'", m.Command())
}
if m.CommandCursor() != 0 {
t.Fatalf("expected cursor at 0, got %d", m.CommandCursor())
}
})
t.Run("backspace in middle", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("123")
m.SetCommandCursor(2)
action := CommandBackspace{}
action.Execute(m)
if m.Command() != "13" {
t.Fatalf("expected '13', got '%s'", m.Command())
}
if m.CommandCursor() != 1 {
t.Fatalf("expected cursor at 1, got %d", m.CommandCursor())
}
})
t.Run("backspace at end", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("123")
m.SetCommandCursor(3)
action := CommandBackspace{}
action.Execute(m)
if m.Command() != "12" {
t.Fatalf("expected '12', got '%s'", m.Command())
}
if m.CommandCursor() != 2 {
t.Fatalf("expected cursor at 2, got %d", m.CommandCursor())
}
})
}
// TestCommandDelete tests delete key in command mode
func TestCommandDelete(t *testing.T) {
t.Run("delete at cursor position 0 deletes character after cursor", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("123")
m.SetCommandCursor(0)
action := CommandDelete{}
action.Execute(m)
// At position 0, delete removes char at position 1 (the next char)
// Result: "1" + "3" = "13"
if m.Command() != "13" {
t.Fatalf("expected '13', got '%s'", m.Command())
}
})
t.Run("delete at cursor position 1", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("123")
m.SetCommandCursor(1)
action := CommandDelete{}
action.Execute(m)
// At position 1, delete removes char at position 2 (the next char)
// Result: "12" + "" = "12"
if m.Command() != "12" {
t.Fatalf("expected '12', got '%s'", m.Command())
}
})
t.Run("delete at last character", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("123")
m.SetCommandCursor(2)
action := CommandDelete{}
action.Execute(m)
if m.Command() != "12" {
t.Fatalf("expected '12', got '%s'", m.Command())
}
})
t.Run("delete at end acts as backspace", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("123")
m.SetCommandCursor(3)
action := CommandDelete{}
action.Execute(m)
if m.Command() != "12" {
t.Fatalf("expected '12', got '%s'", m.Command())
}
if m.CommandCursor() != 2 {
t.Fatalf("expected cursor at 2, got %d", m.CommandCursor())
}
})
}
// TestCommandDeletePreviousWord tests Ctrl+W behavior in command mode
func TestCommandDeletePreviousWord(t *testing.T) {
t.Run("delete word from middle of text", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("hello world")
m.SetCommandCursor(11) // At end
action := CommandDeletePreviousWord{}
action.Execute(m)
if m.Command() != "hello " {
t.Fatalf("expected 'hello ', got '%s'", m.Command())
}
if m.CommandCursor() != 6 {
t.Fatalf("expected cursor at 6, got %d", m.CommandCursor())
}
})
t.Run("delete word with leading spaces", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("hello world")
m.SetCommandCursor(13) // At end
action := CommandDeletePreviousWord{}
action.Execute(m)
if m.Command() != "hello " {
t.Fatalf("expected 'hello ', got '%s'", m.Command())
}
})
t.Run("delete at beginning does nothing", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("hello")
m.SetCommandCursor(0)
action := CommandDeletePreviousWord{}
action.Execute(m)
if m.Command() != "hello" {
t.Fatalf("expected 'hello', got '%s'", m.Command())
}
if m.CommandCursor() != 0 {
t.Fatalf("expected cursor at 0, got %d", m.CommandCursor())
}
})
t.Run("delete punctuation", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("hello...")
m.SetCommandCursor(8) // After the dots
action := CommandDeletePreviousWord{}
action.Execute(m)
if m.Command() != "hello" {
t.Fatalf("expected 'hello', got '%s'", m.Command())
}
if m.CommandCursor() != 5 {
t.Fatalf("expected cursor at 5, got %d", m.CommandCursor())
}
})
t.Run("delete word in middle of command", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("one two three")
m.SetCommandCursor(7) // After "two"
action := CommandDeletePreviousWord{}
action.Execute(m)
if m.Command() != "one three" {
t.Fatalf("expected 'one three', got '%s'", m.Command())
}
if m.CommandCursor() != 4 {
t.Fatalf("expected cursor at 4, got %d", m.CommandCursor())
}
})
t.Run("delete single character word", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("a b c")
m.SetCommandCursor(3) // After "b"
action := CommandDeletePreviousWord{}
action.Execute(m)
if m.Command() != "a c" {
t.Fatalf("expected 'a c', got '%s'", m.Command())
}
})
t.Run("delete from whitespace position", func(t *testing.T) {
m := NewMockModel()
m.SetCommand("hello world")
m.SetCommandCursor(6) // At the space after "hello"
action := CommandDeletePreviousWord{}
action.Execute(m)
// Should skip the space and delete "hello" and the space
if m.Command() != "world" {
t.Fatalf("expected 'world', got '%s'", m.Command())
}
if m.CommandCursor() != 0 {
t.Fatalf("expected cursor at 0, got %d", m.CommandCursor())
}
})
}

View File

@ -13,7 +13,7 @@ func (a DeleteChar) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line)
line := buf.Lines[win.Cursor.Line]
for i := 0; i < a.Count && pos < len(line); i++ {
line = line[:pos] + line[pos+1:]
buf.SetLine(win.Cursor.Line, line)
@ -38,7 +38,7 @@ func (a DeletePrevChar) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line)
line := buf.Lines[win.Cursor.Line]
for i := 0; i < a.Count && pos <= len(line); i++ {
if pos > 0 {
line = line[:pos-1] + line[pos:]
@ -69,7 +69,7 @@ func (a DeleteToEndOfLine) Execute(m Model) tea.Cmd {
// Delete to end of line
pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line)
line := buf.Lines[win.Cursor.Line]
buf.SetLine(win.Cursor.Line, line[:pos])
win.SetCursorCol(pos - 1)

View File

@ -76,7 +76,7 @@ type EnterInsertLineEnd struct {
func (a EnterInsertLineEnd) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
win.SetCursorCol(buf.Lines[win.Cursor.Line].Len())
win.SetCursorCol(len(buf.Lines[win.Cursor.Line]))
// Start recording
m.SetInsertRecording(a.Count, a)
@ -158,7 +158,7 @@ func (a InsertChar) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
l := buf.Lines[y]
if x < len(l) {
buf.SetLine(y, l[:x]+a.Char+l[x:])
} else {
@ -177,7 +177,7 @@ func (a InsertNewline) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
l := buf.Lines[y]
if x == len(l) {
buf.InsertLine(y+1, "")
} else {
@ -197,12 +197,12 @@ func (a InsertBackspace) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
l := buf.Lines[y]
if x > 0 {
buf.SetLine(y, l[:x-1]+l[x:])
win.SetCursorCol(x - 1)
} else if y > 0 {
prevLine := buf.Line(y - 1)
prevLine := buf.Lines[y-1]
newX := len(prevLine)
buf.SetLine(y-1, prevLine+l)
buf.DeleteLine(y)
@ -220,9 +220,9 @@ func (a InsertDelete) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
l := buf.Lines[y]
if x == len(l) && y < buf.LineCount()-1 {
nextLine := buf.Line(y + 1)
nextLine := buf.Lines[y+1]
buf.SetLine(y, l+nextLine)
buf.DeleteLine(y + 1)
} else if x < len(l) {
@ -240,7 +240,7 @@ func (a InsertTab) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
l := buf.Lines[y]
tabs := strings.Repeat(" ", m.Settings().TabStop)
if x < len(l) {
buf.SetLine(y, l[:x]+tabs+l[x:])
@ -275,12 +275,12 @@ func (a InsertDeletePreviousWord) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
line := buf.Line(y)
line := buf.Lines[y]
// At start of line: merge with previous line (same as backspace)
if x == 0 {
if y > 0 {
prevLine := buf.Line(y - 1)
prevLine := buf.Lines[y-1]
newX := len(prevLine)
buf.SetLine(y-1, prevLine+line)
buf.DeleteLine(y)

View File

@ -63,7 +63,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
x := win.Cursor.Col
y := win.Cursor.Line
curLine := buf.Line(y)
curLine := buf.Lines[y]
insertAt := min(x+1, len(curLine))
if len(lines) == 1 {
@ -101,11 +101,11 @@ func (a Paste) Execute(m Model) tea.Cmd {
// Last line: append the suffix
lastLineIdx := y + len(pastedLines) - 1
buf.SetLine(lastLineIdx, buf.Line(lastLineIdx)+suffix)
buf.SetLine(lastLineIdx, buf.Lines[lastLineIdx]+suffix)
// Set cursor to end of last pasted content (before suffix)
win.SetCursorLine(lastLineIdx)
win.SetCursorCol(len(buf.Line(lastLineIdx)) - len(suffix) - 1)
win.SetCursorCol(len(buf.Lines[lastLineIdx]) - len(suffix) - 1)
}
}
default:
@ -175,7 +175,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
x := win.Cursor.Col
y := win.Cursor.Line
curLine := buf.Line(y)
curLine := buf.Lines[y]
insertAt := min(x, len(curLine))
if len(lines) == 1 {
@ -213,11 +213,11 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
// Last line: append the suffix
lastLineIdx := y + len(pastedLines) - 1
buf.SetLine(lastLineIdx, buf.Line(lastLineIdx)+suffix)
buf.SetLine(lastLineIdx, buf.Lines[lastLineIdx]+suffix)
// Set cursor to end of last pasted content (before suffix)
win.SetCursorLine(lastLineIdx)
win.SetCursorCol(len(buf.Line(lastLineIdx)) - len(suffix) - 1)
win.SetCursorCol(len(buf.Lines[lastLineIdx]) - len(suffix) - 1)
}
}
default:
@ -240,11 +240,9 @@ func (a PasteBefore) WithCount(n int) Action {
return PasteBefore{Count: n}
}
// VisualPaste implements Action (p/p in visual mode) - replaces selection with
// register content when Replace flag is set
// VisualPaste implements Action (p in visual mode) - replaces selection with register content
type VisualPaste struct {
Count int
Replace bool
Count int
}
// VisualPaste.Execute: Replaces visual selection with register content (p in visual mode).
@ -268,11 +266,11 @@ func (a VisualPaste) Execute(m Model) tea.Cmd {
switch mode {
case core.VisualMode:
visualCharPaste(m, reg, start, end, a.Replace)
visualCharPaste(m, reg, start, end)
case core.VisualBlockMode:
visualBlockPaste(m, reg, start, end, a.Replace)
visualBlockPaste(m, reg, start, end)
case core.VisualLineMode:
visualLinePaste(m, reg, start, end, a.Replace)
visualLinePaste(m, reg, start, end)
}
// Exit visual mode
@ -297,7 +295,7 @@ func normalizeSelection(m Model) (core.Position, core.Position) {
}
// visualCharPaste: Handles paste operation in visual (character) mode.
func visualCharPaste(m Model, reg core.Register, start, end core.Position, replace bool) {
func visualCharPaste(m Model, reg core.Register, start, end core.Position) {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
@ -313,7 +311,7 @@ func visualCharPaste(m Model, reg core.Register, start, end core.Position, repla
} else if reg.Type == core.CharwiseRegister {
// Charwise paste: insert text at cursor position
if len(reg.Content) == 1 {
line := buf.Line(start.Line)
line := buf.Lines[start.Line]
insertAt := min(start.Col, len(line))
newLine := line[:insertAt] + reg.Content[0] + line[insertAt:]
buf.SetLine(start.Line, newLine)
@ -328,7 +326,7 @@ func visualCharPaste(m Model, reg core.Register, start, end core.Position, repla
for i, content := range reg.Content {
if i == 0 {
// First line: insert at start position
line := buf.Line(start.Line)
line := buf.Lines[start.Line]
insertAt := min(start.Col, len(line))
newLine := line[:insertAt] + content
if len(reg.Content) == 1 {
@ -346,13 +344,11 @@ func visualCharPaste(m Model, reg core.Register, start, end core.Position, repla
}
// Update register with deleted text
if replace {
m.UpdateDefaultRegister(core.CharwiseRegister, []string{deletedText})
}
m.UpdateDefaultRegister(core.CharwiseRegister, []string{deletedText})
}
// visualBlockPaste: Handles paste operation in visual block mode.
func visualBlockPaste(m Model, reg core.Register, start, end core.Position, replace bool) {
func visualBlockPaste(m Model, reg core.Register, start, end core.Position) {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
@ -362,7 +358,7 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
// Extract deleted text (for register)
var deletedLines []string
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
line := buf.Lines[y]
if startCol < len(line) {
ec := min(endCol+1, len(line))
deletedLines = append(deletedLines, line[startCol:ec])
@ -373,7 +369,7 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
// Delete the block selection
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
line := buf.Lines[y]
if startCol >= len(line) {
continue
}
@ -389,7 +385,7 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
}
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
line := buf.Lines[y]
insertAt := min(startCol, len(line))
// Pad with spaces if needed
for len(line) < insertAt {
@ -404,20 +400,18 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
win.SetCursorCol(startCol)
// Update register with deleted block text (joined)
if replace {
m.UpdateDefaultRegister(core.CharwiseRegister, []string{strings.Join(deletedLines, "\n")})
}
m.UpdateDefaultRegister(core.CharwiseRegister, []string{strings.Join(deletedLines, "\n")})
}
// visualLinePaste: Handles paste operation in visual line mode.
func visualLinePaste(m Model, reg core.Register, start, end core.Position, replace bool) {
func visualLinePaste(m Model, reg core.Register, start, end core.Position) {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
// Extract deleted lines (for register)
var deletedLines []string
for y := start.Line; y <= end.Line; y++ {
deletedLines = append(deletedLines, buf.Line(y))
deletedLines = append(deletedLines, buf.Lines[y])
}
// Delete the selected lines (from end to start to preserve indices)
@ -457,9 +451,7 @@ func visualLinePaste(m Model, reg core.Register, start, end core.Position, repla
win.SetCursorCol(0)
// Update register with deleted lines
if replace {
m.UpdateDefaultRegister(core.LinewiseRegister, deletedLines)
}
m.UpdateDefaultRegister(core.LinewiseRegister, deletedLines)
}
// extractCharSelection: Extracts text from a character selection range.
@ -467,7 +459,7 @@ func extractCharSelection(m Model, start, end core.Position) string {
buf := m.ActiveBuffer()
if start.Line == end.Line {
line := buf.Line(start.Line)
line := buf.Lines[start.Line]
endCol := min(end.Col+1, len(line))
startCol := min(start.Col, len(line))
if startCol >= endCol {
@ -480,7 +472,7 @@ func extractCharSelection(m Model, start, end core.Position) string {
var result strings.Builder
// First line: from start.Col to end
firstLine := buf.Line(start.Line)
firstLine := buf.Lines[start.Line]
if start.Col < len(firstLine) {
result.WriteString(firstLine[start.Col:])
}
@ -488,12 +480,12 @@ func extractCharSelection(m Model, start, end core.Position) string {
// Middle lines: entire lines
for y := start.Line + 1; y < end.Line; y++ {
result.WriteString(buf.Line(y))
result.WriteString(buf.Lines[y])
result.WriteString("\n")
}
// Last line: from beginning to end.Col
lastLine := buf.Line(end.Line)
lastLine := buf.Lines[end.Line]
endCol := min(end.Col+1, len(lastLine))
result.WriteString(lastLine[:endCol])
@ -506,12 +498,12 @@ func deleteCharSelectionForPaste(m Model, start, end core.Position) {
buf := m.ActiveBuffer()
if start.Line == end.Line {
line := buf.Line(start.Line)
line := buf.Lines[start.Line]
endCol := min(end.Col+1, len(line))
buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
} else {
startLine := buf.Line(start.Line)
endLine := buf.Line(end.Line)
startLine := buf.Lines[start.Line]
endLine := buf.Lines[end.Line]
prefix := ""
if start.Col < len(startLine) {
@ -539,5 +531,5 @@ var _ Repeatable = VisualPaste{}
// VisualPaste.WithCount: Returns a new VisualPaste with the given count.
func (a VisualPaste) WithCount(n int) Action {
return VisualPaste{Count: n, Replace: a.Replace}
return VisualPaste{Count: n}
}

View File

@ -1,129 +0,0 @@
package action
import (
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
type ReplaceChar struct {
Char string
Count int
}
func (m ReplaceChar) WithChar(char string) Motion {
m.Char = char
return m
}
func (m ReplaceChar) Type() core.MotionType {
return core.CharwiseInclusive
}
// WithCount sets the count (required by Repeatable interface)
func (m ReplaceChar) WithCount(n int) Action {
m.Count = n
return m
}
func (a ReplaceChar) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
if buf.UndoStack != nil && !buf.UndoStack.Recording() {
buf.UndoStack.BeginBlock(win.Cursor)
}
pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line)
for i := 0; i < a.Count && pos < len(line); i++ {
line = line[:pos] + a.Char + line[pos+1:]
buf.SetLine(win.Cursor.Line, line)
pos++
}
win.SetCursorCol(pos - 1)
m.SetMode(core.NormalMode)
if buf.UndoStack != nil {
buf.UndoStack.EndBlock(win.Cursor)
}
return nil
}
type EnterReplace struct {
Count int
}
func (a EnterReplace) WithCount(n int) Action {
a.Count = n
return a
}
func (a EnterReplace) Execute(m Model) tea.Cmd {
m.SetMode(core.ReplaceMode)
return nil
}
type ReplaceModeChar struct {
Char string
}
// ReplaceModeChar.Execute: Inserts a single character at the cursor position, overwriting current
// character.
func (a ReplaceModeChar) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
if x < len(l) {
buf.SetLine(y, l[:x]+a.Char+l[x+1:])
} else {
buf.SetLine(y, l+a.Char)
}
win.SetCursorCol(x + len(a.Char))
return nil
}
// ReplaceNewline splits the current line at the cursor (enter key)
type ReplaceNewline struct{}
// ReplaceNewline.Execute: Splits the current line at the cursor (Enter key).
func (a ReplaceNewline) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
if x == len(l) {
buf.InsertLine(y+1, "")
} else {
buf.SetLine(y, l[:x])
buf.InsertLine(y+1, l[x+1:])
}
win.SetCursorPos(y+1, 0)
return nil
}
// ReplaceTab inserts spaces equal to the tab size
type ReplaceTab struct{}
// ReplaceTab.Execute: Inserts spaces equal to the tab size (Tab key).
func (a ReplaceTab) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
tabs := strings.Repeat(" ", m.Settings().TabStop)
if x < len(l) {
buf.SetLine(y, l[:x]+tabs+l[x+1:])
} else {
buf.SetLine(y, l+tabs)
}
win.SetCursorCol(x + len(tabs))
return nil
}

View File

@ -63,7 +63,7 @@ func writeBuffer(m action.Model, buf *core.Buffer, args []string, force bool) (t
// Using a bufio.Writer because its more efficient
writer := bufio.NewWriter(file)
for _, line := range buf.Lines {
n, err := writer.WriteString(line.String() + "\n")
n, err := writer.WriteString(line + "\n")
if err != nil {
return nil, err
}

View File

@ -20,7 +20,7 @@ type Buffer struct {
// File data
Filename string
Filetype string
Lines []*GapBuffer // Changed from []string to []*GapBuffer
Lines []string
// Flags (not used yet)
Modified bool
@ -42,7 +42,7 @@ func (b *Buffer) Line(idx int) string {
if idx < 0 || idx >= len(b.Lines) {
return ""
}
return b.Lines[idx].String()
return b.Lines[idx]
}
// Buffer.SetLine: Set the content of the line at an index. Does nothing if the
@ -51,9 +51,9 @@ func (b *Buffer) SetLine(idx int, content string) {
if idx >= 0 && idx < len(b.Lines) {
// Record set line in undo stack
if b.UndoStack != nil {
b.UndoStack.RecordSetLine(idx, b.Lines[idx].String(), content)
b.UndoStack.RecordSetLine(idx, b.Lines[idx], content)
}
b.Lines[idx].Set(content)
b.Lines[idx] = content
}
b.Modified = true
}
@ -74,8 +74,7 @@ func (b *Buffer) InsertLine(idx int, content string) {
b.UndoStack.RecordInsertLine(idx, content)
}
newLine := NewGapBuffer(content)
b.Lines = append(b.Lines[:idx], append([]*GapBuffer{newLine}, b.Lines[idx:]...)...)
b.Lines = append(b.Lines[:idx], append([]string{content}, b.Lines[idx:]...)...)
b.Modified = true
}
@ -85,7 +84,7 @@ func (b *Buffer) DeleteLine(idx int) {
if idx >= 0 && idx < len(b.Lines) {
// Record delete line in undo stack
if b.UndoStack != nil {
b.UndoStack.RecordDeleteLine(idx, b.Lines[idx].String())
b.UndoStack.RecordDeleteLine(idx, b.Lines[idx])
}
b.Lines = append(b.Lines[:idx], b.Lines[idx+1:]...)
}
@ -122,7 +121,7 @@ func (b *Buffer) Undo(w *Window) bool {
case SetLineChange:
// Restore old data
if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines[change.Line].Set(change.OldData)
b.Lines[change.Line] = change.OldData
}
case InsertLineChange:
// Remove the inserted line
@ -132,8 +131,7 @@ func (b *Buffer) Undo(w *Window) bool {
case DeleteLineChange:
// Re-insert the deleted line
if change.Line <= len(b.Lines) {
newLine := NewGapBuffer(change.OldData)
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
b.Lines = append(b.Lines[:change.Line], append([]string{change.OldData}, b.Lines[change.Line:]...)...)
}
}
@ -167,13 +165,12 @@ func (b *Buffer) Redo(w *Window) bool {
case SetLineChange:
// Apply new data
if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines[change.Line].Set(change.NewData)
b.Lines[change.Line] = change.NewData
}
case InsertLineChange:
// Re-insert the line
if change.Line <= len(b.Lines) {
newLine := NewGapBuffer(change.NewData)
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
b.Lines = append(b.Lines[:change.Line], append([]string{change.NewData}, b.Lines[change.Line:]...)...)
}
case DeleteLineChange:
// Re-delete the line
@ -211,10 +208,7 @@ func (b *Buffer) SetFiletype(filetype string) {
// Buffer.SetLines: Replace all lines in the buffer with the provided lines.
// This is useful when loading a file or resetting buffer content.
func (b *Buffer) SetLines(lines []string) {
b.Lines = make([]*GapBuffer, len(lines))
for i, line := range lines {
b.Lines[i] = NewGapBuffer(line)
}
b.Lines = lines
}
// Buffer.SetModified: Set the modified flag for this buffer. A modified buffer

View File

@ -16,7 +16,7 @@ func NewBufferBuilder() *BufferBuilder {
Type: ScatchBuffer, // Default buffer type
Filename: "",
Filetype: "",
Lines: []*GapBuffer{NewEmptyGapBuffer()},
Lines: []string{""},
Modified: false,
Loaded: false,
Listed: false,
@ -40,10 +40,7 @@ func (b *BufferBuilder) WithFiletype(filetype string) *BufferBuilder {
// BufferBuilder.WithLines: Attaches a lines to the buffer that is being built.
func (b *BufferBuilder) WithLines(lines []string) *BufferBuilder {
b.buffer.Lines = make([]*GapBuffer, len(lines))
for i, line := range lines {
b.buffer.Lines[i] = NewGapBuffer(line)
}
b.buffer.Lines = lines
return b
}

View File

@ -1,180 +0,0 @@
package core
// GapBuffer represents a single line of text using the gap buffer data structure.
// It maintains a gap (empty space) in the buffer where the cursor is positioned,
// making insertions and deletions at the cursor position very efficient (O(1)).
type GapBuffer struct {
buffer []rune // The underlying buffer containing text and gap
gapStart int // Index where the gap starts
gapEnd int // Index where the gap ends (exclusive)
}
// NewGapBuffer: creates a new gap buffer with the given initial content.
// The gap is positioned at the end of the content.
func NewGapBuffer(content string) *GapBuffer {
runes := []rune(content)
initialCapacity := len(runes) + 16 // Extra space for the gap
buffer := make([]rune, initialCapacity)
copy(buffer, runes)
return &GapBuffer{
buffer: buffer,
gapStart: len(runes),
gapEnd: initialCapacity,
}
}
// NewEmptyGapBuffer: Creates a new empty gap buffer with default capacity.
func NewEmptyGapBuffer() *GapBuffer {
initialCapacity := 16
return &GapBuffer{
buffer: make([]rune, initialCapacity),
gapStart: 0,
gapEnd: initialCapacity,
}
}
// GapBuffer.String: Converts the gap buffer to a string, excluding the gap.
func (gb *GapBuffer) String() string {
result := make([]rune, 0, gb.Len())
result = append(result, gb.buffer[:gb.gapStart]...)
result = append(result, gb.buffer[gb.gapEnd:]...)
return string(result)
}
// GapBuffer.Len: Returns the length of the text (excluding the gap).
func (gb *GapBuffer) Len() int {
return len(gb.buffer) - (gb.gapEnd - gb.gapStart)
}
// GapBuffer.GapSize: Returns the current size of the gap.
func (gb *GapBuffer) GapSize() int {
return gb.gapEnd - gb.gapStart
}
// GapBuffer.Insert: Inserts a string at the specified position.
// This moves the gap to the position first, then inserts the text.
func (gb *GapBuffer) Insert(pos int, text string) {
if pos < 0 || pos > gb.Len() {
return // Invalid position
}
runes := []rune(text)
if len(runes) == 0 {
return
}
// Move gap to insertion position
gb.moveGap(pos)
// Ensure gap is large enough
if gb.GapSize() < len(runes) {
gb.grow(len(runes))
}
// Insert runes at gap start
copy(gb.buffer[gb.gapStart:], runes)
gb.gapStart += len(runes)
}
// GapBuffer.Delete: Deletes count runes starting at position pos.
func (gb *GapBuffer) Delete(pos, count int) {
if pos < 0 || pos >= gb.Len() || count <= 0 {
return
}
// Clamp count to not exceed buffer length
if pos+count > gb.Len() {
count = gb.Len() - pos
}
// Move gap to deletion position
gb.moveGap(pos)
// Expand gap to absorb deleted characters
gb.gapEnd += count
}
// GapBuffer.RuneAt: Returns the rune at the specified position.
func (gb *GapBuffer) RuneAt(pos int) rune {
if pos < 0 || pos >= gb.Len() {
return 0
}
if pos < gb.gapStart {
return gb.buffer[pos]
}
return gb.buffer[pos+gb.GapSize()]
}
// GapBuffer.Substring: Returns a substring from start to end (exclusive).
func (gb *GapBuffer) Substring(start, end int) string {
if start < 0 {
start = 0
}
if end > gb.Len() {
end = gb.Len()
}
if start >= end {
return ""
}
result := make([]rune, 0, end-start)
for i := start; i < end; i++ {
result = append(result, gb.RuneAt(i))
}
return string(result)
}
// GapBuffer.moveGap: Moves the gap to the specified position.
func (gb *GapBuffer) moveGap(pos int) {
if pos < gb.gapStart {
// Move gap left: shift text from [pos, gapStart) to [gapEnd-delta, gapEnd)
delta := gb.gapStart - pos
copy(gb.buffer[gb.gapEnd-delta:gb.gapEnd], gb.buffer[pos:gb.gapStart])
gb.gapStart = pos
gb.gapEnd -= delta
} else if pos > gb.gapStart {
// Move gap right: shift text from [gapEnd, gapEnd+delta) to [gapStart, gapStart+delta)
delta := pos - gb.gapStart
copy(gb.buffer[gb.gapStart:gb.gapStart+delta], gb.buffer[gb.gapEnd:gb.gapEnd+delta])
gb.gapStart += delta
gb.gapEnd += delta
}
}
// GapBuffer.grow: Expands the buffer to accommodate at least minGapSize additional characters.
func (gb *GapBuffer) grow(minGapSize int) {
oldLen := len(gb.buffer)
newGapSize := minGapSize * 2 // Double the required size for future insertions
newLen := oldLen + newGapSize - gb.GapSize()
newBuffer := make([]rune, newLen)
// Copy text before gap
copy(newBuffer, gb.buffer[:gb.gapStart])
// Copy text after gap to new position
newGapEnd := newLen - (oldLen - gb.gapEnd)
copy(newBuffer[newGapEnd:], gb.buffer[gb.gapEnd:])
gb.buffer = newBuffer
gb.gapEnd = newGapEnd
}
// GapBuffer.Set: Replaces the entire content of the gap buffer.
func (gb *GapBuffer) Set(content string) {
runes := []rune(content)
capacity := len(runes) + 16
gb.buffer = make([]rune, capacity)
copy(gb.buffer, runes)
gb.gapStart = len(runes)
gb.gapEnd = capacity
}
// GapBuffer.Clear: Removes all content from the gap buffer.
func (gb *GapBuffer) Clear() {
gb.gapStart = 0
gb.gapEnd = len(gb.buffer)
}

View File

@ -1,455 +0,0 @@
package core
import "testing"
func TestGapBufferString(t *testing.T) {
t.Run("gapBuffer.String() on empty buffer returns \"\"", func(t *testing.T) {
buf := NewEmptyGapBuffer()
str := buf.String()
if str != "" {
t.Fatalf("buf.String() expected '', got '%s'", str)
}
})
t.Run("gapBuffer.String() on string returns the string", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
str := buf.String()
if str != "Hello world" {
t.Fatalf("buf.String() expected 'Hello world', got '%s'", str)
}
})
t.Run("gapBuffer.String() after moving gap returns the string", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
str := buf.String()
if str != "Hello world" {
t.Fatalf("buf.String() expected 'Hello world', got '%s'", str)
}
})
t.Run("gapBuffer.String() after growing gap returns the string", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.grow(16)
str := buf.String()
if str != "Hello world" {
t.Fatalf("buf.String() expected 'Hello world', got '%s'", str)
}
})
t.Run("gapBuffer.String() handles unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hello 世界 🌍")
str := buf.String()
if str != "Hello 世界 🌍" {
t.Fatalf("buf.String() expected 'Hello 世界 🌍', got '%s'", str)
}
})
}
func TestGapBufferLen(t *testing.T) {
t.Run("empty buffer has length 0", func(t *testing.T) {
buf := NewEmptyGapBuffer()
if buf.Len() != 0 {
t.Fatalf("expected length 0, got %d", buf.Len())
}
})
t.Run("buffer with content returns correct length", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Len() != 5 {
t.Fatalf("expected length 5, got %d", buf.Len())
}
})
t.Run("length with unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hi 🌍")
if buf.Len() != 4 {
t.Fatalf("expected length 4, got %d", buf.Len())
}
})
}
func TestGapBufferInsert(t *testing.T) {
t.Run("insert at beginning", func(t *testing.T) {
buf := NewGapBuffer("world")
buf.Insert(0, "Hello ")
if buf.String() != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.String())
}
})
t.Run("insert at end", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(5, " world")
if buf.String() != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.String())
}
})
t.Run("insert in middle", func(t *testing.T) {
buf := NewGapBuffer("Helloworld")
buf.Insert(5, " ")
if buf.String() != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.String())
}
})
t.Run("insert into empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Insert(0, "Hello")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("insert empty string does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(2, "")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("insert at invalid position does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(-1, "X")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
buf.Insert(100, "X")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("insert unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Insert(5, " 世界")
if buf.String() != "Hello 世界" {
t.Fatalf("expected 'Hello 世界', got '%s'", buf.String())
}
})
t.Run("multiple consecutive insertions", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Insert(0, "a")
buf.Insert(1, "b")
buf.Insert(2, "c")
if buf.String() != "abc" {
t.Fatalf("expected 'abc', got '%s'", buf.String())
}
})
t.Run("insert large text triggers grow", func(t *testing.T) {
buf := NewGapBuffer("Hello")
longText := "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
buf.Insert(5, longText)
expected := "Hello" + longText
if buf.String() != expected {
t.Fatalf("expected '%s', got '%s'", expected, buf.String())
}
})
}
func TestGapBufferDelete(t *testing.T) {
t.Run("delete from beginning", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Delete(0, 6)
if buf.String() != "world" {
t.Fatalf("expected 'world', got '%s'", buf.String())
}
})
t.Run("delete from end", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Delete(5, 6)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("delete from middle", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Delete(5, 1)
if buf.String() != "Helloworld" {
t.Fatalf("expected 'Helloworld', got '%s'", buf.String())
}
})
t.Run("delete single character", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(1, 1)
if buf.String() != "Hllo" {
t.Fatalf("expected 'Hllo', got '%s'", buf.String())
}
})
t.Run("delete all content", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(0, 5)
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("delete with count exceeding length", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(2, 100)
if buf.String() != "He" {
t.Fatalf("expected 'He', got '%s'", buf.String())
}
})
t.Run("delete at invalid position does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(-1, 2)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
buf.Delete(100, 2)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("delete with zero count does nothing", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Delete(2, 0)
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("delete unicode characters", func(t *testing.T) {
buf := NewGapBuffer("Hello 世界")
buf.Delete(6, 2)
if buf.String() != "Hello " {
t.Fatalf("expected 'Hello ', got '%s'", buf.String())
}
})
}
func TestGapBufferRuneAt(t *testing.T) {
t.Run("get rune at valid position", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.RuneAt(0) != 'H' {
t.Fatalf("expected 'H', got '%c'", buf.RuneAt(0))
}
if buf.RuneAt(4) != 'o' {
t.Fatalf("expected 'o', got '%c'", buf.RuneAt(4))
}
})
t.Run("get rune before gap", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
if buf.RuneAt(5) != ' ' {
t.Fatalf("expected ' ', got '%c'", buf.RuneAt(5))
}
})
t.Run("get rune after gap", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
if buf.RuneAt(6) != 'w' {
t.Fatalf("expected 'w', got '%c'", buf.RuneAt(6))
}
})
t.Run("get rune at invalid position returns 0", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.RuneAt(-1) != 0 {
t.Fatalf("expected 0, got '%c'", buf.RuneAt(-1))
}
if buf.RuneAt(100) != 0 {
t.Fatalf("expected 0, got '%c'", buf.RuneAt(100))
}
})
t.Run("get unicode rune", func(t *testing.T) {
buf := NewGapBuffer("🌍世界")
if buf.RuneAt(0) != '🌍' {
t.Fatalf("expected '🌍', got '%c'", buf.RuneAt(0))
}
if buf.RuneAt(1) != '世' {
t.Fatalf("expected '世', got '%c'", buf.RuneAt(1))
}
})
}
func TestGapBufferSubstring(t *testing.T) {
t.Run("substring from middle", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
if buf.Substring(0, 5) != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.Substring(0, 5))
}
if buf.Substring(6, 11) != "world" {
t.Fatalf("expected 'world', got '%s'", buf.Substring(6, 11))
}
})
t.Run("substring entire content", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Substring(0, 5) != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.Substring(0, 5))
}
})
t.Run("substring with start >= end returns empty", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Substring(3, 3) != "" {
t.Fatalf("expected '', got '%s'", buf.Substring(3, 3))
}
if buf.Substring(3, 2) != "" {
t.Fatalf("expected '', got '%s'", buf.Substring(3, 2))
}
})
t.Run("substring clamps to buffer bounds", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.Substring(-5, 3) != "Hel" {
t.Fatalf("expected 'Hel', got '%s'", buf.Substring(-5, 3))
}
if buf.Substring(2, 100) != "llo" {
t.Fatalf("expected 'llo', got '%s'", buf.Substring(2, 100))
}
})
t.Run("substring with gap in middle", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.moveGap(6)
if buf.Substring(0, 11) != "Hello world" {
t.Fatalf("expected 'Hello world', got '%s'", buf.Substring(0, 11))
}
})
t.Run("substring with unicode", func(t *testing.T) {
buf := NewGapBuffer("Hi 世界")
if buf.Substring(3, 5) != "世界" {
t.Fatalf("expected '世界', got '%s'", buf.Substring(3, 5))
}
})
}
func TestGapBufferSet(t *testing.T) {
t.Run("set replaces content", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Set("Goodbye")
if buf.String() != "Goodbye" {
t.Fatalf("expected 'Goodbye', got '%s'", buf.String())
}
})
t.Run("set to empty string", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Set("")
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("set on empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Set("Hello")
if buf.String() != "Hello" {
t.Fatalf("expected 'Hello', got '%s'", buf.String())
}
})
t.Run("set resets gap position", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.moveGap(2)
buf.Set("World")
if buf.String() != "World" {
t.Fatalf("expected 'World', got '%s'", buf.String())
}
if buf.gapStart != 5 {
t.Fatalf("expected gap start at 5, got %d", buf.gapStart)
}
})
}
func TestGapBufferClear(t *testing.T) {
t.Run("clear removes all content", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Clear()
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("clear on empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Clear()
if buf.String() != "" {
t.Fatalf("expected '', got '%s'", buf.String())
}
})
t.Run("clear resets gap to start", func(t *testing.T) {
buf := NewGapBuffer("Hello")
buf.Clear()
if buf.gapStart != 0 {
t.Fatalf("expected gap start at 0, got %d", buf.gapStart)
}
if buf.gapEnd != len(buf.buffer) {
t.Fatalf("expected gap end at %d, got %d", len(buf.buffer), buf.gapEnd)
}
})
}
func TestGapBufferGapSize(t *testing.T) {
t.Run("gap size on new buffer", func(t *testing.T) {
buf := NewGapBuffer("Hello")
if buf.GapSize() != 16 {
t.Fatalf("expected gap size 16, got %d", buf.GapSize())
}
})
t.Run("gap size on empty buffer", func(t *testing.T) {
buf := NewEmptyGapBuffer()
if buf.GapSize() != 16 {
t.Fatalf("expected gap size 16, got %d", buf.GapSize())
}
})
t.Run("gap size decreases after insert", func(t *testing.T) {
buf := NewGapBuffer("Hello")
initialGapSize := buf.GapSize()
buf.Insert(5, "XX")
if buf.GapSize() != initialGapSize-2 {
t.Fatalf("expected gap size %d, got %d", initialGapSize-2, buf.GapSize())
}
})
}
func TestGapBufferComplex(t *testing.T) {
t.Run("sequence of operations", func(t *testing.T) {
buf := NewEmptyGapBuffer()
buf.Insert(0, "Hello")
buf.Insert(5, " world")
buf.Delete(5, 1)
buf.Insert(5, ", ")
if buf.String() != "Hello, world" {
t.Fatalf("expected 'Hello, world', got '%s'", buf.String())
}
})
t.Run("insert and delete at different positions", func(t *testing.T) {
buf := NewGapBuffer("abcdefgh")
buf.Delete(2, 4)
buf.Insert(2, "XX")
if buf.String() != "abXXgh" {
t.Fatalf("expected 'abXXgh', got '%s'", buf.String())
}
})
t.Run("editing with gap movement", func(t *testing.T) {
buf := NewGapBuffer("Hello world")
buf.Insert(0, ">> ")
buf.Insert(buf.Len(), " <<")
if buf.String() != ">> Hello world <<" {
t.Fatalf("expected '>> Hello world <<', got '%s'", buf.String())
}
})
}

View File

@ -11,15 +11,13 @@ const (
VisualMode
VisualLineMode
VisualBlockMode
ReplaceMode
WaitingMode // Same as NORMAL output, but cursor is the REPLACE cursor
)
// Mode.ToString: Returns a human-readable string representation of the mode
// for display in the status bar.
func (m Mode) ToString() string {
switch m {
case NormalMode, WaitingMode:
case NormalMode:
return "NORMAL"
case InsertMode:
return "INSERT"
@ -31,8 +29,6 @@ func (m Mode) ToString() string {
return "V-LINE"
case VisualBlockMode:
return "V-BLOCK"
case ReplaceMode:
return "REPLACE"
default:
return "-----"
}

View File

@ -60,7 +60,7 @@ func (w *Window) ClampCursor() {
}
// Clamp column to valid range [0, lineLen]
lineLen := w.Buffer.Lines[w.Cursor.Line].Len()
lineLen := len(w.Buffer.Lines[w.Cursor.Line])
if w.Cursor.Col < 0 {
w.Cursor.Col = 0
} else if lineLen == 0 {

View File

@ -34,16 +34,6 @@ func sendKeys(tm *teatest.TestModel, keys ...string) {
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlR})
case "ctrl+w":
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlW})
case "tab":
tm.Send(tea.KeyMsg{Type: tea.KeyTab})
case "left":
tm.Send(tea.KeyMsg{Type: tea.KeyLeft})
case "right":
tm.Send(tea.KeyMsg{Type: tea.KeyRight})
case "up":
tm.Send(tea.KeyMsg{Type: tea.KeyUp})
case "down":
tm.Send(tea.KeyMsg{Type: tea.KeyDown})
default:
tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)})
}

View File

@ -13,8 +13,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
}
})
@ -24,8 +24,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
}
})
@ -35,8 +35,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hell" {
t.Errorf("lines[0] = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("lines[0] = %q, want 'hell'", m.ActiveBuffer().Lines[0])
}
})
@ -46,8 +46,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x", "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
}
})
}
@ -59,8 +59,8 @@ func TestDeleteCharWithCount(t *testing.T) {
sendKeys(tm, "3", "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0])
}
})
@ -70,8 +70,8 @@ func TestDeleteCharWithCount(t *testing.T) {
sendKeys(tm, "1", "0", "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -81,8 +81,8 @@ func TestDeleteCharWithCount(t *testing.T) {
sendKeys(tm, "2", "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hlo" {
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hlo" {
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0])
}
})
}
@ -94,8 +94,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -108,8 +108,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -119,8 +119,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
}
})
@ -130,8 +130,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab c" {
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ab c" {
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0])
}
})
@ -144,8 +144,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
}
})
@ -155,8 +155,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x", "x", "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" {
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0])
}
})
@ -166,8 +166,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab" {
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ab" {
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0])
}
})
@ -177,8 +177,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "5", "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -188,8 +188,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "abde" {
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "abde" {
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0])
}
})
}
@ -201,8 +201,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
}
})
@ -212,8 +212,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
}
})
@ -223,8 +223,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
}
})
@ -234,8 +234,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X", "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
}
})
}
@ -247,8 +247,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
sendKeys(tm, "3", "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0])
}
})
@ -258,8 +258,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
sendKeys(tm, "1", "0", "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "o" {
t.Errorf("lines[0] = %q, want 'o'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "o" {
t.Errorf("lines[0] = %q, want 'o'", m.ActiveBuffer().Lines[0])
}
})
@ -269,8 +269,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
sendKeys(tm, "2", "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hlo" {
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hlo" {
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0])
}
})
}
@ -282,8 +282,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -296,8 +296,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -310,8 +310,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
}
})
@ -321,8 +321,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "b" {
t.Errorf("Line(0) = %q, want 'b'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "b" {
t.Errorf("Line(0) = %q, want 'b'", m.ActiveBuffer().Lines[0])
}
})
@ -332,8 +332,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab c" {
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ab c" {
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0])
}
})
@ -346,8 +346,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
}
})
@ -357,8 +357,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X", "X", "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" {
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0])
}
})
@ -368,8 +368,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab" {
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ab" {
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0])
}
})
@ -379,8 +379,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "5", "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "c" {
t.Errorf("Line(0) = %q, want 'c'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "c" {
t.Errorf("Line(0) = %q, want 'c'", m.ActiveBuffer().Lines[0])
}
})
@ -390,8 +390,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "abde" {
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "abde" {
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0])
}
})
@ -413,8 +413,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "3", "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" {
t.Errorf("Line(0) = %q, want 'ho'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("Line(0) = %q, want 'ho'", m.ActiveBuffer().Lines[0])
}
// Cursor should be at position 1 after deleting 3 chars backward from position 4
if m.ActiveWindow().Cursor.Col != 1 {
@ -428,11 +428,11 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
}
})
@ -442,8 +442,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " " {
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " " {
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0])
}
})
@ -453,8 +453,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" {
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 3 {
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
@ -469,8 +469,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -480,8 +480,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -491,8 +491,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0])
}
})
@ -514,8 +514,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -528,11 +528,11 @@ func TestDeleteToEndOfLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %q, want '3'", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "hi" {
t.Errorf("Line(1) = %q, want 'hi'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "hi" {
t.Errorf("Line(1) = %q, want 'hi'", m.ActiveBuffer().Lines[1])
}
})
@ -545,8 +545,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %q, want '1'", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
}
})
}
@ -558,8 +558,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -572,8 +572,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
})
@ -583,11 +583,11 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[2].String() != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2])
}
})
@ -597,8 +597,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
}
// Cursor should clamp to last char
if m.ActiveWindow().Cursor.Col != 1 {
@ -612,8 +612,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -623,8 +623,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " " {
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " " {
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0])
}
})
@ -634,8 +634,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -645,8 +645,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
}
})
@ -656,8 +656,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[1].String() != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
}
})
@ -667,8 +667,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "first" {
t.Errorf("Line(0) = %q, want 'first'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "first" {
t.Errorf("Line(0) = %q, want 'first'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())

View File

@ -25,8 +25,8 @@ func TestEnterInsert(t *testing.T) {
sendKeys(tm, "i", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
}
})
@ -36,8 +36,8 @@ func TestEnterInsert(t *testing.T) {
sendKeys(tm, "i", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
}
})
@ -70,8 +70,8 @@ func TestEnterInsertAfter(t *testing.T) {
sendKeys(tm, "a", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hXello" {
t.Errorf("lines[0] = %q, want 'hXello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hXello" {
t.Errorf("lines[0] = %q, want 'hXello'", m.ActiveBuffer().Lines[0])
}
})
@ -81,8 +81,8 @@ func TestEnterInsertAfter(t *testing.T) {
sendKeys(tm, "a", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helXlo" {
t.Errorf("lines[0] = %q, want 'helXlo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helXlo" {
t.Errorf("lines[0] = %q, want 'helXlo'", m.ActiveBuffer().Lines[0])
}
})
}
@ -94,8 +94,8 @@ func TestEnterInsertLineStart(t *testing.T) {
sendKeys(tm, "I", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
}
})
@ -105,8 +105,8 @@ func TestEnterInsertLineStart(t *testing.T) {
sendKeys(tm, "I", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
}
})
}
@ -118,8 +118,8 @@ func TestEnterInsertLineEnd(t *testing.T) {
sendKeys(tm, "A", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0])
}
})
@ -129,8 +129,8 @@ func TestEnterInsertLineEnd(t *testing.T) {
sendKeys(tm, "A", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0])
}
})
}
@ -147,8 +147,8 @@ func TestOpenLineBelow(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "new" {
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "new" {
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1])
}
})
@ -161,8 +161,8 @@ func TestOpenLineBelow(t *testing.T) {
if m.ActiveBuffer().LineCount() != 4 {
t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[2].String() != "new" {
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "new" {
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2])
}
})
@ -175,8 +175,8 @@ func TestOpenLineBelow(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[2].String() != "new" {
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "new" {
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2])
}
})
@ -206,8 +206,8 @@ func TestOpenLineBelowWithCount(t *testing.T) {
t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
}
for i := 1; i <= 3; i++ {
if m.ActiveBuffer().Lines[i].String() != "x" {
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String())
if m.ActiveBuffer().Lines[i] != "x" {
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i])
}
}
})
@ -221,11 +221,11 @@ func TestOpenLineBelowWithCount(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "ab" {
t.Errorf("lines[1] = %q, want 'ab'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "ab" {
t.Errorf("lines[1] = %q, want 'ab'", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "ab" {
t.Errorf("lines[2] = %q, want 'ab'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "ab" {
t.Errorf("lines[2] = %q, want 'ab'", m.ActiveBuffer().Lines[2])
}
})
}
@ -240,8 +240,8 @@ func TestOpenLineAbove(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "new" {
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "new" {
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1])
}
})
@ -254,8 +254,8 @@ func TestOpenLineAbove(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "new" {
t.Errorf("lines[0] = %q, want 'new'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "new" {
t.Errorf("lines[0] = %q, want 'new'", m.ActiveBuffer().Lines[0])
}
})
@ -282,8 +282,8 @@ func TestOpenLineAboveWithCount(t *testing.T) {
t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
}
for i := 0; i < 3; i++ {
if m.ActiveBuffer().Lines[i].String() != "x" {
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String())
if m.ActiveBuffer().Lines[i] != "x" {
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i])
}
}
})
@ -301,11 +301,11 @@ func TestInsertModeEnter(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != " world" {
t.Errorf("lines[1] = %q, want ' world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != " world" {
t.Errorf("lines[1] = %q, want ' world'", m.ActiveBuffer().Lines[1])
}
})
@ -318,11 +318,11 @@ func TestInsertModeEnter(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("lines[1] = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("lines[1] = %q, want ''", m.ActiveBuffer().Lines[1])
}
})
@ -335,11 +335,11 @@ func TestInsertModeEnter(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "hello" {
t.Errorf("lines[1] = %q, want 'hello'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "hello" {
t.Errorf("lines[1] = %q, want 'hello'", m.ActiveBuffer().Lines[1])
}
})
}
@ -351,8 +351,8 @@ func TestInsertModeBackspace(t *testing.T) {
sendKeys(tm, "i", "backspace", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
}
})
@ -365,8 +365,8 @@ func TestInsertModeBackspace(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0])
}
})
@ -376,8 +376,8 @@ func TestInsertModeBackspace(t *testing.T) {
sendKeys(tm, "i", "backspace", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -387,8 +387,8 @@ func TestInsertModeBackspace(t *testing.T) {
sendKeys(tm, "i", "backspace", "backspace", "backspace", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "he" {
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0])
}
})
}
@ -400,8 +400,8 @@ func TestInsertModeDelete(t *testing.T) {
sendKeys(tm, "i", "delete", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "word" {
t.Errorf("lines[0] = %q, want 'word'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "word" {
t.Errorf("lines[0] = %q, want 'word'", m.ActiveBuffer().Lines[0])
}
})
@ -414,8 +414,8 @@ func TestInsertModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0])
}
})
@ -428,8 +428,8 @@ func TestInsertModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0])
}
})
@ -439,8 +439,8 @@ func TestInsertModeDelete(t *testing.T) {
sendKeys(tm, "i", "delete", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -450,8 +450,8 @@ func TestInsertModeDelete(t *testing.T) {
sendKeys(tm, "i", "delete", "delete", "delete", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" {
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0])
}
})
@ -464,8 +464,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "left", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
}
})
@ -475,8 +475,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "right", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
}
})
@ -486,11 +486,11 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "up", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "world" {
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1])
}
})
@ -500,11 +500,11 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "down", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "woXrld" {
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "woXrld" {
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1])
}
})
@ -514,8 +514,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "left", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
}
})
@ -525,8 +525,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "a", "right", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0])
}
})
@ -536,8 +536,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "up", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
}
})
@ -547,8 +547,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "down", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
}
})
@ -558,8 +558,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "up", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hiX" {
t.Errorf("lines[0] = %q, want 'hiX'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hiX" {
t.Errorf("lines[0] = %q, want 'hiX'", m.ActiveBuffer().Lines[0])
}
})
@ -569,8 +569,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "down", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[1].String() != "hiX" {
t.Errorf("lines[1] = %q, want 'hiX'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "hiX" {
t.Errorf("lines[1] = %q, want 'hiX'", m.ActiveBuffer().Lines[1])
}
})
@ -580,8 +580,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
sendKeys(tm, "i", "right", "right", "down", "X", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[1].String() != "woXrld" {
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "woXrld" {
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1])
}
})
}
@ -593,8 +593,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 5 {
t.Errorf("CursorX() = %d, want '5'", m.ActiveWindow().Cursor.Col)
@ -607,8 +607,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
@ -621,8 +621,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello wo" {
t.Errorf("lines[0] = %q, want 'hello wo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello wo" {
t.Errorf("lines[0] = %q, want 'hello wo'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 7 {
t.Errorf("CursorX() = %d, want '7'", m.ActiveWindow().Cursor.Col)
@ -655,8 +655,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want '1'", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
@ -672,8 +672,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "i", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -686,8 +686,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "i", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -700,8 +700,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "..." {
t.Errorf("lines[0] = %q, want '...'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "..." {
t.Errorf("lines[0] = %q, want '...'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
@ -714,8 +714,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello\t" {
t.Errorf("lines[0] = %q, want 'hello\\t'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello\t" {
t.Errorf("lines[0] = %q, want 'hello\\t'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 5 {
t.Errorf("CursorX() = %d, want 5", m.ActiveWindow().Cursor.Col)
@ -731,8 +731,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
@ -748,8 +748,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)

View File

@ -176,7 +176,7 @@ func TestMoveToLineEnd(t *testing.T) {
sendKeys(tm, "$")
m := getFinalModel(t, tm)
want := len(lines[0]) - 1
want := len(lines[0])
if m.ActiveWindow().Cursor.Col != want {
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
}
@ -188,7 +188,7 @@ func TestMoveToLineEnd(t *testing.T) {
sendKeys(tm, "$")
m := getFinalModel(t, tm)
want := len(lines[0]) - 1
want := len(lines[0])
if m.ActiveWindow().Cursor.Col != want {
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
}
@ -200,7 +200,7 @@ func TestMoveToLineEnd(t *testing.T) {
sendKeys(tm, "$")
m := getFinalModel(t, tm)
want := len(lines[0]) - 1
want := len(lines[0])
if m.ActiveWindow().Cursor.Col != want {
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
}
@ -633,8 +633,8 @@ func TestMoveToColumnWithOperator(t *testing.T) {
// 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())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
})
@ -646,8 +646,8 @@ func TestMoveToColumnWithOperator(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "o world" {
t.Errorf("Line(0) = %q, want 'o world'", m.ActiveBuffer().Lines[0])
}
})
@ -720,8 +720,8 @@ func TestMoveToColumnInVisualMode(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
})
}

View File

@ -928,8 +928,8 @@ func TestMoveForwardWORDWithOperator(t *testing.T) {
m := getFinalModel(t, tm)
// Should delete "hello.world " (including trailing space)
if m.ActiveBuffer().Lines[0].String() != "next" {
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "next" {
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0])
}
})
@ -946,11 +946,11 @@ func TestMoveForwardWORDWithOperator(t *testing.T) {
sendKeys(tm2, "d", "w")
m2 := getFinalModel(t, tm2)
if m1.ActiveBuffer().Lines[0].String() != "next" {
t.Errorf("dW: Line(0) = %q, want 'next'", m1.ActiveBuffer().Lines[0].String())
if m1.ActiveBuffer().Lines[0] != "next" {
t.Errorf("dW: Line(0) = %q, want 'next'", m1.ActiveBuffer().Lines[0])
}
if m2.ActiveBuffer().Lines[0].String() != ".world next" {
t.Errorf("dw: Line(0) = %q, want '.world next'", m2.ActiveBuffer().Lines[0].String())
if m2.ActiveBuffer().Lines[0] != ".world next" {
t.Errorf("dw: Line(0) = %q, want '.world next'", m2.ActiveBuffer().Lines[0])
}
})
@ -960,8 +960,8 @@ func TestMoveForwardWORDWithOperator(t *testing.T) {
sendKeys(tm, "d", "2", "W")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "three four" {
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0])
}
})
@ -1028,8 +1028,8 @@ func TestMoveForwardWORDInVisualMode(t *testing.T) {
m := getFinalModel(t, tm)
// Visual selection from 0 to 12, delete "hello.world "
if m.ActiveBuffer().Lines[0].String() != "ext" {
t.Errorf("Line(0) = %q, want 'ext'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ext" {
t.Errorf("Line(0) = %q, want 'ext'", m.ActiveBuffer().Lines[0])
}
})
@ -1557,8 +1557,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
m := getFinalModel(t, tm)
// Should delete "hello.world" leaving " next"
if m.ActiveBuffer().Lines[0].String() != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0])
}
})
@ -1570,8 +1570,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
m1 := getFinalModel(t, tm1)
// 'de' should delete "hello" leaving ".world next"
if m1.ActiveBuffer().Lines[0].String() != ".world next" {
t.Errorf("'de': Line(0) = %q, want '.world next'", m1.ActiveBuffer().Lines[0].String())
if m1.ActiveBuffer().Lines[0] != ".world next" {
t.Errorf("'de': Line(0) = %q, want '.world next'", m1.ActiveBuffer().Lines[0])
}
// Now test 'dE'
@ -1580,8 +1580,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
m2 := getFinalModel(t, tm2)
// 'dE' should delete "hello.world" leaving " next"
if m2.ActiveBuffer().Lines[0].String() != " next" {
t.Errorf("'dE': Line(0) = %q, want ' next'", m2.ActiveBuffer().Lines[0].String())
if m2.ActiveBuffer().Lines[0] != " next" {
t.Errorf("'dE': Line(0) = %q, want ' next'", m2.ActiveBuffer().Lines[0])
}
})
@ -1592,8 +1592,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
m := getFinalModel(t, tm)
// Should delete "one.a two.b" leaving " three"
if m.ActiveBuffer().Lines[0].String() != " three" {
t.Errorf("Line(0) = %q, want ' three'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " three" {
t.Errorf("Line(0) = %q, want ' three'", m.ActiveBuffer().Lines[0])
}
})
@ -1653,8 +1653,8 @@ func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
m := getFinalModel(t, tm)
// Should delete "hello.world" leaving " next"
if m.ActiveBuffer().Lines[0].String() != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0])
}
})
@ -1685,816 +1685,3 @@ func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
}
})
}
// --- B Motion Tests ---
func TestMoveBackwardWORD(t *testing.T) {
t.Run("test 'B' moves backward one WORD", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
sendKeys(tm, "B")
m := getFinalModel(t, tm)
// Should move to start of "world" (index 6)
if m.ActiveWindow().Cursor.Col != 6 {
t.Errorf("CursorX() = %d, want 6", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'BB' moves backward two WORDs", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "B", "B")
m := getFinalModel(t, tm)
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test '2B' moves backward two WORDs with count", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "2", "B")
m := getFinalModel(t, tm)
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'B' on punctuation-heavy text", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "B")
m := getFinalModel(t, tm)
// "hello.world" is one WORD, should move to "next" (index 12)
if m.ActiveWindow().Cursor.Col != 12 {
t.Errorf("CursorX() = %d, want 12", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'B' vs 'b' on punctuation", func(t *testing.T) {
lines := []string{"hello.world next"}
// Test 'b'
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm1, "b")
m1 := getFinalModel(t, tm1)
// 'b' moves to start of "next" (index 12)
if m1.ActiveWindow().Cursor.Col != 12 {
t.Errorf("'b': CursorX() = %d, want 12", m1.ActiveWindow().Cursor.Col)
}
// Test 'B'
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm2, "B")
m2 := getFinalModel(t, tm2)
// 'B' treats "hello.world" as one WORD, moves to index 12
if m2.ActiveWindow().Cursor.Col != 12 {
t.Errorf("'B': CursorX() = %d, want 12", m2.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'B' crosses lines backward", func(t *testing.T) {
lines := []string{"hello", "world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 1})
sendKeys(tm, "B")
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)
}
})
t.Run("test 'B' at beginning of file", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLines(t, lines) // Cursor at 0,0
sendKeys(tm, "B")
m := getFinalModel(t, tm)
// Should stay at 0,0
if m.ActiveWindow().Cursor.Col != 0 || m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("Cursor = (%d,%d), want (0,0)", m.ActiveWindow().Cursor.Col, m.ActiveWindow().Cursor.Line)
}
})
t.Run("test 'B' with multiple spaces", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 13, Line: 0})
sendKeys(tm, "B")
m := getFinalModel(t, tm)
// Should skip spaces and move to "hello"
if m.ActiveWindow().Cursor.Col != 9 {
t.Errorf("CursorX() = %d, want 9", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'B' on empty lines", func(t *testing.T) {
lines := []string{"hello", "", "world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 2})
sendKeys(tm, "B")
m := getFinalModel(t, tm)
if m.ActiveWindow().Cursor.Line != 2 {
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'B' complex code-like text", func(t *testing.T) {
lines := []string{"foo.bar(baz) next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
sendKeys(tm, "B")
m := getFinalModel(t, tm)
// "foo.bar(baz)" is one WORD, should move to "next" (index 13)
if m.ActiveWindow().Cursor.Col != 13 {
t.Errorf("CursorX() = %d, want 13", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'BB' complex code-like text", func(t *testing.T) {
lines := []string{"foo.bar(baz) next.thing"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 22, Line: 0})
sendKeys(tm, "B", "B")
m := getFinalModel(t, tm)
// Should move to start of first WORD "foo.bar(baz)" (index 0)
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
}
})
}
func TestMoveBackwardWORDWithOperator(t *testing.T) {
t.Run("test 'dB' deletes backward WORD including punctuation", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "d", "B")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello.world t" {
t.Errorf("Line(0) = %q, want 'hello.world t'", m.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'dB' vs 'db' on dotted text", func(t *testing.T) {
lines := []string{"hello.world next"}
// First test 'db'
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm1, "d", "b")
m1 := getFinalModel(t, tm1)
if m1.ActiveBuffer().Lines[0].String() != "hello.world t" {
t.Errorf("'db': Line(0) = %q, want 'hello.world t'", m1.ActiveBuffer().Lines[0].String())
}
// Now test 'dB'
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm2, "d", "B")
m2 := getFinalModel(t, tm2)
if m2.ActiveBuffer().Lines[0].String() != "hello.world t" {
t.Errorf("'dB': Line(0) = %q, want 'hello.world t'", m2.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'd2B' deletes two WORDs backward", func(t *testing.T) {
lines := []string{"one.a two.b three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
sendKeys(tm, "d", "2", "B")
m := getFinalModel(t, tm)
// Should delete "two.b three" leaving "one.a e"
if m.ActiveBuffer().Lines[0].String() != "one.a e" {
t.Errorf("Line(0) = %q, want 'one.a e'", m.ActiveBuffer().Lines[0].String())
}
})
// BUG: This is a failing tests, cursor is not moving at start of yank
t.Run("test 'yB' yanks WORD including punctuation", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "y", "B")
m := getFinalModel(t, tm)
// Text should remain unchanged
if m.ActiveBuffer().Lines[0].String() != "hello.world next" {
t.Errorf("Line(0) = %q, want 'hello.world next'", m.ActiveBuffer().Lines[0].String())
}
// Cursor should be at start of yanked region (index 12)
if m.ActiveWindow().Cursor.Col != 12 {
t.Errorf("CursorX() = %d, want 12", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'cB' changes WORD backward", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "c", "B")
m := getFinalModel(t, tm)
// Should delete " next" and enter insert mode
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hello.world t" {
t.Errorf("Line(0) = %q, want 'hello.world t'", m.ActiveBuffer().Lines[0].String())
}
})
}
func TestMoveBackwardWORDVisualMode(t *testing.T) {
t.Run("test 'vB' selects backward WORD", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "v", "B")
m := getFinalModel(t, tm)
if m.Mode() != core.VisualMode {
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
}
// Cursor at start of "next" (index 12)
if m.ActiveWindow().Cursor.Col != 12 {
t.Errorf("CursorX() = %d, want 12", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'vBd' deletes selected WORD", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "v", "B", "d")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello.world " {
t.Errorf("Line(0) = %q, want 'hello.world '", m.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'v2B' selects backward two WORDs", func(t *testing.T) {
lines := []string{"foo.bar baz.qux rest"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 19, Line: 0})
sendKeys(tm, "v", "2", "B")
m := getFinalModel(t, tm)
// Cursor at start of "baz.qux" (index 8)
if m.ActiveWindow().Cursor.Col != 8 {
t.Errorf("CursorX() = %d, want 8", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'VB' in visual line mode", func(t *testing.T) {
lines := []string{"hello.world", "next line"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 1})
sendKeys(tm, "V", "B")
m := getFinalModel(t, tm)
if m.Mode() != core.VisualLineMode {
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
}
// Should select both lines
if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
}
})
}
// --- ge Motion Tests ---
func TestMoveBackwardWordEnd(t *testing.T) {
t.Run("test 'ge' moves backward to previous word end", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should move to end of "hello" (index 4)
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gege' moves backward two word ends", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "g", "e", "g", "e")
m := getFinalModel(t, tm)
// Should move to end of "one" (index 2)
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test '2ge' moves backward two word ends with count", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "2", "g", "e")
m := getFinalModel(t, tm)
// Should move to end of "one" (index 2)
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'ge' on punctuation-heavy text", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should move to end of "world" (index 10)
if m.ActiveWindow().Cursor.Col != 10 {
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'ge' crosses lines backward", func(t *testing.T) {
lines := []string{"hello", "world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should move to end of "hello" on previous line (index 4)
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
}
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'ge' at beginning of file", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLines(t, lines) // Cursor at 0,0
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should stay at 0,0
if m.ActiveWindow().Cursor.Col != 0 || m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("Cursor = (%d,%d), want (0,0)", m.ActiveWindow().Cursor.Col, m.ActiveWindow().Cursor.Line)
}
})
t.Run("test 'ge' with multiple spaces", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 13, Line: 0})
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should skip spaces and move to end of "hello" (index 4)
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'ge' on empty lines", func(t *testing.T) {
lines := []string{"hello", "", "world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should skip empty line and move to end of "hello" (index 4)
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
}
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'ge' respects word classes", func(t *testing.T) {
lines := []string{"foo-bar baz"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should move to end of "bar" (index 6)
if m.ActiveWindow().Cursor.Col != 6 {
t.Errorf("CursorX() = %d, want 6", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'ge' on method chain", func(t *testing.T) {
lines := []string{"obj.method().chain() next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 24, Line: 0})
sendKeys(tm, "g", "e")
m := getFinalModel(t, tm)
// Should move to end of ")" (index 19)
if m.ActiveWindow().Cursor.Col != 19 {
t.Errorf("CursorX() = %d, want 19", m.ActiveWindow().Cursor.Col)
}
})
}
func TestMoveBackwardWordEndWithOperator(t *testing.T) {
t.Run("test 'dge' deletes backward to word end", func(t *testing.T) {
lines := []string{"hello world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "d", "g", "e")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
t.Errorf("Line(0) = %q, want 'hello worl'", m.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'd2ge' deletes backward two word ends", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "d", "2", "g", "e")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "on" {
t.Errorf("Line(0) = %q, want 'on'", m.ActiveBuffer().Lines[0].String())
}
})
// BUG: This is a failing tests, cursor is not moving at start of yank
t.Run("test 'yge' yanks backward to word end", func(t *testing.T) {
lines := []string{"hello world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "y", "g", "e")
m := getFinalModel(t, tm)
// Text should remain unchanged
if m.ActiveBuffer().Lines[0].String() != "hello world next" {
t.Errorf("Line(0) = %q, want 'hello world next'", m.ActiveBuffer().Lines[0].String())
}
// Cursor should be at end of "world" (index 10)
if m.ActiveWindow().Cursor.Col != 10 {
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'cge' changes backward to word end", func(t *testing.T) {
lines := []string{"hello world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "c", "g", "e")
m := getFinalModel(t, tm)
// Should delete and enter insert mode
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
t.Errorf("Line(0) = %q, want 'hello worl'", m.ActiveBuffer().Lines[0].String())
}
})
}
func TestMoveBackwardWordEndVisualMode(t *testing.T) {
t.Run("test 'vge' selects backward to word end", func(t *testing.T) {
lines := []string{"hello world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "v", "g", "e")
m := getFinalModel(t, tm)
if m.Mode() != core.VisualMode {
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
}
// Cursor at end of "world" (index 10)
if m.ActiveWindow().Cursor.Col != 10 {
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'vged' deletes selection", func(t *testing.T) {
lines := []string{"hello world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "v", "g", "e", "d")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
t.Errorf("Line(0) = %q, want 'hello worl'", m.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'v2ge' selects backward two word ends", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "v", "2", "g", "e")
m := getFinalModel(t, tm)
// Cursor at end of "one" (index 2)
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'Vge' in visual line mode", func(t *testing.T) {
lines := []string{"hello", "world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
sendKeys(tm, "V", "g", "e")
m := getFinalModel(t, tm)
if m.Mode() != core.VisualLineMode {
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
}
// Should select both lines
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
}
})
}
// --- gE Motion Tests ---
func TestMoveBackwardWORDEnd(t *testing.T) {
t.Run("test 'gE' moves backward to previous WORD end", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// Should move to end of "hello" (index 4)
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gEgE' moves backward two WORD ends", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "g", "E", "g", "E")
m := getFinalModel(t, tm)
// Should move to end of "one" (index 2)
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test '2gE' moves backward two WORD ends with count", func(t *testing.T) {
lines := []string{"one two three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
sendKeys(tm, "2", "g", "E")
m := getFinalModel(t, tm)
// Should move to end of "one" (index 2)
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gE' on punctuation-heavy text", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// "hello.world" is one WORD ending at index 10
if m.ActiveWindow().Cursor.Col != 10 {
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gE' vs 'ge' on punctuation", func(t *testing.T) {
lines := []string{"hello.world next"}
// Test 'ge'
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm1, "g", "e")
m1 := getFinalModel(t, tm1)
// 'ge' treats punctuation as separate word
if m1.ActiveWindow().Cursor.Col != 10 {
t.Errorf("'ge': CursorX() = %d, want 10", m1.ActiveWindow().Cursor.Col)
}
// Test 'gE'
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm2, "g", "E")
m2 := getFinalModel(t, tm2)
// 'gE' treats "hello.world" as one WORD
if m2.ActiveWindow().Cursor.Col != 10 {
t.Errorf("'gE': CursorX() = %d, want 10", m2.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gE' crosses lines backward", func(t *testing.T) {
lines := []string{"hello", "world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// Should move to end of "hello" on previous line (index 4)
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
}
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gE' at beginning of file", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLines(t, lines) // Cursor at 0,0
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// Should stay at 0,0
if m.ActiveWindow().Cursor.Col != 0 || m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("Cursor = (%d,%d), want (0,0)", m.ActiveWindow().Cursor.Col, m.ActiveWindow().Cursor.Line)
}
})
t.Run("test 'gE' with multiple spaces", func(t *testing.T) {
lines := []string{"hello world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 13, Line: 0})
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// Should skip spaces and move to end of "hello" (index 4)
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gE' on empty lines", func(t *testing.T) {
lines := []string{"hello", "", "world"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// Should skip empty line and move to end of "hello" (index 4)
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
}
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gE' complex code-like text", func(t *testing.T) {
lines := []string{"foo.bar(baz) next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// "foo.bar(baz)" is one WORD ending at index 11
if m.ActiveWindow().Cursor.Col != 11 {
t.Errorf("CursorX() = %d, want 11", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'gE' on method chain", func(t *testing.T) {
lines := []string{"obj.method().chain() next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 24, Line: 0})
sendKeys(tm, "g", "E")
m := getFinalModel(t, tm)
// Entire chain is one WORD, ends at index 19
if m.ActiveWindow().Cursor.Col != 19 {
t.Errorf("CursorX() = %d, want 19", m.ActiveWindow().Cursor.Col)
}
})
}
func TestMoveBackwardWORDEndWithOperator(t *testing.T) {
t.Run("test 'dgE' deletes backward to WORD end", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "d", "g", "E")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello.worl" {
t.Errorf("Line(0) = %q, want 'hello.worl'", m.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'dgE' vs 'dge' on dotted text", func(t *testing.T) {
lines := []string{"hello.world next"}
// First test 'dge'
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm1, "d", "g", "e")
m1 := getFinalModel(t, tm1)
// 'dge' should delete to end of "world"
if m1.ActiveBuffer().Lines[0].String() != "hello.worl" {
t.Errorf("'dge': Line(0) = %q, want 'hello.worl'", m1.ActiveBuffer().Lines[0].String())
}
// Now test 'dgE'
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm2, "d", "g", "E")
m2 := getFinalModel(t, tm2)
if m2.ActiveBuffer().Lines[0].String() != "hello.worl" {
t.Errorf("'dgE': Line(0) = %q, want 'hello.worl'", m2.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'd2gE' deletes backward two WORD ends", func(t *testing.T) {
lines := []string{"one.a two.b three"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
sendKeys(tm, "d", "2", "g", "E")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "one." {
t.Errorf("Line(0) = %q, want 'one.'", m.ActiveBuffer().Lines[0].String())
}
})
// BUG: This is a failing tests, cursor is not moving at start of yank
t.Run("test 'ygE' yanks backward to WORD end", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "y", "g", "E")
m := getFinalModel(t, tm)
// Text should remain unchanged
if m.ActiveBuffer().Lines[0].String() != "hello.world next" {
t.Errorf("Line(0) = %q, want 'hello.world next'", m.ActiveBuffer().Lines[0].String())
}
// Cursor should be at end of "hello.world" (index 10)
if m.ActiveWindow().Cursor.Col != 10 {
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'cgE' changes backward to WORD end", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "c", "g", "E")
m := getFinalModel(t, tm)
// Should delete and enter insert mode
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hello.worl" {
t.Errorf("Line(0) = %q, want 'hello.world t'", m.ActiveBuffer().Lines[0].String())
}
})
}
func TestMoveBackwardWORDEndVisualMode(t *testing.T) {
t.Run("test 'vgE' selects backward to WORD end", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "v", "g", "E")
m := getFinalModel(t, tm)
if m.Mode() != core.VisualMode {
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
}
// Cursor at end of "hello.world" (index 10)
if m.ActiveWindow().Cursor.Col != 10 {
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'vgEd' deletes selection", func(t *testing.T) {
lines := []string{"hello.world next"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
sendKeys(tm, "v", "g", "E", "d")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello.worl" {
t.Errorf("Line(0) = %q, want 'hello.worl'", m.ActiveBuffer().Lines[0].String())
}
})
t.Run("test 'v2gE' selects backward two WORD ends", func(t *testing.T) {
lines := []string{"foo.bar baz.qux rest"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 19, Line: 0})
sendKeys(tm, "v", "2", "g", "E")
m := getFinalModel(t, tm)
// Cursor at end of "foo.bar" (index 6)
if m.ActiveWindow().Cursor.Col != 6 {
t.Errorf("CursorX() = %d, want 6", m.ActiveWindow().Cursor.Col)
}
})
t.Run("test 'VgE' in visual line mode", func(t *testing.T) {
lines := []string{"hello.world", "next line"}
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
sendKeys(tm, "V", "g", "E")
m := getFinalModel(t, tm)
if m.Mode() != core.VisualLineMode {
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
}
// Should select both lines
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
}
})
}

View File

@ -26,11 +26,11 @@ func TestChangeLine(t *testing.T) {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
// First line should be empty (ready for insert)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
}
})
@ -45,14 +45,14 @@ func TestChangeLine(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "line three" {
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line three" {
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2])
}
})
@ -67,11 +67,11 @@ func TestChangeLine(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
})
@ -109,8 +109,8 @@ func TestChangeLine(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -148,11 +148,11 @@ func TestChangeLineWithCount(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1])
}
})
@ -171,14 +171,14 @@ func TestChangeLineWithCount(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "one" {
t.Errorf("Line(0) = %q, want 'one'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "one" {
t.Errorf("Line(0) = %q, want 'one'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "five" {
t.Errorf("Line(2) = %q, want 'five'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "five" {
t.Errorf("Line(2) = %q, want 'five'", m.ActiveBuffer().Lines[2])
}
})
@ -196,8 +196,8 @@ func TestChangeLineWithCount(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -235,8 +235,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "ello world" {
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello world" {
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0])
}
})
@ -251,8 +251,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "llo world" {
t.Errorf("Line(0) = %q, want 'llo world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo world" {
t.Errorf("Line(0) = %q, want 'llo world'", m.ActiveBuffer().Lines[0])
}
})
@ -267,8 +267,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hell world" {
t.Errorf("Line(0) = %q, want 'hell world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hell world" {
t.Errorf("Line(0) = %q, want 'hell world'", m.ActiveBuffer().Lines[0])
}
})
@ -283,8 +283,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
}
})
@ -299,8 +299,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0])
}
})
@ -317,8 +317,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
}
// ^ is exclusive motion, so position 8 (space) is not included
// Delete positions 3-7 ("hello"), leaving " " + " world" = " world"
if m.ActiveBuffer().Lines[0].String() != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
})
}
@ -339,8 +339,8 @@ func TestChangeWithWordMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0])
}
})
@ -355,8 +355,8 @@ func TestChangeWithWordMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "heworld" {
t.Errorf("Line(0) = %q, want 'heworld'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heworld" {
t.Errorf("Line(0) = %q, want 'heworld'", m.ActiveBuffer().Lines[0])
}
})
@ -371,8 +371,8 @@ func TestChangeWithWordMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
})
@ -387,8 +387,8 @@ func TestChangeWithWordMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0])
}
})
@ -403,8 +403,8 @@ func TestChangeWithWordMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "three four" {
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0])
}
})
@ -419,8 +419,8 @@ func TestChangeWithWordMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "next" {
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "next" {
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0])
}
})
@ -435,8 +435,8 @@ func TestChangeWithWordMotion(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0])
}
})
}
@ -461,11 +461,11 @@ func TestChangeWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1])
}
})
@ -484,11 +484,11 @@ func TestChangeWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1])
}
})
@ -507,11 +507,11 @@ func TestChangeWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "four" {
t.Errorf("Line(1) = %q, want 'four'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "four" {
t.Errorf("Line(1) = %q, want 'four'", m.ActiveBuffer().Lines[1])
}
})
}
@ -536,8 +536,8 @@ func TestChangeWithJumpMotion(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -556,8 +556,8 @@ func TestChangeWithJumpMotion(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -576,11 +576,11 @@ func TestChangeWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
})
}
@ -601,8 +601,8 @@ func TestChangeToEndOfLine(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
}
})
@ -617,8 +617,8 @@ func TestChangeToEndOfLine(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -634,8 +634,8 @@ func TestChangeToEndOfLine(t *testing.T) {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
// Should delete last char
if m.ActiveBuffer().Lines[0].String() != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0])
}
})
@ -673,8 +673,8 @@ func TestSubstituteCharacter(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "ello" {
t.Errorf("Line(0) = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("Line(0) = %q, want 'ello'", m.ActiveBuffer().Lines[0])
}
})
@ -689,8 +689,8 @@ func TestSubstituteCharacter(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0])
}
})
@ -705,8 +705,8 @@ func TestSubstituteCharacter(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0])
}
})
}
@ -727,8 +727,8 @@ func TestSubstituteLine(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -740,14 +740,14 @@ func TestSubstituteLine(t *testing.T) {
sendKeys(tm, "S")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "line three" {
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line three" {
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2])
}
})
@ -766,11 +766,11 @@ func TestSubstituteLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "three" {
t.Errorf("Line(1) = %q, want 'three'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "three" {
t.Errorf("Line(1) = %q, want 'three'", m.ActiveBuffer().Lines[1])
}
})
}
@ -791,8 +791,8 @@ func TestVisualModeChange(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "ello world" {
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello world" {
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0])
}
})
@ -807,8 +807,8 @@ func TestVisualModeChange(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
})
@ -823,8 +823,8 @@ func TestVisualModeChange(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
}
})
@ -878,8 +878,8 @@ func TestVisualLineModeChange(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
})
@ -898,14 +898,14 @@ func TestVisualLineModeChange(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "line four" {
t.Errorf("Line(2) = %q, want 'line four'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line four" {
t.Errorf("Line(2) = %q, want 'line four'", m.ActiveBuffer().Lines[2])
}
})
@ -946,8 +946,8 @@ func TestChangeEdgeCases(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -963,8 +963,8 @@ func TestChangeEdgeCases(t *testing.T) {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
// cw on last word should change to end of line
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
}
})
@ -979,8 +979,8 @@ func TestChangeEdgeCases(t *testing.T) {
if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})

View File

@ -24,8 +24,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
}
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0])
}
})
@ -44,11 +44,11 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line)
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "testing" {
t.Errorf("Line(1) = %s, want 'testing'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "testing" {
t.Errorf("Line(1) = %s, want 'testing'", m.ActiveBuffer().Lines[1])
}
})
@ -67,8 +67,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -87,8 +87,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
}
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0])
}
})
@ -107,12 +107,12 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line)
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "another line" {
t.Errorf("Line(1) = %s, want 'another line'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "another line" {
t.Errorf("Line(1) = %s, want 'another line'", m.ActiveBuffer().Lines[1])
}
})
@ -131,8 +131,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -151,8 +151,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
}
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -176,8 +176,8 @@ func TestDeleteLine(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
}
})
@ -190,8 +190,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -210,8 +210,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
}
if m.ActiveBuffer().Lines[0].String() != "ello" {
t.Errorf("Line(0) = %s, want 'ello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("Line(0) = %s, want 'ello'", m.ActiveBuffer().Lines[0])
}
})
@ -224,8 +224,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want '2'", m.ActiveWindow().Cursor.Col)
}
if m.ActiveBuffer().Lines[0].String() != "helo" {
t.Errorf("Line(0) = %s, want 'helo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("Line(0) = %s, want 'helo'", m.ActiveBuffer().Lines[0])
}
})
@ -238,8 +238,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want '4'", m.ActiveWindow().Cursor.Col)
}
if m.ActiveBuffer().Lines[0].String() != "hell" {
t.Errorf("Line(0) = %s, want 'hell'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %s, want 'hell'", m.ActiveBuffer().Lines[0])
}
})
@ -252,8 +252,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -267,8 +267,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
if m.ActiveWindow().Cursor.Col != 1 {
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
}
if m.ActiveBuffer().Lines[0].String() != "hllo" {
t.Errorf("Line(0) = %q, want 'hllo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hllo" {
t.Errorf("Line(0) = %q, want 'hllo'", m.ActiveBuffer().Lines[0])
}
})
@ -282,8 +282,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
if m.ActiveWindow().Cursor.Col != 3 {
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
}
if m.ActiveBuffer().Lines[0].String() != "helo" {
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0])
}
})
@ -293,8 +293,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
sendKeys(tm, "2", "d", "l")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0])
}
})
@ -304,8 +304,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
sendKeys(tm, "d", "2", "l")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0])
}
})
@ -315,8 +315,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
sendKeys(tm, "2", "d", "h")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heo" {
t.Errorf("Line(0) = %q, want 'heo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heo" {
t.Errorf("Line(0) = %q, want 'heo'", m.ActiveBuffer().Lines[0])
}
})
}
@ -331,11 +331,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 4" {
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 4" {
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1])
}
if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
@ -351,8 +351,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -370,11 +370,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
}
})
@ -387,11 +387,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
}
})
@ -404,11 +404,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
}
})
@ -421,11 +421,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 4" {
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 4" {
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1])
}
if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
@ -443,11 +443,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 3" {
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 3" {
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1])
}
})
@ -460,8 +460,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -477,11 +477,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
}
})
@ -494,11 +494,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
}
})
@ -512,8 +512,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
})
@ -527,8 +527,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
}
})
}
@ -541,8 +541,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "d", "w")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -555,8 +555,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "d", "w")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heworld" {
t.Errorf("Line(0) = %q, want \"heworld\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "heworld" {
t.Errorf("Line(0) = %q, want \"heworld\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
@ -569,8 +569,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "d", "w")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
}
})
@ -580,8 +580,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "2", "d", "w")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "three four" {
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0])
}
})
@ -591,8 +591,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "d", "2", "w")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "three four" {
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0])
}
})
@ -603,8 +603,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// 'w' motion stops at punctuation, so it should delete "hello"
if m.ActiveBuffer().Lines[0].String() != ", world" {
t.Errorf("Line(0) = %q, want \", world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != ", world" {
t.Errorf("Line(0) = %q, want \", world\"", m.ActiveBuffer().Lines[0])
}
})
@ -616,8 +616,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// 'e' is inclusive - deletes "hello" (cols 0-4 inclusive), leaves " world"
if m.ActiveBuffer().Lines[0].String() != " world" {
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -631,8 +631,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// From 'l' (col 2) to 'o' (col 4) inclusive → deletes "llo"
if m.ActiveBuffer().Lines[0].String() != "he world" {
t.Errorf("Line(0) = %q, want \"he world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "he world" {
t.Errorf("Line(0) = %q, want \"he world\"", m.ActiveBuffer().Lines[0])
}
})
@ -643,8 +643,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// From 'o' of "hello" (col 4) to 'd' of "world" (col 10) inclusive
if m.ActiveBuffer().Lines[0].String() != "hell" {
t.Errorf("Line(0) = %q, want \"hell\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want \"hell\"", m.ActiveBuffer().Lines[0])
}
})
@ -655,8 +655,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// Deletes "one" and " two" (to end of second word inclusive)
if m.ActiveBuffer().Lines[0].String() != " three four" {
t.Errorf("Line(0) = %q, want \" three four\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " three four" {
t.Errorf("Line(0) = %q, want \" three four\"", m.ActiveBuffer().Lines[0])
}
})
@ -667,8 +667,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "d", "b")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0])
}
})
@ -679,8 +679,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// cursor at 'r' of "world", db should delete back to start of "world"
if m.ActiveBuffer().Lines[0].String() != "hello rld" {
t.Errorf("Line(0) = %q, want \"hello rld\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello rld" {
t.Errorf("Line(0) = %q, want \"hello rld\"", m.ActiveBuffer().Lines[0])
}
})
@ -691,8 +691,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// cursor at 'w', db should delete "hello " back
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
}
})
@ -703,8 +703,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// cursor at 'f' of "four", 2db should delete "two three "
if m.ActiveBuffer().Lines[0].String() != "one four" {
t.Errorf("Line(0) = %q, want \"one four\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "one four" {
t.Errorf("Line(0) = %q, want \"one four\"", m.ActiveBuffer().Lines[0])
}
})
@ -715,8 +715,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm)
// cursor at 'd' (last char), db should delete back to start of "world"
if m.ActiveBuffer().Lines[0].String() != "hello d" {
t.Errorf("Line(0) = %q, want \"hello d\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello d" {
t.Errorf("Line(0) = %q, want \"hello d\"", m.ActiveBuffer().Lines[0])
}
})
}
@ -729,8 +729,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "0")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0])
}
})
@ -740,8 +740,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "0")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -754,8 +754,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "0")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "d" {
t.Errorf("Line(0) = %q, want \"d\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "d" {
t.Errorf("Line(0) = %q, want \"d\"", m.ActiveBuffer().Lines[0])
}
})
@ -765,8 +765,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "0")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo world" {
t.Errorf("Line(0) = %q, want \"lo world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lo world" {
t.Errorf("Line(0) = %q, want \"lo world\"", m.ActiveBuffer().Lines[0])
}
})
@ -777,8 +777,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
}
})
@ -788,8 +788,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
}
})
@ -799,8 +799,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
t.Errorf("Line(0) = %q, want \"hello worl\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello worl" {
t.Errorf("Line(0) = %q, want \"hello worl\"", m.ActiveBuffer().Lines[0])
}
})
@ -810,11 +810,11 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "second line" {
t.Errorf("Line(1) = %q, want \"second line\"", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "second line" {
t.Errorf("Line(1) = %q, want \"second line\"", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
@ -827,8 +827,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
@ -843,8 +843,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
m := getFinalModel(t, tm)
// From col 0 to first non-whitespace (col 3, 'h') - deletes leading spaces
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -858,8 +858,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
m := getFinalModel(t, tm)
// From col 1 to first non-whitespace (col 3, 'h') - deletes cols 1-2
if m.ActiveBuffer().Lines[0].String() != " hello world" {
t.Errorf("Line(0) = %q, want \" hello world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " hello world" {
t.Errorf("Line(0) = %q, want \" hello world\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 1 {
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
@ -874,8 +874,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
m := getFinalModel(t, tm)
// From col 6 ('l') back to col 3 ('h')
// Should delete "hel" leaving " lo world"
if m.ActiveBuffer().Lines[0].String() != " lo world" {
t.Errorf("Line(0) = %q, want \" lo world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " lo world" {
t.Errorf("Line(0) = %q, want \" lo world\"", m.ActiveBuffer().Lines[0])
}
})
@ -887,8 +887,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
m := getFinalModel(t, tm)
// From col 5 (' ') back to col 0 ('h')
// Should delete "hello" leaving " world"
if m.ActiveBuffer().Lines[0].String() != " world" {
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0])
}
})
}
@ -903,11 +903,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
}
})
@ -920,8 +920,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -934,11 +934,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
}
})
@ -962,11 +962,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 4" {
t.Errorf("Line(0) = %q, want 'line 4'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 4" {
t.Errorf("Line(0) = %q, want 'line 4'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
}
})
@ -979,8 +979,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -993,11 +993,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 3" {
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 3" {
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1])
}
})
@ -1021,8 +1021,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -1035,8 +1035,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -256,8 +256,8 @@ func TestHalfPageScrollDown(t *testing.T) {
if m.ActiveWindow().Cursor.Line != 22 {
t.Errorf("CursorY() = %d, want 22", m.ActiveWindow().Cursor.Line)
}
if m.ActiveWindow().Cursor.Col > m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len() {
t.Errorf("CursorX() = %d exceeds line length %d", m.ActiveWindow().Cursor.Col, m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len())
if m.ActiveWindow().Cursor.Col > len(m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line]) {
t.Errorf("CursorX() = %d exceeds line length %d", m.ActiveWindow().Cursor.Col, len(m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line]))
}
})
@ -412,271 +412,6 @@ func TestHalfPageScrollRoundTrip(t *testing.T) {
})
}
// Tests use terminal 80x30: viewportH=28, full-scroll=28, scrollOff=8, safe zone relY 8-19.
func TestFullPageScrollDown(t *testing.T) {
t.Run("ctrl+f scrolls viewport down by full page", func(t *testing.T) {
// cursor at line 15 (relY=15, in safe zone), scrollY starts at 0
// After ctrl+f: newScrollY=28, newCursorY=28+15=43
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
sendKeys(tm, "ctrl+f")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 28 {
t.Errorf("ScrollY() = %d, want 28", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 43 {
t.Errorf("CursorY() = %d, want 43", m.ActiveWindow().Cursor.Line)
}
})
t.Run("ctrl+f preserves cursor relative position in viewport", func(t *testing.T) {
// relY=15 before and after
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
sendKeys(tm, "ctrl+f")
m := getFinalModel(t, tm)
relY := m.ActiveWindow().Cursor.Line - m.ActiveWindow().ScrollY
if relY != 15 {
t.Errorf("relative position = %d, want 15", relY)
}
})
t.Run("ctrl+f clamps cursor to scrollOff when cursor is near top", func(t *testing.T) {
// cursor at line 0 (relY=0 < scrollOff=8), clamp to scrollOff
// After ctrl+f: newScrollY=28, newCursorY=28+8=36
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
sendKeys(tm, "ctrl+f")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 28 {
t.Errorf("ScrollY() = %d, want 28", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 36 {
t.Errorf("CursorY() = %d, want 36", m.ActiveWindow().Cursor.Line)
}
})
t.Run("ctrl+f at end of file does not scroll past max", func(t *testing.T) {
// 50 lines, viewport 28: maxScroll=22
// cursor at line 45: AdjustScroll puts cursor at scrollY=22 (clamped), relY=23
// After ctrl+f: newScrollY clamped to 22, relY=23>19 clamped to 19, newCursorY=41
lines := generateLines(50)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 45}, 80, 30)
sendKeys(tm, "ctrl+f")
m := getFinalModel(t, tm)
maxScroll := 50 - 28
if m.ActiveWindow().ScrollY > maxScroll {
t.Errorf("ScrollY() = %d, want <= %d", m.ActiveWindow().ScrollY, maxScroll)
}
if m.ActiveWindow().Cursor.Line != 41 {
t.Errorf("CursorY() = %d, want 41", m.ActiveWindow().Cursor.Line)
}
})
t.Run("ctrl+f on file smaller than viewport does not crash", func(t *testing.T) {
// 20 lines < viewport 28: maxScroll=0, scrollY stays 0
// relY=0 < scrollOff, clamp to 8; newCursorY=0+8=8
lines := generateLines(20)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
sendKeys(tm, "ctrl+f")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 0 {
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 8 {
t.Errorf("CursorY() = %d, want 8", m.ActiveWindow().Cursor.Line)
}
})
t.Run("ctrl+f clamps cursor x to new line length", func(t *testing.T) {
// Lines 0-27: "hello world" (11 chars), lines 28-99: "hi" (2 chars)
// cursor at line 5, cursorX=10
// ctrl+f: relY=5<8 clamp to 8; newScrollY=28; newCursorY=36 (a "hi" line)
// ClampCursorX: 10 >= 2, so cursorX=2
lines := make([]string, 100)
for i := range 28 {
lines[i] = "hello world"
}
for i := 28; i < 100; i++ {
lines[i] = "hi"
}
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 10, Line: 5}, 80, 30)
sendKeys(tm, "ctrl+f")
m := getFinalModel(t, tm)
if m.ActiveWindow().Cursor.Line != 36 {
t.Errorf("CursorY() = %d, want 36", m.ActiveWindow().Cursor.Line)
}
if m.ActiveWindow().Cursor.Col > m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len() {
t.Errorf("CursorX() = %d exceeds line length %d", m.ActiveWindow().Cursor.Col, m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len())
}
})
t.Run("multiple ctrl+f presses scroll incrementally", func(t *testing.T) {
// cursor at line 15, scrollY=0, relY=15
// ctrl+f #1: scrollY=28, cursorY=43, relY=15
// ctrl+f #2: scrollY=56, cursorY=71, relY=15
lines := generateLines(200)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
sendKeys(tm, "ctrl+f", "ctrl+f")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 56 {
t.Errorf("ScrollY() = %d, want 56", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 71 {
t.Errorf("CursorY() = %d, want 71", m.ActiveWindow().Cursor.Line)
}
})
}
func TestFullPageScrollUp(t *testing.T) {
t.Run("ctrl+b scrolls viewport up by full page", func(t *testing.T) {
// cursor at line 50: AdjustScroll -> scrollY=31, relY=19
// After ctrl+b: newScrollY=3, newCursorY=3+19=22
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
sendKeys(tm, "ctrl+b")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 3 {
t.Errorf("ScrollY() = %d, want 3", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 22 {
t.Errorf("CursorY() = %d, want 22", m.ActiveWindow().Cursor.Line)
}
})
t.Run("ctrl+b preserves cursor relative position in viewport", func(t *testing.T) {
// relY=19 before and after
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
sendKeys(tm, "ctrl+b")
m := getFinalModel(t, tm)
relY := m.ActiveWindow().Cursor.Line - m.ActiveWindow().ScrollY
if relY != 19 {
t.Errorf("relative position = %d, want 19", relY)
}
})
t.Run("ctrl+b at top of file does not make scrollY negative", func(t *testing.T) {
// cursor at line 20, scrollY=0, relY=20 (clamped to 19 by scrollOff)
// AdjustScroll: relY=20>19, clamp to 19, scrollY stays 0, cursorY=19
// ctrl+b: newScrollY=max(0,-28)=0, relY=19 preserved, cursorY=19
lines := generateLines(100)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 20}, 80, 30)
sendKeys(tm, "ctrl+b")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY < 0 {
t.Errorf("ScrollY() = %d, want >= 0", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().ScrollY != 0 {
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
}
// Cursor should be clamped to safe zone bottom (relY=19)
if m.ActiveWindow().Cursor.Line != 19 {
t.Errorf("CursorY() = %d, want 19", m.ActiveWindow().Cursor.Line)
}
})
t.Run("ctrl+b clamps cursor to scrollOff when cursor is near top of viewport", func(t *testing.T) {
// cursor at line 30, scrollY=0, relY=30>19 -> clamp to 19, cursorY=19
// ctrl+b: newScrollY=0; relY=19; newCursorY=19
lines := generateLines(100)
// First normalize the position
m := getFinalModel(t, newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 30}, 80, 30))
initialY := m.ActiveWindow().Cursor.Line
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: initialY}, 80, 30)
sendKeys(tm, "ctrl+b")
m2 := getFinalModel(t, tm)
if m2.ActiveWindow().ScrollY != 0 {
t.Errorf("ScrollY() = %d, want 0", m2.ActiveWindow().ScrollY)
}
// Cursor should remain in safe zone
relY := m2.ActiveWindow().Cursor.Line - m2.ActiveWindow().ScrollY
if relY < 8 || relY > 19 {
t.Errorf("relY = %d, want in range [8, 19]", relY)
}
})
t.Run("multiple ctrl+b presses scroll incrementally", func(t *testing.T) {
// cursor at line 80: AdjustScroll -> scrollY=61, relY=19
// ctrl+b #1: newScrollY=33, cursorY=52
// ctrl+b #2: newScrollY=5, cursorY=24
lines := generateLines(200)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 80}, 80, 30)
sendKeys(tm, "ctrl+b", "ctrl+b")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 5 {
t.Errorf("ScrollY() = %d, want 5", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 24 {
t.Errorf("CursorY() = %d, want 24", m.ActiveWindow().Cursor.Line)
}
})
}
func TestFullPageScrollRoundTrip(t *testing.T) {
t.Run("ctrl+f then ctrl+b returns cursor to original position", func(t *testing.T) {
// cursor at line 15, scrollY=0, relY=15
// ctrl+f: scrollY=28, cursorY=43, relY=15
// ctrl+b: newScrollY=max(0,28-28)=0, cursorY=0+15=15
lines := generateLines(200)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
sendKeys(tm, "ctrl+f", "ctrl+b")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 0 {
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 15 {
t.Errorf("CursorY() = %d, want 15", m.ActiveWindow().Cursor.Line)
}
})
t.Run("ctrl+b then ctrl+f returns cursor to original position", func(t *testing.T) {
// cursor at line 50: AdjustScroll -> scrollY=31, relY=19
// ctrl+b: scrollY=3, cursorY=22, relY=19
// ctrl+f: scrollY=31, cursorY=50, relY=19
lines := generateLines(200)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
sendKeys(tm, "ctrl+b", "ctrl+f")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 31 {
t.Errorf("ScrollY() = %d, want 31", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 50 {
t.Errorf("CursorY() = %d, want 50", m.ActiveWindow().Cursor.Line)
}
})
t.Run("alternating ctrl+f and ctrl+b maintains scroll stability", func(t *testing.T) {
lines := generateLines(200)
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
sendKeys(tm, "ctrl+f", "ctrl+b", "ctrl+f", "ctrl+b")
m := getFinalModel(t, tm)
if m.ActiveWindow().ScrollY != 0 {
t.Errorf("ScrollY() = %d, want 0 after 2 round trips", m.ActiveWindow().ScrollY)
}
if m.ActiveWindow().Cursor.Line != 15 {
t.Errorf("CursorY() = %d, want 15 after 2 round trips", m.ActiveWindow().Cursor.Line)
}
})
}
func TestScrollWithCount(t *testing.T) {
t.Run("5j scrolls appropriately", func(t *testing.T) {
lines := generateLines(50)

View File

@ -33,8 +33,8 @@ func TestTextObjectInnerWord(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
})
@ -44,8 +44,8 @@ func TestTextObjectInnerWord(t *testing.T) {
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.ActiveBuffer().Lines[0] != " world" {
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
if m.Mode() != core.InsertMode {
t.Errorf("Expected insert mode after ciw")
@ -138,8 +138,8 @@ func TestTextObjectAroundWord(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0])
}
})
@ -149,8 +149,8 @@ func TestTextObjectAroundWord(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0])
}
})
}
@ -179,8 +179,8 @@ func TestTextObjectInnerWORD(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != " test" {
t.Errorf("lines[0] = %q, want ' test'", m.ActiveBuffer().Lines[0])
}
})
}
@ -205,8 +205,8 @@ func TestTextObjectAroundWORD(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "baz" {
t.Errorf("lines[0] = %q, want 'baz'", m.ActiveBuffer().Lines[0])
}
})
}
@ -248,8 +248,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "<>" {
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0])
}
})
@ -259,8 +259,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -271,8 +271,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
m := getFinalModel(t, tm)
// Should remain unchanged
if m.ActiveBuffer().Lines[0].String() != "<>" {
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "<>" {
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0])
}
})
@ -321,8 +321,8 @@ func TestTextObjectParentheses(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "func()" {
t.Errorf("lines[0] = %q, want 'func()'", m.ActiveBuffer().Lines[0])
}
})
@ -332,8 +332,8 @@ func TestTextObjectParentheses(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "func" {
t.Errorf("lines[0] = %q, want 'func'", m.ActiveBuffer().Lines[0])
}
})
@ -343,8 +343,8 @@ func TestTextObjectParentheses(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "()" {
t.Errorf("lines[0] = %q, want '()'", m.ActiveBuffer().Lines[0])
}
})
}
@ -368,8 +368,8 @@ func TestTextObjectBraces(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "{}" {
t.Errorf("lines[0] = %q, want '{}'", m.ActiveBuffer().Lines[0])
}
})
@ -379,8 +379,8 @@ func TestTextObjectBraces(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
}
@ -404,8 +404,8 @@ func TestTextObjectBrackets(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "[]" {
t.Errorf("lines[0] = %q, want '[]'", m.ActiveBuffer().Lines[0])
}
})
@ -415,8 +415,8 @@ func TestTextObjectBrackets(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
}
@ -440,8 +440,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != `""` {
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0])
}
})
@ -451,8 +451,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
@ -462,8 +462,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != `""` {
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0])
}
})
}
@ -487,8 +487,8 @@ func TestTextObjectSingleQuotes(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "''" {
t.Errorf("lines[0] = %q, want \"''\"", m.ActiveBuffer().Lines[0])
}
})
@ -498,8 +498,8 @@ func TestTextObjectSingleQuotes(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
}
@ -523,8 +523,8 @@ func TestTextObjectBackticks(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "``" {
t.Errorf("lines[0] = %q, want '``'", m.ActiveBuffer().Lines[0])
}
})
@ -534,8 +534,8 @@ func TestTextObjectBackticks(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
}
@ -551,8 +551,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != " b c" {
t.Errorf("lines[0] = %q, want ' b c'", m.ActiveBuffer().Lines[0])
}
})
@ -564,8 +564,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "<world>" {
t.Errorf("lines[0] = %q, want '<world>'", m.ActiveBuffer().Lines[0])
}
})
@ -577,8 +577,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[1] != "testarg" {
t.Errorf("lines[1] = %q, want 'testarg'", m.ActiveBuffer().Lines[1])
}
})
@ -589,8 +589,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "before (hello) after" {
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0])
}
})
@ -600,8 +600,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "before () after" {
t.Errorf("lines[0] = %q, want 'before () after'", m.ActiveBuffer().Lines[0])
}
})
@ -612,8 +612,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "before after" {
t.Errorf("lines[0] = %q, want 'before after'", m.ActiveBuffer().Lines[0])
}
})
@ -624,8 +624,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "text () more" {
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0])
}
})
@ -638,8 +638,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "text () more" {
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0])
}
})
@ -650,8 +650,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "() bar (baz)" {
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0])
}
})
@ -663,8 +663,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "(foo) bar ()" {
t.Errorf("lines[0] = %q, want '(foo) bar ()'", m.ActiveBuffer().Lines[0])
}
})
@ -676,8 +676,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "() bar (baz)" {
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0])
}
})
@ -688,8 +688,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != `foo "" baz "qux"` {
t.Errorf("lines[0] = %q, want 'foo \"\" baz \"qux\"'", m.ActiveBuffer().Lines[0])
}
})
@ -701,8 +701,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != `"foo" bar ""` {
t.Errorf("lines[0] = %q, want '\"foo\" bar \"\"'", m.ActiveBuffer().Lines[0])
}
})
}
@ -727,8 +727,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"func test() {",
"}",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -746,8 +746,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
expected := []string{
"func test() ",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -792,8 +792,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"function(",
")",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -815,8 +815,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"outer {",
"}",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -841,8 +841,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
" more",
"}",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -869,8 +869,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
" }",
"}",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -889,8 +889,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"function(arg) {",
"}",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -909,8 +909,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"function(arg) {",
"}",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -933,8 +933,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"}",
"after",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -959,21 +959,12 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
" more",
")",
}
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, 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) {

View File

@ -31,8 +31,8 @@ func TestUndoBasicOperations(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
// Verify undo stack is empty
@ -49,8 +49,8 @@ func TestUndoBasicOperations(t *testing.T) {
sendKeys(tm, "ctrl+r") // Redo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
}
// Verify redo stack is empty
@ -70,8 +70,8 @@ func TestUndoBasicOperations(t *testing.T) {
sendKeys(tm, "u") // Undo first x -> "hello"
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("After 3 undos: lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("After 3 undos: lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
// Verify undo stack is empty
@ -87,8 +87,8 @@ func TestUndoBasicOperations(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
}
@ -104,8 +104,8 @@ func TestUndoCursorRestoration(t *testing.T) {
if m.ActiveWindow().Cursor.Col != 6 {
t.Errorf("cursor col = %d, want 6", m.ActiveWindow().Cursor.Col)
}
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
}
})
@ -137,8 +137,8 @@ func TestUndoInsertMode(t *testing.T) {
sendKeys(tm, "u") // Undo once
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
// Verify only one undo was needed
@ -167,8 +167,8 @@ func TestUndoInsertMode(t *testing.T) {
sendKeys(tm, "u")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("After 2 undos: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("After 2 undos: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
// Verify undo stack is empty
@ -191,7 +191,7 @@ func TestUndoInsertMode(t *testing.T) {
sendKeys(tm, "u")
m := getFinalModel(t, tm)
if len(m.ActiveBuffer().Lines) != 1 || m.ActiveBuffer().Lines[0].String() != "" {
if len(m.ActiveBuffer().Lines) != 1 || m.ActiveBuffer().Lines[0] != "" {
t.Errorf("After undo: got %d lines with content %q, want 1 empty line",
len(m.ActiveBuffer().Lines), m.ActiveBuffer().Lines)
}
@ -209,8 +209,8 @@ func TestUndoInsertMode(t *testing.T) {
sendKeys(tm, "u")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("After undo: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("After undo: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
})
}
@ -230,8 +230,8 @@ func TestUndoDeleteOperator(t *testing.T) {
if len(m.ActiveBuffer().Lines) != 3 {
t.Errorf("line count = %d, want 3", len(m.ActiveBuffer().Lines))
}
if m.ActiveBuffer().Lines[0].String() != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0])
}
})
@ -245,11 +245,11 @@ func TestUndoDeleteOperator(t *testing.T) {
if len(m.ActiveBuffer().Lines) != 4 {
t.Errorf("line count = %d, want 4", len(m.ActiveBuffer().Lines))
}
if m.ActiveBuffer().Lines[0].String() != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[2].String() != "line3" {
t.Errorf("lines[2] = %q, want 'line3'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line3" {
t.Errorf("lines[2] = %q, want 'line3'", m.ActiveBuffer().Lines[2])
}
})
@ -260,8 +260,8 @@ func TestUndoDeleteOperator(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world foo" {
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world foo" {
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0])
}
})
@ -272,8 +272,8 @@ func TestUndoDeleteOperator(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 6 {
t.Errorf("cursor col = %d, want 6", m.ActiveWindow().Cursor.Col)
@ -291,8 +291,8 @@ func TestUndoChangeOperator(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "original line" {
t.Errorf("lines[0] = %q, want 'original line'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "original line" {
t.Errorf("lines[0] = %q, want 'original line'", m.ActiveBuffer().Lines[0])
}
})
@ -305,8 +305,8 @@ func TestUndoChangeOperator(t *testing.T) {
sendKeys(tm, "u")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
}
})
@ -319,8 +319,8 @@ func TestUndoChangeOperator(t *testing.T) {
sendKeys(tm, "u")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -333,8 +333,8 @@ func TestUndoChangeOperator(t *testing.T) {
sendKeys(tm, "u")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "original" {
t.Errorf("lines[0] = %q, want 'original'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "original" {
t.Errorf("lines[0] = %q, want 'original'", m.ActiveBuffer().Lines[0])
}
})
}
@ -353,8 +353,8 @@ func TestUndoVisualMode(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
}
})
@ -370,8 +370,8 @@ func TestUndoVisualMode(t *testing.T) {
if len(m.ActiveBuffer().Lines) != 3 {
t.Errorf("line count = %d, want 3", len(m.ActiveBuffer().Lines))
}
if m.ActiveBuffer().Lines[0].String() != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0])
}
})
@ -385,11 +385,11 @@ func TestUndoVisualMode(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "world" {
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1])
}
})
@ -404,8 +404,8 @@ func TestUndoVisualMode(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
}
})
}
@ -422,8 +422,8 @@ func TestUndoTextObjects(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world foo" {
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world foo" {
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0])
}
})
@ -434,8 +434,8 @@ func TestUndoTextObjects(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world foo" {
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world foo" {
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0])
}
})
@ -448,8 +448,8 @@ func TestUndoTextObjects(t *testing.T) {
sendKeys(tm, "u") // Undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "before (hello) after" {
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "before (hello) after" {
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0])
}
})
}
@ -470,8 +470,8 @@ func TestUndoRedoSequences(t *testing.T) {
sendKeys(tm, "ctrl+r", "ctrl+r")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("After 2 redos: lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("After 2 redos: lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
}
})
@ -490,8 +490,8 @@ func TestUndoRedoSequences(t *testing.T) {
}
// Verify content
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
}
})
@ -512,8 +512,8 @@ func TestUndoRedoSequences(t *testing.T) {
sendKeys(tm, "esc")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "newline3" {
t.Errorf("After insert: lines[0] = %q, want 'newline3'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "newline3" {
t.Errorf("After insert: lines[0] = %q, want 'newline3'", m.ActiveBuffer().Lines[0])
}
})
}
@ -529,8 +529,8 @@ func TestUndoEdgeCases(t *testing.T) {
sendKeys(tm, "u") // Undo when nothing to undo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -540,8 +540,8 @@ func TestUndoEdgeCases(t *testing.T) {
sendKeys(tm, "ctrl+r") // Redo when nothing to redo
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
}
})
@ -554,8 +554,8 @@ func TestUndoEdgeCases(t *testing.T) {
sendKeys(tm, "ctrl+r") // Try redo again (should do nothing)
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
}
})
@ -569,8 +569,8 @@ func TestUndoEdgeCases(t *testing.T) {
if len(m.ActiveBuffer().Lines) != 1 {
t.Errorf("line count = %d, want 1", len(m.ActiveBuffer().Lines))
}
if m.ActiveBuffer().Lines[0].String() != "only line" {
t.Errorf("lines[0] = %q, want 'only line'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "only line" {
t.Errorf("lines[0] = %q, want 'only line'", m.ActiveBuffer().Lines[0])
}
})
}
@ -594,8 +594,8 @@ func TestUndoMultiLineOperations(t *testing.T) {
}
for i := 0; i < 5; i++ {
expected := "line" + string(rune('1'+i))
if m.ActiveBuffer().Lines[i].String() != expected {
t.Errorf("lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), expected)
if m.ActiveBuffer().Lines[i] != expected {
t.Errorf("lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i], expected)
}
}
})
@ -610,8 +610,8 @@ func TestUndoMultiLineOperations(t *testing.T) {
if len(m.ActiveBuffer().Lines) != 3 {
t.Errorf("line count = %d, want 3", len(m.ActiveBuffer().Lines))
}
if m.ActiveBuffer().Lines[0].String() != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0])
}
})
@ -627,8 +627,8 @@ func TestUndoMultiLineOperations(t *testing.T) {
if len(m.ActiveBuffer().Lines) != 2 {
t.Errorf("line count = %d, want 2", len(m.ActiveBuffer().Lines))
}
if m.ActiveBuffer().Lines[0].String() != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line1" {
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0])
}
})
@ -644,8 +644,8 @@ func TestUndoMultiLineOperations(t *testing.T) {
if len(m.ActiveBuffer().Lines) != 2 {
t.Errorf("line count = %d, want 2", len(m.ActiveBuffer().Lines))
}
if m.ActiveBuffer().Lines[1].String() != "line2" {
t.Errorf("lines[1] = %q, want 'line2'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line2" {
t.Errorf("lines[1] = %q, want 'line2'", m.ActiveBuffer().Lines[1])
}
})
}
@ -688,8 +688,8 @@ func TestUndoStackStructure(t *testing.T) {
sendKeys(tm, "u")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" {
t.Errorf("After undo: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("After undo: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().UndoStack.CanUndo() {
t.Error("Expected empty undo stack after single undo")
@ -755,8 +755,8 @@ func TestUndoComplexScenarios(t *testing.T) {
sendKeys(tm, "x") // Delete 'a' -> "bc"
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "bc" {
t.Errorf("lines[0] = %q, want 'bc'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "bc" {
t.Errorf("lines[0] = %q, want 'bc'", m.ActiveBuffer().Lines[0])
}
})
}
@ -781,8 +781,8 @@ func TestUndoPasteOperations(t *testing.T) {
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
}
for i, exp := range expected {
if m.ActiveBuffer().Lines[i].String() != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), exp)
if m.ActiveBuffer().Lines[i] != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i], exp)
}
}
// Cursor should be back at line2
@ -806,8 +806,8 @@ func TestUndoPasteOperations(t *testing.T) {
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
}
for i, exp := range expected {
if m.ActiveBuffer().Lines[i].String() != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), exp)
if m.ActiveBuffer().Lines[i] != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i], exp)
}
}
// Cursor should be back at line2
@ -827,7 +827,7 @@ func TestUndoPasteOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"hello world"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -848,8 +848,8 @@ func TestUndoPasteOperations(t *testing.T) {
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
}
for i, exp := range expected {
if m.ActiveBuffer().Lines[i].String() != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), exp)
if m.ActiveBuffer().Lines[i] != exp {
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i], exp)
}
}
})
@ -865,7 +865,7 @@ func TestUndoPasteOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"base"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -879,7 +879,7 @@ func TestUndoPasteOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"test"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -899,7 +899,7 @@ func TestUndoComplexCountOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"1", "2", "3", "4", "5", "6", "7"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
// Cursor should be back at line 3 (index 2)
@ -918,7 +918,7 @@ func TestUndoComplexCountOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"one two three four five"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -932,7 +932,7 @@ func TestUndoComplexCountOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"abcdefghijklmnopqrstuvwxyz"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
// Cursor should be back at column 4 (index of 'e', 0-based)
@ -952,7 +952,7 @@ func TestUndoComplexCountOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"line1", "line2", "line3", "line4"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -966,7 +966,7 @@ func TestUndoComplexCountOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"word1 word2 word3 word4 word5"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
})
@ -981,7 +981,7 @@ func TestUndoComplexCountOperations(t *testing.T) {
m := getFinalModel(t, tm)
expected := []string{"A", "B", "C", "D"}
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
if !equalStringSlices(m.ActiveBuffer().Lines, expected) {
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
}
// Cursor should be back at line C (index 2)

View File

@ -125,8 +125,8 @@ func TestVisualModeDelete(t *testing.T) {
sendKeys(tm, "v", "d")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" {
t.Errorf("Line(0) = %q, want \"ello\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("Line(0) = %q, want \"ello\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -139,8 +139,8 @@ func TestVisualModeDelete(t *testing.T) {
sendKeys(tm, "v", "l", "l", "l", "d")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "o world" {
t.Errorf("Line(0) = %q, want \"o world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "o world" {
t.Errorf("Line(0) = %q, want \"o world\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -154,8 +154,8 @@ func TestVisualModeDelete(t *testing.T) {
// anchor=3, cursor=1 → normalized start=1, end=3 → delete "ell" → "ho"
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" {
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 1 {
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
@ -172,8 +172,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "held" {
t.Errorf("Line(0) = %q, want \"held\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "held" {
t.Errorf("Line(0) = %q, want \"held\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
@ -192,8 +192,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -209,8 +209,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "testing" {
t.Errorf("Line(0) = %q, want \"testing\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "testing" {
t.Errorf("Line(0) = %q, want \"testing\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -227,8 +227,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "hello" {
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0])
}
})
@ -241,11 +241,11 @@ func TestVisualModeDelete(t *testing.T) {
// "hello"[:0]+"hello"[2:] = "llo"
// "world"[:0]+"world"[2:] = "rld"
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" {
t.Errorf("Line(0) = %q, want \"llo\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want \"llo\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "rld" {
t.Errorf("Line(1) = %q, want \"rld\"", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "rld" {
t.Errorf("Line(1) = %q, want \"rld\"", m.ActiveBuffer().Lines[1])
}
if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -264,11 +264,11 @@ func TestVisualModeDelete(t *testing.T) {
// "hello"[:1]+"hello"[4:] = "h"+"o" = "ho"
// "world"[:1]+"world"[4:] = "w"+"d" = "wd"
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" {
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "wd" {
t.Errorf("Line(1) = %q, want \"wd\"", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "wd" {
t.Errorf("Line(1) = %q, want \"wd\"", m.ActiveBuffer().Lines[1])
}
})
}
@ -302,8 +302,8 @@ func TestVisualModeWordMotions(t *testing.T) {
m := getFinalModel(t, tm)
// Deletes from 0 to 6 inclusive = "hello w", leaves "orld"
if m.ActiveBuffer().Lines[0].String() != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0])
}
})
@ -333,8 +333,8 @@ func TestVisualModeWordMotions(t *testing.T) {
m := getFinalModel(t, tm)
// Deletes "hello"
if m.ActiveBuffer().Lines[0].String() != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
}
})
@ -364,8 +364,8 @@ func TestVisualModeWordMotions(t *testing.T) {
m := getFinalModel(t, tm)
// Deletes from "h" (0) to "w" (6) inclusive
if m.ActiveBuffer().Lines[0].String() != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0])
}
})
@ -398,8 +398,9 @@ func TestVisualModeJumpMotions(t *testing.T) {
if m.ActiveWindow().Anchor.Col != 0 {
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
}
if m.ActiveWindow().Cursor.Col != 10 {
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
// $ moves past end of line
if m.ActiveWindow().Cursor.Col != 11 {
t.Errorf("CursorX() = %d, want 11", m.ActiveWindow().Cursor.Col)
}
})
@ -411,8 +412,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
sendKeys(tm, "v", "$", "d")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
}
})
@ -441,8 +442,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
m := getFinalModel(t, tm)
// Deletes from 'h' (0) to 'w' (6) inclusive
if m.ActiveBuffer().Lines[0].String() != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0])
}
})
@ -492,8 +493,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "lin 3" {
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lin 3" {
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0])
}
})
@ -526,8 +527,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "lin 3" {
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "lin 3" {
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0])
}
})
}
@ -563,8 +564,8 @@ func TestVisualLineModeJumpMotions(t *testing.T) {
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())
if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
}
})

View File

@ -46,14 +46,14 @@ func TestYankLineBasic(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2])
}
})
@ -583,8 +583,8 @@ func TestYankWithCharwiseMotions(t *testing.T) {
sendKeys(tm, "y", "w")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
}
})
}
@ -659,8 +659,8 @@ func TestYankVisualCharwise(t *testing.T) {
sendKeys(tm, "v", "l", "l", "l", "l", "y")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" {
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
}
})
}
@ -904,8 +904,8 @@ func TestYankRegisterBehavior(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[1].String() != "to copy" {
t.Errorf("Line(1) = %q, want 'to copy'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "to copy" {
t.Errorf("Line(1) = %q, want 'to copy'", m.ActiveBuffer().Lines[1])
}
})
}
@ -1053,8 +1053,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
sendKeys(tm, "v", "l", "l", "l", "l", "y", "$", "p")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worldhello" {
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello worldhello" {
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0])
}
})
@ -1067,8 +1067,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
sendKeys(tm, "v", "$", "y", "0", "P")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "worldhello world" {
t.Errorf("Line(0) = %q, want 'worldhello world'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "worldhello world" {
t.Errorf("Line(0) = %q, want 'worldhello world'", m.ActiveBuffer().Lines[0])
}
})
@ -1084,8 +1084,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
if m.ActiveBuffer().LineCount() != 4 {
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[2].String() != "line 1" {
t.Errorf("Line(2) = %q, want 'line 1'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line 1" {
t.Errorf("Line(2) = %q, want 'line 1'", m.ActiveBuffer().Lines[2])
}
})
@ -1101,11 +1101,11 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
if m.ActiveBuffer().LineCount() != 6 {
t.Errorf("LineCount() = %d, want 6", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[4].String() != "line 1" {
t.Errorf("Line(4) = %q, want 'line 1'", m.ActiveBuffer().Lines[4].String())
if m.ActiveBuffer().Lines[4] != "line 1" {
t.Errorf("Line(4) = %q, want 'line 1'", m.ActiveBuffer().Lines[4])
}
if m.ActiveBuffer().Lines[5].String() != "line 2" {
t.Errorf("Line(5) = %q, want 'line 2'", m.ActiveBuffer().Lines[5].String())
if m.ActiveBuffer().Lines[5] != "line 2" {
t.Errorf("Line(5) = %q, want 'line 2'", m.ActiveBuffer().Lines[5])
}
})
@ -1121,11 +1121,11 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
if m.ActiveBuffer().LineCount() != 4 {
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
}
})
@ -1140,14 +1140,14 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "original" {
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "original" {
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "original" {
t.Errorf("Line(1) = %q, want 'original'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "original" {
t.Errorf("Line(1) = %q, want 'original'", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "other" {
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "other" {
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2])
}
})
@ -1162,14 +1162,14 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "original" {
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "original" {
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "other" {
t.Errorf("Line(1) = %q, want 'other'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "other" {
t.Errorf("Line(1) = %q, want 'other'", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "other" {
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "other" {
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2])
}
})
@ -1182,8 +1182,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
sendKeys(tm, "y", "w", "$", "p")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worldhello " {
t.Errorf("Line(0) = %q, want 'hello worldhello '", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello worldhello " {
t.Errorf("Line(0) = %q, want 'hello worldhello '", m.ActiveBuffer().Lines[0])
}
})
@ -1196,8 +1196,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
sendKeys(tm, "y", "e", "$", "p")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worldhello" {
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "hello worldhello" {
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0])
}
})
@ -1210,8 +1210,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
sendKeys(tm, "v", "l", "l", "y", "$", "p")
m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "abcdefghcde" {
t.Errorf("Line(0) = %q, want 'abcdefghcde'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "abcdefghcde" {
t.Errorf("Line(0) = %q, want 'abcdefghcde'", m.ActiveBuffer().Lines[0])
}
})
@ -1246,14 +1246,14 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
}
if m.ActiveBuffer().Lines[0].String() != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String())
if m.ActiveBuffer().Lines[0] != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
}
if m.ActiveBuffer().Lines[1].String() != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2])
}
})
@ -1270,17 +1270,17 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
t.Errorf("LineCount() = %d, want 7", m.ActiveBuffer().LineCount())
}
// Original + 2 copies of 2 lines = 3 + 4 = 7
if m.ActiveBuffer().Lines[1].String() != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String())
if m.ActiveBuffer().Lines[1] != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
}
if m.ActiveBuffer().Lines[2].String() != "line 2" {
t.Errorf("Line(2) = %q, want 'line 2'", m.ActiveBuffer().Lines[2].String())
if m.ActiveBuffer().Lines[2] != "line 2" {
t.Errorf("Line(2) = %q, want 'line 2'", m.ActiveBuffer().Lines[2])
}
if m.ActiveBuffer().Lines[3].String() != "line 1" {
t.Errorf("Line(3) = %q, want 'line 1'", m.ActiveBuffer().Lines[3].String())
if m.ActiveBuffer().Lines[3] != "line 1" {
t.Errorf("Line(3) = %q, want 'line 1'", m.ActiveBuffer().Lines[3])
}
if m.ActiveBuffer().Lines[4].String() != "line 2" {
t.Errorf("Line(4) = %q, want 'line 2'", m.ActiveBuffer().Lines[4].String())
if m.ActiveBuffer().Lines[4] != "line 2" {
t.Errorf("Line(4) = %q, want 'line 2'", m.ActiveBuffer().Lines[4])
}
})
}

View File

@ -11,7 +11,76 @@ type ModelBuilder struct {
model Model
}
// NewModelBuilder: Builds and returns a new model, using the default color scheme (kanagawa-wave).
// RPGLE
// abap
// algol
// algol_nu
// arduino
// ashen
// aura-theme-dark
// aura-theme-dark-soft
// autumn
// average
// base16-snazzy
// borland
// bw
// catppuccin-frappe
// catppuccin-latte
// catppuccin-macchiato
// catppuccin-mocha
// colorful
// doom-one
// doom-one2
// dracula
// emacs
// evergarden
// friendly
// fruity
// github
// github-dark
// gruvbox
// gruvbox-light
// hr_high_contrast
// hrdark
// igor
// lovelace
// manni
// modus-operandi
// modus-vivendi
// monokai
// monokailight
// murphy
// native
// nord
// nordic
// onedark
// onesenterprise
// paraiso-dark
// paraiso-light
// pastie
// perldoc
// pygments
// rainbow_dash
// rose-pine
// rose-pine-dawn
// rose-pine-moon
// rrt
// solarized-dark
// solarized-dark256
// solarized-light
// swapoff
// tango
// tokyonight-day
// tokyonight-moon
// tokyonight-night
// tokyonight-storm
// trac
// vim
// vs
// vulcan
// witchhazel
// xcode
// xcode-dark
func NewModelBuilder() *ModelBuilder {
chromaStyle := styles.Get("kanagawa-wave")

View File

@ -1,95 +1,83 @@
package editor
import (
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/motion"
tea "github.com/charmbracelet/bubbletea"
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
// Model.Update: Handles BubbleTea messages including window resizes and key
// presses. Routes input to the handler and adjusts scroll after updates.
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmd tea.Cmd
switch msg := msg.(type) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.termHeight = msg.Height
m.termWidth = msg.Width
case tea.WindowSizeMsg:
m.termHeight = msg.Height
m.termWidth = msg.Width
// TODO: Implement a layout method that handles this
//
// func (m *Model) layoutWindows() {
// if len(m.windows) == 0 {
// return
// }
//
// if len(m.windows) == 1 {
// // Single window - full screen
// m.windows[0].Width = m.termWidth
// m.windows[0].Height = m.termHeight
// return
// }
//
// // Multiple windows - distribute space
// // This is where you'd implement split layout logic
// // For example, horizontal split:
// halfHeight := m.termHeight / 2
// for i, win := range m.windows {
// win.Width = m.termWidth
// if i < len(m.windows)-1 {
// win.Height = halfHeight
// } else {
// // Last window gets remainder
// win.Height = m.termHeight - (halfHeight * (len(m.windows) - 1))
// }
// }
// }
for i := range m.windows {
m.windows[i].Height = msg.Height
m.windows[i].Width = msg.Width
}
// TODO: Implement a layout method that handles this
//
// func (m *Model) layoutWindows() {
// if len(m.windows) == 0 {
// return
// }
//
// if len(m.windows) == 1 {
// // Single window - full screen
// m.windows[0].Width = m.termWidth
// m.windows[0].Height = m.termHeight
// return
// }
//
// // Multiple windows - distribute space
// // This is where you'd implement split layout logic
// // For example, horizontal split:
// halfHeight := m.termHeight / 2
// for i, win := range m.windows {
// win.Width = m.termWidth
// if i < len(m.windows)-1 {
// win.Height = halfHeight
// } else {
// // Last window gets remainder
// win.Height = m.termHeight - (halfHeight * (len(m.windows) - 1))
// }
// }
// }
for i := range m.windows {
m.windows[i].Height = msg.Height
m.windows[i].Width = msg.Width
}
// TODO: This is not great, totally temporary. But I don't like vim's handling, so this is up to me
case tea.MouseMsg:
switch msg.Button {
case tea.MouseButtonWheelUp:
scrollAction := motion.ScrollUpPage{Divisor: 4} // Quarter page
cmd = scrollAction.Execute(m)
case tea.MouseButtonWheelDown:
scrollAction := motion.ScrollDownPage{Divisor: 4} // Quarter page
cmd = scrollAction.Execute(m)
}
case tea.KeyMsg:
// TODO: This needs to be removed, but for now its required for the tests.
// Ctrl+C always quits regardless of mode
if msg.Type == tea.KeyCtrlC {
return m, tea.Quit
}
case tea.KeyMsg:
// TODO: This needs to be removed, but for now its required for the tests.
// Ctrl+C always quits regardless of mode
if msg.Type == tea.KeyCtrlC {
return m, tea.Quit
}
// TODO: This is not great
// TODO: Any vim action should exit also
// Simple override for command output mode for now
if m.Mode() == core.CommandOutputMode {
// TODO: Implement g/G/d/u
switch msg.String() {
case "enter":
m.SetMode(core.NormalMode)
m.SetCommandOutput(&core.CommandOutput{})
case "j":
m.CommandOutput().ScrollDown(m.termHeight)
case "k":
m.CommandOutput().ScrollUp()
}
} else {
cmd = m.input.Handle(m, msg.String())
}
}
// TODO: This is not great
// TODO: Any vim action should exit also
// Simple override for command output mode for now
if m.Mode() == core.CommandOutputMode {
// TODO: Implement g/G/d/u
switch msg.String() {
case "enter":
m.SetMode(core.NormalMode)
m.SetCommandOutput(&core.CommandOutput{})
case "j":
m.CommandOutput().ScrollDown(m.termHeight)
case "k":
m.CommandOutput().ScrollUp()
}
} else {
cmd = m.input.Handle(m, msg.String())
}
}
// Keep cursor in view after any update
win := m.ActiveWindow()
win.AdjustScroll()
// Keep cursor in view after any update
win := m.ActiveWindow()
win.AdjustScroll()
return m, cmd
return m, cmd
}

View File

@ -2,11 +2,12 @@ package editor
import (
"fmt"
"strconv"
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/charmbracelet/lipgloss"
)
@ -23,10 +24,6 @@ func (m Model) View() string {
styles := m.Styles()
options := win.Options
// Adjust gutter to fit line len
maxLineLen := len(strconv.Itoa(win.Buffer.LineCount()))
options.GutterSize = max(options.GutterSize, maxLineLen+2)
// Draw window
view := viewWindow(win, styles, options, m.Mode())
@ -55,7 +52,12 @@ func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mo
end := w.ScrollY + w.ViewportHeight()
// Chroma stuff
lexer := style.GetLexer(buf)
name := strings.ReplaceAll(buf.Filetype, ".", "")
lexer := lexers.Get(name)
if lexer == nil {
lexer = lexers.Fallback
}
lexer = chroma.Coalesce(lexer) // Merge tokens together
// Draw buffer lines
for lineNum := start; lineNum < end; lineNum++ {

View File

@ -41,7 +41,6 @@ type Handler struct {
normalKeymap *Keymap
visualKeymap *Keymap
insertKeymap *Keymap
replaceKeymap *Keymap
commandKeymap *Keymap
currentKeymap *Keymap
@ -54,7 +53,6 @@ func NewHandler() *Handler {
normalKeymap: NewNormalKeymap(),
visualKeymap: NewVisualKeymap(),
insertKeymap: NewInsertKeymap(),
replaceKeymap: NewReplaceKeymap(),
commandKeymap: NewCommandKeymap(),
currentKeymap: nil,
}
@ -79,7 +77,7 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
h.recordingKeys = []string{} // Clear recording on ESC
h.Reset()
if m.Mode() == core.InsertMode || m.Mode() == core.ReplaceMode {
if m.Mode() == core.InsertMode {
// Before exiting insert mode, end the block in the undo stack
win := m.ActiveWindow()
buf := m.ActiveBuffer()
@ -97,8 +95,6 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
switch m.Mode() {
case core.InsertMode:
return h.handleInsertKey(m, key)
case core.ReplaceMode:
return h.handleReplaceKey(m, key)
case core.CommandMode:
return h.handleCommandKey(m, key)
}
@ -176,9 +172,6 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
func (h *Handler) dispatch(m action.Model, kind string, binding any, key string) tea.Cmd {
// Handle character motions (f/t/F/T) - transition to waiting state
if kind == "char_motion" {
if key == "r" {
m.SetMode(core.WaitingMode)
}
h.charMotionType = key
h.state = StateWaitingForChar
return nil
@ -369,11 +362,7 @@ func (h *Handler) handleCharMotion(m action.Model, key string) tea.Cmd {
// Apply count if supported
if r, ok := mot.(action.Repeatable); ok {
result := r.WithCount(count)
// WithCount returns Action, but char motions still implement Motion
if m, ok := result.(action.Motion); ok {
mot = m
}
mot = r.WithCount(count).(action.Motion)
}
// If operator pending (e.g., "df{char}"), get range and operate
@ -389,14 +378,7 @@ func (h *Handler) handleCharMotion(m action.Model, key string) tea.Cmd {
// Otherwise just execute the motion
cmd := h.executeMotion(m, mot)
// ReplaceChar modifies the buffer, so it should be repeatable with '.'
// (unlike f/t/F/T which are pure motions)
if _, isReplace := mot.(action.ReplaceChar); isReplace {
h.RecordAndReset(m)
} else {
h.Reset()
}
h.Reset()
return cmd
}
@ -574,32 +556,6 @@ func (h *Handler) handleInsertKey(m action.Model, key string) tea.Cmd {
return action.InsertChar{Char: key}.Execute(m)
}
func (h *Handler) handleReplaceKey(m action.Model, key string) tea.Cmd {
buf := m.ActiveBuffer()
win := m.ActiveWindow()
// Start undo block on first insert key
if buf.UndoStack != nil && !buf.UndoStack.Recording() {
buf.UndoStack.BeginBlock(win.Cursor)
}
// Record the key for count replay (e.g. 5i...)
m.SetInsertKeys(append(m.InsertKeys(), key))
m.SetLastChangeKeys(append(m.LastChangeKeys(), key))
// Check the insert keymap first
kind, binding := h.replaceKeymap.Lookup(key)
switch kind {
case "action":
return binding.(action.Action).Execute(m)
case "motion":
return binding.(action.Motion).Execute(m)
}
// Fallback: treat as a regular character to "insert"
return action.ReplaceModeChar{Char: key}.Execute(m)
}
// Handler.handleCommandKey: Processes a keypress in command mode, executing
// it as an action or inserting it into the command line. This does not record
// anything into the undo stack.

View File

@ -38,13 +38,8 @@ func NewNormalKeymap() *Keymap {
"e": motion.MoveForwardWordEnd{Count: 1},
"E": motion.MoveForwardWORDEnd{Count: 1},
"b": motion.MoveBackwardWord{Count: 1},
"B": motion.MoveBackwardWORD{Count: 1},
"ge": motion.MoveBackwardWordEnd{Count: 1},
"gE": motion.MoveBackwardWORDEnd{Count: 1},
"ctrl+u": motion.ScrollUpPage{Divisor: 2},
"ctrl+d": motion.ScrollDownPage{Divisor: 2},
"ctrl+b": motion.ScrollUpPage{Divisor: 1},
"ctrl+f": motion.ScrollDownPage{Divisor: 1},
"ctrl+u": motion.ScrollUpHalfPage{},
"ctrl+d": motion.ScrollDownHalfPage{},
";": action.RepeatFind{Count: 1, Reverse: false},
",": action.RepeatFind{Count: 1, Reverse: true},
},
@ -77,14 +72,12 @@ func NewNormalKeymap() *Keymap {
"u": action.Undo{},
"ctrl+r": action.Redo{},
".": action.Repeat{Count: 1},
"R": action.EnterReplace{},
},
charMotions: map[string]action.Motion{
"f": action.FindChar{Forward: true, Inclusive: true, Repeated: false},
"F": action.FindChar{Forward: false, Inclusive: true, Repeated: false},
"t": action.FindChar{Forward: true, Inclusive: false, Repeated: false},
"T": action.FindChar{Forward: false, Inclusive: false, Repeated: false},
"r": action.ReplaceChar{Count: 1},
},
modifiers: map[string]any{
"i": nil,
@ -115,31 +108,22 @@ func NewNormalKeymap() *Keymap {
func NewVisualKeymap() *Keymap {
return &Keymap{
motions: map[string]action.Motion{
"j": motion.MoveDown{Count: 1},
"k": motion.MoveUp{Count: 1},
"h": motion.MoveLeft{Count: 1},
"l": motion.MoveRight{Count: 1},
"G": motion.MoveToBottom{},
"gg": motion.MoveToTop{},
"0": motion.MoveToLineStart{},
"$": motion.MoveToLineEnd{},
"_": motion.MoveToLineContentStart{},
"^": motion.MoveToLineContentStart{},
"|": motion.MoveToColumn{Count: 0},
"w": motion.MoveForwardWord{Count: 1},
"W": motion.MoveForwardWORD{Count: 1},
"e": motion.MoveForwardWordEnd{Count: 1},
"E": motion.MoveForwardWORDEnd{Count: 1},
"b": motion.MoveBackwardWord{Count: 1},
"B": motion.MoveBackwardWORD{Count: 1},
"ge": motion.MoveBackwardWordEnd{Count: 1},
"gE": motion.MoveBackwardWORDEnd{Count: 1},
"ctrl+u": motion.ScrollUpPage{Divisor: 2},
"ctrl+d": motion.ScrollDownPage{Divisor: 2},
"ctrl+b": motion.ScrollUpPage{Divisor: 1},
"ctrl+f": motion.ScrollDownPage{Divisor: 1},
";": action.RepeatFind{Count: 1, Reverse: false},
",": action.RepeatFind{Count: 1, Reverse: true},
"j": motion.MoveDown{Count: 1},
"k": motion.MoveUp{Count: 1},
"h": motion.MoveLeft{Count: 1},
"l": motion.MoveRight{Count: 1},
"G": motion.MoveToBottom{},
"gg": motion.MoveToTop{},
"0": motion.MoveToLineStart{},
"$": motion.MoveToLineEnd{},
"_": motion.MoveToLineContentStart{},
"^": motion.MoveToLineContentStart{},
"|": motion.MoveToColumn{Count: 0},
"w": motion.MoveForwardWord{Count: 1},
"W": motion.MoveForwardWORD{Count: 1},
"e": motion.MoveForwardWordEnd{Count: 1},
"E": motion.MoveForwardWORDEnd{Count: 1},
"b": motion.MoveBackwardWord{Count: 1},
// TODO: O and o. These are fun ones! Should be simple too
},
operators: map[string]action.Operator{
@ -148,12 +132,9 @@ func NewVisualKeymap() *Keymap {
"X": operator.DeleteOperator{},
"y": operator.YankOperator{},
"c": operator.ChangeOperator{},
"s": operator.ChangeOperator{}, // Same as c in visual mode
"R": operator.ChangeOperator{}, // Seems to do the same thing
},
actions: map[string]action.Action{
"p": action.VisualPaste{Count: 1, Replace: true},
"P": action.VisualPaste{Count: 1, Replace: false},
"p": action.VisualPaste{Count: 1},
".": action.Repeat{Count: 1},
// ":": action.EnterComandMode{}, // Different OP
},
@ -206,26 +187,7 @@ func NewInsertKeymap() *Keymap {
"ctrl+w": action.InsertDeletePreviousWord{},
},
}
}
// NewReplaceKeymap: Creates a keymap for replace mode with editing actions. All actions
func NewReplaceKeymap() *Keymap {
return &Keymap{
motions: map[string]action.Motion{
"down": motion.MoveDown{Count: 1},
"up": motion.MoveUp{Count: 1},
"left": motion.MoveLeft{Count: 1},
"right": motion.MoveRight{Count: 1},
},
operators: map[string]action.Operator{}, // this will likely be empty
actions: map[string]action.Action{
"enter": action.ReplaceNewline{},
"backspace": action.InsertBackspace{},
"delete": action.InsertDelete{},
"tab": action.ReplaceTab{}, // TODO: This needs replacing
"ctrl+w": action.InsertDeletePreviousWord{},
},
}
}
// NewCommandKeymap: Creates a keymap for command mode with command line editing.

View File

@ -80,7 +80,7 @@ type MoveRight struct {
func (a MoveRight) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
lineLen := buf.Lines[win.Cursor.Line].Len()
lineLen := len(buf.Lines[win.Cursor.Line])
for i := 0; i < a.Count && win.Cursor.Col <= lineLen; i++ {
win.SetCursorCol(win.Cursor.Col + 1)
}

View File

@ -50,7 +50,7 @@ type MoveToLineEnd struct{}
func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
win.SetCursorCol(buf.Lines[win.Cursor.Line].Len() - 1)
win.SetCursorCol(len(buf.Lines[win.Cursor.Line]))
return nil
}
@ -65,7 +65,7 @@ func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
line := buf.Line(win.Cursor.Line)
line := buf.Lines[win.Cursor.Line]
x := 0
for x < len(line) {
ch := line[x]
@ -96,7 +96,7 @@ func (a MoveToColumn) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
line := buf.Line(win.Cursor.Line)
line := buf.Lines[win.Cursor.Line]
col := min(a.Count-1, len(line)-1)
win.SetCursorCol(col)
@ -111,23 +111,21 @@ func (a MoveToColumn) WithCount(n int) action.Action {
// TODO: Count for these, maybe?
// ScrollDownPage implements Motion (ctrl+d) - linewise
type ScrollDownPage struct {
Divisor int
}
// ScrollDownHalfPage implements Motion (ctrl+d) - linewise
type ScrollDownHalfPage struct{}
// ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the
// cursor's relative position in the viewport.
func (a ScrollDownPage) Execute(m action.Model) tea.Cmd {
func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
viewportHeight := win.ViewportHeight()
viewportHeight := win.Height - 2
if viewportHeight <= 0 {
return nil
}
scroll := viewportHeight / a.Divisor
scroll := viewportHeight / 2
scrollOff := win.Options.ScrollOff
// Current relative position in viewport
@ -154,24 +152,22 @@ func (a ScrollDownPage) Execute(m action.Model) tea.Cmd {
return nil
}
func (a ScrollDownPage) Type() core.MotionType { return core.Linewise }
func (a ScrollDownHalfPage) Type() core.MotionType { return core.Linewise }
// ScrollUpPage implements Motion (ctrl+u) - linewise
type ScrollUpPage struct {
Divisor int
}
// ScrollUpHalfPage implements Motion (ctrl+u) - linewise
type ScrollUpHalfPage struct{}
// ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the
// cursor's relative position in the viewport.
func (a ScrollUpPage) Execute(m action.Model) tea.Cmd {
func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
viewportHeight := win.ViewportHeight()
viewportHeight := win.Height - 2
if viewportHeight <= 0 {
return nil
}
scroll := viewportHeight / a.Divisor
scroll := viewportHeight / 2
scrollOff := win.Options.ScrollOff
// Current relative position in viewport
@ -197,4 +193,4 @@ func (a ScrollUpPage) Execute(m action.Model) tea.Cmd {
return nil
}
func (a ScrollUpPage) Type() core.MotionType { return core.Linewise }
func (a ScrollUpHalfPage) Type() core.MotionType { return core.Linewise }

View File

@ -24,7 +24,7 @@ func isWordPunctuation(c byte) bool {
// nextWordStart: Finds the start of the next word from position (x,y), handling
// word boundaries and line crossing.
func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
line := buf.Lines[y]
// Skip current class
if x < len(line) {
@ -59,7 +59,7 @@ func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
// Move to first char of next line
y++
line = buf.Line(y)
line = buf.Lines[y]
x = 0
// If the first char of the new line is no whitespace, stay here!
@ -74,7 +74,7 @@ func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
// nextWORDStart: Finds the start of the next WORD from position (x,y), treating
// all non-whitespace as a single class.
func nextWORDStart(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
line := buf.Lines[y]
// Skip current WORD (all non-whitespace is one class for W)
for x < len(line) && line[x] != ' ' && line[x] != '\t' {
@ -100,7 +100,7 @@ func nextWORDStart(buf *core.Buffer, x, y int) (int, int) {
// Move to first char of next line
y++
line = buf.Line(y)
line = buf.Lines[y]
x = 0
// If the first char of the new line is no whitespace, stay here!
@ -115,7 +115,7 @@ func nextWORDStart(buf *core.Buffer, x, y int) (int, int) {
// nextWordEnd: Finds the end of the next word from position (x,y), respecting
// word character classes.
func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
line := buf.Lines[y]
// Advance once to avoid being stuck on the current end
x++
@ -128,7 +128,7 @@ func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
// Otherwise, move to next line
y++
x = 0
line = buf.Line(y)
line = buf.Lines[y]
}
// Skip whitespace and cross lines if needed
@ -150,7 +150,7 @@ func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
// Move to first char of next line
y++
line = buf.Line(y)
line = buf.Lines[y]
x = 0
}
@ -174,7 +174,7 @@ func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
// nextWORDEnd: Finds the end of the next WORD from position (x,y), treating
// all non-whitespace as a single class.
func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
line := buf.Lines[y]
// Advance once to avoid being stuck on the current end
x++
@ -187,7 +187,7 @@ func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
// Otherwise, move to next line
y++
x = 0
line = buf.Line(y)
line = buf.Lines[y]
}
// Skip whitespace and cross lines if needed
@ -209,7 +209,7 @@ func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
// Move to first char of next line
y++
line = buf.Line(y)
line = buf.Lines[y]
x = 0
}
@ -224,7 +224,7 @@ func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
// prevWordStart: Finds the start of the previous word from position (x,y),
// moving backward through character classes.
func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
line := buf.Lines[y]
// Back one to avoid being stuck on the current start
x--
@ -233,7 +233,7 @@ func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
return 0, 0 // beginning of file, stay put
}
y--
line = buf.Line(y)
line = buf.Lines[y]
x = len(line) - 1
if x < 0 {
return 0, y // landed on an empty line
@ -252,7 +252,7 @@ func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
return 0, 0
}
y--
line = buf.Line(y)
line = buf.Lines[y]
x = len(line) - 1
if len(line) == 0 {
return 0, y // empty line acts as a word boundary
@ -412,290 +412,3 @@ func (a MoveBackwardWord) Type() core.MotionType { return core.CharwiseExclusive
func (a MoveBackwardWord) WithCount(n int) action.Action {
return MoveBackwardWord{Count: n}
}
// prevWORDStart: Finds the start of the previous WORD from position (x,y),
// treating all non-whitespace as a single class.
func prevWORDStart(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
// Back one to avoid being stuck on the current start
x--
if x < 0 {
if y == 0 {
return 0, 0 // beginning of file, stay put
}
y--
line = buf.Line(y)
x = len(line) - 1
if x < 0 {
return 0, y // landed on an empty line
}
}
// Skip whitespace backward, crossing lines if needed
for {
for x >= 0 && (line[x] == ' ' || line[x] == '\t') {
x--
}
if x >= 0 {
break // landed on a non-whitespace char
}
if y == 0 {
return 0, 0
}
y--
line = buf.Line(y)
x = len(line) - 1
if len(line) == 0 {
return 0, y // empty line acts as a word boundary
}
}
// Skip to the start of the WORD (all non-whitespace is one class)
for x-1 >= 0 && line[x-1] != ' ' && line[x-1] != '\t' {
x--
}
return x, y
}
// prevWordEnd: Finds the end of the previous word from position (x,y),
// respecting word character classes.
func prevWordEnd(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
origY := y
// Back one to avoid being stuck on the current end
x--
if x < 0 {
if y == 0 {
return 0, 0 // beginning of file, stay put
}
y--
line = buf.Line(y)
x = len(line) - 1
// Don't return early for empty line - we'll handle it in whitespace skip
}
// Skip backward through current word class if we're on one
// BUT: if we crossed lines in the "back one" step, we're already at the end of a word
if y == origY && x >= 0 && line[x] != ' ' && line[x] != '\t' {
if isWordChar(line[x]) {
// Skip word characters
for x >= 0 && isWordChar(line[x]) {
x--
if x < 0 {
if y == 0 {
return 0, 0
}
y--
line = buf.Line(y)
x = len(line) - 1
if x < 0 {
return 0, y
}
}
}
} else {
// Skip punctuation
for x >= 0 && isWordPunctuation(line[x]) {
x--
if x < 0 {
if y == 0 {
return 0, 0
}
y--
line = buf.Line(y)
x = len(line) - 1
if x < 0 {
return 0, y
}
}
}
}
}
// Skip whitespace backward, crossing lines if needed
for {
for x >= 0 && (line[x] == ' ' || line[x] == '\t') {
x--
}
if x >= 0 {
break // landed on a non-whitespace char, this is our word end!
}
if y == 0 {
return 0, 0
}
y--
line = buf.Line(y)
x = len(line) - 1
if len(line) == 0 {
return 0, y // empty line acts as a word boundary
}
}
// Now x,y is at the start of the target word. Move forward to its end.
if x >= 0 {
if isWordChar(line[x]) {
for x+1 < len(line) && isWordChar(line[x+1]) {
x++
}
} else if isWordPunctuation(line[x]) {
for x+1 < len(line) && isWordPunctuation(line[x+1]) {
x++
}
}
}
return x, y
}
// prevWORDEnd: Finds the end of the previous WORD from position (x,y),
// treating all non-whitespace as a single class.
func prevWORDEnd(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y)
origY := y
// Back one to avoid being stuck on the current end
x--
if x < 0 {
if y == 0 {
return 0, 0 // beginning of file, stay put
}
y--
line = buf.Line(y)
x = len(line) - 1
// Don't return early for empty line - we'll handle it in whitespace skip
}
// Skip backward through current WORD if we're on one
// BUT: if we crossed lines in the "back one" step, we're already at the end of a WORD
if y == origY && x >= 0 && line[x] != ' ' && line[x] != '\t' {
for x >= 0 && line[x] != ' ' && line[x] != '\t' {
x--
if x < 0 {
if y == 0 {
return 0, 0
}
y--
line = buf.Line(y)
x = len(line) - 1
if x < 0 {
return 0, y
}
}
}
}
// Skip whitespace backward, crossing lines if needed
for {
for x >= 0 && (line[x] == ' ' || line[x] == '\t') {
x--
}
if x >= 0 {
break // landed on a non-whitespace char, this is our WORD end!
}
if y == 0 {
return 0, 0
}
y--
line = buf.Line(y)
x = len(line) - 1
if len(line) == 0 {
return 0, y // empty line acts as a word boundary
}
}
// Now x,y is at the start of the target WORD. Move forward to its end.
if x >= 0 {
for x+1 < len(line) && line[x+1] != ' ' && line[x+1] != '\t' {
x++
}
}
return x, y
}
// MoveBackwardWORD implements Motion (B) - charwise
type MoveBackwardWORD struct {
Count int
}
// MoveBackwardWORD.Execute: Moves the cursor backward by Count WORDs (B motion).
func (a MoveBackwardWORD) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x := win.Cursor.Col
y := win.Cursor.Line
for i := 0; i < a.Count; i++ {
x, y = prevWORDStart(buf, x, y)
}
win.SetCursorCol(x)
win.SetCursorLine(y)
return nil
}
// MoveBackwardWORD.Type: Returns CharwiseExclusive for backward WORD motion.
func (a MoveBackwardWORD) Type() core.MotionType { return core.CharwiseExclusive }
// MoveBackwardWORD.WithCount: Returns a new MoveBackwardWORD with the given count.
func (a MoveBackwardWORD) WithCount(n int) action.Action {
return MoveBackwardWORD{Count: n}
}
// MoveBackwardWordEnd implements Motion (ge) - charwise
type MoveBackwardWordEnd struct {
Count int
}
// MoveBackwardWordEnd.Execute: Moves the cursor to the end of the previous word (ge motion).
func (a MoveBackwardWordEnd) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x := win.Cursor.Col
y := win.Cursor.Line
for i := 0; i < a.Count; i++ {
x, y = prevWordEnd(buf, x, y)
}
win.SetCursorCol(x)
win.SetCursorLine(y)
return nil
}
// MoveBackwardWordEnd.Type: Returns CharwiseInclusive for backward word-end motion.
func (a MoveBackwardWordEnd) Type() core.MotionType { return core.CharwiseInclusive }
// MoveBackwardWordEnd.WithCount: Returns a new MoveBackwardWordEnd with the given count.
func (a MoveBackwardWordEnd) WithCount(n int) action.Action {
return MoveBackwardWordEnd{Count: n}
}
// MoveBackwardWORDEnd implements Motion (gE) - charwise
type MoveBackwardWORDEnd struct {
Count int
}
// MoveBackwardWORDEnd.Execute: Moves the cursor to the end of the previous WORD (gE motion).
func (a MoveBackwardWORDEnd) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x := win.Cursor.Col
y := win.Cursor.Line
for i := 0; i < a.Count; i++ {
x, y = prevWORDEnd(buf, x, y)
}
win.SetCursorCol(x)
win.SetCursorLine(y)
return nil
}
// MoveBackwardWORDEnd.Type: Returns CharwiseInclusive for backward WORD-end motion.
func (a MoveBackwardWORDEnd) Type() core.MotionType { return core.CharwiseInclusive }
// MoveBackwardWORDEnd.WithCount: Returns a new MoveBackwardWORDEnd with the given count.
func (a MoveBackwardWORDEnd) WithCount(n int) action.Action {
return MoveBackwardWORDEnd{Count: n}
}

View File

@ -68,18 +68,18 @@ func changeCharSelection(m action.Model, start, end core.Position) {
var deletedText string
if start.Line == end.Line {
line := buf.Line(start.Line)
line := buf.Lines[start.Line]
endCol := min(end.Col+1, len(line))
deletedText = line[start.Col:endCol]
buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
} else {
startLine := buf.Line(start.Line)
endLine := buf.Line(end.Line)
startLine := buf.Lines[start.Line]
endLine := buf.Lines[end.Line]
// Extract deleted text
deletedText = startLine[start.Col:] + "\n"
for y := start.Line + 1; y < end.Line; y++ {
deletedText += buf.Line(y) + "\n"
deletedText += buf.Lines[y] + "\n"
}
endCol := min(end.Col+1, len(endLine))
deletedText += endLine[:endCol]
@ -113,7 +113,7 @@ func changeLineSelection(m action.Model, start, end core.Position) {
var lines []string
for i := end.Line; i >= start.Line; i-- {
lines = append([]string{buf.Line(i)}, lines...)
lines = append([]string{buf.Lines[i]}, lines...)
buf.DeleteLine(i)
}
@ -138,7 +138,7 @@ func changeBlockSelection(m action.Model, start, end core.Position) {
endCol := max(start.Col, end.Col)
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
line := buf.Lines[y]
if startCol >= len(line) {
continue
}
@ -168,7 +168,7 @@ func (o ChangeOperator) DoublePress(m action.Model, count int) tea.Cmd {
// Collect lines to delete (always delete at startY since lines shift up)
for range opCount {
lines = append(lines, buf.Line(startY))
lines = append(lines, buf.Lines[startY])
buf.DeleteLine(startY)
}

View File

@ -39,7 +39,7 @@ func (o DeleteOperator) DoublePress(m action.Model, count int) tea.Cmd {
for range opCount {
y := win.Cursor.Line
lines = append(lines, buf.Line(y))
lines = append(lines, buf.Lines[y])
buf.DeleteLine(y)
@ -99,12 +99,12 @@ func deleteCharSelection(m action.Model, start, end core.Position) {
buf := m.ActiveBuffer()
if start.Line == end.Line {
line := buf.Line(start.Line)
line := buf.Lines[start.Line]
endCol := min(end.Col+1, len(line))
buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
} else {
startLine := buf.Line(start.Line)
endLine := buf.Line(end.Line)
startLine := buf.Lines[start.Line]
endLine := buf.Lines[end.Line]
prefix := startLine[:start.Col]
suffix := ""
@ -131,7 +131,7 @@ func deleteLineSelection(m action.Model, start, end core.Position) {
var lines []string
for i := end.Line; i >= start.Line; i-- {
lines = append(lines, buf.Line(i))
lines = append(lines, buf.Lines[i])
buf.DeleteLine(i)
}
@ -159,7 +159,7 @@ func deleteBlockSelection(m action.Model, start, end core.Position) {
endCol := max(start.Col, end.Col)
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
line := buf.Lines[y]
if startCol >= len(line) {
continue
}

View File

@ -30,14 +30,8 @@ func (o YankOperator) Operate(m action.Model, start, end core.Position, mtype co
})
}
// Normalize so cursor is set to the earlier position (important for backward motions)
cursorPos := start
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
cursorPos = end
}
win.SetCursorCol(cursorPos.Col)
win.SetCursorLine(cursorPos.Line)
win.SetCursorCol(start.Col)
win.SetCursorLine(start.Line)
return nil
}
@ -57,7 +51,7 @@ func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
var lines []string
for i := range opCount {
lines = append(lines, buf.Line(y+i))
lines = append(lines, buf.Lines[y+i])
}
// Put her in the register!
@ -72,7 +66,17 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
switch {
case mtype.IsCharwise():
line := buf.Line(start.Line)
// This shouldn't happen
// if start.Line != end.Line {
// m.SetCommandOutput(&core.CommandOutput{
// Lines: []string{"Start line and end line must match for charwise yank operations."},
// Inline: true,
// IsError: true,
// })
// return
// }
line := buf.Lines[start.Line]
startX := min(start.Col, end.Col)
endX := max(start.Col, end.Col)
@ -87,19 +91,22 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
cnt := line[startX:endX]
m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
win := m.ActiveWindow()
win.SetCursorCol(startX)
win.SetCursorLine(start.Line)
case mtype == core.Linewise:
// This shouldn't happen
// if start.Col != end.Col {
// m.SetCommandOutput(&core.CommandOutput{
// Lines: []string{"Start column and end column must match for linewise yank operations."},
// Inline: true,
// IsError: true,
// })
// return
// }
// These don't need to be validated, they are validated before being passed into the function
startY := min(start.Line, end.Line)
endY := max(start.Line, end.Line)
var cnt []string
for i := startY; i <= endY; i++ {
cnt = append(cnt, buf.Line(i))
}
cnt := buf.Lines[startY : endY+1]
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
}
}
@ -115,7 +122,7 @@ func yankVisualMode(m action.Model, start, end core.Position) {
// Single line selection
if start.Line == end.Line {
line := buf.Line(start.Line)
line := buf.Lines[start.Line]
endCol := min(end.Col+1, len(line)) // +1 because visual selection is inclusive
startCol := min(start.Col, len(line))
cnt := line[startCol:endCol]
@ -127,17 +134,17 @@ func yankVisualMode(m action.Model, start, end core.Position) {
var content []string
// First line: from start.Col to end of line
firstLine := buf.Line(start.Line)
firstLine := buf.Lines[start.Line]
startCol := min(start.Col, len(firstLine))
content = append(content, firstLine[startCol:])
// Middle lines: entire lines
for y := start.Line + 1; y < end.Line; y++ {
content = append(content, buf.Line(y))
content = append(content, buf.Lines[y])
}
// Last line: from beginning to end.Col (inclusive)
lastLine := buf.Line(end.Line)
lastLine := buf.Lines[end.Line]
endCol := min(end.Col+1, len(lastLine))
content = append(content, lastLine[:endCol])
@ -162,10 +169,7 @@ func yankVisualLineMode(m action.Model, start, end core.Position) {
startY := min(start.Line, end.Line)
endY := max(start.Line, end.Line)
var cnt []string
for i := startY; i <= endY; i++ {
cnt = append(cnt, buf.Line(i))
}
cnt := buf.Lines[startY : endY+1]
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
}
@ -183,7 +187,7 @@ func yankVisualBlockMode(m action.Model, start, end core.Position) {
var content []string
for y := startY; y <= endY; y++ {
line := buf.Line(y)
line := buf.Lines[y]
// Handle lines shorter than the block selection
if startX >= len(line) {

View File

@ -1,11 +1,8 @@
package style
import (
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/charmbracelet/lipgloss"
)
@ -15,7 +12,6 @@ type Styles struct {
CursorNormal lipgloss.Style
CursorInsert lipgloss.Style
CursorCommand lipgloss.Style
CursorReplace lipgloss.Style
// Gutter (line numbers)
Gutter lipgloss.Style
@ -48,7 +44,6 @@ func DefaultStyles() Styles {
CursorNormal: lipgloss.NewStyle().Reverse(true),
CursorInsert: lipgloss.NewStyle().Underline(true),
CursorCommand: lipgloss.NewStyle().Reverse(true),
CursorReplace: lipgloss.NewStyle().Underline(true),
Gutter: lipgloss.NewStyle().
Background(lipgloss.Color("236")).
@ -97,17 +92,12 @@ func ChromaStyles(chromaStyle *chroma.Style) Styles {
CursorInsert: lipgloss.NewStyle().
Background(lipgloss.Color(bgString)).
Bold(true).
Underline(true),
CursorCommand: lipgloss.NewStyle().
Background(lipgloss.Color(bgString)).
Reverse(true),
CursorReplace: lipgloss.NewStyle().
Background(lipgloss.Color(bgString)).
Underline(true),
Gutter: lipgloss.NewStyle().
Background(lipgloss.Color(
darkenColor(lineNumbers.Background, 0.9).String()),
@ -170,8 +160,6 @@ func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
return s.CursorInsert
case core.CommandMode:
return s.CursorCommand
case core.ReplaceMode:
return s.CursorReplace
default:
return s.CursorNormal
}
@ -186,11 +174,6 @@ func (s Styles) CursorStyle(mode core.Mode, style lipgloss.Style) lipgloss.Style
return lipgloss.NewStyle().
Background(style.GetForeground()).
Foreground(style.GetBackground())
case core.ReplaceMode, core.WaitingMode:
return lipgloss.NewStyle().
Background(style.GetBackground()).
Foreground(style.GetForeground()).
Underline(true)
default:
return lipgloss.NewStyle().
Background(s.BackgroundStyle.GetBackground()).
@ -250,33 +233,3 @@ func darkenColor(c chroma.Colour, factor float64) chroma.Colour {
b := uint8(float64(c.Blue()) * factor)
return chroma.NewColour(r, g, b)
}
// GetLexer: Uses buffer meta data or content to pick a lexer for use in applying
// highlights.
func GetLexer(buf *core.Buffer) chroma.Lexer {
var lexer chroma.Lexer
if buf.Filetype != "" {
lexer = lexers.Get(strings.TrimPrefix(buf.Filetype, "."))
}
if lexer == nil && buf.Filename != "" {
lexer = lexers.Match(buf.Filename)
}
if lexer == nil && len(buf.Lines) > 0 {
// Get first few lines for content analysis
var content strings.Builder
for i := 0; i < min(len(buf.Lines), 10); i++ {
content.WriteString(buf.Lines[i].String() + "\n")
}
lexer = lexers.Analyse(content.String())
}
if lexer == nil {
lexer = lexers.Fallback
}
lexer = chroma.Coalesce(lexer) // Merge tokens together
return lexer
}

View File

@ -75,14 +75,8 @@ func (to Delimiter) GetRange(m action.Model, cursor core.Position, modifier stri
return cursor, cursor, core.CharwiseExclusive
}
// Convert buffer lines to strings for delimiter finding
var lines []string
for i := 0; i < buf.LineCount(); i++ {
lines = append(lines, buf.Line(i))
}
// Use multi-line delimiter pair finding
start, end, found := findMultiLineDelimiterPair(lines, startDelim, endDelim, cursor, modifier == "a")
start, end, found := findMultiLineDelimiterPair(buf.Lines, startDelim, endDelim, cursor, modifier == "a")
if !found {
return cursor, cursor, core.CharwiseExclusive

View File

@ -10,7 +10,7 @@ type Word struct{}
func (to Word) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
buf := m.ActiveBuffer()
line := buf.Line(cursor.Line)
line := buf.Lines[cursor.Line]
// Find word boundaries
start := findWordStart(line, cursor.Col)
@ -28,7 +28,7 @@ type WORD struct{}
func (to WORD) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
buf := m.ActiveBuffer()
line := buf.Line(cursor.Line)
line := buf.Lines[cursor.Line]
// Find word boundaries
start := findWORDStart(line, cursor.Col)

25
qodo.md
View File

@ -1,25 +0,0 @@
### The Core Commands
* `/review`
Asks Gemini to read the entire PR diff and provide a structured summary, score the PR, identify potential bugs, and suggest high-level fixes.
* `/describe`
Automatically rewrites the PR's title and description based on the actual code changes. (Great for when you just want to push code and not write documentation).
* `/improve`
Scans the code and provides actionable, copy-pasteable snippets to improve the code, focusing on performance, security, and best practices.
* `/ask "<your question>"`
Turns the PR comment section into a chat window. It uses the PR diff as context. Example: `/ask "Did I properly handle the null pointer edge cases in the new database function?"`
### Specialized Tools
* `/test`
Asks the AI to generate unit tests specifically tailored for the new or modified code in the PR.
* `/update_changelog`
Automatically drafts an update for your `CHANGELOG.md` file based on the PR's contents.
* `/generate_labels`
Analyzes the code changes and recommends appropriate labels for the PR (e.g., `bug`, `enhancement`, `refactor`).
* `/help`
Forces the bot to reply with a quick cheat sheet of all available commands and usage instructions in case you forget them.
### Pro-Tip: Steering the AI
You can actually pass arguments directly to the commands to give Gemini specific instructions for that specific run.
For example, if you want a review but want it to be hyper-paranoid about security, you can type:
`/review --pr_reviewer.extra_instructions="Focus heavily on potential security vulnerabilities and SQL injection risks."`