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] `b` - Backward to start of word
- [x] `W` - Forward to start of WORD (whitespace-delimited) - [x] `W` - Forward to start of WORD (whitespace-delimited)
- [x] `E` - Forward to end of WORD - [x] `E` - Forward to end of WORD
- [x] `B` - Backward to start of WORD - [ ] `B` - Backward to start of WORD
- [x] `ge` - Backward to end of word - [ ] `ge` - Backward to end of word
### Line Movement ### Line Movement
- [x] `0` - Move to start of line - [x] `0` - Move to start of line
@ -34,8 +34,8 @@
### Scroll ### Scroll
- [x] `ctrl+u` - Scroll up half page - [x] `ctrl+u` - Scroll up half page
- [x] `ctrl+d` - Scroll down half page - [x] `ctrl+d` - Scroll down half page
- [x] `ctrl+b` - Scroll up full page - [ ] `ctrl+b` - Scroll up full page
- [x] `ctrl+f` - Scroll down full page - [ ] `ctrl+f` - Scroll down full page
- [ ] `ctrl+y` - Scroll up one line - [ ] `ctrl+y` - Scroll up one line
- [ ] `ctrl+e` - Scroll down one line - [ ] `ctrl+e` - Scroll down one line
- [ ] `zz` - Center cursor on screen - [ ] `zz` - Center cursor on screen
@ -129,12 +129,12 @@
### Undo/Redo ### Undo/Redo
- [x] `u` - Undo - [x] `u` - Undo
- [x] `ctrl+r` - Redo - [x] `ctrl+r` - Redo
- [x] `.` - Repeat last change - [ ] `.` - Repeat last change
- [ ] `U` - Undo all changes on line - [ ] `U` - Undo all changes on line
### Other Normal Mode ### Other Normal Mode
- [x] `r{char}` - Replace character - [ ] `r{char}` - Replace character
- [x] `R` - Replace mode - [ ] `R` - Replace mode
- [ ] `~` - Swap case of character - [ ] `~` - Swap case of character
- [ ] `ctrl+a` - Increment number - [ ] `ctrl+a` - Increment number
- [ ] `ctrl+x` - Decrement number - [ ] `ctrl+x` - Decrement number
@ -219,7 +219,7 @@
- [x] `:q!` - Force quit - [x] `:q!` - Force quit
- [x] `:e {file}` - Edit file - [x] `:e {file}` - Edit file
- [x] `:bn` / `:bp` - Next/previous buffer - [x] `:bn` / `:bp` - Next/previous buffer
- [x] `:{range}` - Go to line - [ ] `:{range}` - Go to line
- [ ] `:%s/old/new/g` - Search and replace - [ ] `:%s/old/new/g` - Search and replace
- [ ] `:!{cmd}` - Run shell command - [ ] `:!{cmd}` - Run shell command
- [ ] `:help` - Show help - [ ] `:help` - Show help
@ -408,3 +408,63 @@ Buffers are in-memory representations of files. A buffer exists for each open fi
- [ ] Spell check - [ ] 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 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. 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 ## TODO List
- Ops like change, and substitute and such should add to paste reg - Ops like change, and substitute and such should add to paste reg
- Delete op should also 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(). prog = program.NewProgramBuilder().
EmptyProgram(). EmptyProgram().
WithOpt(tea.WithAltScreen()). WithOpt(tea.WithAltScreen()).
WithOpt(tea.WithMouseCellMotion()).
Build() Build()
} else { } else {
prog = program.NewProgramBuilder(). prog = program.NewProgramBuilder().
FileProgram(args[0]). FileProgram(args[0]).
WithOpt(tea.WithAltScreen()). WithOpt(tea.WithAltScreen()).
WithOpt(tea.WithMouseCellMotion()).
Build() Build()
} }

View File

@ -24,8 +24,6 @@
glibc_multi glibc_multi
]; ];
name = "Gim";
# Define the shell that will be executed. # Define the shell that will be executed.
# Here, we explicitly use zsh. # Here, we explicitly use zsh.
# Note: pkgs.zsh needs to be included in `packages` or `nativeBuildInputs` # 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() buf := m.ActiveBuffer()
pos := win.Cursor.Col pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line) line := buf.Lines[win.Cursor.Line]
// Save deleted text to register // Save deleted text to register
if pos < len(line) { if pos < len(line) {
@ -51,7 +51,7 @@ func (a SubstituteChar) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
pos := win.Cursor.Col 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) // Calculate how many chars to delete (limited by line length)
count := min(a.Count, len(line)-pos) count := min(a.Count, len(line)-pos)
@ -97,7 +97,7 @@ func (a SubstituteLine) Execute(m Model) tea.Cmd {
// Collect and delete lines // Collect and delete lines
for range count { for range count {
lines = append(lines, buf.Line(y)) lines = append(lines, buf.Lines[y])
buf.DeleteLine(y) buf.DeleteLine(y)
} }

View File

@ -1,8 +1,6 @@
package action package action
import ( import (
"strconv"
"git.gophernest.net/azpect/TextEditor/internal/core" "git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
@ -141,12 +139,6 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
// history = append([]string{cmdLine}, history...) // history = append([]string{cmdLine}, history...)
m.SetCommandHistory(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) cmd, err := a.Registry.Execute(m, cmdLine)
if err != nil { if err != nil {
out := core.CommandOutput{ out := core.CommandOutput{
@ -160,23 +152,3 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
return 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() buf := m.ActiveBuffer()
pos := win.Cursor.Col 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++ { for i := 0; i < a.Count && pos < len(line); i++ {
line = line[:pos] + line[pos+1:] line = line[:pos] + line[pos+1:]
buf.SetLine(win.Cursor.Line, line) buf.SetLine(win.Cursor.Line, line)
@ -38,7 +38,7 @@ func (a DeletePrevChar) Execute(m Model) tea.Cmd {
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
pos := win.Cursor.Col 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++ { for i := 0; i < a.Count && pos <= len(line); i++ {
if pos > 0 { if pos > 0 {
line = line[:pos-1] + line[pos:] line = line[:pos-1] + line[pos:]
@ -69,7 +69,7 @@ func (a DeleteToEndOfLine) Execute(m Model) tea.Cmd {
// Delete to end of line // Delete to end of line
pos := win.Cursor.Col pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line) line := buf.Lines[win.Cursor.Line]
buf.SetLine(win.Cursor.Line, line[:pos]) buf.SetLine(win.Cursor.Line, line[:pos])
win.SetCursorCol(pos - 1) win.SetCursorCol(pos - 1)

View File

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

View File

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

View File

@ -20,7 +20,7 @@ type Buffer struct {
// File data // File data
Filename string Filename string
Filetype string Filetype string
Lines []*GapBuffer // Changed from []string to []*GapBuffer Lines []string
// Flags (not used yet) // Flags (not used yet)
Modified bool Modified bool
@ -42,7 +42,7 @@ func (b *Buffer) Line(idx int) string {
if idx < 0 || idx >= len(b.Lines) { if idx < 0 || idx >= len(b.Lines) {
return "" 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 // 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) { if idx >= 0 && idx < len(b.Lines) {
// Record set line in undo stack // Record set line in undo stack
if b.UndoStack != nil { 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 b.Modified = true
} }
@ -74,8 +74,7 @@ func (b *Buffer) InsertLine(idx int, content string) {
b.UndoStack.RecordInsertLine(idx, content) b.UndoStack.RecordInsertLine(idx, content)
} }
newLine := NewGapBuffer(content) b.Lines = append(b.Lines[:idx], append([]string{content}, b.Lines[idx:]...)...)
b.Lines = append(b.Lines[:idx], append([]*GapBuffer{newLine}, b.Lines[idx:]...)...)
b.Modified = true b.Modified = true
} }
@ -85,7 +84,7 @@ func (b *Buffer) DeleteLine(idx int) {
if idx >= 0 && idx < len(b.Lines) { if idx >= 0 && idx < len(b.Lines) {
// Record delete line in undo stack // Record delete line in undo stack
if b.UndoStack != nil { 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:]...) b.Lines = append(b.Lines[:idx], b.Lines[idx+1:]...)
} }
@ -122,7 +121,7 @@ func (b *Buffer) Undo(w *Window) bool {
case SetLineChange: case SetLineChange:
// Restore old data // Restore old data
if change.Line >= 0 && change.Line < len(b.Lines) { if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines[change.Line].Set(change.OldData) b.Lines[change.Line] = change.OldData
} }
case InsertLineChange: case InsertLineChange:
// Remove the inserted line // Remove the inserted line
@ -132,8 +131,7 @@ func (b *Buffer) Undo(w *Window) bool {
case DeleteLineChange: case DeleteLineChange:
// Re-insert the deleted line // Re-insert the deleted line
if change.Line <= len(b.Lines) { if change.Line <= len(b.Lines) {
newLine := NewGapBuffer(change.OldData) b.Lines = append(b.Lines[:change.Line], append([]string{change.OldData}, b.Lines[change.Line:]...)...)
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
} }
} }
@ -167,13 +165,12 @@ func (b *Buffer) Redo(w *Window) bool {
case SetLineChange: case SetLineChange:
// Apply new data // Apply new data
if change.Line >= 0 && change.Line < len(b.Lines) { if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines[change.Line].Set(change.NewData) b.Lines[change.Line] = change.NewData
} }
case InsertLineChange: case InsertLineChange:
// Re-insert the line // Re-insert the line
if change.Line <= len(b.Lines) { if change.Line <= len(b.Lines) {
newLine := NewGapBuffer(change.NewData) b.Lines = append(b.Lines[:change.Line], append([]string{change.NewData}, b.Lines[change.Line:]...)...)
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
} }
case DeleteLineChange: case DeleteLineChange:
// Re-delete the line // 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. // Buffer.SetLines: Replace all lines in the buffer with the provided lines.
// This is useful when loading a file or resetting buffer content. // This is useful when loading a file or resetting buffer content.
func (b *Buffer) SetLines(lines []string) { func (b *Buffer) SetLines(lines []string) {
b.Lines = make([]*GapBuffer, len(lines)) b.Lines = lines
for i, line := range lines {
b.Lines[i] = NewGapBuffer(line)
}
} }
// Buffer.SetModified: Set the modified flag for this buffer. A modified buffer // 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 Type: ScatchBuffer, // Default buffer type
Filename: "", Filename: "",
Filetype: "", Filetype: "",
Lines: []*GapBuffer{NewEmptyGapBuffer()}, Lines: []string{""},
Modified: false, Modified: false,
Loaded: false, Loaded: false,
Listed: 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. // BufferBuilder.WithLines: Attaches a lines to the buffer that is being built.
func (b *BufferBuilder) WithLines(lines []string) *BufferBuilder { func (b *BufferBuilder) WithLines(lines []string) *BufferBuilder {
b.buffer.Lines = make([]*GapBuffer, len(lines)) b.buffer.Lines = lines
for i, line := range lines {
b.buffer.Lines[i] = NewGapBuffer(line)
}
return b 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 VisualMode
VisualLineMode VisualLineMode
VisualBlockMode VisualBlockMode
ReplaceMode
WaitingMode // Same as NORMAL output, but cursor is the REPLACE cursor
) )
// Mode.ToString: Returns a human-readable string representation of the mode // Mode.ToString: Returns a human-readable string representation of the mode
// for display in the status bar. // for display in the status bar.
func (m Mode) ToString() string { func (m Mode) ToString() string {
switch m { switch m {
case NormalMode, WaitingMode: case NormalMode:
return "NORMAL" return "NORMAL"
case InsertMode: case InsertMode:
return "INSERT" return "INSERT"
@ -31,8 +29,6 @@ func (m Mode) ToString() string {
return "V-LINE" return "V-LINE"
case VisualBlockMode: case VisualBlockMode:
return "V-BLOCK" return "V-BLOCK"
case ReplaceMode:
return "REPLACE"
default: default:
return "-----" return "-----"
} }

View File

@ -60,7 +60,7 @@ func (w *Window) ClampCursor() {
} }
// Clamp column to valid range [0, lineLen] // 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 { if w.Cursor.Col < 0 {
w.Cursor.Col = 0 w.Cursor.Col = 0
} else if lineLen == 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}) tm.Send(tea.KeyMsg{Type: tea.KeyCtrlR})
case "ctrl+w": case "ctrl+w":
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlW}) 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: default:
tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)}) tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)})
} }

View File

@ -13,8 +13,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" { if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -24,8 +24,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" { if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -35,8 +35,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hell" { if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("lines[0] = %q, want 'hell'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hell'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -46,8 +46,8 @@ func TestDeleteChar(t *testing.T) {
sendKeys(tm, "x", "x") sendKeys(tm, "x", "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" { if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -59,8 +59,8 @@ func TestDeleteCharWithCount(t *testing.T) {
sendKeys(tm, "3", "x") sendKeys(tm, "3", "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" { if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "1", "0", "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -81,8 +81,8 @@ func TestDeleteCharWithCount(t *testing.T) {
sendKeys(tm, "2", "x") sendKeys(tm, "2", "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hlo" { if m.ActiveBuffer().Lines[0] != "hlo" {
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -94,8 +94,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -108,8 +108,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -119,8 +119,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "a" { if m.ActiveBuffer().Lines[0] != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -130,8 +130,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab c" { if m.ActiveBuffer().Lines[0] != "ab c" {
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "world" { if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "x", "x", "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" { if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -166,8 +166,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab" { if m.ActiveBuffer().Lines[0] != "ab" {
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -177,8 +177,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "5", "x") sendKeys(tm, "5", "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -188,8 +188,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
sendKeys(tm, "x") sendKeys(tm, "x")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "abde" { if m.ActiveBuffer().Lines[0] != "abde" {
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -201,8 +201,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" { if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -212,8 +212,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" { if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -223,8 +223,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" { if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -234,8 +234,8 @@ func TestDeleteCharBackward(t *testing.T) {
sendKeys(tm, "X", "X") sendKeys(tm, "X", "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" { if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -247,8 +247,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
sendKeys(tm, "3", "X") sendKeys(tm, "3", "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" { if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "1", "0", "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "o" { if m.ActiveBuffer().Lines[0] != "o" {
t.Errorf("lines[0] = %q, want 'o'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'o'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -269,8 +269,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
sendKeys(tm, "2", "X") sendKeys(tm, "2", "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hlo" { if m.ActiveBuffer().Lines[0] != "hlo" {
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -282,8 +282,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -296,8 +296,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -310,8 +310,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "a" { if m.ActiveBuffer().Lines[0] != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -321,8 +321,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "b" { if m.ActiveBuffer().Lines[0] != "b" {
t.Errorf("Line(0) = %q, want 'b'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'b'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -332,8 +332,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab c" { if m.ActiveBuffer().Lines[0] != "ab c" {
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "world" { if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "X", "X", "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" { if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -368,8 +368,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ab" { if m.ActiveBuffer().Lines[0] != "ab" {
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -379,8 +379,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "5", "X") sendKeys(tm, "5", "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "c" { if m.ActiveBuffer().Lines[0] != "c" {
t.Errorf("Line(0) = %q, want 'c'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'c'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -390,8 +390,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "abde" { if m.ActiveBuffer().Lines[0] != "abde" {
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -413,8 +413,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "3", "X") sendKeys(tm, "3", "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" { if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("Line(0) = %q, want 'ho'", m.ActiveBuffer().Lines[0].String()) 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 // Cursor should be at position 1 after deleting 3 chars backward from position 4
if m.ActiveWindow().Cursor.Col != 1 { if m.ActiveWindow().Cursor.Col != 1 {
@ -428,11 +428,11 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "world" { if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
} }
}) })
@ -442,8 +442,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " " { if m.ActiveBuffer().Lines[0] != " " {
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0])
} }
}) })
@ -453,8 +453,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
sendKeys(tm, "X") sendKeys(tm, "X")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" { if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 3 { if m.ActiveWindow().Cursor.Col != 3 {
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
@ -469,8 +469,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -480,8 +480,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -491,8 +491,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hell" { if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -514,8 +514,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -528,11 +528,11 @@ func TestDeleteToEndOfLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %q, want '3'", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %q, want '3'", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "he" { if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "hi" { if m.ActiveBuffer().Lines[1] != "hi" {
t.Errorf("Line(1) = %q, want 'hi'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %q, want '1'", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %q, want '1'", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "he" { if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -558,8 +558,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -572,8 +572,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
}) })
@ -583,11 +583,11 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[2].String() != "line 3" { if m.ActiveBuffer().Lines[2] != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String()) t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2])
} }
}) })
@ -597,8 +597,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "he" { if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
} }
// Cursor should clamp to last char // Cursor should clamp to last char
if m.ActiveWindow().Cursor.Col != 1 { if m.ActiveWindow().Cursor.Col != 1 {
@ -612,8 +612,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -623,8 +623,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " " { if m.ActiveBuffer().Lines[0] != " " {
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0])
} }
}) })
@ -634,8 +634,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -645,8 +645,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "a" { if m.ActiveBuffer().Lines[0] != "a" {
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -656,8 +656,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[1].String() != "world" { if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
} }
}) })
@ -667,8 +667,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
sendKeys(tm, "D") sendKeys(tm, "D")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "first" { if m.ActiveBuffer().Lines[0] != "first" {
t.Errorf("Line(0) = %q, want 'first'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'first'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().LineCount() != 3 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) 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") sendKeys(tm, "i", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" { if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" { if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "a", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hXello" { if m.ActiveBuffer().Lines[0] != "hXello" {
t.Errorf("lines[0] = %q, want 'hXello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "a", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helXlo" { if m.ActiveBuffer().Lines[0] != "helXlo" {
t.Errorf("lines[0] = %q, want 'helXlo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "I", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" { if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "I", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" { if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "A", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helloX" { if m.ActiveBuffer().Lines[0] != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "A", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helloX" { if m.ActiveBuffer().Lines[0] != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "new" { if m.ActiveBuffer().Lines[1] != "new" {
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 4 {
t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[2].String() != "new" { if m.ActiveBuffer().Lines[2] != "new" {
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[2].String() != "new" { if m.ActiveBuffer().Lines[2] != "new" {
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String()) 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()) t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
} }
for i := 1; i <= 3; i++ { for i := 1; i <= 3; i++ {
if m.ActiveBuffer().Lines[i].String() != "x" { if m.ActiveBuffer().Lines[i] != "x" {
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "ab" { if m.ActiveBuffer().Lines[1] != "ab" {
t.Errorf("lines[1] = %q, want 'ab'", m.ActiveBuffer().Lines[1].String()) t.Errorf("lines[1] = %q, want 'ab'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "ab" { if m.ActiveBuffer().Lines[2] != "ab" {
t.Errorf("lines[2] = %q, want 'ab'", m.ActiveBuffer().Lines[2].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "new" { if m.ActiveBuffer().Lines[1] != "new" {
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "new" { if m.ActiveBuffer().Lines[0] != "new" {
t.Errorf("lines[0] = %q, want 'new'", m.ActiveBuffer().Lines[0].String()) 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()) t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
} }
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
if m.ActiveBuffer().Lines[i].String() != "x" { if m.ActiveBuffer().Lines[i] != "x" {
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != " world" { if m.ActiveBuffer().Lines[1] != " world" {
t.Errorf("lines[1] = %q, want ' world'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("lines[1] = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("lines[1] = %q, want ''", m.ActiveBuffer().Lines[1])
} }
}) })
@ -335,11 +335,11 @@ func TestInsertModeEnter(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "hello" { if m.ActiveBuffer().Lines[1] != "hello" {
t.Errorf("lines[1] = %q, want 'hello'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "i", "backspace", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helo" { if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "helloworld" { if m.ActiveBuffer().Lines[0] != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "backspace", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "backspace", "backspace", "backspace", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "he" { if m.ActiveBuffer().Lines[0] != "he" {
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "delete", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "word" { if m.ActiveBuffer().Lines[0] != "word" {
t.Errorf("lines[0] = %q, want 'word'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "helloworld" { if m.ActiveBuffer().Lines[0] != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "delete", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "delete", "delete", "delete", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" { if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "left", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" { if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "right", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" { if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "up", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" { if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "world" { if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "i", "down", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "woXrld" { if m.ActiveBuffer().Lines[1] != "woXrld" {
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "i", "left", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "Xhello" { if m.ActiveBuffer().Lines[0] != "Xhello" {
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "a", "right", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "helloX" { if m.ActiveBuffer().Lines[0] != "helloX" {
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "up", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" { if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "down", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heXllo" { if m.ActiveBuffer().Lines[0] != "heXllo" {
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "up", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hiX" { if m.ActiveBuffer().Lines[0] != "hiX" {
t.Errorf("lines[0] = %q, want 'hiX'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "i", "down", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[1].String() != "hiX" { if m.ActiveBuffer().Lines[1] != "hiX" {
t.Errorf("lines[1] = %q, want 'hiX'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "i", "right", "right", "down", "X", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[1].String() != "woXrld" { if m.ActiveBuffer().Lines[1] != "woXrld" {
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 5 { if m.ActiveWindow().Cursor.Col != 5 {
t.Errorf("CursorX() = %d, want '5'", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello wo" { if m.ActiveBuffer().Lines[0] != "hello wo" {
t.Errorf("lines[0] = %q, want 'hello wo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hello wo'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 7 { if m.ActiveWindow().Cursor.Col != 7 {
t.Errorf("CursorX() = %d, want '7'", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want '7'", m.ActiveWindow().Cursor.Col)
@ -655,8 +655,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want '1'", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want '1'", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "i", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "i", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo" { if m.ActiveBuffer().Lines[0] != "lo" {
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "..." { if m.ActiveBuffer().Lines[0] != "..." {
t.Errorf("lines[0] = %q, want '...'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '...'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 2 { if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello\t" { if m.ActiveBuffer().Lines[0] != "hello\t" {
t.Errorf("lines[0] = %q, want 'hello\\t'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'hello\\t'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 5 { if m.ActiveWindow().Cursor.Col != 5 {
t.Errorf("CursorX() = %d, want 5", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 5", m.ActiveWindow().Cursor.Col)
@ -731,8 +731,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "helloworld" { if m.ActiveBuffer().Lines[0] != "helloworld" {
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 4 { if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "a", "ctrl+w", "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)

View File

@ -176,7 +176,7 @@ func TestMoveToLineEnd(t *testing.T) {
sendKeys(tm, "$") sendKeys(tm, "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
want := len(lines[0]) - 1 want := len(lines[0])
if m.ActiveWindow().Cursor.Col != want { if m.ActiveWindow().Cursor.Col != want {
t.Errorf("CursorX() = %d, want %d", 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, "$") sendKeys(tm, "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
want := len(lines[0]) - 1 want := len(lines[0])
if m.ActiveWindow().Cursor.Col != want { if m.ActiveWindow().Cursor.Col != want {
t.Errorf("CursorX() = %d, want %d", 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, "$") sendKeys(tm, "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
want := len(lines[0]) - 1 want := len(lines[0])
if m.ActiveWindow().Cursor.Col != want { if m.ActiveWindow().Cursor.Col != want {
t.Errorf("CursorX() = %d, want %d", 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 // Deletes from column 1 to current position (exclusive), so "hello" deleted
// Result depends on inclusive/exclusive behavior // Result depends on inclusive/exclusive behavior
// In Vim: d| from col 5 deletes chars 0-4, leaving " world" // In Vim: d| from col 5 deletes chars 0-4, leaving " world"
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -646,8 +646,8 @@ func TestMoveToColumnWithOperator(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Deletes from cursor (0) to column 5 (index 4), so "hell" deleted // Deletes from cursor (0) to column 5 (index 4), so "hell" deleted
// Result: "o world" // Result: "o world"
if m.ActiveBuffer().Lines[0].String() != "o world" { if m.ActiveBuffer().Lines[0] != "o world" {
t.Errorf("Line(0) = %q, want 'o world'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Visual selection from 0 to 4 inclusive, delete "hello" // Visual selection from 0 to 4 inclusive, delete "hello"
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Should delete "hello.world " (including trailing space) // Should delete "hello.world " (including trailing space)
if m.ActiveBuffer().Lines[0].String() != "next" { if m.ActiveBuffer().Lines[0] != "next" {
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -946,11 +946,11 @@ func TestMoveForwardWORDWithOperator(t *testing.T) {
sendKeys(tm2, "d", "w") sendKeys(tm2, "d", "w")
m2 := getFinalModel(t, tm2) m2 := getFinalModel(t, tm2)
if m1.ActiveBuffer().Lines[0].String() != "next" { if m1.ActiveBuffer().Lines[0] != "next" {
t.Errorf("dW: Line(0) = %q, want 'next'", m1.ActiveBuffer().Lines[0].String()) t.Errorf("dW: Line(0) = %q, want 'next'", m1.ActiveBuffer().Lines[0])
} }
if m2.ActiveBuffer().Lines[0].String() != ".world next" { if m2.ActiveBuffer().Lines[0] != ".world next" {
t.Errorf("dw: Line(0) = %q, want '.world next'", m2.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "2", "W")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "three four" { if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Visual selection from 0 to 12, delete "hello.world " // Visual selection from 0 to 12, delete "hello.world "
if m.ActiveBuffer().Lines[0].String() != "ext" { if m.ActiveBuffer().Lines[0] != "ext" {
t.Errorf("Line(0) = %q, want 'ext'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'ext'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -1557,8 +1557,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Should delete "hello.world" leaving " next" // Should delete "hello.world" leaving " next"
if m.ActiveBuffer().Lines[0].String() != " next" { if m.ActiveBuffer().Lines[0] != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -1570,8 +1570,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
m1 := getFinalModel(t, tm1) m1 := getFinalModel(t, tm1)
// 'de' should delete "hello" leaving ".world next" // 'de' should delete "hello" leaving ".world next"
if m1.ActiveBuffer().Lines[0].String() != ".world next" { if m1.ActiveBuffer().Lines[0] != ".world next" {
t.Errorf("'de': Line(0) = %q, want '.world next'", m1.ActiveBuffer().Lines[0].String()) t.Errorf("'de': Line(0) = %q, want '.world next'", m1.ActiveBuffer().Lines[0])
} }
// Now test 'dE' // Now test 'dE'
@ -1580,8 +1580,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
m2 := getFinalModel(t, tm2) m2 := getFinalModel(t, tm2)
// 'dE' should delete "hello.world" leaving " next" // 'dE' should delete "hello.world" leaving " next"
if m2.ActiveBuffer().Lines[0].String() != " next" { if m2.ActiveBuffer().Lines[0] != " next" {
t.Errorf("'dE': Line(0) = %q, want ' next'", m2.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Should delete "one.a two.b" leaving " three" // Should delete "one.a two.b" leaving " three"
if m.ActiveBuffer().Lines[0].String() != " three" { if m.ActiveBuffer().Lines[0] != " three" {
t.Errorf("Line(0) = %q, want ' three'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ' three'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -1653,8 +1653,8 @@ func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Should delete "hello.world" leaving " next" // Should delete "hello.world" leaving " next"
if m.ActiveBuffer().Lines[0].String() != " next" { if m.ActiveBuffer().Lines[0] != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String()) 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()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
// First line should be empty (ready for insert) // First line should be empty (ready for insert)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "world" { if m.ActiveBuffer().Lines[1] != "world" {
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "line one" { if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "line three" { if m.ActiveBuffer().Lines[2] != "line three" {
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
}) })
@ -109,8 +109,8 @@ func TestChangeLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -148,11 +148,11 @@ func TestChangeLineWithCount(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line three" { if m.ActiveBuffer().Lines[1] != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "one" { if m.ActiveBuffer().Lines[0] != "one" {
t.Errorf("Line(0) = %q, want 'one'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'one'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "five" { if m.ActiveBuffer().Lines[2] != "five" {
t.Errorf("Line(2) = %q, want 'five'", m.ActiveBuffer().Lines[2].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -235,8 +235,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
if m.Mode() != core.InsertMode { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "ello world" { if m.ActiveBuffer().Lines[0] != "ello world" {
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "llo world" { if m.ActiveBuffer().Lines[0] != "llo world" {
t.Errorf("Line(0) = %q, want 'llo world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "hell world" { if m.ActiveBuffer().Lines[0] != "hell world" {
t.Errorf("Line(0) = %q, want 'hell world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String()) 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 // ^ is exclusive motion, so position 8 (space) is not included
// Delete positions 3-7 ("hello"), leaving " " + " world" = " world" // Delete positions 3-7 ("hello"), leaving " " + " world" = " world"
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "heworld" { if m.ActiveBuffer().Lines[0] != "heworld" {
t.Errorf("Line(0) = %q, want 'heworld'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "three four" { if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "next" { if m.ActiveBuffer().Lines[0] != "next" {
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != " next" { if m.ActiveBuffer().Lines[0] != " next" {
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line three" { if m.ActiveBuffer().Lines[1] != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line three" { if m.ActiveBuffer().Lines[1] != "line three" {
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "four" { if m.ActiveBuffer().Lines[1] != "four" {
t.Errorf("Line(1) = %q, want 'four'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -556,8 +556,8 @@ func TestChangeWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -576,11 +576,11 @@ func TestChangeWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line one" { if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
}) })
} }
@ -601,8 +601,8 @@ func TestChangeToEndOfLine(t *testing.T) {
if m.Mode() != core.InsertMode { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) 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()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
// Should delete last char // Should delete last char
if m.ActiveBuffer().Lines[0].String() != "hell" { if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "ello" { if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("Line(0) = %q, want 'ello'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "llo" { if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "hell" { if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -740,14 +740,14 @@ func TestSubstituteLine(t *testing.T) {
sendKeys(tm, "S") sendKeys(tm, "S")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "line one" { if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "line three" { if m.ActiveBuffer().Lines[2] != "line three" {
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "three" { if m.ActiveBuffer().Lines[1] != "three" {
t.Errorf("Line(1) = %q, want 'three'", m.ActiveBuffer().Lines[1].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "ello world" { if m.ActiveBuffer().Lines[0] != "ello world" {
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
}) })
@ -898,14 +898,14 @@ func TestVisualLineModeChange(t *testing.T) {
if m.ActiveBuffer().LineCount() != 3 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line one" { if m.ActiveBuffer().Lines[0] != "line one" {
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "" { if m.ActiveBuffer().Lines[1] != "" {
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "line four" { if m.ActiveBuffer().Lines[2] != "line four" {
t.Errorf("Line(2) = %q, want 'line four'", m.ActiveBuffer().Lines[2].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) 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()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
// cw on last word should change to end of line // cw on last word should change to end of line
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) 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 { if m.Mode() != core.InsertMode {
t.Errorf("Mode() = %v, want InsertMode", m.Mode()) t.Errorf("Mode() = %v, want InsertMode", m.Mode())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
} }
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line)
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "testing" { if m.ActiveBuffer().Lines[1] != "testing" {
t.Errorf("Line(1) = %s, want 'testing'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
} }
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line)
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "another line" { if m.ActiveBuffer().Lines[1] != "another line" {
t.Errorf("Line(1) = %s, want 'another line'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -176,8 +176,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -190,8 +190,8 @@ func TestDeleteLine(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Line != 0 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line) 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 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
} }
if m.ActiveBuffer().Lines[0].String() != "ello" { if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("Line(0) = %s, want 'ello'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want '2'", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want '2'", m.ActiveWindow().Cursor.Col)
} }
if m.ActiveBuffer().Lines[0].String() != "helo" { if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("Line(0) = %s, want 'helo'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Col != 4 {
t.Errorf("CursorX() = %d, want '4'", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want '4'", m.ActiveWindow().Cursor.Col)
} }
if m.ActiveBuffer().Lines[0].String() != "hell" { if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %s, want 'hell'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Col != 1 {
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
} }
if m.ActiveBuffer().Lines[0].String() != "hllo" { if m.ActiveBuffer().Lines[0] != "hllo" {
t.Errorf("Line(0) = %q, want 'hllo'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Col != 3 {
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
} }
if m.ActiveBuffer().Lines[0].String() != "helo" { if m.ActiveBuffer().Lines[0] != "helo" {
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "2", "d", "l")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" { if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "2", "l")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" { if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "2", "d", "h")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heo" { if m.ActiveBuffer().Lines[0] != "heo" {
t.Errorf("Line(0) = %q, want 'heo'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 4" { if m.ActiveBuffer().Lines[1] != "line 4" {
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveWindow().Cursor.Line != 1 { if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
@ -351,8 +351,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 3" { if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Line != 0 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -370,11 +370,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 2" { if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 5" { if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 5" { if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 4" { if m.ActiveBuffer().Lines[1] != "line 4" {
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveWindow().Cursor.Line != 1 { if m.ActiveWindow().Cursor.Line != 1 {
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
@ -443,11 +443,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 2" { if m.ActiveBuffer().Lines[0] != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 3" { if m.ActiveBuffer().Lines[1] != "line 3" {
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 3" { if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Line != 0 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -477,11 +477,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 5" { if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 5" { if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 3" { if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -555,8 +555,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "d", "w") sendKeys(tm, "d", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "heworld" { if m.ActiveBuffer().Lines[0] != "heworld" {
t.Errorf("Line(0) = %q, want \"heworld\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"heworld\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 2 { if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
@ -569,8 +569,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
sendKeys(tm, "d", "w") sendKeys(tm, "d", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "2", "d", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "three four" { if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "2", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "three four" { if m.ActiveBuffer().Lines[0] != "three four" {
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// 'w' motion stops at punctuation, so it should delete "hello" // 'w' motion stops at punctuation, so it should delete "hello"
if m.ActiveBuffer().Lines[0].String() != ", world" { if m.ActiveBuffer().Lines[0] != ", world" {
t.Errorf("Line(0) = %q, want \", world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \", world\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -616,8 +616,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// 'e' is inclusive - deletes "hello" (cols 0-4 inclusive), leaves " world" // 'e' is inclusive - deletes "hello" (cols 0-4 inclusive), leaves " world"
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -631,8 +631,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// From 'l' (col 2) to 'o' (col 4) inclusive → deletes "llo" // From 'l' (col 2) to 'o' (col 4) inclusive → deletes "llo"
if m.ActiveBuffer().Lines[0].String() != "he world" { if m.ActiveBuffer().Lines[0] != "he world" {
t.Errorf("Line(0) = %q, want \"he world\"", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// From 'o' of "hello" (col 4) to 'd' of "world" (col 10) inclusive // From 'o' of "hello" (col 4) to 'd' of "world" (col 10) inclusive
if m.ActiveBuffer().Lines[0].String() != "hell" { if m.ActiveBuffer().Lines[0] != "hell" {
t.Errorf("Line(0) = %q, want \"hell\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"hell\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -655,8 +655,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Deletes "one" and " two" (to end of second word inclusive) // Deletes "one" and " two" (to end of second word inclusive)
if m.ActiveBuffer().Lines[0].String() != " three four" { if m.ActiveBuffer().Lines[0] != " three four" {
t.Errorf("Line(0) = %q, want \" three four\"", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "b")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" { if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// cursor at 'r' of "world", db should delete back to start of "world" // cursor at 'r' of "world", db should delete back to start of "world"
if m.ActiveBuffer().Lines[0].String() != "hello rld" { if m.ActiveBuffer().Lines[0] != "hello rld" {
t.Errorf("Line(0) = %q, want \"hello rld\"", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// cursor at 'w', db should delete "hello " back // cursor at 'w', db should delete "hello " back
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -703,8 +703,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// cursor at 'f' of "four", 2db should delete "two three " // cursor at 'f' of "four", 2db should delete "two three "
if m.ActiveBuffer().Lines[0].String() != "one four" { if m.ActiveBuffer().Lines[0] != "one four" {
t.Errorf("Line(0) = %q, want \"one four\"", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// cursor at 'd' (last char), db should delete back to start of "world" // cursor at 'd' (last char), db should delete back to start of "world"
if m.ActiveBuffer().Lines[0].String() != "hello d" { if m.ActiveBuffer().Lines[0] != "hello d" {
t.Errorf("Line(0) = %q, want \"hello d\"", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "0")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" { if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "0")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -754,8 +754,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "0") sendKeys(tm, "d", "0")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "d" { if m.ActiveBuffer().Lines[0] != "d" {
t.Errorf("Line(0) = %q, want \"d\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"d\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -765,8 +765,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "0") sendKeys(tm, "d", "0")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "lo world" { if m.ActiveBuffer().Lines[0] != "lo world" {
t.Errorf("Line(0) = %q, want \"lo world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"lo world\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -777,8 +777,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$") sendKeys(tm, "d", "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -788,8 +788,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$") sendKeys(tm, "d", "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -799,8 +799,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$") sendKeys(tm, "d", "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worl" { if m.ActiveBuffer().Lines[0] != "hello worl" {
t.Errorf("Line(0) = %q, want \"hello worl\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"hello worl\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -810,11 +810,11 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$") sendKeys(tm, "d", "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "second line" { if m.ActiveBuffer().Lines[1] != "second line" {
t.Errorf("Line(1) = %q, want \"second line\"", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want \"second line\"", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
@ -827,8 +827,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
sendKeys(tm, "d", "$") sendKeys(tm, "d", "$")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
@ -843,8 +843,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// From col 0 to first non-whitespace (col 3, 'h') - deletes leading spaces // From col 0 to first non-whitespace (col 3, 'h') - deletes leading spaces
if m.ActiveBuffer().Lines[0].String() != "hello world" { if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
@ -858,8 +858,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// From col 1 to first non-whitespace (col 3, 'h') - deletes cols 1-2 // From col 1 to first non-whitespace (col 3, 'h') - deletes cols 1-2
if m.ActiveBuffer().Lines[0].String() != " hello world" { if m.ActiveBuffer().Lines[0] != " hello world" {
t.Errorf("Line(0) = %q, want \" hello world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \" hello world\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 1 { if m.ActiveWindow().Cursor.Col != 1 {
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
@ -874,8 +874,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// From col 6 ('l') back to col 3 ('h') // From col 6 ('l') back to col 3 ('h')
// Should delete "hel" leaving " lo world" // Should delete "hel" leaving " lo world"
if m.ActiveBuffer().Lines[0].String() != " lo world" { if m.ActiveBuffer().Lines[0] != " lo world" {
t.Errorf("Line(0) = %q, want \" lo world\"", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// From col 5 (' ') back to col 0 ('h') // From col 5 (' ') back to col 0 ('h')
// Should delete "hello" leaving " world" // Should delete "hello" leaving " world"
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 2" { if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -934,11 +934,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 2" { if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 4" { if m.ActiveBuffer().Lines[0] != "line 4" {
t.Errorf("Line(0) = %q, want 'line 4'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 4'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 5" { if m.ActiveBuffer().Lines[1] != "line 5" {
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -993,11 +993,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 2 { if m.ActiveBuffer().LineCount() != 2 {
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 2" { if m.ActiveBuffer().Lines[0] != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 3" { if m.ActiveBuffer().Lines[1] != "line 3" {
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -1035,8 +1035,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Cursor.Line != 22 {
t.Errorf("CursorY() = %d, want 22", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want 22", m.ActiveWindow().Cursor.Line)
} }
if 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, m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len()) 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) { func TestScrollWithCount(t *testing.T) {
t.Run("5j scrolls appropriately", func(t *testing.T) { t.Run("5j scrolls appropriately", func(t *testing.T) {
lines := generateLines(50) lines := generateLines(50)

View File

@ -33,8 +33,8 @@ func TestTextObjectInnerWord(t *testing.T) {
sendKeys(tm, "d", "i", "w") sendKeys(tm, "d", "i", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "c", "i", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0])
} }
if m.Mode() != core.InsertMode { if m.Mode() != core.InsertMode {
t.Errorf("Expected insert mode after ciw") t.Errorf("Expected insert mode after ciw")
@ -138,8 +138,8 @@ func TestTextObjectAroundWord(t *testing.T) {
sendKeys(tm, "d", "a", "w") sendKeys(tm, "d", "a", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "a", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "i", "W")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " test" { if m.ActiveBuffer().Lines[0] != " test" {
t.Errorf("lines[0] = %q, want ' test'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "d", "a", "W")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "baz" { if m.ActiveBuffer().Lines[0] != "baz" {
t.Errorf("lines[0] = %q, want 'baz'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'baz'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -248,8 +248,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
sendKeys(tm, "d", "i", "<") sendKeys(tm, "d", "i", "<")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "<>" { if m.ActiveBuffer().Lines[0] != "<>" {
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -259,8 +259,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
sendKeys(tm, "d", "a", "<") sendKeys(tm, "d", "a", "<")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -271,8 +271,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Should remain unchanged // Should remain unchanged
if m.ActiveBuffer().Lines[0].String() != "<>" { if m.ActiveBuffer().Lines[0] != "<>" {
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -321,8 +321,8 @@ func TestTextObjectParentheses(t *testing.T) {
sendKeys(tm, "d", "i", "(") sendKeys(tm, "d", "i", "(")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "func()" { if m.ActiveBuffer().Lines[0] != "func()" {
t.Errorf("lines[0] = %q, want 'func()'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'func()'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -332,8 +332,8 @@ func TestTextObjectParentheses(t *testing.T) {
sendKeys(tm, "d", "a", "(") sendKeys(tm, "d", "a", "(")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "func" { if m.ActiveBuffer().Lines[0] != "func" {
t.Errorf("lines[0] = %q, want 'func'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want 'func'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -343,8 +343,8 @@ func TestTextObjectParentheses(t *testing.T) {
sendKeys(tm, "d", "i", "(") sendKeys(tm, "d", "i", "(")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "()" { if m.ActiveBuffer().Lines[0] != "()" {
t.Errorf("lines[0] = %q, want '()'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '()'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -368,8 +368,8 @@ func TestTextObjectBraces(t *testing.T) {
sendKeys(tm, "d", "i", "{") sendKeys(tm, "d", "i", "{")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "{}" { if m.ActiveBuffer().Lines[0] != "{}" {
t.Errorf("lines[0] = %q, want '{}'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '{}'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -379,8 +379,8 @@ func TestTextObjectBraces(t *testing.T) {
sendKeys(tm, "d", "a", "{") sendKeys(tm, "d", "a", "{")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -404,8 +404,8 @@ func TestTextObjectBrackets(t *testing.T) {
sendKeys(tm, "d", "i", "[") sendKeys(tm, "d", "i", "[")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "[]" { if m.ActiveBuffer().Lines[0] != "[]" {
t.Errorf("lines[0] = %q, want '[]'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '[]'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -415,8 +415,8 @@ func TestTextObjectBrackets(t *testing.T) {
sendKeys(tm, "d", "a", "[") sendKeys(tm, "d", "a", "[")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -440,8 +440,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
sendKeys(tm, "d", "i", `"`) sendKeys(tm, "d", "i", `"`)
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != `""` { if m.ActiveBuffer().Lines[0] != `""` {
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -451,8 +451,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
sendKeys(tm, "d", "a", `"`) sendKeys(tm, "d", "a", `"`)
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
@ -462,8 +462,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
sendKeys(tm, "d", "i", `"`) sendKeys(tm, "d", "i", `"`)
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != `""` { if m.ActiveBuffer().Lines[0] != `""` {
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -487,8 +487,8 @@ func TestTextObjectSingleQuotes(t *testing.T) {
sendKeys(tm, "d", "i", "'") sendKeys(tm, "d", "i", "'")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "''" { if m.ActiveBuffer().Lines[0] != "''" {
t.Errorf("lines[0] = %q, want \"''\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want \"''\"", m.ActiveBuffer().Lines[0])
} }
}) })
@ -498,8 +498,8 @@ func TestTextObjectSingleQuotes(t *testing.T) {
sendKeys(tm, "d", "a", "'") sendKeys(tm, "d", "a", "'")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -523,8 +523,8 @@ func TestTextObjectBackticks(t *testing.T) {
sendKeys(tm, "d", "i", "`") sendKeys(tm, "d", "i", "`")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "``" { if m.ActiveBuffer().Lines[0] != "``" {
t.Errorf("lines[0] = %q, want '``'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '``'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -534,8 +534,8 @@ func TestTextObjectBackticks(t *testing.T) {
sendKeys(tm, "d", "a", "`") sendKeys(tm, "d", "a", "`")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -551,8 +551,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
sendKeys(tm, "d", "i", "w") sendKeys(tm, "d", "i", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != " b c" { if m.ActiveBuffer().Lines[0] != " b c" {
t.Errorf("lines[0] = %q, want ' b c'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want ' b c'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -564,8 +564,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
sendKeys(tm, "esc") sendKeys(tm, "esc")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "<world>" { if m.ActiveBuffer().Lines[0] != "<world>" {
t.Errorf("lines[0] = %q, want '<world>'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '<world>'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -577,8 +577,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// 'p' pastes after cursor, so "arg" is pasted after 't' -> "testarg" // 'p' pastes after cursor, so "arg" is pasted after 't' -> "testarg"
if m.ActiveBuffer().Lines[1].String() != "testarg" { if m.ActiveBuffer().Lines[1] != "testarg" {
t.Errorf("lines[1] = %q, want 'testarg'", m.ActiveBuffer().Lines[1].String()) t.Errorf("lines[1] = %q, want 'testarg'", m.ActiveBuffer().Lines[1])
} }
}) })
@ -589,8 +589,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Should remain unchanged since cursor is not inside parens // Should remain unchanged since cursor is not inside parens
if m.ActiveBuffer().Lines[0].String() != "before (hello) after" { if m.ActiveBuffer().Lines[0] != "before (hello) after" {
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0].String()) 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", "(") sendKeys(tm, "d", "i", "(")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "before () after" { if m.ActiveBuffer().Lines[0] != "before () after" {
t.Errorf("lines[0] = %q, want 'before () after'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// 'a' should delete including the delimiters // 'a' should delete including the delimiters
if m.ActiveBuffer().Lines[0].String() != "before after" { if m.ActiveBuffer().Lines[0] != "before after" {
t.Errorf("lines[0] = %q, want 'before after'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Cursor on '(' at position 5, should still select inside // Cursor on '(' at position 5, should still select inside
if m.ActiveBuffer().Lines[0].String() != "text () more" { if m.ActiveBuffer().Lines[0] != "text () more" {
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Cursor on ')', should still select inside // Cursor on ')', should still select inside
if m.ActiveBuffer().Lines[0].String() != "text () more" { if m.ActiveBuffer().Lines[0] != "text () more" {
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Should select the first pair it finds // Should select the first pair it finds
if m.ActiveBuffer().Lines[0].String() != "() bar (baz)" { if m.ActiveBuffer().Lines[0] != "() bar (baz)" {
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Should search forward and find the second pair // Should search forward and find the second pair
if m.ActiveBuffer().Lines[0].String() != "(foo) bar ()" { if m.ActiveBuffer().Lines[0] != "(foo) bar ()" {
t.Errorf("lines[0] = %q, want '(foo) bar ()'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Should select the first pair since cursor is inside it // Should select the first pair since cursor is inside it
if m.ActiveBuffer().Lines[0].String() != "() bar (baz)" { if m.ActiveBuffer().Lines[0] != "() bar (baz)" {
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Should find and select first quoted string // Should find and select first quoted string
if m.ActiveBuffer().Lines[0].String() != `foo "" baz "qux"` { if m.ActiveBuffer().Lines[0] != `foo "" baz "qux"` {
t.Errorf("lines[0] = %q, want 'foo \"\" baz \"qux\"'", m.ActiveBuffer().Lines[0].String()) 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) m := getFinalModel(t, tm)
// Should search forward and find second string // Should search forward and find second string
if m.ActiveBuffer().Lines[0].String() != `"foo" bar ""` { if m.ActiveBuffer().Lines[0] != `"foo" bar ""` {
t.Errorf("lines[0] = %q, want '\"foo\" bar \"\"'", m.ActiveBuffer().Lines[0].String()) t.Errorf("lines[0] = %q, want '\"foo\" bar \"\"'", m.ActiveBuffer().Lines[0])
} }
}) })
} }
@ -727,8 +727,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"func test() {", "func test() {",
"}", "}",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -746,8 +746,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
expected := []string{ expected := []string{
"func test() ", "func test() ",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -792,8 +792,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"function(", "function(",
")", ")",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -815,8 +815,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"outer {", "outer {",
"}", "}",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -841,8 +841,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
" more", " more",
"}", "}",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), 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) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -889,8 +889,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"function(arg) {", "function(arg) {",
"}", "}",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -909,8 +909,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"function(arg) {", "function(arg) {",
"}", "}",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -933,8 +933,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
"}", "}",
"after", "after",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected) t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
} }
}) })
@ -959,21 +959,12 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
" more", " more",
")", ")",
} }
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) { if !slicesEqual(m.ActiveBuffer().Lines, expected) {
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), 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 // Helper function to compare slices
func slicesEqual(a, b []string) bool { func slicesEqual(a, b []string) bool {
if len(a) != len(b) { if len(a) != len(b) {

View File

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

View File

@ -125,8 +125,8 @@ func TestVisualModeDelete(t *testing.T) {
sendKeys(tm, "v", "d") sendKeys(tm, "v", "d")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ello" { if m.ActiveBuffer().Lines[0] != "ello" {
t.Errorf("Line(0) = %q, want \"ello\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"ello\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "v", "l", "l", "l", "d")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "o world" { if m.ActiveBuffer().Lines[0] != "o world" {
t.Errorf("Line(0) = %q, want \"o world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"o world\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) 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" // anchor=3, cursor=1 → normalized start=1, end=3 → delete "ell" → "ho"
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" { if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 1 { if m.ActiveWindow().Cursor.Col != 1 {
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
@ -172,8 +172,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "held" { if m.ActiveBuffer().Lines[0] != "held" {
t.Errorf("Line(0) = %q, want \"held\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"held\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Col != 2 { if m.ActiveWindow().Cursor.Col != 2 {
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col) t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
@ -192,8 +192,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "world" { if m.ActiveBuffer().Lines[0] != "world" {
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Line != 0 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -209,8 +209,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "testing" { if m.ActiveBuffer().Lines[0] != "testing" {
t.Errorf("Line(0) = %q, want \"testing\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"testing\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveWindow().Cursor.Line != 0 { if m.ActiveWindow().Cursor.Line != 0 {
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line) t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
@ -227,8 +227,8 @@ func TestVisualModeDelete(t *testing.T) {
if m.ActiveBuffer().LineCount() != 1 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "hello" { if m.ActiveBuffer().Lines[0] != "hello" {
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0].String()) 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" // "hello"[:0]+"hello"[2:] = "llo"
// "world"[:0]+"world"[2:] = "rld" // "world"[:0]+"world"[2:] = "rld"
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "llo" { if m.ActiveBuffer().Lines[0] != "llo" {
t.Errorf("Line(0) = %q, want \"llo\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"llo\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "rld" { if m.ActiveBuffer().Lines[1] != "rld" {
t.Errorf("Line(1) = %q, want \"rld\"", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want \"rld\"", m.ActiveBuffer().Lines[1])
} }
if m.ActiveWindow().Cursor.Col != 0 { if m.ActiveWindow().Cursor.Col != 0 {
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col) 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" // "hello"[:1]+"hello"[4:] = "h"+"o" = "ho"
// "world"[:1]+"world"[4:] = "w"+"d" = "wd" // "world"[:1]+"world"[4:] = "w"+"d" = "wd"
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "ho" { if m.ActiveBuffer().Lines[0] != "ho" {
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "wd" { if m.ActiveBuffer().Lines[1] != "wd" {
t.Errorf("Line(1) = %q, want \"wd\"", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want \"wd\"", m.ActiveBuffer().Lines[1])
} }
}) })
} }
@ -302,8 +302,8 @@ func TestVisualModeWordMotions(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Deletes from 0 to 6 inclusive = "hello w", leaves "orld" // Deletes from 0 to 6 inclusive = "hello w", leaves "orld"
if m.ActiveBuffer().Lines[0].String() != "orld" { if m.ActiveBuffer().Lines[0] != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -333,8 +333,8 @@ func TestVisualModeWordMotions(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Deletes "hello" // Deletes "hello"
if m.ActiveBuffer().Lines[0].String() != " world" { if m.ActiveBuffer().Lines[0] != " world" {
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
} }
}) })
@ -364,8 +364,8 @@ func TestVisualModeWordMotions(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Deletes from "h" (0) to "w" (6) inclusive // Deletes from "h" (0) to "w" (6) inclusive
if m.ActiveBuffer().Lines[0].String() != "orld" { if m.ActiveBuffer().Lines[0] != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveWindow().Anchor.Col != 0 {
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col) t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
} }
if m.ActiveWindow().Cursor.Col != 10 { // $ moves past end of line
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col) 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") sendKeys(tm, "v", "$", "d")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello " { if m.ActiveBuffer().Lines[0] != "hello " {
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
} }
}) })
@ -441,8 +442,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
// Deletes from 'h' (0) to 'w' (6) inclusive // Deletes from 'h' (0) to 'w' (6) inclusive
if m.ActiveBuffer().Lines[0].String() != "orld" { if m.ActiveBuffer().Lines[0] != "orld" {
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "lin 3" { if m.ActiveBuffer().Lines[0] != "lin 3" {
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "lin 3" { if m.ActiveBuffer().Lines[0] != "lin 3" {
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 1 {
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "" { if m.ActiveBuffer().Lines[0] != "" {
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 1" { if m.ActiveBuffer().Lines[0] != "line 1" {
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 2" { if m.ActiveBuffer().Lines[1] != "line 2" {
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "line 3" { if m.ActiveBuffer().Lines[2] != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String()) 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") sendKeys(tm, "y", "w")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" { if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "v", "l", "l", "l", "l", "y")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello world" { if m.ActiveBuffer().Lines[0] != "hello world" {
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[1].String() != "to copy" { if m.ActiveBuffer().Lines[1] != "to copy" {
t.Errorf("Line(1) = %q, want 'to copy'", m.ActiveBuffer().Lines[1].String()) 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") sendKeys(tm, "v", "l", "l", "l", "l", "y", "$", "p")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worldhello" { if m.ActiveBuffer().Lines[0] != "hello worldhello" {
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "v", "$", "y", "0", "P")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "worldhello world" { if m.ActiveBuffer().Lines[0] != "worldhello world" {
t.Errorf("Line(0) = %q, want 'worldhello world'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 4 {
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[2].String() != "line 1" { if m.ActiveBuffer().Lines[2] != "line 1" {
t.Errorf("Line(2) = %q, want 'line 1'", m.ActiveBuffer().Lines[2].String()) 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 { if m.ActiveBuffer().LineCount() != 6 {
t.Errorf("LineCount() = %d, want 6", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 6", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[4].String() != "line 1" { if m.ActiveBuffer().Lines[4] != "line 1" {
t.Errorf("Line(4) = %q, want 'line 1'", m.ActiveBuffer().Lines[4].String()) t.Errorf("Line(4) = %q, want 'line 1'", m.ActiveBuffer().Lines[4])
} }
if m.ActiveBuffer().Lines[5].String() != "line 2" { if m.ActiveBuffer().Lines[5] != "line 2" {
t.Errorf("Line(5) = %q, want 'line 2'", m.ActiveBuffer().Lines[5].String()) 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 { if m.ActiveBuffer().LineCount() != 4 {
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 3" { if m.ActiveBuffer().Lines[0] != "line 3" {
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 1" { if m.ActiveBuffer().Lines[1] != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "original" { if m.ActiveBuffer().Lines[0] != "original" {
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "original" { if m.ActiveBuffer().Lines[1] != "original" {
t.Errorf("Line(1) = %q, want 'original'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'original'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "other" { if m.ActiveBuffer().Lines[2] != "other" {
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "original" { if m.ActiveBuffer().Lines[0] != "original" {
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "other" { if m.ActiveBuffer().Lines[1] != "other" {
t.Errorf("Line(1) = %q, want 'other'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'other'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "other" { if m.ActiveBuffer().Lines[2] != "other" {
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2].String()) 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") sendKeys(tm, "y", "w", "$", "p")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worldhello " { if m.ActiveBuffer().Lines[0] != "hello worldhello " {
t.Errorf("Line(0) = %q, want 'hello worldhello '", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "y", "e", "$", "p")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "hello worldhello" { if m.ActiveBuffer().Lines[0] != "hello worldhello" {
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0].String()) 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") sendKeys(tm, "v", "l", "l", "y", "$", "p")
m := getFinalModel(t, tm) m := getFinalModel(t, tm)
if m.ActiveBuffer().Lines[0].String() != "abcdefghcde" { if m.ActiveBuffer().Lines[0] != "abcdefghcde" {
t.Errorf("Line(0) = %q, want 'abcdefghcde'", m.ActiveBuffer().Lines[0].String()) 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 { if m.ActiveBuffer().LineCount() != 3 {
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount()) t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
} }
if m.ActiveBuffer().Lines[0].String() != "line 2" { if m.ActiveBuffer().Lines[0] != "line 2" {
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String()) t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
} }
if m.ActiveBuffer().Lines[1].String() != "line 1" { if m.ActiveBuffer().Lines[1] != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "line 3" { if m.ActiveBuffer().Lines[2] != "line 3" {
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String()) 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()) t.Errorf("LineCount() = %d, want 7", m.ActiveBuffer().LineCount())
} }
// Original + 2 copies of 2 lines = 3 + 4 = 7 // Original + 2 copies of 2 lines = 3 + 4 = 7
if m.ActiveBuffer().Lines[1].String() != "line 1" { if m.ActiveBuffer().Lines[1] != "line 1" {
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String()) t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
} }
if m.ActiveBuffer().Lines[2].String() != "line 2" { if m.ActiveBuffer().Lines[2] != "line 2" {
t.Errorf("Line(2) = %q, want 'line 2'", m.ActiveBuffer().Lines[2].String()) t.Errorf("Line(2) = %q, want 'line 2'", m.ActiveBuffer().Lines[2])
} }
if m.ActiveBuffer().Lines[3].String() != "line 1" { if m.ActiveBuffer().Lines[3] != "line 1" {
t.Errorf("Line(3) = %q, want 'line 1'", m.ActiveBuffer().Lines[3].String()) t.Errorf("Line(3) = %q, want 'line 1'", m.ActiveBuffer().Lines[3])
} }
if m.ActiveBuffer().Lines[4].String() != "line 2" { if m.ActiveBuffer().Lines[4] != "line 2" {
t.Errorf("Line(4) = %q, want 'line 2'", m.ActiveBuffer().Lines[4].String()) t.Errorf("Line(4) = %q, want 'line 2'", m.ActiveBuffer().Lines[4])
} }
}) })
} }

View File

@ -11,7 +11,76 @@ type ModelBuilder struct {
model Model 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 { func NewModelBuilder() *ModelBuilder {
chromaStyle := styles.Get("kanagawa-wave") chromaStyle := styles.Get("kanagawa-wave")

View File

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

View File

@ -2,11 +2,12 @@ package editor
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"git.gophernest.net/azpect/TextEditor/internal/core" "git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style" "git.gophernest.net/azpect/TextEditor/internal/style"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
) )
@ -23,10 +24,6 @@ func (m Model) View() string {
styles := m.Styles() styles := m.Styles()
options := win.Options 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 // Draw window
view := viewWindow(win, styles, options, m.Mode()) 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() end := w.ScrollY + w.ViewportHeight()
// Chroma stuff // 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 // Draw buffer lines
for lineNum := start; lineNum < end; lineNum++ { for lineNum := start; lineNum < end; lineNum++ {

View File

@ -41,7 +41,6 @@ type Handler struct {
normalKeymap *Keymap normalKeymap *Keymap
visualKeymap *Keymap visualKeymap *Keymap
insertKeymap *Keymap insertKeymap *Keymap
replaceKeymap *Keymap
commandKeymap *Keymap commandKeymap *Keymap
currentKeymap *Keymap currentKeymap *Keymap
@ -54,7 +53,6 @@ func NewHandler() *Handler {
normalKeymap: NewNormalKeymap(), normalKeymap: NewNormalKeymap(),
visualKeymap: NewVisualKeymap(), visualKeymap: NewVisualKeymap(),
insertKeymap: NewInsertKeymap(), insertKeymap: NewInsertKeymap(),
replaceKeymap: NewReplaceKeymap(),
commandKeymap: NewCommandKeymap(), commandKeymap: NewCommandKeymap(),
currentKeymap: nil, 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.recordingKeys = []string{} // Clear recording on ESC
h.Reset() 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 // Before exiting insert mode, end the block in the undo stack
win := m.ActiveWindow() win := m.ActiveWindow()
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
@ -97,8 +95,6 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
switch m.Mode() { switch m.Mode() {
case core.InsertMode: case core.InsertMode:
return h.handleInsertKey(m, key) return h.handleInsertKey(m, key)
case core.ReplaceMode:
return h.handleReplaceKey(m, key)
case core.CommandMode: case core.CommandMode:
return h.handleCommandKey(m, key) 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 { 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 // Handle character motions (f/t/F/T) - transition to waiting state
if kind == "char_motion" { if kind == "char_motion" {
if key == "r" {
m.SetMode(core.WaitingMode)
}
h.charMotionType = key h.charMotionType = key
h.state = StateWaitingForChar h.state = StateWaitingForChar
return nil return nil
@ -369,11 +362,7 @@ func (h *Handler) handleCharMotion(m action.Model, key string) tea.Cmd {
// Apply count if supported // Apply count if supported
if r, ok := mot.(action.Repeatable); ok { if r, ok := mot.(action.Repeatable); ok {
result := r.WithCount(count) mot = r.WithCount(count).(action.Motion)
// WithCount returns Action, but char motions still implement Motion
if m, ok := result.(action.Motion); ok {
mot = m
}
} }
// If operator pending (e.g., "df{char}"), get range and operate // 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 // Otherwise just execute the motion
cmd := h.executeMotion(m, mot) cmd := h.executeMotion(m, mot)
h.Reset()
// 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()
}
return cmd return cmd
} }
@ -574,32 +556,6 @@ func (h *Handler) handleInsertKey(m action.Model, key string) tea.Cmd {
return action.InsertChar{Char: key}.Execute(m) 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 // Handler.handleCommandKey: Processes a keypress in command mode, executing
// it as an action or inserting it into the command line. This does not record // it as an action or inserting it into the command line. This does not record
// anything into the undo stack. // anything into the undo stack.

View File

@ -38,13 +38,8 @@ func NewNormalKeymap() *Keymap {
"e": motion.MoveForwardWordEnd{Count: 1}, "e": motion.MoveForwardWordEnd{Count: 1},
"E": motion.MoveForwardWORDEnd{Count: 1}, "E": motion.MoveForwardWORDEnd{Count: 1},
"b": motion.MoveBackwardWord{Count: 1}, "b": motion.MoveBackwardWord{Count: 1},
"B": motion.MoveBackwardWORD{Count: 1}, "ctrl+u": motion.ScrollUpHalfPage{},
"ge": motion.MoveBackwardWordEnd{Count: 1}, "ctrl+d": motion.ScrollDownHalfPage{},
"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: false},
",": action.RepeatFind{Count: 1, Reverse: true}, ",": action.RepeatFind{Count: 1, Reverse: true},
}, },
@ -77,14 +72,12 @@ func NewNormalKeymap() *Keymap {
"u": action.Undo{}, "u": action.Undo{},
"ctrl+r": action.Redo{}, "ctrl+r": action.Redo{},
".": action.Repeat{Count: 1}, ".": action.Repeat{Count: 1},
"R": action.EnterReplace{},
}, },
charMotions: map[string]action.Motion{ charMotions: map[string]action.Motion{
"f": action.FindChar{Forward: true, Inclusive: true, Repeated: false}, "f": action.FindChar{Forward: true, Inclusive: true, Repeated: false},
"F": action.FindChar{Forward: false, 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: true, Inclusive: false, Repeated: false},
"T": action.FindChar{Forward: false, Inclusive: false, Repeated: false}, "T": action.FindChar{Forward: false, Inclusive: false, Repeated: false},
"r": action.ReplaceChar{Count: 1},
}, },
modifiers: map[string]any{ modifiers: map[string]any{
"i": nil, "i": nil,
@ -115,31 +108,22 @@ func NewNormalKeymap() *Keymap {
func NewVisualKeymap() *Keymap { func NewVisualKeymap() *Keymap {
return &Keymap{ return &Keymap{
motions: map[string]action.Motion{ motions: map[string]action.Motion{
"j": motion.MoveDown{Count: 1}, "j": motion.MoveDown{Count: 1},
"k": motion.MoveUp{Count: 1}, "k": motion.MoveUp{Count: 1},
"h": motion.MoveLeft{Count: 1}, "h": motion.MoveLeft{Count: 1},
"l": motion.MoveRight{Count: 1}, "l": motion.MoveRight{Count: 1},
"G": motion.MoveToBottom{}, "G": motion.MoveToBottom{},
"gg": motion.MoveToTop{}, "gg": motion.MoveToTop{},
"0": motion.MoveToLineStart{}, "0": motion.MoveToLineStart{},
"$": motion.MoveToLineEnd{}, "$": motion.MoveToLineEnd{},
"_": motion.MoveToLineContentStart{}, "_": motion.MoveToLineContentStart{},
"^": motion.MoveToLineContentStart{}, "^": motion.MoveToLineContentStart{},
"|": motion.MoveToColumn{Count: 0}, "|": motion.MoveToColumn{Count: 0},
"w": motion.MoveForwardWord{Count: 1}, "w": motion.MoveForwardWord{Count: 1},
"W": motion.MoveForwardWORD{Count: 1}, "W": motion.MoveForwardWORD{Count: 1},
"e": motion.MoveForwardWordEnd{Count: 1}, "e": motion.MoveForwardWordEnd{Count: 1},
"E": motion.MoveForwardWORDEnd{Count: 1}, "E": motion.MoveForwardWORDEnd{Count: 1},
"b": motion.MoveBackwardWord{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},
// TODO: O and o. These are fun ones! Should be simple too // TODO: O and o. These are fun ones! Should be simple too
}, },
operators: map[string]action.Operator{ operators: map[string]action.Operator{
@ -148,12 +132,9 @@ func NewVisualKeymap() *Keymap {
"X": operator.DeleteOperator{}, "X": operator.DeleteOperator{},
"y": operator.YankOperator{}, "y": operator.YankOperator{},
"c": operator.ChangeOperator{}, "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{ actions: map[string]action.Action{
"p": action.VisualPaste{Count: 1, Replace: true}, "p": action.VisualPaste{Count: 1},
"P": action.VisualPaste{Count: 1, Replace: false},
".": action.Repeat{Count: 1}, ".": action.Repeat{Count: 1},
// ":": action.EnterComandMode{}, // Different OP // ":": action.EnterComandMode{}, // Different OP
}, },
@ -206,26 +187,7 @@ func NewInsertKeymap() *Keymap {
"ctrl+w": action.InsertDeletePreviousWord{}, "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. // 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 { func (a MoveRight) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow() win := m.ActiveWindow()
buf := m.ActiveBuffer() 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++ { for i := 0; i < a.Count && win.Cursor.Col <= lineLen; i++ {
win.SetCursorCol(win.Cursor.Col + 1) win.SetCursorCol(win.Cursor.Col + 1)
} }

View File

@ -50,7 +50,7 @@ type MoveToLineEnd struct{}
func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd { func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow() win := m.ActiveWindow()
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
win.SetCursorCol(buf.Lines[win.Cursor.Line].Len() - 1) win.SetCursorCol(len(buf.Lines[win.Cursor.Line]))
return nil return nil
} }
@ -65,7 +65,7 @@ func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow() win := m.ActiveWindow()
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
line := buf.Line(win.Cursor.Line) line := buf.Lines[win.Cursor.Line]
x := 0 x := 0
for x < len(line) { for x < len(line) {
ch := line[x] ch := line[x]
@ -96,7 +96,7 @@ func (a MoveToColumn) Execute(m action.Model) tea.Cmd {
win := m.ActiveWindow() win := m.ActiveWindow()
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
line := buf.Line(win.Cursor.Line) line := buf.Lines[win.Cursor.Line]
col := min(a.Count-1, len(line)-1) col := min(a.Count-1, len(line)-1)
win.SetCursorCol(col) win.SetCursorCol(col)
@ -111,23 +111,21 @@ func (a MoveToColumn) WithCount(n int) action.Action {
// TODO: Count for these, maybe? // TODO: Count for these, maybe?
// ScrollDownPage implements Motion (ctrl+d) - linewise // ScrollDownHalfPage implements Motion (ctrl+d) - linewise
type ScrollDownPage struct { type ScrollDownHalfPage struct{}
Divisor int
}
// ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the // ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the
// cursor's relative position in the viewport. // 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() win := m.ActiveWindow()
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
viewportHeight := win.ViewportHeight() viewportHeight := win.Height - 2
if viewportHeight <= 0 { if viewportHeight <= 0 {
return nil return nil
} }
scroll := viewportHeight / a.Divisor scroll := viewportHeight / 2
scrollOff := win.Options.ScrollOff scrollOff := win.Options.ScrollOff
// Current relative position in viewport // Current relative position in viewport
@ -154,24 +152,22 @@ func (a ScrollDownPage) Execute(m action.Model) tea.Cmd {
return nil 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 // ScrollUpHalfPage implements Motion (ctrl+u) - linewise
type ScrollUpPage struct { type ScrollUpHalfPage struct{}
Divisor int
}
// ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the // ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the
// cursor's relative position in the viewport. // 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() win := m.ActiveWindow()
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
viewportHeight := win.ViewportHeight() viewportHeight := win.Height - 2
if viewportHeight <= 0 { if viewportHeight <= 0 {
return nil return nil
} }
scroll := viewportHeight / a.Divisor scroll := viewportHeight / 2
scrollOff := win.Options.ScrollOff scrollOff := win.Options.ScrollOff
// Current relative position in viewport // Current relative position in viewport
@ -197,4 +193,4 @@ func (a ScrollUpPage) Execute(m action.Model) tea.Cmd {
return nil 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 // nextWordStart: Finds the start of the next word from position (x,y), handling
// word boundaries and line crossing. // word boundaries and line crossing.
func nextWordStart(buf *core.Buffer, x, y int) (int, int) { func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
line := buf.Line(y) line := buf.Lines[y]
// Skip current class // Skip current class
if x < len(line) { 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 // Move to first char of next line
y++ y++
line = buf.Line(y) line = buf.Lines[y]
x = 0 x = 0
// If the first char of the new line is no whitespace, stay here! // 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 // nextWORDStart: Finds the start of the next WORD from position (x,y), treating
// all non-whitespace as a single class. // all non-whitespace as a single class.
func nextWORDStart(buf *core.Buffer, x, y int) (int, int) { 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) // Skip current WORD (all non-whitespace is one class for W)
for x < len(line) && line[x] != ' ' && line[x] != '\t' { 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 // Move to first char of next line
y++ y++
line = buf.Line(y) line = buf.Lines[y]
x = 0 x = 0
// If the first char of the new line is no whitespace, stay here! // 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 // nextWordEnd: Finds the end of the next word from position (x,y), respecting
// word character classes. // word character classes.
func nextWordEnd(buf *core.Buffer, x, y int) (int, int) { 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 // Advance once to avoid being stuck on the current end
x++ x++
@ -128,7 +128,7 @@ func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
// Otherwise, move to next line // Otherwise, move to next line
y++ y++
x = 0 x = 0
line = buf.Line(y) line = buf.Lines[y]
} }
// Skip whitespace and cross lines if needed // 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 // Move to first char of next line
y++ y++
line = buf.Line(y) line = buf.Lines[y]
x = 0 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 // nextWORDEnd: Finds the end of the next WORD from position (x,y), treating
// all non-whitespace as a single class. // all non-whitespace as a single class.
func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) { 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 // Advance once to avoid being stuck on the current end
x++ x++
@ -187,7 +187,7 @@ func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
// Otherwise, move to next line // Otherwise, move to next line
y++ y++
x = 0 x = 0
line = buf.Line(y) line = buf.Lines[y]
} }
// Skip whitespace and cross lines if needed // 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 // Move to first char of next line
y++ y++
line = buf.Line(y) line = buf.Lines[y]
x = 0 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), // prevWordStart: Finds the start of the previous word from position (x,y),
// moving backward through character classes. // moving backward through character classes.
func prevWordStart(buf *core.Buffer, x, y int) (int, int) { 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 // Back one to avoid being stuck on the current start
x-- x--
@ -233,7 +233,7 @@ func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
return 0, 0 // beginning of file, stay put return 0, 0 // beginning of file, stay put
} }
y-- y--
line = buf.Line(y) line = buf.Lines[y]
x = len(line) - 1 x = len(line) - 1
if x < 0 { if x < 0 {
return 0, y // landed on an empty line 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 return 0, 0
} }
y-- y--
line = buf.Line(y) line = buf.Lines[y]
x = len(line) - 1 x = len(line) - 1
if len(line) == 0 { if len(line) == 0 {
return 0, y // empty line acts as a word boundary 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 { func (a MoveBackwardWord) WithCount(n int) action.Action {
return MoveBackwardWord{Count: n} 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 var deletedText string
if start.Line == end.Line { if start.Line == end.Line {
line := buf.Line(start.Line) line := buf.Lines[start.Line]
endCol := min(end.Col+1, len(line)) endCol := min(end.Col+1, len(line))
deletedText = line[start.Col:endCol] deletedText = line[start.Col:endCol]
buf.SetLine(start.Line, line[:start.Col]+line[endCol:]) buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
} else { } else {
startLine := buf.Line(start.Line) startLine := buf.Lines[start.Line]
endLine := buf.Line(end.Line) endLine := buf.Lines[end.Line]
// Extract deleted text // Extract deleted text
deletedText = startLine[start.Col:] + "\n" deletedText = startLine[start.Col:] + "\n"
for y := start.Line + 1; y < end.Line; y++ { 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)) endCol := min(end.Col+1, len(endLine))
deletedText += endLine[:endCol] deletedText += endLine[:endCol]
@ -113,7 +113,7 @@ func changeLineSelection(m action.Model, start, end core.Position) {
var lines []string var lines []string
for i := end.Line; i >= start.Line; i-- { 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) buf.DeleteLine(i)
} }
@ -138,7 +138,7 @@ func changeBlockSelection(m action.Model, start, end core.Position) {
endCol := max(start.Col, end.Col) endCol := max(start.Col, end.Col)
for y := start.Line; y <= end.Line; y++ { for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y) line := buf.Lines[y]
if startCol >= len(line) { if startCol >= len(line) {
continue 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) // Collect lines to delete (always delete at startY since lines shift up)
for range opCount { for range opCount {
lines = append(lines, buf.Line(startY)) lines = append(lines, buf.Lines[startY])
buf.DeleteLine(startY) buf.DeleteLine(startY)
} }

View File

@ -39,7 +39,7 @@ func (o DeleteOperator) DoublePress(m action.Model, count int) tea.Cmd {
for range opCount { for range opCount {
y := win.Cursor.Line y := win.Cursor.Line
lines = append(lines, buf.Line(y)) lines = append(lines, buf.Lines[y])
buf.DeleteLine(y) buf.DeleteLine(y)
@ -99,12 +99,12 @@ func deleteCharSelection(m action.Model, start, end core.Position) {
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
if start.Line == end.Line { if start.Line == end.Line {
line := buf.Line(start.Line) line := buf.Lines[start.Line]
endCol := min(end.Col+1, len(line)) endCol := min(end.Col+1, len(line))
buf.SetLine(start.Line, line[:start.Col]+line[endCol:]) buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
} else { } else {
startLine := buf.Line(start.Line) startLine := buf.Lines[start.Line]
endLine := buf.Line(end.Line) endLine := buf.Lines[end.Line]
prefix := startLine[:start.Col] prefix := startLine[:start.Col]
suffix := "" suffix := ""
@ -131,7 +131,7 @@ func deleteLineSelection(m action.Model, start, end core.Position) {
var lines []string var lines []string
for i := end.Line; i >= start.Line; i-- { for i := end.Line; i >= start.Line; i-- {
lines = append(lines, buf.Line(i)) lines = append(lines, buf.Lines[i])
buf.DeleteLine(i) buf.DeleteLine(i)
} }
@ -159,7 +159,7 @@ func deleteBlockSelection(m action.Model, start, end core.Position) {
endCol := max(start.Col, end.Col) endCol := max(start.Col, end.Col)
for y := start.Line; y <= end.Line; y++ { for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y) line := buf.Lines[y]
if startCol >= len(line) { if startCol >= len(line) {
continue 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) win.SetCursorCol(start.Col)
cursorPos := start win.SetCursorLine(start.Line)
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
cursorPos = end
}
win.SetCursorCol(cursorPos.Col)
win.SetCursorLine(cursorPos.Line)
return nil return nil
} }
@ -57,7 +51,7 @@ func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
var lines []string var lines []string
for i := range opCount { for i := range opCount {
lines = append(lines, buf.Line(y+i)) lines = append(lines, buf.Lines[y+i])
} }
// Put her in the register! // Put her in the register!
@ -72,7 +66,17 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
switch { switch {
case mtype.IsCharwise(): 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) startX := min(start.Col, end.Col)
endX := max(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] cnt := line[startX:endX]
m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt}) m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
win := m.ActiveWindow()
win.SetCursorCol(startX)
win.SetCursorLine(start.Line)
case mtype == core.Linewise: 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 // These don't need to be validated, they are validated before being passed into the function
startY := min(start.Line, end.Line) startY := min(start.Line, end.Line)
endY := max(start.Line, end.Line) endY := max(start.Line, end.Line)
var cnt []string cnt := buf.Lines[startY : endY+1]
for i := startY; i <= endY; i++ {
cnt = append(cnt, buf.Line(i))
}
m.UpdateDefaultRegister(core.LinewiseRegister, cnt) m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
} }
} }
@ -115,7 +122,7 @@ func yankVisualMode(m action.Model, start, end core.Position) {
// Single line selection // Single line selection
if start.Line == end.Line { 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 endCol := min(end.Col+1, len(line)) // +1 because visual selection is inclusive
startCol := min(start.Col, len(line)) startCol := min(start.Col, len(line))
cnt := line[startCol:endCol] cnt := line[startCol:endCol]
@ -127,17 +134,17 @@ func yankVisualMode(m action.Model, start, end core.Position) {
var content []string var content []string
// First line: from start.Col to end of line // 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)) startCol := min(start.Col, len(firstLine))
content = append(content, firstLine[startCol:]) content = append(content, firstLine[startCol:])
// Middle lines: entire lines // Middle lines: entire lines
for y := start.Line + 1; y < end.Line; y++ { 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) // 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)) endCol := min(end.Col+1, len(lastLine))
content = append(content, lastLine[:endCol]) content = append(content, lastLine[:endCol])
@ -162,10 +169,7 @@ func yankVisualLineMode(m action.Model, start, end core.Position) {
startY := min(start.Line, end.Line) startY := min(start.Line, end.Line)
endY := max(start.Line, end.Line) endY := max(start.Line, end.Line)
var cnt []string cnt := buf.Lines[startY : endY+1]
for i := startY; i <= endY; i++ {
cnt = append(cnt, buf.Line(i))
}
m.UpdateDefaultRegister(core.LinewiseRegister, cnt) m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
} }
@ -183,7 +187,7 @@ func yankVisualBlockMode(m action.Model, start, end core.Position) {
var content []string var content []string
for y := startY; y <= endY; y++ { for y := startY; y <= endY; y++ {
line := buf.Line(y) line := buf.Lines[y]
// Handle lines shorter than the block selection // Handle lines shorter than the block selection
if startX >= len(line) { if startX >= len(line) {

View File

@ -1,11 +1,8 @@
package style package style
import ( import (
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core" "git.gophernest.net/azpect/TextEditor/internal/core"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
) )
@ -15,7 +12,6 @@ type Styles struct {
CursorNormal lipgloss.Style CursorNormal lipgloss.Style
CursorInsert lipgloss.Style CursorInsert lipgloss.Style
CursorCommand lipgloss.Style CursorCommand lipgloss.Style
CursorReplace lipgloss.Style
// Gutter (line numbers) // Gutter (line numbers)
Gutter lipgloss.Style Gutter lipgloss.Style
@ -48,7 +44,6 @@ func DefaultStyles() Styles {
CursorNormal: lipgloss.NewStyle().Reverse(true), CursorNormal: lipgloss.NewStyle().Reverse(true),
CursorInsert: lipgloss.NewStyle().Underline(true), CursorInsert: lipgloss.NewStyle().Underline(true),
CursorCommand: lipgloss.NewStyle().Reverse(true), CursorCommand: lipgloss.NewStyle().Reverse(true),
CursorReplace: lipgloss.NewStyle().Underline(true),
Gutter: lipgloss.NewStyle(). Gutter: lipgloss.NewStyle().
Background(lipgloss.Color("236")). Background(lipgloss.Color("236")).
@ -97,17 +92,12 @@ func ChromaStyles(chromaStyle *chroma.Style) Styles {
CursorInsert: lipgloss.NewStyle(). CursorInsert: lipgloss.NewStyle().
Background(lipgloss.Color(bgString)). Background(lipgloss.Color(bgString)).
Bold(true).
Underline(true), Underline(true),
CursorCommand: lipgloss.NewStyle(). CursorCommand: lipgloss.NewStyle().
Background(lipgloss.Color(bgString)). Background(lipgloss.Color(bgString)).
Reverse(true), Reverse(true),
CursorReplace: lipgloss.NewStyle().
Background(lipgloss.Color(bgString)).
Underline(true),
Gutter: lipgloss.NewStyle(). Gutter: lipgloss.NewStyle().
Background(lipgloss.Color( Background(lipgloss.Color(
darkenColor(lineNumbers.Background, 0.9).String()), darkenColor(lineNumbers.Background, 0.9).String()),
@ -170,8 +160,6 @@ func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
return s.CursorInsert return s.CursorInsert
case core.CommandMode: case core.CommandMode:
return s.CursorCommand return s.CursorCommand
case core.ReplaceMode:
return s.CursorReplace
default: default:
return s.CursorNormal return s.CursorNormal
} }
@ -186,11 +174,6 @@ func (s Styles) CursorStyle(mode core.Mode, style lipgloss.Style) lipgloss.Style
return lipgloss.NewStyle(). return lipgloss.NewStyle().
Background(style.GetForeground()). Background(style.GetForeground()).
Foreground(style.GetBackground()) Foreground(style.GetBackground())
case core.ReplaceMode, core.WaitingMode:
return lipgloss.NewStyle().
Background(style.GetBackground()).
Foreground(style.GetForeground()).
Underline(true)
default: default:
return lipgloss.NewStyle(). return lipgloss.NewStyle().
Background(s.BackgroundStyle.GetBackground()). Background(s.BackgroundStyle.GetBackground()).
@ -250,33 +233,3 @@ func darkenColor(c chroma.Colour, factor float64) chroma.Colour {
b := uint8(float64(c.Blue()) * factor) b := uint8(float64(c.Blue()) * factor)
return chroma.NewColour(r, g, b) 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 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 // 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 { if !found {
return cursor, cursor, core.CharwiseExclusive 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) { func (to Word) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
line := buf.Line(cursor.Line) line := buf.Lines[cursor.Line]
// Find word boundaries // Find word boundaries
start := findWordStart(line, cursor.Col) 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) { func (to WORD) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
buf := m.ActiveBuffer() buf := m.ActiveBuffer()
line := buf.Line(cursor.Line) line := buf.Lines[cursor.Line]
// Find word boundaries // Find word boundaries
start := findWORDStart(line, cursor.Col) 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."`