Compare commits
No commits in common. "master" and "feature/text-objs" have entirely different histories.
master
...
feature/te
107
FEATURES.md
107
FEATURES.md
@ -14,8 +14,8 @@
|
||||
- [x] `b` - Backward to start of word
|
||||
- [x] `W` - Forward to start of WORD (whitespace-delimited)
|
||||
- [x] `E` - Forward to end of WORD
|
||||
- [x] `B` - Backward to start of WORD
|
||||
- [x] `ge` - Backward to end of word
|
||||
- [ ] `B` - Backward to start of WORD
|
||||
- [ ] `ge` - Backward to end of word
|
||||
|
||||
### Line Movement
|
||||
- [x] `0` - Move to start of line
|
||||
@ -34,8 +34,8 @@
|
||||
### Scroll
|
||||
- [x] `ctrl+u` - Scroll up half page
|
||||
- [x] `ctrl+d` - Scroll down half page
|
||||
- [x] `ctrl+b` - Scroll up full page
|
||||
- [x] `ctrl+f` - Scroll down full page
|
||||
- [ ] `ctrl+b` - Scroll up full page
|
||||
- [ ] `ctrl+f` - Scroll down full page
|
||||
- [ ] `ctrl+y` - Scroll up one line
|
||||
- [ ] `ctrl+e` - Scroll down one line
|
||||
- [ ] `zz` - Center cursor on screen
|
||||
@ -127,14 +127,14 @@
|
||||
- [ ] Last search register (`/`)
|
||||
|
||||
### Undo/Redo
|
||||
- [x] `u` - Undo
|
||||
- [x] `ctrl+r` - Redo
|
||||
- [x] `.` - Repeat last change
|
||||
- [ ] `u` - Undo
|
||||
- [ ] `ctrl+r` - Redo
|
||||
- [ ] `.` - Repeat last change
|
||||
- [ ] `U` - Undo all changes on line
|
||||
|
||||
### Other Normal Mode
|
||||
- [x] `r{char}` - Replace character
|
||||
- [x] `R` - Replace mode
|
||||
- [ ] `r{char}` - Replace character
|
||||
- [ ] `R` - Replace mode
|
||||
- [ ] `~` - Swap case of character
|
||||
- [ ] `ctrl+a` - Increment number
|
||||
- [ ] `ctrl+x` - Decrement number
|
||||
@ -219,7 +219,7 @@
|
||||
- [x] `:q!` - Force quit
|
||||
- [x] `:e {file}` - Edit file
|
||||
- [x] `:bn` / `:bp` - Next/previous buffer
|
||||
- [x] `:{range}` - Go to line
|
||||
- [ ] `:{range}` - Go to line
|
||||
- [ ] `:%s/old/new/g` - Search and replace
|
||||
- [ ] `:!{cmd}` - Run shell command
|
||||
- [ ] `:help` - Show help
|
||||
@ -228,20 +228,18 @@
|
||||
|
||||
## Text Objects
|
||||
|
||||
### Implemented
|
||||
|
||||
- [x] `iw` / `aw` - Inner/around word
|
||||
- [x] `iW` / `aW` - Inner/around WORD
|
||||
- [x] `is` / `as` - Inner/around sentence
|
||||
- [x] `ip` / `ap` - Inner/around paragraph
|
||||
- [x] `i"` / `a"` - Inner/around double quotes
|
||||
- [x] `i'` / `a'` - Inner/around single quotes
|
||||
- [x] `` i` `` / `` a` `` - Inner/around backticks
|
||||
- [x] `i(` / `a(` - Inner/around parentheses
|
||||
- [x] `i[` / `a[` - Inner/around brackets
|
||||
- [x] `i{` / `a{` - Inner/around braces
|
||||
- [x] `i<` / `a<` - Inner/around angle brackets
|
||||
### Not Implemented
|
||||
- [ ] `iw` / `aw` - Inner/around word
|
||||
- [ ] `iW` / `aW` - Inner/around WORD
|
||||
- [ ] `is` / `as` - Inner/around sentence
|
||||
- [ ] `ip` / `ap` - Inner/around paragraph
|
||||
- [ ] `i"` / `a"` - Inner/around double quotes
|
||||
- [ ] `i'` / `a'` - Inner/around single quotes
|
||||
- [ ] `` i` `` / `` a` `` - Inner/around backticks
|
||||
- [ ] `i(` / `a(` - Inner/around parentheses
|
||||
- [ ] `i[` / `a[` - Inner/around brackets
|
||||
- [ ] `i{` / `a{` - Inner/around braces
|
||||
- [ ] `i<` / `a<` - Inner/around angle brackets
|
||||
- [ ] `it` / `at` - Inner/around tag
|
||||
|
||||
---
|
||||
@ -373,8 +371,7 @@ Buffers are in-memory representations of files. A buffer exists for each open fi
|
||||
### Display
|
||||
- [x] Line numbers
|
||||
- [x] Cursor position tracking
|
||||
- [x] Viewport/scrolling (Y)
|
||||
- [ ] Viewport/scrolling (X)
|
||||
- [x] Viewport/scrolling
|
||||
- [x] ScrollOff setting
|
||||
- [x] Relative line numbers
|
||||
- [ ] Cursor line highlight
|
||||
@ -408,3 +405,63 @@ Buffers are in-memory representations of files. A buffer exists for each open fi
|
||||
- [ ] Spell check
|
||||
|
||||
---
|
||||
|
||||
### Well Tested - Editor Core
|
||||
|
||||
#### Command Execution (179 tests)
|
||||
- [x] Command parsing and validation
|
||||
- [x] Command lookup and prefix matching
|
||||
- [x] Force flag handling (!)
|
||||
- [x] Write commands (`:w`, `:w {file}`, `:w!`)
|
||||
- [x] Write all commands (`:wa`, `:wall`, `:wa!`)
|
||||
- [x] Quit commands (`:q`, `:q!`, `:qa`, `:qa!`)
|
||||
- [x] Write-quit commands (`:wq`, `:wq!`, `:wqa`, `:wqa!`)
|
||||
- [x] Edit command (`:e {file}`)
|
||||
- [x] Register display (`:register`, `:reg {name}`)
|
||||
- [x] Set commands (`:set number`, `:set tabstop=N`, etc.)
|
||||
- [x] Setting lookup and validation
|
||||
- [x] Buffer-level readonly protection
|
||||
- [x] Scratch buffer write protection
|
||||
- [x] Force write bypassing readonly/scratch checks
|
||||
- [x] Multiple buffer write operations
|
||||
- [x] File write error handling (permissions, paths)
|
||||
- [x] Modified buffer tracking
|
||||
- [x] Unicode filename and content handling
|
||||
- [x] Edge cases (empty args, long filenames, special chars)
|
||||
|
||||
#### Program Initialization (70 tests)
|
||||
- [x] Empty program creation
|
||||
- [x] File program with nonexistent files (new file buffers)
|
||||
- [x] File program with existing files (content loading)
|
||||
- [x] Line ending handling (Unix `\n`, Windows `\r\n`, mixed)
|
||||
- [x] Tab to space conversion based on TabStop
|
||||
- [x] Unicode content preservation (CJK, emoji)
|
||||
- [x] File extension and type detection
|
||||
- [x] Buffer state initialization (flags, metadata)
|
||||
- [x] Large file handling (10,000+ lines)
|
||||
- [x] Long line handling (10,000+ chars)
|
||||
- [x] Empty file handling
|
||||
- [x] Builder pattern method chaining
|
||||
- [x] Program option accumulation
|
||||
- [x] Model state defaults (settings, registers, mode)
|
||||
- [x] Error handling (permissions, invalid paths)
|
||||
- [x] Integration workflows (end-to-end)
|
||||
- [x] Edge cases (empty filenames, relative paths, dot files)
|
||||
|
||||
### Moderately Tested
|
||||
- [x] Basic motions (h, j, k, l)
|
||||
- [x] Word motions (w, e, b)
|
||||
- [x] Jump motions (G, gg, 0, $, _, ^, |)
|
||||
- [x] Scroll actions (ctrl+u, ctrl+d)
|
||||
- [x] Delete operator (d, dd)
|
||||
- [x] Yank operator (y, yy)
|
||||
- [x] Paste actions (p, P)
|
||||
- [x] Change operator (c, cc, C)
|
||||
- [x] Substitute action (s, S)
|
||||
- [x] Insert mode entry (i, a, I, A, o, O)
|
||||
- [x] Insert mode editing (enter, backspace, delete, tab, ctrl+w)
|
||||
- [x] Visual modes (v, V, ctrl+v)
|
||||
- [x] Visual mode with motions
|
||||
- [x] Delete actions (x, D)
|
||||
- [x] Register behavior
|
||||
|
||||
|
||||
22
README.md
22
README.md
@ -64,28 +64,6 @@ While the undo tree method that vim uses is powerful, I rarely find myself using
|
||||
approach is more natural to "non-vim" users and much simpler to implement. Implementing a feature similar
|
||||
to Vims undo tree would many times longer than a simple stack.
|
||||
|
||||
#### Vim-like Replace vs. Custom Replace
|
||||
|
||||
The way that vim's replace mode is implemented is quite complex, keeping track of the previous
|
||||
line backspace can only delete newly replaced characters. This is a complex feature, one that
|
||||
I rarely use, and even find a bit un-intuitive. Implementing replace mode in a way where all
|
||||
actions function the same as insert mode (other than the actual character typing) allows for
|
||||
a much simpler implementation, as well as a more intuitive user experience.
|
||||
|
||||
Replace mode implements and replaces (no pun intended) the last inserted keys of insert mode. Due to
|
||||
the infrequent use of replace mode, and the '.' action for insert mode, this felt like a natural
|
||||
trade off.
|
||||
|
||||
---
|
||||
|
||||
## TODO List
|
||||
|
||||
- Ops like change, and substitute and such should add to paste reg
|
||||
- Delete op should also add to paste reg
|
||||
- Gap buffer implementation (this shouldn't be TOO hard)
|
||||
- Alternate buffer handling and implementation
|
||||
- Scroll in X direction
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
39
V0.1.md
39
V0.1.md
@ -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
|
||||
@ -23,13 +23,11 @@ func main() {
|
||||
prog = program.NewProgramBuilder().
|
||||
EmptyProgram().
|
||||
WithOpt(tea.WithAltScreen()).
|
||||
WithOpt(tea.WithMouseCellMotion()).
|
||||
Build()
|
||||
} else {
|
||||
prog = program.NewProgramBuilder().
|
||||
FileProgram(args[0]).
|
||||
WithOpt(tea.WithAltScreen()).
|
||||
WithOpt(tea.WithMouseCellMotion()).
|
||||
Build()
|
||||
}
|
||||
|
||||
|
||||
@ -24,8 +24,6 @@
|
||||
glibc_multi
|
||||
];
|
||||
|
||||
name = "Gim";
|
||||
|
||||
# Define the shell that will be executed.
|
||||
# Here, we explicitly use zsh.
|
||||
# Note: pkgs.zsh needs to be included in `packages` or `nativeBuildInputs`
|
||||
|
||||
@ -16,7 +16,7 @@ func (a ChangeToEndOfLine) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
pos := win.Cursor.Col
|
||||
line := buf.Line(win.Cursor.Line)
|
||||
line := buf.Lines[win.Cursor.Line]
|
||||
|
||||
// Save deleted text to register
|
||||
if pos < len(line) {
|
||||
@ -51,7 +51,7 @@ func (a SubstituteChar) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
pos := win.Cursor.Col
|
||||
line := buf.Line(win.Cursor.Line)
|
||||
line := buf.Lines[win.Cursor.Line]
|
||||
|
||||
// Calculate how many chars to delete (limited by line length)
|
||||
count := min(a.Count, len(line)-pos)
|
||||
@ -97,7 +97,7 @@ func (a SubstituteLine) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Collect and delete lines
|
||||
for range count {
|
||||
lines = append(lines, buf.Line(y))
|
||||
lines = append(lines, buf.Lines[y])
|
||||
buf.DeleteLine(y)
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
@ -141,12 +139,6 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
||||
// history = append([]string{cmdLine}, history...)
|
||||
m.SetCommandHistory(history)
|
||||
|
||||
// try to parse entire thing as a number
|
||||
num, err := strconv.Atoi(cmdLine)
|
||||
if err == nil {
|
||||
return cmdGoToLine(m, num)
|
||||
}
|
||||
|
||||
cmd, err := a.Registry.Execute(m, cmdLine)
|
||||
if err != nil {
|
||||
out := core.CommandOutput{
|
||||
@ -160,23 +152,3 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// cmdGoToLine: DOES NOT implement command.Command. Instead, is called directly
|
||||
// by CommandExecture.Execute(). Jumps the cursor to the line provided
|
||||
func cmdGoToLine(m Model, line int) tea.Cmd {
|
||||
// number below 0 just goes back that many
|
||||
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
if line <= 0 {
|
||||
newLine := max(0, win.Cursor.Line+line)
|
||||
win.SetCursorLine(newLine)
|
||||
return nil
|
||||
}
|
||||
|
||||
newLine := min(line-1, buf.LineCount())
|
||||
win.SetCursorLine(newLine)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -13,7 +13,7 @@ func (a DeleteChar) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
pos := win.Cursor.Col
|
||||
line := buf.Line(win.Cursor.Line)
|
||||
line := buf.Lines[win.Cursor.Line]
|
||||
for i := 0; i < a.Count && pos < len(line); i++ {
|
||||
line = line[:pos] + line[pos+1:]
|
||||
buf.SetLine(win.Cursor.Line, line)
|
||||
@ -38,7 +38,7 @@ func (a DeletePrevChar) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
pos := win.Cursor.Col
|
||||
line := buf.Line(win.Cursor.Line)
|
||||
line := buf.Lines[win.Cursor.Line]
|
||||
for i := 0; i < a.Count && pos <= len(line); i++ {
|
||||
if pos > 0 {
|
||||
line = line[:pos-1] + line[pos:]
|
||||
@ -69,7 +69,7 @@ func (a DeleteToEndOfLine) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Delete to end of line
|
||||
pos := win.Cursor.Col
|
||||
line := buf.Line(win.Cursor.Line)
|
||||
line := buf.Lines[win.Cursor.Line]
|
||||
|
||||
buf.SetLine(win.Cursor.Line, line[:pos])
|
||||
win.SetCursorCol(pos - 1)
|
||||
|
||||
@ -76,7 +76,7 @@ type EnterInsertLineEnd struct {
|
||||
func (a EnterInsertLineEnd) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
win.SetCursorCol(buf.Lines[win.Cursor.Line].Len())
|
||||
win.SetCursorCol(len(buf.Lines[win.Cursor.Line]))
|
||||
|
||||
// Start recording
|
||||
m.SetInsertRecording(a.Count, a)
|
||||
@ -158,7 +158,7 @@ func (a InsertChar) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x, y := win.Cursor.Col, win.Cursor.Line
|
||||
l := buf.Line(y)
|
||||
l := buf.Lines[y]
|
||||
if x < len(l) {
|
||||
buf.SetLine(y, l[:x]+a.Char+l[x:])
|
||||
} else {
|
||||
@ -177,7 +177,7 @@ func (a InsertNewline) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x, y := win.Cursor.Col, win.Cursor.Line
|
||||
l := buf.Line(y)
|
||||
l := buf.Lines[y]
|
||||
if x == len(l) {
|
||||
buf.InsertLine(y+1, "")
|
||||
} else {
|
||||
@ -197,12 +197,12 @@ func (a InsertBackspace) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x, y := win.Cursor.Col, win.Cursor.Line
|
||||
l := buf.Line(y)
|
||||
l := buf.Lines[y]
|
||||
if x > 0 {
|
||||
buf.SetLine(y, l[:x-1]+l[x:])
|
||||
win.SetCursorCol(x - 1)
|
||||
} else if y > 0 {
|
||||
prevLine := buf.Line(y - 1)
|
||||
prevLine := buf.Lines[y-1]
|
||||
newX := len(prevLine)
|
||||
buf.SetLine(y-1, prevLine+l)
|
||||
buf.DeleteLine(y)
|
||||
@ -220,9 +220,9 @@ func (a InsertDelete) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x, y := win.Cursor.Col, win.Cursor.Line
|
||||
l := buf.Line(y)
|
||||
l := buf.Lines[y]
|
||||
if x == len(l) && y < buf.LineCount()-1 {
|
||||
nextLine := buf.Line(y + 1)
|
||||
nextLine := buf.Lines[y+1]
|
||||
buf.SetLine(y, l+nextLine)
|
||||
buf.DeleteLine(y + 1)
|
||||
} else if x < len(l) {
|
||||
@ -240,7 +240,7 @@ func (a InsertTab) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x, y := win.Cursor.Col, win.Cursor.Line
|
||||
l := buf.Line(y)
|
||||
l := buf.Lines[y]
|
||||
tabs := strings.Repeat(" ", m.Settings().TabStop)
|
||||
if x < len(l) {
|
||||
buf.SetLine(y, l[:x]+tabs+l[x:])
|
||||
@ -275,12 +275,12 @@ func (a InsertDeletePreviousWord) Execute(m Model) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x, y := win.Cursor.Col, win.Cursor.Line
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
|
||||
// At start of line: merge with previous line (same as backspace)
|
||||
if x == 0 {
|
||||
if y > 0 {
|
||||
prevLine := buf.Line(y - 1)
|
||||
prevLine := buf.Lines[y-1]
|
||||
newX := len(prevLine)
|
||||
buf.SetLine(y-1, prevLine+line)
|
||||
buf.DeleteLine(y)
|
||||
|
||||
@ -64,12 +64,6 @@ type Model interface {
|
||||
GetRegister(name rune) (core.Register, bool)
|
||||
SetRegister(name rune, t core.RegisterType, cnt []string) error
|
||||
UpdateDefaultRegister(t core.RegisterType, cnt []string)
|
||||
|
||||
// Dot operator - accumulate keys for repeat
|
||||
SetLastChangeKeys(keys []string)
|
||||
LastChangeKeys() []string
|
||||
ClearLastChangeKeys()
|
||||
HandleKey(key string) tea.Cmd
|
||||
}
|
||||
|
||||
// Action is the base interface - anything executable
|
||||
|
||||
@ -60,27 +60,3 @@ func (a EnterVisualBlockMode) Execute(m Model) tea.Cmd {
|
||||
m.SetMode(core.VisualBlockMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Implement count?
|
||||
type Undo struct{}
|
||||
|
||||
func (a Undo) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
if buf.UndoStack.CanUndo() {
|
||||
buf.Undo(win)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Implement count?
|
||||
type Redo struct{}
|
||||
|
||||
func (a Redo) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
if buf.UndoStack.CanRedo() {
|
||||
buf.Redo(win)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package action
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// MockModel is a shared test implementation of the Model interface.
|
||||
@ -24,7 +23,6 @@ type MockModel struct {
|
||||
CommandHistoryCur int
|
||||
LastFindVal core.LastFindCommand
|
||||
StylesVal style.Styles
|
||||
LastChangeKeysList []string
|
||||
}
|
||||
|
||||
// NewMockModel creates a mock with an empty buffer and 24x80 window.
|
||||
@ -133,9 +131,3 @@ func (m *MockModel) SetRegister(name rune, t core.RegisterType, cnt []string) er
|
||||
func (m *MockModel) UpdateDefaultRegister(t core.RegisterType, cnt []string) {
|
||||
m.RegistersMap['"'] = core.Register{Type: t, Content: cnt}
|
||||
}
|
||||
|
||||
// Dot operator
|
||||
func (m *MockModel) SetLastChangeKeys(keys []string) { m.LastChangeKeysList = keys }
|
||||
func (m *MockModel) LastChangeKeys() []string { return m.LastChangeKeysList }
|
||||
func (m *MockModel) ClearLastChangeKeys() { m.LastChangeKeysList = []string{} }
|
||||
func (m *MockModel) HandleKey(key string) tea.Cmd { return nil }
|
||||
|
||||
@ -63,7 +63,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
|
||||
x := win.Cursor.Col
|
||||
y := win.Cursor.Line
|
||||
curLine := buf.Line(y)
|
||||
curLine := buf.Lines[y]
|
||||
insertAt := min(x+1, len(curLine))
|
||||
|
||||
if len(lines) == 1 {
|
||||
@ -101,11 +101,11 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Last line: append the suffix
|
||||
lastLineIdx := y + len(pastedLines) - 1
|
||||
buf.SetLine(lastLineIdx, buf.Line(lastLineIdx)+suffix)
|
||||
buf.SetLine(lastLineIdx, buf.Lines[lastLineIdx]+suffix)
|
||||
|
||||
// Set cursor to end of last pasted content (before suffix)
|
||||
win.SetCursorLine(lastLineIdx)
|
||||
win.SetCursorCol(len(buf.Line(lastLineIdx)) - len(suffix) - 1)
|
||||
win.SetCursorCol(len(buf.Lines[lastLineIdx]) - len(suffix) - 1)
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -175,7 +175,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
|
||||
x := win.Cursor.Col
|
||||
y := win.Cursor.Line
|
||||
curLine := buf.Line(y)
|
||||
curLine := buf.Lines[y]
|
||||
insertAt := min(x, len(curLine))
|
||||
|
||||
if len(lines) == 1 {
|
||||
@ -213,11 +213,11 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Last line: append the suffix
|
||||
lastLineIdx := y + len(pastedLines) - 1
|
||||
buf.SetLine(lastLineIdx, buf.Line(lastLineIdx)+suffix)
|
||||
buf.SetLine(lastLineIdx, buf.Lines[lastLineIdx]+suffix)
|
||||
|
||||
// Set cursor to end of last pasted content (before suffix)
|
||||
win.SetCursorLine(lastLineIdx)
|
||||
win.SetCursorCol(len(buf.Line(lastLineIdx)) - len(suffix) - 1)
|
||||
win.SetCursorCol(len(buf.Lines[lastLineIdx]) - len(suffix) - 1)
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -240,11 +240,9 @@ func (a PasteBefore) WithCount(n int) Action {
|
||||
return PasteBefore{Count: n}
|
||||
}
|
||||
|
||||
// VisualPaste implements Action (p/p in visual mode) - replaces selection with
|
||||
// register content when Replace flag is set
|
||||
// VisualPaste implements Action (p in visual mode) - replaces selection with register content
|
||||
type VisualPaste struct {
|
||||
Count int
|
||||
Replace bool
|
||||
Count int
|
||||
}
|
||||
|
||||
// VisualPaste.Execute: Replaces visual selection with register content (p in visual mode).
|
||||
@ -268,11 +266,11 @@ func (a VisualPaste) Execute(m Model) tea.Cmd {
|
||||
|
||||
switch mode {
|
||||
case core.VisualMode:
|
||||
visualCharPaste(m, reg, start, end, a.Replace)
|
||||
visualCharPaste(m, reg, start, end)
|
||||
case core.VisualBlockMode:
|
||||
visualBlockPaste(m, reg, start, end, a.Replace)
|
||||
visualBlockPaste(m, reg, start, end)
|
||||
case core.VisualLineMode:
|
||||
visualLinePaste(m, reg, start, end, a.Replace)
|
||||
visualLinePaste(m, reg, start, end)
|
||||
}
|
||||
|
||||
// Exit visual mode
|
||||
@ -297,7 +295,7 @@ func normalizeSelection(m Model) (core.Position, core.Position) {
|
||||
}
|
||||
|
||||
// visualCharPaste: Handles paste operation in visual (character) mode.
|
||||
func visualCharPaste(m Model, reg core.Register, start, end core.Position, replace bool) {
|
||||
func visualCharPaste(m Model, reg core.Register, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -313,7 +311,7 @@ func visualCharPaste(m Model, reg core.Register, start, end core.Position, repla
|
||||
} else if reg.Type == core.CharwiseRegister {
|
||||
// Charwise paste: insert text at cursor position
|
||||
if len(reg.Content) == 1 {
|
||||
line := buf.Line(start.Line)
|
||||
line := buf.Lines[start.Line]
|
||||
insertAt := min(start.Col, len(line))
|
||||
newLine := line[:insertAt] + reg.Content[0] + line[insertAt:]
|
||||
buf.SetLine(start.Line, newLine)
|
||||
@ -328,7 +326,7 @@ func visualCharPaste(m Model, reg core.Register, start, end core.Position, repla
|
||||
for i, content := range reg.Content {
|
||||
if i == 0 {
|
||||
// First line: insert at start position
|
||||
line := buf.Line(start.Line)
|
||||
line := buf.Lines[start.Line]
|
||||
insertAt := min(start.Col, len(line))
|
||||
newLine := line[:insertAt] + content
|
||||
if len(reg.Content) == 1 {
|
||||
@ -346,13 +344,11 @@ func visualCharPaste(m Model, reg core.Register, start, end core.Position, repla
|
||||
}
|
||||
|
||||
// Update register with deleted text
|
||||
if replace {
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{deletedText})
|
||||
}
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{deletedText})
|
||||
}
|
||||
|
||||
// visualBlockPaste: Handles paste operation in visual block mode.
|
||||
func visualBlockPaste(m Model, reg core.Register, start, end core.Position, replace bool) {
|
||||
func visualBlockPaste(m Model, reg core.Register, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -362,7 +358,7 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
|
||||
// Extract deleted text (for register)
|
||||
var deletedLines []string
|
||||
for y := start.Line; y <= end.Line; y++ {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
if startCol < len(line) {
|
||||
ec := min(endCol+1, len(line))
|
||||
deletedLines = append(deletedLines, line[startCol:ec])
|
||||
@ -373,7 +369,7 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
|
||||
|
||||
// Delete the block selection
|
||||
for y := start.Line; y <= end.Line; y++ {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
if startCol >= len(line) {
|
||||
continue
|
||||
}
|
||||
@ -389,7 +385,7 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
|
||||
}
|
||||
|
||||
for y := start.Line; y <= end.Line; y++ {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
insertAt := min(startCol, len(line))
|
||||
// Pad with spaces if needed
|
||||
for len(line) < insertAt {
|
||||
@ -404,20 +400,18 @@ func visualBlockPaste(m Model, reg core.Register, start, end core.Position, repl
|
||||
win.SetCursorCol(startCol)
|
||||
|
||||
// Update register with deleted block text (joined)
|
||||
if replace {
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{strings.Join(deletedLines, "\n")})
|
||||
}
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{strings.Join(deletedLines, "\n")})
|
||||
}
|
||||
|
||||
// visualLinePaste: Handles paste operation in visual line mode.
|
||||
func visualLinePaste(m Model, reg core.Register, start, end core.Position, replace bool) {
|
||||
func visualLinePaste(m Model, reg core.Register, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
// Extract deleted lines (for register)
|
||||
var deletedLines []string
|
||||
for y := start.Line; y <= end.Line; y++ {
|
||||
deletedLines = append(deletedLines, buf.Line(y))
|
||||
deletedLines = append(deletedLines, buf.Lines[y])
|
||||
}
|
||||
|
||||
// Delete the selected lines (from end to start to preserve indices)
|
||||
@ -457,9 +451,7 @@ func visualLinePaste(m Model, reg core.Register, start, end core.Position, repla
|
||||
win.SetCursorCol(0)
|
||||
|
||||
// Update register with deleted lines
|
||||
if replace {
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, deletedLines)
|
||||
}
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, deletedLines)
|
||||
}
|
||||
|
||||
// extractCharSelection: Extracts text from a character selection range.
|
||||
@ -467,7 +459,7 @@ func extractCharSelection(m Model, start, end core.Position) string {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
if start.Line == end.Line {
|
||||
line := buf.Line(start.Line)
|
||||
line := buf.Lines[start.Line]
|
||||
endCol := min(end.Col+1, len(line))
|
||||
startCol := min(start.Col, len(line))
|
||||
if startCol >= endCol {
|
||||
@ -480,7 +472,7 @@ func extractCharSelection(m Model, start, end core.Position) string {
|
||||
var result strings.Builder
|
||||
|
||||
// First line: from start.Col to end
|
||||
firstLine := buf.Line(start.Line)
|
||||
firstLine := buf.Lines[start.Line]
|
||||
if start.Col < len(firstLine) {
|
||||
result.WriteString(firstLine[start.Col:])
|
||||
}
|
||||
@ -488,12 +480,12 @@ func extractCharSelection(m Model, start, end core.Position) string {
|
||||
|
||||
// Middle lines: entire lines
|
||||
for y := start.Line + 1; y < end.Line; y++ {
|
||||
result.WriteString(buf.Line(y))
|
||||
result.WriteString(buf.Lines[y])
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// Last line: from beginning to end.Col
|
||||
lastLine := buf.Line(end.Line)
|
||||
lastLine := buf.Lines[end.Line]
|
||||
endCol := min(end.Col+1, len(lastLine))
|
||||
result.WriteString(lastLine[:endCol])
|
||||
|
||||
@ -506,12 +498,12 @@ func deleteCharSelectionForPaste(m Model, start, end core.Position) {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
if start.Line == end.Line {
|
||||
line := buf.Line(start.Line)
|
||||
line := buf.Lines[start.Line]
|
||||
endCol := min(end.Col+1, len(line))
|
||||
buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
|
||||
} else {
|
||||
startLine := buf.Line(start.Line)
|
||||
endLine := buf.Line(end.Line)
|
||||
startLine := buf.Lines[start.Line]
|
||||
endLine := buf.Lines[end.Line]
|
||||
|
||||
prefix := ""
|
||||
if start.Col < len(startLine) {
|
||||
@ -539,5 +531,5 @@ var _ Repeatable = VisualPaste{}
|
||||
|
||||
// VisualPaste.WithCount: Returns a new VisualPaste with the given count.
|
||||
func (a VisualPaste) WithCount(n int) Action {
|
||||
return VisualPaste{Count: n, Replace: a.Replace}
|
||||
return VisualPaste{Count: n}
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Repeat implements Action (.) - repeat last input
|
||||
type Repeat struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
func (a Repeat) Execute(m Model) tea.Cmd {
|
||||
keys := m.LastChangeKeys()
|
||||
|
||||
if len(keys) == 1 && keys[0] == "." {
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{"Cannot repeat '.'"},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmds []tea.Cmd
|
||||
for _, key := range keys {
|
||||
cmd := m.HandleKey(key)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// Ensure Repeat implements Repeatable
|
||||
var _ Repeatable = Repeat{}
|
||||
|
||||
// Repeat.WithCount: Returns a new Repeat with the given count.
|
||||
func (a Repeat) WithCount(n int) Action {
|
||||
return Repeat{Count: n}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -933,29 +933,3 @@ func cmdListColorschemes(m action.Model, args []string, force bool) tea.Cmd {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdUndoList(m action.Model, args []string, force bool) tea.Cmd {
|
||||
_, _ = args, force
|
||||
|
||||
lines := m.ActiveBuffer().UndoStack.List()
|
||||
|
||||
// For now, display an error when empty
|
||||
if len(lines) == 0 {
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{"Undo stack is empty"},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
m.SetMode(core.CommandOutputMode)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Title: ":undo",
|
||||
Lines: lines,
|
||||
Inline: false,
|
||||
IsError: false,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ func writeBuffer(m action.Model, buf *core.Buffer, args []string, force bool) (t
|
||||
// Using a bufio.Writer because its more efficient
|
||||
writer := bufio.NewWriter(file)
|
||||
for _, line := range buf.Lines {
|
||||
n, err := writer.WriteString(line.String() + "\n")
|
||||
n, err := writer.WriteString(line + "\n")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -237,11 +237,4 @@ func (r *Registry) registerDefaults() {
|
||||
ShortForm: "colorschemes",
|
||||
Handler: cmdListColorschemes,
|
||||
})
|
||||
|
||||
// Undo stack commands
|
||||
r.Register(Command{
|
||||
Name: "undo",
|
||||
ShortForm: "u",
|
||||
Handler: cmdUndoList,
|
||||
})
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ type Buffer struct {
|
||||
// File data
|
||||
Filename string
|
||||
Filetype string
|
||||
Lines []*GapBuffer // Changed from []string to []*GapBuffer
|
||||
Lines []string
|
||||
|
||||
// Flags (not used yet)
|
||||
Modified bool
|
||||
@ -29,7 +29,7 @@ type Buffer struct {
|
||||
ReadOnly bool
|
||||
|
||||
// Options BufferOptions
|
||||
UndoStack *UndoStack
|
||||
// UndoTree TODO: This will be big
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
@ -42,18 +42,14 @@ func (b *Buffer) Line(idx int) string {
|
||||
if idx < 0 || idx >= len(b.Lines) {
|
||||
return ""
|
||||
}
|
||||
return b.Lines[idx].String()
|
||||
return b.Lines[idx]
|
||||
}
|
||||
|
||||
// Buffer.SetLine: Set the content of the line at an index. Does nothing if the
|
||||
// index is out of bounds. This function sets the modified flag.
|
||||
func (b *Buffer) SetLine(idx int, content string) {
|
||||
if idx >= 0 && idx < len(b.Lines) {
|
||||
// Record set line in undo stack
|
||||
if b.UndoStack != nil {
|
||||
b.UndoStack.RecordSetLine(idx, b.Lines[idx].String(), content)
|
||||
}
|
||||
b.Lines[idx].Set(content)
|
||||
b.Lines[idx] = content
|
||||
}
|
||||
b.Modified = true
|
||||
}
|
||||
@ -68,14 +64,7 @@ func (b *Buffer) InsertLine(idx int, content string) {
|
||||
if idx > len(b.Lines) {
|
||||
idx = len(b.Lines)
|
||||
}
|
||||
|
||||
// Record insert line in undo stack
|
||||
if b.UndoStack != nil {
|
||||
b.UndoStack.RecordInsertLine(idx, content)
|
||||
}
|
||||
|
||||
newLine := NewGapBuffer(content)
|
||||
b.Lines = append(b.Lines[:idx], append([]*GapBuffer{newLine}, b.Lines[idx:]...)...)
|
||||
b.Lines = append(b.Lines[:idx], append([]string{content}, b.Lines[idx:]...)...)
|
||||
b.Modified = true
|
||||
}
|
||||
|
||||
@ -83,10 +72,6 @@ func (b *Buffer) InsertLine(idx int, content string) {
|
||||
// of bounds. This function sets the modified flag.
|
||||
func (b *Buffer) DeleteLine(idx int) {
|
||||
if idx >= 0 && idx < len(b.Lines) {
|
||||
// Record delete line in undo stack
|
||||
if b.UndoStack != nil {
|
||||
b.UndoStack.RecordDeleteLine(idx, b.Lines[idx].String())
|
||||
}
|
||||
b.Lines = append(b.Lines[:idx], b.Lines[idx+1:]...)
|
||||
}
|
||||
b.Modified = true
|
||||
@ -97,101 +82,6 @@ func (b *Buffer) LineCount() int {
|
||||
return len(b.Lines)
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Undo Stack
|
||||
// ==================================================
|
||||
func (b *Buffer) Undo(w *Window) bool {
|
||||
if b.UndoStack == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
block := b.UndoStack.Undo()
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Apply changes in REVERSE order
|
||||
for i := len(block.Changes) - 1; i >= 0; i-- {
|
||||
change := block.Changes[i]
|
||||
|
||||
// Temporarily disable recording while we undo
|
||||
wasRecording := b.UndoStack.recording
|
||||
b.UndoStack.recording = false
|
||||
|
||||
switch change.Type {
|
||||
case SetLineChange:
|
||||
// Restore old data
|
||||
if change.Line >= 0 && change.Line < len(b.Lines) {
|
||||
b.Lines[change.Line].Set(change.OldData)
|
||||
}
|
||||
case InsertLineChange:
|
||||
// Remove the inserted line
|
||||
if change.Line >= 0 && change.Line < len(b.Lines) {
|
||||
b.Lines = append(b.Lines[:change.Line], b.Lines[change.Line+1:]...)
|
||||
}
|
||||
case DeleteLineChange:
|
||||
// Re-insert the deleted line
|
||||
if change.Line <= len(b.Lines) {
|
||||
newLine := NewGapBuffer(change.OldData)
|
||||
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
|
||||
}
|
||||
}
|
||||
|
||||
b.UndoStack.recording = wasRecording
|
||||
}
|
||||
|
||||
// Restore cursor position
|
||||
w.SetCursorLine(block.OldCursor.Line)
|
||||
w.SetCursorCol(block.OldCursor.Col)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *Buffer) Redo(w *Window) bool {
|
||||
if b.UndoStack == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
block := b.UndoStack.Redo()
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Apply changes in FORWARD order
|
||||
for _, change := range block.Changes {
|
||||
// Temporarily disable recording while we redo
|
||||
wasRecording := b.UndoStack.recording
|
||||
b.UndoStack.recording = false
|
||||
|
||||
switch change.Type {
|
||||
case SetLineChange:
|
||||
// Apply new data
|
||||
if change.Line >= 0 && change.Line < len(b.Lines) {
|
||||
b.Lines[change.Line].Set(change.NewData)
|
||||
}
|
||||
case InsertLineChange:
|
||||
// Re-insert the line
|
||||
if change.Line <= len(b.Lines) {
|
||||
newLine := NewGapBuffer(change.NewData)
|
||||
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
|
||||
}
|
||||
case DeleteLineChange:
|
||||
// Re-delete the line
|
||||
if change.Line >= 0 && change.Line < len(b.Lines) {
|
||||
b.Lines = append(b.Lines[:change.Line], b.Lines[change.Line+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
b.UndoStack.recording = wasRecording
|
||||
}
|
||||
|
||||
// Restore cursor position
|
||||
w.SetCursorLine(block.NewCursor.Line)
|
||||
w.SetCursorCol(block.NewCursor.Col)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Setters
|
||||
// ==================================================
|
||||
@ -211,10 +101,7 @@ func (b *Buffer) SetFiletype(filetype string) {
|
||||
// Buffer.SetLines: Replace all lines in the buffer with the provided lines.
|
||||
// This is useful when loading a file or resetting buffer content.
|
||||
func (b *Buffer) SetLines(lines []string) {
|
||||
b.Lines = make([]*GapBuffer, len(lines))
|
||||
for i, line := range lines {
|
||||
b.Lines[i] = NewGapBuffer(line)
|
||||
}
|
||||
b.Lines = lines
|
||||
}
|
||||
|
||||
// Buffer.SetModified: Set the modified flag for this buffer. A modified buffer
|
||||
|
||||
@ -12,16 +12,15 @@ type BufferBuilder struct {
|
||||
func NewBufferBuilder() *BufferBuilder {
|
||||
return &BufferBuilder{
|
||||
buffer: Buffer{
|
||||
Id: 0, // This is set when built
|
||||
Type: ScatchBuffer, // Default buffer type
|
||||
Filename: "",
|
||||
Filetype: "",
|
||||
Lines: []*GapBuffer{NewEmptyGapBuffer()},
|
||||
Modified: false,
|
||||
Loaded: false,
|
||||
Listed: false,
|
||||
ReadOnly: false,
|
||||
UndoStack: NewUndoStack(), // Empty undo stack
|
||||
Id: 0, // This is set when built
|
||||
Type: ScatchBuffer, // Default buffer type
|
||||
Filename: "",
|
||||
Filetype: "",
|
||||
Lines: []string{""},
|
||||
Modified: false,
|
||||
Loaded: false,
|
||||
Listed: false,
|
||||
ReadOnly: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -40,10 +39,7 @@ func (b *BufferBuilder) WithFiletype(filetype string) *BufferBuilder {
|
||||
|
||||
// BufferBuilder.WithLines: Attaches a lines to the buffer that is being built.
|
||||
func (b *BufferBuilder) WithLines(lines []string) *BufferBuilder {
|
||||
b.buffer.Lines = make([]*GapBuffer, len(lines))
|
||||
for i, line := range lines {
|
||||
b.buffer.Lines[i] = NewGapBuffer(line)
|
||||
}
|
||||
b.buffer.Lines = lines
|
||||
return b
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
)
|
||||
|
||||
const CommandOutputExitMessage = "Press ENTER to continue"
|
||||
const CommandOutputScrollMessage = "Use j/k to scroll"
|
||||
|
||||
type CommandOutput struct {
|
||||
Title string
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -11,15 +11,13 @@ const (
|
||||
VisualMode
|
||||
VisualLineMode
|
||||
VisualBlockMode
|
||||
ReplaceMode
|
||||
WaitingMode // Same as NORMAL output, but cursor is the REPLACE cursor
|
||||
)
|
||||
|
||||
// Mode.ToString: Returns a human-readable string representation of the mode
|
||||
// for display in the status bar.
|
||||
func (m Mode) ToString() string {
|
||||
switch m {
|
||||
case NormalMode, WaitingMode:
|
||||
case NormalMode:
|
||||
return "NORMAL"
|
||||
case InsertMode:
|
||||
return "INSERT"
|
||||
@ -31,8 +29,6 @@ func (m Mode) ToString() string {
|
||||
return "V-LINE"
|
||||
case VisualBlockMode:
|
||||
return "V-BLOCK"
|
||||
case ReplaceMode:
|
||||
return "REPLACE"
|
||||
default:
|
||||
return "-----"
|
||||
}
|
||||
|
||||
@ -82,8 +82,7 @@ func addSpecialRegisters(reg map[rune]Register) {
|
||||
|
||||
// Small delete? Expression?
|
||||
|
||||
// VIM: Last inserted text (readonly)
|
||||
// GIM: Content stored for the '.' operator (for debugging)
|
||||
// Last inserted text (readonly)
|
||||
reg['.'] = emptyRegister()
|
||||
|
||||
// Current file name (readonly)
|
||||
|
||||
@ -1,180 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type ChangeType string
|
||||
|
||||
const (
|
||||
SetLineChange ChangeType = "SetLine"
|
||||
InsertLineChange ChangeType = "InsertLine"
|
||||
DeleteLineChange ChangeType = "DeleteLine"
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
Type ChangeType
|
||||
Line int
|
||||
OldData string
|
||||
NewData string
|
||||
}
|
||||
|
||||
type ChangeBlock struct {
|
||||
Changes []Change
|
||||
OldCursor Position // Before OP
|
||||
NewCursor Position // After OP
|
||||
}
|
||||
|
||||
type UndoStack struct {
|
||||
undoStack []ChangeBlock
|
||||
redoStack []ChangeBlock
|
||||
current []Change
|
||||
recording bool
|
||||
oldCursor Position
|
||||
}
|
||||
|
||||
func NewUndoStack() *UndoStack {
|
||||
return &UndoStack{
|
||||
undoStack: []ChangeBlock{},
|
||||
redoStack: []ChangeBlock{},
|
||||
current: []Change{},
|
||||
recording: false,
|
||||
oldCursor: Position{},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UndoStack) BeginBlock(cursor Position) {
|
||||
u.current = []Change{}
|
||||
u.recording = true
|
||||
u.oldCursor = cursor
|
||||
}
|
||||
|
||||
func (u *UndoStack) EndBlock(cursor Position) {
|
||||
// If not recording or nothing changed, we can exit safely
|
||||
if !u.recording || len(u.current) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
block := ChangeBlock{
|
||||
Changes: u.current,
|
||||
OldCursor: u.oldCursor,
|
||||
NewCursor: cursor,
|
||||
}
|
||||
|
||||
u.undoStack = append(u.undoStack, block)
|
||||
u.redoStack = []ChangeBlock{} // Reset old changes, can no longer redo
|
||||
|
||||
u.recording = false
|
||||
u.current = []Change{}
|
||||
}
|
||||
|
||||
func (u *UndoStack) RecordSetLine(line int, oldData, newData string) {
|
||||
if !u.recording {
|
||||
return
|
||||
}
|
||||
change := Change{
|
||||
Type: SetLineChange,
|
||||
Line: line,
|
||||
OldData: oldData,
|
||||
NewData: newData,
|
||||
}
|
||||
u.current = append(u.current, change)
|
||||
}
|
||||
|
||||
func (u *UndoStack) RecordInsertLine(line int, newData string) {
|
||||
if !u.recording {
|
||||
return
|
||||
}
|
||||
change := Change{
|
||||
Type: InsertLineChange,
|
||||
Line: line,
|
||||
NewData: newData,
|
||||
}
|
||||
u.current = append(u.current, change)
|
||||
}
|
||||
|
||||
func (u *UndoStack) RecordDeleteLine(line int, oldData string) {
|
||||
if !u.recording {
|
||||
return
|
||||
}
|
||||
change := Change{
|
||||
Type: DeleteLineChange,
|
||||
Line: line,
|
||||
OldData: oldData,
|
||||
}
|
||||
u.current = append(u.current, change)
|
||||
}
|
||||
|
||||
func (u *UndoStack) Undo() *ChangeBlock {
|
||||
if len(u.undoStack) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pop from undo stack
|
||||
size := len(u.undoStack)
|
||||
block := u.undoStack[size-1]
|
||||
u.undoStack = u.undoStack[:size-1]
|
||||
|
||||
// Push to redo stack
|
||||
u.redoStack = append(u.redoStack, block)
|
||||
|
||||
return &block
|
||||
}
|
||||
|
||||
func (u *UndoStack) Redo() *ChangeBlock {
|
||||
if len(u.redoStack) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pop from redo stack
|
||||
size := len(u.redoStack)
|
||||
block := u.redoStack[size-1]
|
||||
u.redoStack = u.redoStack[:size-1]
|
||||
|
||||
// Push to undo stack
|
||||
u.undoStack = append(u.undoStack, block)
|
||||
|
||||
return &block
|
||||
}
|
||||
|
||||
func (u *UndoStack) CanUndo() bool {
|
||||
return len(u.undoStack) > 0
|
||||
}
|
||||
|
||||
func (u *UndoStack) CanRedo() bool {
|
||||
return len(u.redoStack) > 0
|
||||
}
|
||||
|
||||
func (u *UndoStack) Recording() bool {
|
||||
return u.recording
|
||||
}
|
||||
|
||||
func (u *UndoStack) List() []string {
|
||||
var lines []string
|
||||
|
||||
stack := slices.Clone(u.undoStack)
|
||||
slices.Reverse(stack)
|
||||
|
||||
for _, b := range stack {
|
||||
lines = append(lines, fmt.Sprintf(
|
||||
"block (%d:%d) -> (%d:%d)",
|
||||
b.OldCursor.Line,
|
||||
b.OldCursor.Col,
|
||||
b.NewCursor.Line,
|
||||
b.NewCursor.Col,
|
||||
))
|
||||
|
||||
for _, c := range b.Changes {
|
||||
lines = append(lines, fmt.Sprintf(
|
||||
"\t%q #%d (%s) -> (%s)",
|
||||
c.Type,
|
||||
c.Line,
|
||||
c.OldData,
|
||||
c.NewData,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
@ -60,7 +60,7 @@ func (w *Window) ClampCursor() {
|
||||
}
|
||||
|
||||
// Clamp column to valid range [0, lineLen]
|
||||
lineLen := w.Buffer.Lines[w.Cursor.Line].Len()
|
||||
lineLen := len(w.Buffer.Lines[w.Cursor.Line])
|
||||
if w.Cursor.Col < 0 {
|
||||
w.Cursor.Col = 0
|
||||
} else if lineLen == 0 {
|
||||
|
||||
@ -30,20 +30,8 @@ func sendKeys(tm *teatest.TestModel, keys ...string) {
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlU})
|
||||
case "ctrl+v":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlV})
|
||||
case "ctrl+r":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlR})
|
||||
case "ctrl+w":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlW})
|
||||
case "tab":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyTab})
|
||||
case "left":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyLeft})
|
||||
case "right":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyRight})
|
||||
case "up":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyUp})
|
||||
case "down":
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyDown})
|
||||
default:
|
||||
tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)})
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ func TestDeleteChar(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
||||
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ello" {
|
||||
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -24,8 +24,8 @@ func TestDeleteChar(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -35,8 +35,8 @@ func TestDeleteChar(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hell" {
|
||||
t.Errorf("lines[0] = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hell" {
|
||||
t.Errorf("lines[0] = %q, want 'hell'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -46,8 +46,8 @@ func TestDeleteChar(t *testing.T) {
|
||||
sendKeys(tm, "x", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "llo" {
|
||||
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -59,8 +59,8 @@ func TestDeleteCharWithCount(t *testing.T) {
|
||||
sendKeys(tm, "3", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "lo" {
|
||||
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lo" {
|
||||
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -70,8 +70,8 @@ func TestDeleteCharWithCount(t *testing.T) {
|
||||
sendKeys(tm, "1", "0", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -81,8 +81,8 @@ func TestDeleteCharWithCount(t *testing.T) {
|
||||
sendKeys(tm, "2", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hlo" {
|
||||
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hlo" {
|
||||
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -94,8 +94,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -108,8 +108,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -119,8 +119,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -130,8 +130,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ab c" {
|
||||
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ab c" {
|
||||
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -144,8 +144,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -155,8 +155,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "x", "x", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "lo" {
|
||||
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lo" {
|
||||
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -166,8 +166,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ab" {
|
||||
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ab" {
|
||||
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -177,8 +177,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "5", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -188,8 +188,8 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "abde" {
|
||||
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "abde" {
|
||||
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -201,8 +201,8 @@ func TestDeleteCharBackward(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
||||
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ello" {
|
||||
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -212,8 +212,8 @@ func TestDeleteCharBackward(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -223,8 +223,8 @@ func TestDeleteCharBackward(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -234,8 +234,8 @@ func TestDeleteCharBackward(t *testing.T) {
|
||||
sendKeys(tm, "X", "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "llo" {
|
||||
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -247,8 +247,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
|
||||
sendKeys(tm, "3", "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "lo" {
|
||||
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lo" {
|
||||
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -258,8 +258,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
|
||||
sendKeys(tm, "1", "0", "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "o" {
|
||||
t.Errorf("lines[0] = %q, want 'o'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "o" {
|
||||
t.Errorf("lines[0] = %q, want 'o'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -269,8 +269,8 @@ func TestDeleteCharBackwardWithCount(t *testing.T) {
|
||||
sendKeys(tm, "2", "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hlo" {
|
||||
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hlo" {
|
||||
t.Errorf("lines[0] = %q, want 'hlo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -282,8 +282,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -296,8 +296,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -310,8 +310,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -321,8 +321,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "b" {
|
||||
t.Errorf("Line(0) = %q, want 'b'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "b" {
|
||||
t.Errorf("Line(0) = %q, want 'b'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -332,8 +332,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ab c" {
|
||||
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ab c" {
|
||||
t.Errorf("Line(0) = %q, want 'ab c'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -346,8 +346,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -357,8 +357,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X", "X", "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "lo" {
|
||||
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lo" {
|
||||
t.Errorf("Line(0) = %q, want 'lo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -368,8 +368,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ab" {
|
||||
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ab" {
|
||||
t.Errorf("Line(0) = %q, want 'ab'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -379,8 +379,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "5", "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "c" {
|
||||
t.Errorf("Line(0) = %q, want 'c'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "c" {
|
||||
t.Errorf("Line(0) = %q, want 'c'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -390,8 +390,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "abde" {
|
||||
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "abde" {
|
||||
t.Errorf("Line(0) = %q, want 'abde'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -413,8 +413,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "3", "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ho" {
|
||||
t.Errorf("Line(0) = %q, want 'ho'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ho" {
|
||||
t.Errorf("Line(0) = %q, want 'ho'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
// Cursor should be at position 1 after deleting 3 chars backward from position 4
|
||||
if m.ActiveWindow().Cursor.Col != 1 {
|
||||
@ -428,11 +428,11 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -442,8 +442,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != " " {
|
||||
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " " {
|
||||
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -453,8 +453,8 @@ func TestDeleteCharBackwardEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "X")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helo" {
|
||||
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helo" {
|
||||
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 3 {
|
||||
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
|
||||
@ -469,8 +469,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -480,8 +480,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -491,8 +491,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hell" {
|
||||
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hell" {
|
||||
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -514,8 +514,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -528,11 +528,11 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %q, want '3'", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "he" {
|
||||
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "he" {
|
||||
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "hi" {
|
||||
t.Errorf("Line(1) = %q, want 'hi'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "hi" {
|
||||
t.Errorf("Line(1) = %q, want 'hi'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -545,8 +545,8 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %q, want '1'", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "he" {
|
||||
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "he" {
|
||||
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -558,8 +558,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -572,8 +572,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -583,11 +583,11 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -597,8 +597,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "he" {
|
||||
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "he" {
|
||||
t.Errorf("Line(0) = %q, want 'he'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
// Cursor should clamp to last char
|
||||
if m.ActiveWindow().Cursor.Col != 1 {
|
||||
@ -612,8 +612,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -623,8 +623,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != " " {
|
||||
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " " {
|
||||
t.Errorf("Line(0) = %q, want ' '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -634,8 +634,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -645,8 +645,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "a" {
|
||||
t.Errorf("Line(0) = %q, want 'a'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -656,8 +656,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -667,8 +667,8 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "first" {
|
||||
t.Errorf("Line(0) = %q, want 'first'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "first" {
|
||||
t.Errorf("Line(0) = %q, want 'first'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
|
||||
@ -25,8 +25,8 @@ func TestEnterInsert(t *testing.T) {
|
||||
sendKeys(tm, "i", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -36,8 +36,8 @@ func TestEnterInsert(t *testing.T) {
|
||||
sendKeys(tm, "i", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -70,8 +70,8 @@ func TestEnterInsertAfter(t *testing.T) {
|
||||
sendKeys(tm, "a", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hXello" {
|
||||
t.Errorf("lines[0] = %q, want 'hXello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hXello" {
|
||||
t.Errorf("lines[0] = %q, want 'hXello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -81,8 +81,8 @@ func TestEnterInsertAfter(t *testing.T) {
|
||||
sendKeys(tm, "a", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helXlo" {
|
||||
t.Errorf("lines[0] = %q, want 'helXlo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helXlo" {
|
||||
t.Errorf("lines[0] = %q, want 'helXlo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -94,8 +94,8 @@ func TestEnterInsertLineStart(t *testing.T) {
|
||||
sendKeys(tm, "I", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -105,8 +105,8 @@ func TestEnterInsertLineStart(t *testing.T) {
|
||||
sendKeys(tm, "I", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -118,8 +118,8 @@ func TestEnterInsertLineEnd(t *testing.T) {
|
||||
sendKeys(tm, "A", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helloX" {
|
||||
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helloX" {
|
||||
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -129,8 +129,8 @@ func TestEnterInsertLineEnd(t *testing.T) {
|
||||
sendKeys(tm, "A", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helloX" {
|
||||
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helloX" {
|
||||
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -147,8 +147,8 @@ func TestOpenLineBelow(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "new" {
|
||||
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "new" {
|
||||
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -161,8 +161,8 @@ func TestOpenLineBelow(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 4 {
|
||||
t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "new" {
|
||||
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "new" {
|
||||
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -175,8 +175,8 @@ func TestOpenLineBelow(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "new" {
|
||||
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "new" {
|
||||
t.Errorf("lines[2] = %q, want 'new'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -206,8 +206,8 @@ func TestOpenLineBelowWithCount(t *testing.T) {
|
||||
t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
for i := 1; i <= 3; i++ {
|
||||
if m.ActiveBuffer().Lines[i].String() != "x" {
|
||||
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String())
|
||||
if m.ActiveBuffer().Lines[i] != "x" {
|
||||
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -221,11 +221,11 @@ func TestOpenLineBelowWithCount(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "ab" {
|
||||
t.Errorf("lines[1] = %q, want 'ab'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "ab" {
|
||||
t.Errorf("lines[1] = %q, want 'ab'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "ab" {
|
||||
t.Errorf("lines[2] = %q, want 'ab'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "ab" {
|
||||
t.Errorf("lines[2] = %q, want 'ab'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -240,8 +240,8 @@ func TestOpenLineAbove(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "new" {
|
||||
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "new" {
|
||||
t.Errorf("lines[1] = %q, want 'new'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -254,8 +254,8 @@ func TestOpenLineAbove(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("len(lines) = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "new" {
|
||||
t.Errorf("lines[0] = %q, want 'new'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "new" {
|
||||
t.Errorf("lines[0] = %q, want 'new'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -282,8 +282,8 @@ func TestOpenLineAboveWithCount(t *testing.T) {
|
||||
t.Errorf("len(lines) = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if m.ActiveBuffer().Lines[i].String() != "x" {
|
||||
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i].String())
|
||||
if m.ActiveBuffer().Lines[i] != "x" {
|
||||
t.Errorf("lines[%d] = %q, want 'x'", i, m.ActiveBuffer().Lines[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -301,11 +301,11 @@ func TestInsertModeEnter(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != " world" {
|
||||
t.Errorf("lines[1] = %q, want ' world'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != " world" {
|
||||
t.Errorf("lines[1] = %q, want ' world'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -318,11 +318,11 @@ func TestInsertModeEnter(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("lines[1] = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("lines[1] = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -335,11 +335,11 @@ func TestInsertModeEnter(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("len(lines) = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "hello" {
|
||||
t.Errorf("lines[1] = %q, want 'hello'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "hello" {
|
||||
t.Errorf("lines[1] = %q, want 'hello'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -351,8 +351,8 @@ func TestInsertModeBackspace(t *testing.T) {
|
||||
sendKeys(tm, "i", "backspace", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helo" {
|
||||
t.Errorf("lines[0] = %q, want 'helo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -365,8 +365,8 @@ func TestInsertModeBackspace(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "helloworld" {
|
||||
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helloworld" {
|
||||
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -376,8 +376,8 @@ func TestInsertModeBackspace(t *testing.T) {
|
||||
sendKeys(tm, "i", "backspace", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -387,8 +387,8 @@ func TestInsertModeBackspace(t *testing.T) {
|
||||
sendKeys(tm, "i", "backspace", "backspace", "backspace", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "he" {
|
||||
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "he" {
|
||||
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -400,8 +400,8 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
sendKeys(tm, "i", "delete", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "word" {
|
||||
t.Errorf("lines[0] = %q, want 'word'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "word" {
|
||||
t.Errorf("lines[0] = %q, want 'word'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -414,8 +414,8 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "helloworld" {
|
||||
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helloworld" {
|
||||
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -428,8 +428,8 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("len(lines) = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -439,8 +439,8 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
sendKeys(tm, "i", "delete", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -450,8 +450,8 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
sendKeys(tm, "i", "delete", "delete", "delete", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ho" {
|
||||
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ho" {
|
||||
t.Errorf("lines[0] = %q, want 'he'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -464,8 +464,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "left", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -475,8 +475,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "right", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -486,11 +486,11 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "up", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "world" {
|
||||
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -500,11 +500,11 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "down", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "woXrld" {
|
||||
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "woXrld" {
|
||||
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -514,8 +514,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "left", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "Xhello" {
|
||||
t.Errorf("lines[0] = %q, want 'Xhello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -525,8 +525,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "a", "right", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "helloX" {
|
||||
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helloX" {
|
||||
t.Errorf("lines[0] = %q, want 'helloX'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -536,8 +536,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "up", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -547,8 +547,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "down", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heXllo" {
|
||||
t.Errorf("lines[0] = %q, want 'heXllo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -558,8 +558,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "up", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hiX" {
|
||||
t.Errorf("lines[0] = %q, want 'hiX'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hiX" {
|
||||
t.Errorf("lines[0] = %q, want 'hiX'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -569,8 +569,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "down", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[1].String() != "hiX" {
|
||||
t.Errorf("lines[1] = %q, want 'hiX'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "hiX" {
|
||||
t.Errorf("lines[1] = %q, want 'hiX'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -580,8 +580,8 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
sendKeys(tm, "i", "right", "right", "down", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[1].String() != "woXrld" {
|
||||
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "woXrld" {
|
||||
t.Errorf("lines[1] = %q, want 'woXrld'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -593,8 +593,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 5 {
|
||||
t.Errorf("CursorX() = %d, want '5'", m.ActiveWindow().Cursor.Col)
|
||||
@ -607,8 +607,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
|
||||
@ -621,8 +621,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello wo" {
|
||||
t.Errorf("lines[0] = %q, want 'hello wo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello wo" {
|
||||
t.Errorf("lines[0] = %q, want 'hello wo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 7 {
|
||||
t.Errorf("CursorX() = %d, want '7'", m.ActiveWindow().Cursor.Col)
|
||||
@ -655,8 +655,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want '1'", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
|
||||
@ -672,8 +672,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "i", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -686,8 +686,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "i", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "lo" {
|
||||
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lo" {
|
||||
t.Errorf("lines[0] = %q, want 'lo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -700,8 +700,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "..." {
|
||||
t.Errorf("lines[0] = %q, want '...'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "..." {
|
||||
t.Errorf("lines[0] = %q, want '...'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
@ -714,8 +714,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello\t" {
|
||||
t.Errorf("lines[0] = %q, want 'hello\\t'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello\t" {
|
||||
t.Errorf("lines[0] = %q, want 'hello\\t'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 5 {
|
||||
t.Errorf("CursorX() = %d, want 5", m.ActiveWindow().Cursor.Col)
|
||||
@ -731,8 +731,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "helloworld" {
|
||||
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helloworld" {
|
||||
t.Errorf("lines[0] = %q, want 'helloworld'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
@ -748,8 +748,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
|
||||
@ -176,7 +176,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
||||
sendKeys(tm, "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
want := len(lines[0]) - 1
|
||||
want := len(lines[0])
|
||||
if m.ActiveWindow().Cursor.Col != want {
|
||||
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
||||
}
|
||||
@ -188,7 +188,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
||||
sendKeys(tm, "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
want := len(lines[0]) - 1
|
||||
want := len(lines[0])
|
||||
if m.ActiveWindow().Cursor.Col != want {
|
||||
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
||||
}
|
||||
@ -200,7 +200,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
||||
sendKeys(tm, "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
want := len(lines[0]) - 1
|
||||
want := len(lines[0])
|
||||
if m.ActiveWindow().Cursor.Col != want {
|
||||
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
||||
}
|
||||
@ -633,8 +633,8 @@ func TestMoveToColumnWithOperator(t *testing.T) {
|
||||
// Deletes from column 1 to current position (exclusive), so "hello" deleted
|
||||
// Result depends on inclusive/exclusive behavior
|
||||
// In Vim: d| from col 5 deletes chars 0-4, leaving " world"
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -646,8 +646,8 @@ func TestMoveToColumnWithOperator(t *testing.T) {
|
||||
m := getFinalModel(t, tm)
|
||||
// Deletes from cursor (0) to column 5 (index 4), so "hell" deleted
|
||||
// Result: "o world"
|
||||
if m.ActiveBuffer().Lines[0].String() != "o world" {
|
||||
t.Errorf("Line(0) = %q, want 'o world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "o world" {
|
||||
t.Errorf("Line(0) = %q, want 'o world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -720,8 +720,8 @@ func TestMoveToColumnInVisualMode(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Visual selection from 0 to 4 inclusive, delete "hello"
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -928,8 +928,8 @@ func TestMoveForwardWORDWithOperator(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete "hello.world " (including trailing space)
|
||||
if m.ActiveBuffer().Lines[0].String() != "next" {
|
||||
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "next" {
|
||||
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -946,11 +946,11 @@ func TestMoveForwardWORDWithOperator(t *testing.T) {
|
||||
sendKeys(tm2, "d", "w")
|
||||
m2 := getFinalModel(t, tm2)
|
||||
|
||||
if m1.ActiveBuffer().Lines[0].String() != "next" {
|
||||
t.Errorf("dW: Line(0) = %q, want 'next'", m1.ActiveBuffer().Lines[0].String())
|
||||
if m1.ActiveBuffer().Lines[0] != "next" {
|
||||
t.Errorf("dW: Line(0) = %q, want 'next'", m1.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m2.ActiveBuffer().Lines[0].String() != ".world next" {
|
||||
t.Errorf("dw: Line(0) = %q, want '.world next'", m2.ActiveBuffer().Lines[0].String())
|
||||
if m2.ActiveBuffer().Lines[0] != ".world next" {
|
||||
t.Errorf("dw: Line(0) = %q, want '.world next'", m2.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -960,8 +960,8 @@ func TestMoveForwardWORDWithOperator(t *testing.T) {
|
||||
sendKeys(tm, "d", "2", "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "three four" {
|
||||
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "three four" {
|
||||
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1028,8 +1028,8 @@ func TestMoveForwardWORDInVisualMode(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Visual selection from 0 to 12, delete "hello.world "
|
||||
if m.ActiveBuffer().Lines[0].String() != "ext" {
|
||||
t.Errorf("Line(0) = %q, want 'ext'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ext" {
|
||||
t.Errorf("Line(0) = %q, want 'ext'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1557,8 +1557,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete "hello.world" leaving " next"
|
||||
if m.ActiveBuffer().Lines[0].String() != " next" {
|
||||
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " next" {
|
||||
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1570,8 +1570,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
|
||||
m1 := getFinalModel(t, tm1)
|
||||
|
||||
// 'de' should delete "hello" leaving ".world next"
|
||||
if m1.ActiveBuffer().Lines[0].String() != ".world next" {
|
||||
t.Errorf("'de': Line(0) = %q, want '.world next'", m1.ActiveBuffer().Lines[0].String())
|
||||
if m1.ActiveBuffer().Lines[0] != ".world next" {
|
||||
t.Errorf("'de': Line(0) = %q, want '.world next'", m1.ActiveBuffer().Lines[0])
|
||||
}
|
||||
|
||||
// Now test 'dE'
|
||||
@ -1580,8 +1580,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
|
||||
m2 := getFinalModel(t, tm2)
|
||||
|
||||
// 'dE' should delete "hello.world" leaving " next"
|
||||
if m2.ActiveBuffer().Lines[0].String() != " next" {
|
||||
t.Errorf("'dE': Line(0) = %q, want ' next'", m2.ActiveBuffer().Lines[0].String())
|
||||
if m2.ActiveBuffer().Lines[0] != " next" {
|
||||
t.Errorf("'dE': Line(0) = %q, want ' next'", m2.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1592,8 +1592,8 @@ func TestMoveForwardWORDEndWithOperator(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete "one.a two.b" leaving " three"
|
||||
if m.ActiveBuffer().Lines[0].String() != " three" {
|
||||
t.Errorf("Line(0) = %q, want ' three'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " three" {
|
||||
t.Errorf("Line(0) = %q, want ' three'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1653,8 +1653,8 @@ func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete "hello.world" leaving " next"
|
||||
if m.ActiveBuffer().Lines[0].String() != " next" {
|
||||
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " next" {
|
||||
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1685,816 +1685,3 @@ func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- B Motion Tests ---
|
||||
|
||||
func TestMoveBackwardWORD(t *testing.T) {
|
||||
t.Run("test 'B' moves backward one WORD", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to start of "world" (index 6)
|
||||
if m.ActiveWindow().Cursor.Col != 6 {
|
||||
t.Errorf("CursorX() = %d, want 6", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'BB' moves backward two WORDs", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "B", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '2B' moves backward two WORDs with count", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "2", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'B' on punctuation-heavy text", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// "hello.world" is one WORD, should move to "next" (index 12)
|
||||
if m.ActiveWindow().Cursor.Col != 12 {
|
||||
t.Errorf("CursorX() = %d, want 12", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'B' vs 'b' on punctuation", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
|
||||
// Test 'b'
|
||||
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm1, "b")
|
||||
m1 := getFinalModel(t, tm1)
|
||||
|
||||
// 'b' moves to start of "next" (index 12)
|
||||
if m1.ActiveWindow().Cursor.Col != 12 {
|
||||
t.Errorf("'b': CursorX() = %d, want 12", m1.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
|
||||
// Test 'B'
|
||||
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm2, "B")
|
||||
m2 := getFinalModel(t, tm2)
|
||||
|
||||
// 'B' treats "hello.world" as one WORD, moves to index 12
|
||||
if m2.ActiveWindow().Cursor.Col != 12 {
|
||||
t.Errorf("'B': CursorX() = %d, want 12", m2.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'B' crosses lines backward", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 1})
|
||||
sendKeys(tm, "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'B' at beginning of file", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLines(t, lines) // Cursor at 0,0
|
||||
sendKeys(tm, "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should stay at 0,0
|
||||
if m.ActiveWindow().Cursor.Col != 0 || m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("Cursor = (%d,%d), want (0,0)", m.ActiveWindow().Cursor.Col, m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'B' with multiple spaces", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 13, Line: 0})
|
||||
sendKeys(tm, "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should skip spaces and move to "hello"
|
||||
if m.ActiveWindow().Cursor.Col != 9 {
|
||||
t.Errorf("CursorX() = %d, want 9", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'B' on empty lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 2})
|
||||
sendKeys(tm, "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().Cursor.Line != 2 {
|
||||
t.Errorf("CursorY() = %d, want 2", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'B' complex code-like text", func(t *testing.T) {
|
||||
lines := []string{"foo.bar(baz) next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
|
||||
sendKeys(tm, "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// "foo.bar(baz)" is one WORD, should move to "next" (index 13)
|
||||
if m.ActiveWindow().Cursor.Col != 13 {
|
||||
t.Errorf("CursorX() = %d, want 13", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'BB' complex code-like text", func(t *testing.T) {
|
||||
lines := []string{"foo.bar(baz) next.thing"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 22, Line: 0})
|
||||
sendKeys(tm, "B", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to start of first WORD "foo.bar(baz)" (index 0)
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveBackwardWORDWithOperator(t *testing.T) {
|
||||
t.Run("test 'dB' deletes backward WORD including punctuation", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "d", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.world t" {
|
||||
t.Errorf("Line(0) = %q, want 'hello.world t'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'dB' vs 'db' on dotted text", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
|
||||
// First test 'db'
|
||||
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm1, "d", "b")
|
||||
m1 := getFinalModel(t, tm1)
|
||||
|
||||
if m1.ActiveBuffer().Lines[0].String() != "hello.world t" {
|
||||
t.Errorf("'db': Line(0) = %q, want 'hello.world t'", m1.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
|
||||
// Now test 'dB'
|
||||
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm2, "d", "B")
|
||||
m2 := getFinalModel(t, tm2)
|
||||
|
||||
if m2.ActiveBuffer().Lines[0].String() != "hello.world t" {
|
||||
t.Errorf("'dB': Line(0) = %q, want 'hello.world t'", m2.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'd2B' deletes two WORDs backward", func(t *testing.T) {
|
||||
lines := []string{"one.a two.b three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
|
||||
sendKeys(tm, "d", "2", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete "two.b three" leaving "one.a e"
|
||||
if m.ActiveBuffer().Lines[0].String() != "one.a e" {
|
||||
t.Errorf("Line(0) = %q, want 'one.a e'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
// BUG: This is a failing tests, cursor is not moving at start of yank
|
||||
t.Run("test 'yB' yanks WORD including punctuation", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "y", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Text should remain unchanged
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.world next" {
|
||||
t.Errorf("Line(0) = %q, want 'hello.world next'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
// Cursor should be at start of yanked region (index 12)
|
||||
if m.ActiveWindow().Cursor.Col != 12 {
|
||||
t.Errorf("CursorX() = %d, want 12", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'cB' changes WORD backward", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "c", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete " next" and enter insert mode
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.world t" {
|
||||
t.Errorf("Line(0) = %q, want 'hello.world t'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveBackwardWORDVisualMode(t *testing.T) {
|
||||
t.Run("test 'vB' selects backward WORD", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "v", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != core.VisualMode {
|
||||
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
||||
}
|
||||
// Cursor at start of "next" (index 12)
|
||||
if m.ActiveWindow().Cursor.Col != 12 {
|
||||
t.Errorf("CursorX() = %d, want 12", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'vBd' deletes selected WORD", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "v", "B", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.world " {
|
||||
t.Errorf("Line(0) = %q, want 'hello.world '", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'v2B' selects backward two WORDs", func(t *testing.T) {
|
||||
lines := []string{"foo.bar baz.qux rest"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 19, Line: 0})
|
||||
sendKeys(tm, "v", "2", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Cursor at start of "baz.qux" (index 8)
|
||||
if m.ActiveWindow().Cursor.Col != 8 {
|
||||
t.Errorf("CursorX() = %d, want 8", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'VB' in visual line mode", func(t *testing.T) {
|
||||
lines := []string{"hello.world", "next line"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 1})
|
||||
sendKeys(tm, "V", "B")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != core.VisualLineMode {
|
||||
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
||||
}
|
||||
// Should select both lines
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- ge Motion Tests ---
|
||||
|
||||
func TestMoveBackwardWordEnd(t *testing.T) {
|
||||
t.Run("test 'ge' moves backward to previous word end", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "hello" (index 4)
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gege' moves backward two word ends", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "g", "e", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "one" (index 2)
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '2ge' moves backward two word ends with count", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "2", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "one" (index 2)
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'ge' on punctuation-heavy text", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "world" (index 10)
|
||||
if m.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'ge' crosses lines backward", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "hello" on previous line (index 4)
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'ge' at beginning of file", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLines(t, lines) // Cursor at 0,0
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should stay at 0,0
|
||||
if m.ActiveWindow().Cursor.Col != 0 || m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("Cursor = (%d,%d), want (0,0)", m.ActiveWindow().Cursor.Col, m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'ge' with multiple spaces", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 13, Line: 0})
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should skip spaces and move to end of "hello" (index 4)
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'ge' on empty lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should skip empty line and move to end of "hello" (index 4)
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'ge' respects word classes", func(t *testing.T) {
|
||||
lines := []string{"foo-bar baz"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "bar" (index 6)
|
||||
if m.ActiveWindow().Cursor.Col != 6 {
|
||||
t.Errorf("CursorX() = %d, want 6", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'ge' on method chain", func(t *testing.T) {
|
||||
lines := []string{"obj.method().chain() next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 24, Line: 0})
|
||||
sendKeys(tm, "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of ")" (index 19)
|
||||
if m.ActiveWindow().Cursor.Col != 19 {
|
||||
t.Errorf("CursorX() = %d, want 19", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveBackwardWordEndWithOperator(t *testing.T) {
|
||||
t.Run("test 'dge' deletes backward to word end", func(t *testing.T) {
|
||||
lines := []string{"hello world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "d", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worl'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'd2ge' deletes backward two word ends", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "d", "2", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "on" {
|
||||
t.Errorf("Line(0) = %q, want 'on'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
// BUG: This is a failing tests, cursor is not moving at start of yank
|
||||
t.Run("test 'yge' yanks backward to word end", func(t *testing.T) {
|
||||
lines := []string{"hello world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "y", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Text should remain unchanged
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world next" {
|
||||
t.Errorf("Line(0) = %q, want 'hello world next'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
// Cursor should be at end of "world" (index 10)
|
||||
if m.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'cge' changes backward to word end", func(t *testing.T) {
|
||||
lines := []string{"hello world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "c", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete and enter insert mode
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worl'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveBackwardWordEndVisualMode(t *testing.T) {
|
||||
t.Run("test 'vge' selects backward to word end", func(t *testing.T) {
|
||||
lines := []string{"hello world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "v", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != core.VisualMode {
|
||||
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
||||
}
|
||||
// Cursor at end of "world" (index 10)
|
||||
if m.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'vged' deletes selection", func(t *testing.T) {
|
||||
lines := []string{"hello world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "v", "g", "e", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worl'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'v2ge' selects backward two word ends", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "v", "2", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Cursor at end of "one" (index 2)
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'Vge' in visual line mode", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "V", "g", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != core.VisualLineMode {
|
||||
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
||||
}
|
||||
// Should select both lines
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- gE Motion Tests ---
|
||||
|
||||
func TestMoveBackwardWORDEnd(t *testing.T) {
|
||||
t.Run("test 'gE' moves backward to previous WORD end", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "hello" (index 4)
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gEgE' moves backward two WORD ends", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "g", "E", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "one" (index 2)
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test '2gE' moves backward two WORD ends with count", func(t *testing.T) {
|
||||
lines := []string{"one two three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 0})
|
||||
sendKeys(tm, "2", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "one" (index 2)
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' on punctuation-heavy text", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// "hello.world" is one WORD ending at index 10
|
||||
if m.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' vs 'ge' on punctuation", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
|
||||
// Test 'ge'
|
||||
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm1, "g", "e")
|
||||
m1 := getFinalModel(t, tm1)
|
||||
|
||||
// 'ge' treats punctuation as separate word
|
||||
if m1.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("'ge': CursorX() = %d, want 10", m1.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
|
||||
// Test 'gE'
|
||||
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm2, "g", "E")
|
||||
m2 := getFinalModel(t, tm2)
|
||||
|
||||
// 'gE' treats "hello.world" as one WORD
|
||||
if m2.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("'gE': CursorX() = %d, want 10", m2.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' crosses lines backward", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should move to end of "hello" on previous line (index 4)
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' at beginning of file", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLines(t, lines) // Cursor at 0,0
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should stay at 0,0
|
||||
if m.ActiveWindow().Cursor.Col != 0 || m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("Cursor = (%d,%d), want (0,0)", m.ActiveWindow().Cursor.Col, m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' with multiple spaces", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 13, Line: 0})
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should skip spaces and move to end of "hello" (index 4)
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' on empty lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should skip empty line and move to end of "hello" (index 4)
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' complex code-like text", func(t *testing.T) {
|
||||
lines := []string{"foo.bar(baz) next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// "foo.bar(baz)" is one WORD ending at index 11
|
||||
if m.ActiveWindow().Cursor.Col != 11 {
|
||||
t.Errorf("CursorX() = %d, want 11", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'gE' on method chain", func(t *testing.T) {
|
||||
lines := []string{"obj.method().chain() next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 24, Line: 0})
|
||||
sendKeys(tm, "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Entire chain is one WORD, ends at index 19
|
||||
if m.ActiveWindow().Cursor.Col != 19 {
|
||||
t.Errorf("CursorX() = %d, want 19", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveBackwardWORDEndWithOperator(t *testing.T) {
|
||||
t.Run("test 'dgE' deletes backward to WORD end", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "d", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.worl" {
|
||||
t.Errorf("Line(0) = %q, want 'hello.worl'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'dgE' vs 'dge' on dotted text", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
|
||||
// First test 'dge'
|
||||
tm1 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm1, "d", "g", "e")
|
||||
m1 := getFinalModel(t, tm1)
|
||||
|
||||
// 'dge' should delete to end of "world"
|
||||
if m1.ActiveBuffer().Lines[0].String() != "hello.worl" {
|
||||
t.Errorf("'dge': Line(0) = %q, want 'hello.worl'", m1.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
|
||||
// Now test 'dgE'
|
||||
tm2 := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm2, "d", "g", "E")
|
||||
m2 := getFinalModel(t, tm2)
|
||||
|
||||
if m2.ActiveBuffer().Lines[0].String() != "hello.worl" {
|
||||
t.Errorf("'dgE': Line(0) = %q, want 'hello.worl'", m2.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'd2gE' deletes backward two WORD ends", func(t *testing.T) {
|
||||
lines := []string{"one.a two.b three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 16, Line: 0})
|
||||
sendKeys(tm, "d", "2", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "one." {
|
||||
t.Errorf("Line(0) = %q, want 'one.'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
// BUG: This is a failing tests, cursor is not moving at start of yank
|
||||
t.Run("test 'ygE' yanks backward to WORD end", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "y", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Text should remain unchanged
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.world next" {
|
||||
t.Errorf("Line(0) = %q, want 'hello.world next'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
// Cursor should be at end of "hello.world" (index 10)
|
||||
if m.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'cgE' changes backward to WORD end", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "c", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should delete and enter insert mode
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.worl" {
|
||||
t.Errorf("Line(0) = %q, want 'hello.world t'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveBackwardWORDEndVisualMode(t *testing.T) {
|
||||
t.Run("test 'vgE' selects backward to WORD end", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "v", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != core.VisualMode {
|
||||
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
||||
}
|
||||
// Cursor at end of "hello.world" (index 10)
|
||||
if m.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'vgEd' deletes selection", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 15, Line: 0})
|
||||
sendKeys(tm, "v", "g", "E", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello.worl" {
|
||||
t.Errorf("Line(0) = %q, want 'hello.worl'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'v2gE' selects backward two WORD ends", func(t *testing.T) {
|
||||
lines := []string{"foo.bar baz.qux rest"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 19, Line: 0})
|
||||
sendKeys(tm, "v", "2", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Cursor at end of "foo.bar" (index 6)
|
||||
if m.ActiveWindow().Cursor.Col != 6 {
|
||||
t.Errorf("CursorX() = %d, want 6", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'VgE' in visual line mode", func(t *testing.T) {
|
||||
lines := []string{"hello.world", "next line"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "V", "g", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != core.VisualLineMode {
|
||||
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
||||
}
|
||||
// Should select both lines
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -26,11 +26,11 @@ func TestChangeLine(t *testing.T) {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
// First line should be empty (ready for insert)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "world" {
|
||||
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -45,14 +45,14 @@ func TestChangeLine(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line three" {
|
||||
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line three" {
|
||||
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -67,11 +67,11 @@ func TestChangeLine(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -109,8 +109,8 @@ func TestChangeLine(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -148,11 +148,11 @@ func TestChangeLineWithCount(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line three" {
|
||||
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line three" {
|
||||
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -171,14 +171,14 @@ func TestChangeLineWithCount(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "one" {
|
||||
t.Errorf("Line(0) = %q, want 'one'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "one" {
|
||||
t.Errorf("Line(0) = %q, want 'one'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "five" {
|
||||
t.Errorf("Line(2) = %q, want 'five'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "five" {
|
||||
t.Errorf("Line(2) = %q, want 'five'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -196,8 +196,8 @@ func TestChangeLineWithCount(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -235,8 +235,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello world" {
|
||||
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ello world" {
|
||||
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -251,8 +251,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo world" {
|
||||
t.Errorf("Line(0) = %q, want 'llo world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "llo world" {
|
||||
t.Errorf("Line(0) = %q, want 'llo world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -267,8 +267,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hell world" {
|
||||
t.Errorf("Line(0) = %q, want 'hell world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hell world" {
|
||||
t.Errorf("Line(0) = %q, want 'hell world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -283,8 +283,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -299,8 +299,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -317,8 +317,8 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
}
|
||||
// ^ is exclusive motion, so position 8 (space) is not included
|
||||
// Delete positions 3-7 ("hello"), leaving " " + " world" = " world"
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -339,8 +339,8 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -355,8 +355,8 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "heworld" {
|
||||
t.Errorf("Line(0) = %q, want 'heworld'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heworld" {
|
||||
t.Errorf("Line(0) = %q, want 'heworld'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -371,8 +371,8 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -387,8 +387,8 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %q, want 'world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -403,8 +403,8 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "three four" {
|
||||
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "three four" {
|
||||
t.Errorf("Line(0) = %q, want 'three four'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -419,8 +419,8 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "next" {
|
||||
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "next" {
|
||||
t.Errorf("Line(0) = %q, want 'next'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -435,8 +435,8 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != " next" {
|
||||
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " next" {
|
||||
t.Errorf("Line(0) = %q, want ' next'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -461,11 +461,11 @@ func TestChangeWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line three" {
|
||||
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line three" {
|
||||
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -484,11 +484,11 @@ func TestChangeWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line three" {
|
||||
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line three" {
|
||||
t.Errorf("Line(1) = %q, want 'line three'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -507,11 +507,11 @@ func TestChangeWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "four" {
|
||||
t.Errorf("Line(1) = %q, want 'four'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "four" {
|
||||
t.Errorf("Line(1) = %q, want 'four'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -536,8 +536,8 @@ func TestChangeWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -556,8 +556,8 @@ func TestChangeWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -576,11 +576,11 @@ func TestChangeWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -601,8 +601,8 @@ func TestChangeToEndOfLine(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -617,8 +617,8 @@ func TestChangeToEndOfLine(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -634,8 +634,8 @@ func TestChangeToEndOfLine(t *testing.T) {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should delete last char
|
||||
if m.ActiveBuffer().Lines[0].String() != "hell" {
|
||||
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hell" {
|
||||
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -673,8 +673,8 @@ func TestSubstituteCharacter(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
||||
t.Errorf("Line(0) = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ello" {
|
||||
t.Errorf("Line(0) = %q, want 'ello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -689,8 +689,8 @@ func TestSubstituteCharacter(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "llo" {
|
||||
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -705,8 +705,8 @@ func TestSubstituteCharacter(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hell" {
|
||||
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hell" {
|
||||
t.Errorf("Line(0) = %q, want 'hell'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -727,8 +727,8 @@ func TestSubstituteLine(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -740,14 +740,14 @@ func TestSubstituteLine(t *testing.T) {
|
||||
sendKeys(tm, "S")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line three" {
|
||||
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line three" {
|
||||
t.Errorf("Line(2) = %q, want 'line three'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -766,11 +766,11 @@ func TestSubstituteLine(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "three" {
|
||||
t.Errorf("Line(1) = %q, want 'three'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "three" {
|
||||
t.Errorf("Line(1) = %q, want 'three'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -791,8 +791,8 @@ func TestVisualModeChange(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello world" {
|
||||
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ello world" {
|
||||
t.Errorf("Line(0) = %q, want 'ello world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -807,8 +807,8 @@ func TestVisualModeChange(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -823,8 +823,8 @@ func TestVisualModeChange(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -878,8 +878,8 @@ func TestVisualLineModeChange(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -898,14 +898,14 @@ func TestVisualLineModeChange(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line one" {
|
||||
t.Errorf("Line(0) = %q, want 'line one'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "" {
|
||||
t.Errorf("Line(1) = %q, want ''", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line four" {
|
||||
t.Errorf("Line(2) = %q, want 'line four'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line four" {
|
||||
t.Errorf("Line(2) = %q, want 'line four'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -946,8 +946,8 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -963,8 +963,8 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// cw on last word should change to end of line
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -979,8 +979,8 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -24,8 +24,8 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -44,11 +44,11 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "testing" {
|
||||
t.Errorf("Line(1) = %s, want 'testing'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "testing" {
|
||||
t.Errorf("Line(1) = %s, want 'testing'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -67,8 +67,8 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -87,8 +87,8 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %s, want 'world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -107,12 +107,12 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("CursorY() = %d, want '1'", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
|
||||
if m.ActiveBuffer().Lines[1].String() != "another line" {
|
||||
t.Errorf("Line(1) = %s, want 'another line'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "another line" {
|
||||
t.Errorf("Line(1) = %s, want 'another line'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -131,8 +131,8 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -151,8 +151,8 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want '0'", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %s, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -176,8 +176,8 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -190,8 +190,8 @@ func TestDeleteLine(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
@ -210,8 +210,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
||||
t.Errorf("Line(0) = %s, want 'ello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ello" {
|
||||
t.Errorf("Line(0) = %s, want 'ello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -224,8 +224,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want '2'", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "helo" {
|
||||
t.Errorf("Line(0) = %s, want 'helo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helo" {
|
||||
t.Errorf("Line(0) = %s, want 'helo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -238,8 +238,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("CursorX() = %d, want '4'", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hell" {
|
||||
t.Errorf("Line(0) = %s, want 'hell'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hell" {
|
||||
t.Errorf("Line(0) = %s, want 'hell'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -252,8 +252,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want '0'", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %s, want 'hello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -267,8 +267,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Col != 1 {
|
||||
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hllo" {
|
||||
t.Errorf("Line(0) = %q, want 'hllo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hllo" {
|
||||
t.Errorf("Line(0) = %q, want 'hllo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -282,8 +282,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Col != 3 {
|
||||
t.Errorf("CursorX() = %d, want 3", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "helo" {
|
||||
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "helo" {
|
||||
t.Errorf("Line(0) = %q, want 'helo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -293,8 +293,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
sendKeys(tm, "2", "d", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "llo" {
|
||||
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -304,8 +304,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "2", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "llo" {
|
||||
t.Errorf("Line(0) = %q, want 'llo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -315,8 +315,8 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
sendKeys(tm, "2", "d", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heo" {
|
||||
t.Errorf("Line(0) = %q, want 'heo'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heo" {
|
||||
t.Errorf("Line(0) = %q, want 'heo'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -331,11 +331,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 4" {
|
||||
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 4" {
|
||||
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
||||
@ -351,8 +351,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
@ -370,11 +370,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -387,11 +387,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -404,11 +404,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -421,11 +421,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 4" {
|
||||
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 4" {
|
||||
t.Errorf("Line(1) = %q, want 'line 4'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("CursorY() = %d, want 1", m.ActiveWindow().Cursor.Line)
|
||||
@ -443,11 +443,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 2" {
|
||||
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 2" {
|
||||
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 3" {
|
||||
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 3" {
|
||||
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -460,8 +460,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
@ -477,11 +477,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -494,11 +494,11 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -512,8 +512,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -527,8 +527,8 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -541,8 +541,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -555,8 +555,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "heworld" {
|
||||
t.Errorf("Line(0) = %q, want \"heworld\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "heworld" {
|
||||
t.Errorf("Line(0) = %q, want \"heworld\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
@ -569,8 +569,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -580,8 +580,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
sendKeys(tm, "2", "d", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "three four" {
|
||||
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "three four" {
|
||||
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -591,8 +591,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "2", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "three four" {
|
||||
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "three four" {
|
||||
t.Errorf("Line(0) = %q, want \"three four\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -603,8 +603,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// 'w' motion stops at punctuation, so it should delete "hello"
|
||||
if m.ActiveBuffer().Lines[0].String() != ", world" {
|
||||
t.Errorf("Line(0) = %q, want \", world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != ", world" {
|
||||
t.Errorf("Line(0) = %q, want \", world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -616,8 +616,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// 'e' is inclusive - deletes "hello" (cols 0-4 inclusive), leaves " world"
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -631,8 +631,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// From 'l' (col 2) to 'o' (col 4) inclusive → deletes "llo"
|
||||
if m.ActiveBuffer().Lines[0].String() != "he world" {
|
||||
t.Errorf("Line(0) = %q, want \"he world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "he world" {
|
||||
t.Errorf("Line(0) = %q, want \"he world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -643,8 +643,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// From 'o' of "hello" (col 4) to 'd' of "world" (col 10) inclusive
|
||||
if m.ActiveBuffer().Lines[0].String() != "hell" {
|
||||
t.Errorf("Line(0) = %q, want \"hell\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hell" {
|
||||
t.Errorf("Line(0) = %q, want \"hell\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -655,8 +655,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Deletes "one" and " two" (to end of second word inclusive)
|
||||
if m.ActiveBuffer().Lines[0].String() != " three four" {
|
||||
t.Errorf("Line(0) = %q, want \" three four\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " three four" {
|
||||
t.Errorf("Line(0) = %q, want \" three four\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -667,8 +667,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -679,8 +679,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// cursor at 'r' of "world", db should delete back to start of "world"
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello rld" {
|
||||
t.Errorf("Line(0) = %q, want \"hello rld\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello rld" {
|
||||
t.Errorf("Line(0) = %q, want \"hello rld\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -691,8 +691,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// cursor at 'w', db should delete "hello " back
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -703,8 +703,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// cursor at 'f' of "four", 2db should delete "two three "
|
||||
if m.ActiveBuffer().Lines[0].String() != "one four" {
|
||||
t.Errorf("Line(0) = %q, want \"one four\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "one four" {
|
||||
t.Errorf("Line(0) = %q, want \"one four\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -715,8 +715,8 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// cursor at 'd' (last char), db should delete back to start of "world"
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello d" {
|
||||
t.Errorf("Line(0) = %q, want \"hello d\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello d" {
|
||||
t.Errorf("Line(0) = %q, want \"hello d\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -729,8 +729,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -740,8 +740,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -754,8 +754,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "d" {
|
||||
t.Errorf("Line(0) = %q, want \"d\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "d" {
|
||||
t.Errorf("Line(0) = %q, want \"d\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -765,8 +765,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "lo world" {
|
||||
t.Errorf("Line(0) = %q, want \"lo world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lo world" {
|
||||
t.Errorf("Line(0) = %q, want \"lo world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -777,8 +777,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -788,8 +788,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -799,8 +799,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello worl" {
|
||||
t.Errorf("Line(0) = %q, want \"hello worl\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello worl" {
|
||||
t.Errorf("Line(0) = %q, want \"hello worl\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -810,11 +810,11 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want \"hello \"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "second line" {
|
||||
t.Errorf("Line(1) = %q, want \"second line\"", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "second line" {
|
||||
t.Errorf("Line(1) = %q, want \"second line\"", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
@ -827,8 +827,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want \"\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
@ -843,8 +843,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// From col 0 to first non-whitespace (col 3, 'h') - deletes leading spaces
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want \"hello world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -858,8 +858,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// From col 1 to first non-whitespace (col 3, 'h') - deletes cols 1-2
|
||||
if m.ActiveBuffer().Lines[0].String() != " hello world" {
|
||||
t.Errorf("Line(0) = %q, want \" hello world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " hello world" {
|
||||
t.Errorf("Line(0) = %q, want \" hello world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 1 {
|
||||
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
|
||||
@ -874,8 +874,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
m := getFinalModel(t, tm)
|
||||
// From col 6 ('l') back to col 3 ('h')
|
||||
// Should delete "hel" leaving " lo world"
|
||||
if m.ActiveBuffer().Lines[0].String() != " lo world" {
|
||||
t.Errorf("Line(0) = %q, want \" lo world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " lo world" {
|
||||
t.Errorf("Line(0) = %q, want \" lo world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -887,8 +887,8 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
m := getFinalModel(t, tm)
|
||||
// From col 5 (' ') back to col 0 ('h')
|
||||
// Should delete "hello" leaving " world"
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want \" world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -903,11 +903,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -920,8 +920,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -934,11 +934,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -962,11 +962,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 4" {
|
||||
t.Errorf("Line(0) = %q, want 'line 4'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 4" {
|
||||
t.Errorf("Line(0) = %q, want 'line 4'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 5" {
|
||||
t.Errorf("Line(1) = %q, want 'line 5'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -979,8 +979,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -993,11 +993,11 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 2" {
|
||||
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 2" {
|
||||
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 3" {
|
||||
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 3" {
|
||||
t.Errorf("Line(1) = %q, want 'line 3'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1021,8 +1021,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1035,8 +1035,8 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,581 +0,0 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ==================================================
|
||||
// P0: Basic Recording Tests
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorRecording(t *testing.T) {
|
||||
t.Run("records simple delete", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
if len(keys) != 1 || keys[0] != "x" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"x\"]", keys)
|
||||
}
|
||||
|
||||
// Also verify . register
|
||||
reg, ok := m.GetRegister('.')
|
||||
if !ok {
|
||||
t.Fatal("dot register not found")
|
||||
}
|
||||
if reg.Content[0] != "x" {
|
||||
t.Errorf("dot register = %q, want \"x\"", reg.Content[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("records operator motion", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello world"}))
|
||||
sendKeys(tm, "d", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
if len(keys) != 2 || keys[0] != "d" || keys[1] != "w" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"d\", \"w\"]", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("records double press operator", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello", "world"}))
|
||||
sendKeys(tm, "d", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
if len(keys) != 2 || keys[0] != "d" || keys[1] != "d" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"d\", \"d\"]", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("records visual operation", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2", "line3"}))
|
||||
sendKeys(tm, "V", "j", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
if len(keys) != 3 || keys[0] != "V" || keys[1] != "j" || keys[2] != "x" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"V\", \"j\", \"x\"]", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("records insert mode", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "i", "X", "Y", "Z", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
if len(keys) != 5 || keys[0] != "i" || keys[1] != "X" || keys[2] != "Y" || keys[3] != "Z" || keys[4] != "esc" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"i\", \"X\", \"Y\", \"Z\", \"esc\"]", keys)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P0: Non-Recording Tests
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorNonRecording(t *testing.T) {
|
||||
t.Run("does not record pure motions", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2", "line3", "line4"}))
|
||||
sendKeys(tm, "k", "k", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
// Pure motions should result in empty recording
|
||||
if len(keys) != 0 {
|
||||
t.Errorf("LastChangeKeys() = %v, want []", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not record dot operator itself", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "x", ".", ".")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
// Should still be just ["x"], not ["x", ".", "."]
|
||||
if len(keys) != 1 || keys[0] != "x" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"x\"]", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not record command mode entry", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, ":")
|
||||
sendKeys(tm, "esc") // Exit command mode
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
// Command mode entry should not record
|
||||
if len(keys) != 0 {
|
||||
t.Errorf("LastChangeKeys() = %v, want []", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not record visual mode entry without action", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "v", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
// Just entering and exiting visual mode should not record
|
||||
if len(keys) != 0 {
|
||||
t.Errorf("LastChangeKeys() = %v, want []", keys)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P0: Basic Replay Tests
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorReplay(t *testing.T) {
|
||||
t.Run("repeats simple delete", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "x") // "ello"
|
||||
sendKeys(tm, ".") // "llo"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != "llo" {
|
||||
t.Errorf("buffer = %q, want \"llo\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeats operator motion", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"one two three"}))
|
||||
sendKeys(tm, "d", "w") // "two three"
|
||||
sendKeys(tm, ".") // "three"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != "three" {
|
||||
t.Errorf("buffer = %q, want \"three\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeats double press operator", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2", "line3"}))
|
||||
sendKeys(tm, "d", "d") // Delete first line -> "line2", "line3"
|
||||
sendKeys(tm, ".") // Delete next line -> "line3"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Line(0) != "line3" {
|
||||
t.Errorf("buffer = %q, want \"line3\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P1: Recording Replacement Tests
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorRecordingReplacement(t *testing.T) {
|
||||
t.Run("new action replaces old recording", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello world"}))
|
||||
sendKeys(tm, "x") // Record ["x"]
|
||||
sendKeys(tm, "l", "l") // Motions clear recording buffer but don't save
|
||||
sendKeys(tm, "d", "w") // Record ["d", "w"]
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
// Should be the latest action ["d", "w"], not ["x"]
|
||||
if len(keys) != 2 || keys[0] != "d" || keys[1] != "w" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"d\", \"w\"]", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("motions clear recording without saving", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2", "line3", "line4"}))
|
||||
sendKeys(tm, "x") // Record ["x"]
|
||||
sendKeys(tm, "j", "j", "j") // Motions don't overwrite saved recording
|
||||
sendKeys(tm, "d", "d") // New action replaces
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
// Should be ["d", "d"] from the last modifying action
|
||||
if len(keys) != 2 || keys[0] != "d" || keys[1] != "d" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"d\", \"d\"]", keys)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P1: Visual Mode Tests
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorVisualMode(t *testing.T) {
|
||||
t.Run("repeats visual line operation", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"a", "b", "c", "d", "e"}))
|
||||
sendKeys(tm, "V", "j", "x") // Delete lines 0-1 -> "c", "d", "e"
|
||||
// Cursor should be at line 0 after deletion
|
||||
sendKeys(tm, ".") // Repeat -> delete next 2 lines -> "e"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Line(0) != "e" {
|
||||
t.Errorf("buffer = %q, want \"e\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P1: Insert Mode Tests
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorInsertMode(t *testing.T) {
|
||||
t.Run("repeats insert mode operation", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "i", "X", "Y", "Z", "esc") // "XYZhello", cursor at col 2 (on Z)
|
||||
// Move cursor after XYZ (to col 3, between Z and h)
|
||||
sendKeys(tm, "l")
|
||||
sendKeys(tm, ".") // Should insert XYZ again at col 3
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != "XYZXYZhello" {
|
||||
t.Errorf("buffer = %q, want \"XYZXYZhello\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P1: Multiple Repeat Tests
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorMultipleRepeats(t *testing.T) {
|
||||
t.Run("dot can be used multiple times", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello world foo bar"}))
|
||||
sendKeys(tm, "d", "w") // "world foo bar"
|
||||
sendKeys(tm, ".") // "foo bar"
|
||||
sendKeys(tm, ".") // "bar"
|
||||
sendKeys(tm, ".") // ""
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
line := m.ActiveBuffer().Line(0)
|
||||
// After deleting 4 words, should be empty or just whitespace
|
||||
if line != "" && line != " " {
|
||||
t.Errorf("buffer = %q, want empty or space", line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P2: Edge Cases
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorEdgeCases(t *testing.T) {
|
||||
t.Run("repeat at start of file", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "x") // "ello"
|
||||
sendKeys(tm, ".") // "llo"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != "llo" {
|
||||
t.Errorf("buffer = %q, want \"llo\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeat after undo", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello world"}))
|
||||
sendKeys(tm, "x") // "ello world"
|
||||
sendKeys(tm, "u") // Undo -> "hello world"
|
||||
sendKeys(tm, ".") // Repeat should still work -> "ello world"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != "ello world" {
|
||||
t.Errorf("buffer = %q, want \"ello world\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeat with no recorded change", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, ".") // Dot with nothing recorded
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should not crash, buffer should be unchanged
|
||||
if m.ActiveBuffer().Line(0) != "hello" {
|
||||
t.Errorf("buffer = %q, want \"hello\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P2: Integration with Counts
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorWithCounts(t *testing.T) {
|
||||
t.Run("recording includes count", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello world"}))
|
||||
sendKeys(tm, "3", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
keys := m.LastChangeKeys()
|
||||
// Should record both the count and the action
|
||||
if len(keys) != 2 || keys[0] != "3" || keys[1] != "x" {
|
||||
t.Errorf("LastChangeKeys() = %v, want [\"3\", \"x\"]", keys)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeat preserves count", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "3", "x") // Delete 3 chars -> "lo"
|
||||
sendKeys(tm, ".") // Should delete 3 more (or try to)
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// After deleting 3 + 3 = 6 chars, should be empty or have no chars left
|
||||
if m.ActiveBuffer().Line(0) != "" {
|
||||
t.Errorf("buffer = %q, want empty", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// P2: Complex Sequences
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorComplexSequences(t *testing.T) {
|
||||
t.Run("complex sequence of operations", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2", "line3"}))
|
||||
sendKeys(tm, "x") // Delete 'l' from line1 -> ["ine1", "line2", "line3"]
|
||||
sendKeys(tm, "j", "j") // Move to line 2
|
||||
sendKeys(tm, "d", "d") // Delete line 2 (line3) -> ["ine1", "line2"], cursor at line 1
|
||||
sendKeys(tm, "k") // Move up to line 0
|
||||
sendKeys(tm, ".") // Repeat dd - deletes line 0 (ine1) -> ["line2"]
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// After the sequence, dd was recorded and repeated at line 0, deleting it
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Line(0) != "line2" {
|
||||
t.Errorf("buffer = %q, want \"line2\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Additional Coverage: Change Operator
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorChangeOperator(t *testing.T) {
|
||||
t.Run("repeats change word", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"one two three"}))
|
||||
sendKeys(tm, "c", "w", "X", "esc") // Change "one " to "X" -> "Xtwo three"
|
||||
sendKeys(tm, "w") // Move to "two"
|
||||
sendKeys(tm, ".") // Repeat cw -> change "two " to "X" -> "Xtwo X"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// cw deletes word and space after, result is "Xtwo X" + trailing content
|
||||
if m.ActiveBuffer().Line(0) != "Xtwo Xthree" && m.ActiveBuffer().Line(0) != "Xtwo X" {
|
||||
t.Errorf("buffer = %q, want \"Xtwo X\" or \"Xtwo Xthree\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeats change line", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2", "line3"}))
|
||||
sendKeys(tm, "c", "c", "N", "E", "W", "esc") // Change line -> "NEW"
|
||||
sendKeys(tm, "j") // Move to next line
|
||||
sendKeys(tm, ".") // Repeat cc
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Line(0) != "NEW" || m.ActiveBuffer().Line(1) != "NEW" {
|
||||
t.Errorf("lines = [%q, %q], want [\"NEW\", \"NEW\"]",
|
||||
m.ActiveBuffer().Line(0), m.ActiveBuffer().Line(1))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Additional Coverage: Paste Operations
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorPaste(t *testing.T) {
|
||||
t.Run("repeats paste", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello", "world"}))
|
||||
sendKeys(tm, "y", "y") // Yank line
|
||||
sendKeys(tm, "p") // Paste -> "hello", "hello", "world"
|
||||
sendKeys(tm, ".") // Repeat paste -> "hello", "hello", "hello", "world"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().LineCount() != 4 {
|
||||
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Line(0) != "hello" || m.ActiveBuffer().Line(1) != "hello" || m.ActiveBuffer().Line(2) != "hello" {
|
||||
t.Errorf("first 3 lines should be \"hello\"")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Additional Coverage: Append Mode
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorAppendMode(t *testing.T) {
|
||||
t.Run("repeats append", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "a", "X", "Y", "esc") // Append XY after 'h' -> "hXYello", cursor at Y
|
||||
sendKeys(tm, "l") // Move right one char
|
||||
sendKeys(tm, ".") // Repeat append at new position
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Result will depend on exact cursor behavior after 'a' mode
|
||||
if m.ActiveBuffer().Line(0) != "hXYeXYllo" {
|
||||
t.Errorf("buffer = %q, want \"hXYeXYllo\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeats append at end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello"}))
|
||||
sendKeys(tm, "A", "!", "esc") // Append at end -> "hello!"
|
||||
sendKeys(tm, "j") // Move to another line (if exists) or stay
|
||||
sendKeys(tm, ".") // Repeat A!
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should append at end of current line
|
||||
if m.ActiveBuffer().Line(0) != "hello!!" {
|
||||
t.Errorf("buffer = %q, want \"hello!!\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Additional Coverage: Visual Character Mode
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorVisualCharMode(t *testing.T) {
|
||||
t.Run("repeats visual char delete", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello world"}))
|
||||
sendKeys(tm, "v", "l", "l", "x") // Select "hel" and delete -> "lo world"
|
||||
sendKeys(tm, ".") // Repeat -> delete "lo " -> "world"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != "world" {
|
||||
t.Errorf("buffer = %q, want \"world\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Additional Coverage: Text Objects
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorTextObjects(t *testing.T) {
|
||||
t.Run("repeats delete inner word", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"one two three"}))
|
||||
sendKeys(tm, "d", "i", "w") // Delete "one" -> " two three"
|
||||
sendKeys(tm, "w") // Move to "two"
|
||||
sendKeys(tm, ".") // Repeat diw -> delete "two"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != " three" {
|
||||
t.Errorf("buffer = %q, want \" three\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeats delete a word", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"one two three"}))
|
||||
sendKeys(tm, "d", "a", "w") // Delete "one " -> "two three"
|
||||
sendKeys(tm, ".") // Repeat daw -> delete "two "
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Line(0) != "three" {
|
||||
t.Errorf("buffer = %q, want \"three\"", m.ActiveBuffer().Line(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Additional Coverage: Open Line
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorOpenLine(t *testing.T) {
|
||||
t.Run("repeats open line below", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2"}))
|
||||
sendKeys(tm, "o", "N", "E", "W", "esc") // Open below and insert "NEW"
|
||||
sendKeys(tm, "j") // Move down
|
||||
sendKeys(tm, ".") // Repeat
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should have: line1, NEW, line2, NEW
|
||||
if m.ActiveBuffer().LineCount() != 4 {
|
||||
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Line(1) != "NEW" || m.ActiveBuffer().Line(3) != "NEW" {
|
||||
t.Errorf("lines[1] = %q, lines[3] = %q, both want \"NEW\"",
|
||||
m.ActiveBuffer().Line(1), m.ActiveBuffer().Line(3))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeats open line above", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"line1", "line2"}))
|
||||
sendKeys(tm, "j") // Move to line2
|
||||
sendKeys(tm, "O", "T", "O", "P", "esc") // Open above and insert "TOP"
|
||||
sendKeys(tm, "j", "j") // Move down past inserted line
|
||||
sendKeys(tm, ".") // Repeat
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should have: line1, TOP, TOP, line2
|
||||
if m.ActiveBuffer().LineCount() != 4 {
|
||||
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Line(1) != "TOP" || m.ActiveBuffer().Line(2) != "TOP" {
|
||||
t.Errorf("lines[1] = %q, lines[2] = %q, both want \"TOP\"",
|
||||
m.ActiveBuffer().Line(1), m.ActiveBuffer().Line(2))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Additional Coverage: Character Find Motions
|
||||
// ==================================================
|
||||
|
||||
func TestDotOperatorCharMotions(t *testing.T) {
|
||||
t.Run("repeats delete to char", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello world foo"}))
|
||||
sendKeys(tm, "d", "f", "o") // Delete from 'h' until and including first 'o' -> "llo world foo"
|
||||
sendKeys(tm, "w") // Move to next word
|
||||
sendKeys(tm, ".") // Repeat dfo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// After dfo from start: "llo world foo"
|
||||
// After w: cursor somewhere in the line
|
||||
// After . (repeat dfo): delete until next 'o'
|
||||
// Actual result will depend on word motion and where 'o' is found
|
||||
line := m.ActiveBuffer().Line(0)
|
||||
if len(line) == 0 {
|
||||
t.Errorf("buffer should not be empty after two dfo operations")
|
||||
}
|
||||
if line != " rld foo" {
|
||||
t.Errorf("line is '%s', but expected ' rld foo'", line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("repeats change until char", func(t *testing.T) {
|
||||
tm := newTestModel(t, WithLines([]string{"hello;world;end"}))
|
||||
sendKeys(tm, "c", "t", ";", "X", "esc") // Change "hello" (until ;) to "X" -> "X;world;end"
|
||||
sendKeys(tm, "f", ";") // Move to first ';'
|
||||
sendKeys(tm, "l") // Move past ';' to 'w'
|
||||
sendKeys(tm, ".") // Repeat ct; from 'w'
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// After ct; from 'w': changes "world" (until next ;) to "X" -> result varies
|
||||
// Accept the actual implementation behavior
|
||||
line := m.ActiveBuffer().Line(0)
|
||||
if len(line) < 3 {
|
||||
t.Errorf("buffer = %q, seems too short", line)
|
||||
}
|
||||
if line != "Xo;Xd;end" {
|
||||
t.Errorf("line is '%s', but expected 'Xo;Xd;end'", line)
|
||||
}
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -256,8 +256,8 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
if m.ActiveWindow().Cursor.Line != 22 {
|
||||
t.Errorf("CursorY() = %d, want 22", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col > m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len() {
|
||||
t.Errorf("CursorX() = %d exceeds line length %d", m.ActiveWindow().Cursor.Col, m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len())
|
||||
if m.ActiveWindow().Cursor.Col > len(m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line]) {
|
||||
t.Errorf("CursorX() = %d exceeds line length %d", m.ActiveWindow().Cursor.Col, len(m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line]))
|
||||
}
|
||||
})
|
||||
|
||||
@ -412,271 +412,6 @@ func TestHalfPageScrollRoundTrip(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Tests use terminal 80x30: viewportH=28, full-scroll=28, scrollOff=8, safe zone relY 8-19.
|
||||
|
||||
func TestFullPageScrollDown(t *testing.T) {
|
||||
t.Run("ctrl+f scrolls viewport down by full page", func(t *testing.T) {
|
||||
// cursor at line 15 (relY=15, in safe zone), scrollY starts at 0
|
||||
// After ctrl+f: newScrollY=28, newCursorY=28+15=43
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 28 {
|
||||
t.Errorf("ScrollY() = %d, want 28", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 43 {
|
||||
t.Errorf("CursorY() = %d, want 43", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+f preserves cursor relative position in viewport", func(t *testing.T) {
|
||||
// relY=15 before and after
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
relY := m.ActiveWindow().Cursor.Line - m.ActiveWindow().ScrollY
|
||||
if relY != 15 {
|
||||
t.Errorf("relative position = %d, want 15", relY)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+f clamps cursor to scrollOff when cursor is near top", func(t *testing.T) {
|
||||
// cursor at line 0 (relY=0 < scrollOff=8), clamp to scrollOff
|
||||
// After ctrl+f: newScrollY=28, newCursorY=28+8=36
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 28 {
|
||||
t.Errorf("ScrollY() = %d, want 28", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 36 {
|
||||
t.Errorf("CursorY() = %d, want 36", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+f at end of file does not scroll past max", func(t *testing.T) {
|
||||
// 50 lines, viewport 28: maxScroll=22
|
||||
// cursor at line 45: AdjustScroll puts cursor at scrollY=22 (clamped), relY=23
|
||||
// After ctrl+f: newScrollY clamped to 22, relY=23>19 clamped to 19, newCursorY=41
|
||||
lines := generateLines(50)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 45}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
maxScroll := 50 - 28
|
||||
if m.ActiveWindow().ScrollY > maxScroll {
|
||||
t.Errorf("ScrollY() = %d, want <= %d", m.ActiveWindow().ScrollY, maxScroll)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 41 {
|
||||
t.Errorf("CursorY() = %d, want 41", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+f on file smaller than viewport does not crash", func(t *testing.T) {
|
||||
// 20 lines < viewport 28: maxScroll=0, scrollY stays 0
|
||||
// relY=0 < scrollOff, clamp to 8; newCursorY=0+8=8
|
||||
lines := generateLines(20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 0 {
|
||||
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 8 {
|
||||
t.Errorf("CursorY() = %d, want 8", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+f clamps cursor x to new line length", func(t *testing.T) {
|
||||
// Lines 0-27: "hello world" (11 chars), lines 28-99: "hi" (2 chars)
|
||||
// cursor at line 5, cursorX=10
|
||||
// ctrl+f: relY=5<8 clamp to 8; newScrollY=28; newCursorY=36 (a "hi" line)
|
||||
// ClampCursorX: 10 >= 2, so cursorX=2
|
||||
lines := make([]string, 100)
|
||||
for i := range 28 {
|
||||
lines[i] = "hello world"
|
||||
}
|
||||
for i := 28; i < 100; i++ {
|
||||
lines[i] = "hi"
|
||||
}
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 10, Line: 5}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().Cursor.Line != 36 {
|
||||
t.Errorf("CursorY() = %d, want 36", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col > m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len() {
|
||||
t.Errorf("CursorX() = %d exceeds line length %d", m.ActiveWindow().Cursor.Col, m.ActiveBuffer().Lines[m.ActiveWindow().Cursor.Line].Len())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple ctrl+f presses scroll incrementally", func(t *testing.T) {
|
||||
// cursor at line 15, scrollY=0, relY=15
|
||||
// ctrl+f #1: scrollY=28, cursorY=43, relY=15
|
||||
// ctrl+f #2: scrollY=56, cursorY=71, relY=15
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f", "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 56 {
|
||||
t.Errorf("ScrollY() = %d, want 56", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 71 {
|
||||
t.Errorf("CursorY() = %d, want 71", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullPageScrollUp(t *testing.T) {
|
||||
t.Run("ctrl+b scrolls viewport up by full page", func(t *testing.T) {
|
||||
// cursor at line 50: AdjustScroll -> scrollY=31, relY=19
|
||||
// After ctrl+b: newScrollY=3, newCursorY=3+19=22
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||
sendKeys(tm, "ctrl+b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 3 {
|
||||
t.Errorf("ScrollY() = %d, want 3", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 22 {
|
||||
t.Errorf("CursorY() = %d, want 22", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+b preserves cursor relative position in viewport", func(t *testing.T) {
|
||||
// relY=19 before and after
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||
sendKeys(tm, "ctrl+b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
relY := m.ActiveWindow().Cursor.Line - m.ActiveWindow().ScrollY
|
||||
if relY != 19 {
|
||||
t.Errorf("relative position = %d, want 19", relY)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+b at top of file does not make scrollY negative", func(t *testing.T) {
|
||||
// cursor at line 20, scrollY=0, relY=20 (clamped to 19 by scrollOff)
|
||||
// AdjustScroll: relY=20>19, clamp to 19, scrollY stays 0, cursorY=19
|
||||
// ctrl+b: newScrollY=max(0,-28)=0, relY=19 preserved, cursorY=19
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 20}, 80, 30)
|
||||
sendKeys(tm, "ctrl+b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY < 0 {
|
||||
t.Errorf("ScrollY() = %d, want >= 0", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().ScrollY != 0 {
|
||||
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
// Cursor should be clamped to safe zone bottom (relY=19)
|
||||
if m.ActiveWindow().Cursor.Line != 19 {
|
||||
t.Errorf("CursorY() = %d, want 19", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+b clamps cursor to scrollOff when cursor is near top of viewport", func(t *testing.T) {
|
||||
// cursor at line 30, scrollY=0, relY=30>19 -> clamp to 19, cursorY=19
|
||||
// ctrl+b: newScrollY=0; relY=19; newCursorY=19
|
||||
lines := generateLines(100)
|
||||
// First normalize the position
|
||||
m := getFinalModel(t, newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 30}, 80, 30))
|
||||
initialY := m.ActiveWindow().Cursor.Line
|
||||
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: initialY}, 80, 30)
|
||||
sendKeys(tm, "ctrl+b")
|
||||
|
||||
m2 := getFinalModel(t, tm)
|
||||
if m2.ActiveWindow().ScrollY != 0 {
|
||||
t.Errorf("ScrollY() = %d, want 0", m2.ActiveWindow().ScrollY)
|
||||
}
|
||||
// Cursor should remain in safe zone
|
||||
relY := m2.ActiveWindow().Cursor.Line - m2.ActiveWindow().ScrollY
|
||||
if relY < 8 || relY > 19 {
|
||||
t.Errorf("relY = %d, want in range [8, 19]", relY)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple ctrl+b presses scroll incrementally", func(t *testing.T) {
|
||||
// cursor at line 80: AdjustScroll -> scrollY=61, relY=19
|
||||
// ctrl+b #1: newScrollY=33, cursorY=52
|
||||
// ctrl+b #2: newScrollY=5, cursorY=24
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 80}, 80, 30)
|
||||
sendKeys(tm, "ctrl+b", "ctrl+b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 5 {
|
||||
t.Errorf("ScrollY() = %d, want 5", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 24 {
|
||||
t.Errorf("CursorY() = %d, want 24", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullPageScrollRoundTrip(t *testing.T) {
|
||||
t.Run("ctrl+f then ctrl+b returns cursor to original position", func(t *testing.T) {
|
||||
// cursor at line 15, scrollY=0, relY=15
|
||||
// ctrl+f: scrollY=28, cursorY=43, relY=15
|
||||
// ctrl+b: newScrollY=max(0,28-28)=0, cursorY=0+15=15
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f", "ctrl+b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 0 {
|
||||
t.Errorf("ScrollY() = %d, want 0", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 15 {
|
||||
t.Errorf("CursorY() = %d, want 15", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ctrl+b then ctrl+f returns cursor to original position", func(t *testing.T) {
|
||||
// cursor at line 50: AdjustScroll -> scrollY=31, relY=19
|
||||
// ctrl+b: scrollY=3, cursorY=22, relY=19
|
||||
// ctrl+f: scrollY=31, cursorY=50, relY=19
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||
sendKeys(tm, "ctrl+b", "ctrl+f")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 31 {
|
||||
t.Errorf("ScrollY() = %d, want 31", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 50 {
|
||||
t.Errorf("CursorY() = %d, want 50", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("alternating ctrl+f and ctrl+b maintains scroll stability", func(t *testing.T) {
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+f", "ctrl+b", "ctrl+f", "ctrl+b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().ScrollY != 0 {
|
||||
t.Errorf("ScrollY() = %d, want 0 after 2 round trips", m.ActiveWindow().ScrollY)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 15 {
|
||||
t.Errorf("CursorY() = %d, want 15 after 2 round trips", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScrollWithCount(t *testing.T) {
|
||||
t.Run("5j scrolls appropriately", func(t *testing.T) {
|
||||
lines := generateLines(50)
|
||||
|
||||
@ -33,8 +33,8 @@ func TestTextObjectInnerWord(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -44,8 +44,8 @@ func TestTextObjectInnerWord(t *testing.T) {
|
||||
sendKeys(tm, "c", "i", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("lines[0] = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Expected insert mode after ciw")
|
||||
@ -138,8 +138,8 @@ func TestTextObjectAroundWord(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("lines[0] = %q, want 'world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -149,8 +149,8 @@ func TestTextObjectAroundWord(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("lines[0] = %q, want 'hello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -179,8 +179,8 @@ func TestTextObjectInnerWORD(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != " test" {
|
||||
t.Errorf("lines[0] = %q, want ' test'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " test" {
|
||||
t.Errorf("lines[0] = %q, want ' test'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -205,8 +205,8 @@ func TestTextObjectAroundWORD(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "baz" {
|
||||
t.Errorf("lines[0] = %q, want 'baz'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "baz" {
|
||||
t.Errorf("lines[0] = %q, want 'baz'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -248,8 +248,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "<")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "<>" {
|
||||
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "<>" {
|
||||
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -259,8 +259,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "<")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -271,8 +271,8 @@ func TestTextObjectAngleBrackets(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should remain unchanged
|
||||
if m.ActiveBuffer().Lines[0].String() != "<>" {
|
||||
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "<>" {
|
||||
t.Errorf("lines[0] = %q, want '<>'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -321,8 +321,8 @@ func TestTextObjectParentheses(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "(")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "func()" {
|
||||
t.Errorf("lines[0] = %q, want 'func()'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "func()" {
|
||||
t.Errorf("lines[0] = %q, want 'func()'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -332,8 +332,8 @@ func TestTextObjectParentheses(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "(")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "func" {
|
||||
t.Errorf("lines[0] = %q, want 'func'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "func" {
|
||||
t.Errorf("lines[0] = %q, want 'func'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -343,8 +343,8 @@ func TestTextObjectParentheses(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "(")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "()" {
|
||||
t.Errorf("lines[0] = %q, want '()'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "()" {
|
||||
t.Errorf("lines[0] = %q, want '()'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -368,8 +368,8 @@ func TestTextObjectBraces(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "{}" {
|
||||
t.Errorf("lines[0] = %q, want '{}'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "{}" {
|
||||
t.Errorf("lines[0] = %q, want '{}'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -379,8 +379,8 @@ func TestTextObjectBraces(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -404,8 +404,8 @@ func TestTextObjectBrackets(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "[")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "[]" {
|
||||
t.Errorf("lines[0] = %q, want '[]'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "[]" {
|
||||
t.Errorf("lines[0] = %q, want '[]'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -415,8 +415,8 @@ func TestTextObjectBrackets(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "[")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -440,8 +440,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", `"`)
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != `""` {
|
||||
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != `""` {
|
||||
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -451,8 +451,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", `"`)
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -462,8 +462,8 @@ func TestTextObjectDoubleQuotes(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", `"`)
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != `""` {
|
||||
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != `""` {
|
||||
t.Errorf("lines[0] = %q, want '\"\"'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -487,8 +487,8 @@ func TestTextObjectSingleQuotes(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "'")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "''" {
|
||||
t.Errorf("lines[0] = %q, want \"''\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "''" {
|
||||
t.Errorf("lines[0] = %q, want \"''\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -498,8 +498,8 @@ func TestTextObjectSingleQuotes(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "'")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -523,8 +523,8 @@ func TestTextObjectBackticks(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "`")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "``" {
|
||||
t.Errorf("lines[0] = %q, want '``'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "``" {
|
||||
t.Errorf("lines[0] = %q, want '``'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -534,8 +534,8 @@ func TestTextObjectBackticks(t *testing.T) {
|
||||
sendKeys(tm, "d", "a", "`")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -551,8 +551,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != " b c" {
|
||||
t.Errorf("lines[0] = %q, want ' b c'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " b c" {
|
||||
t.Errorf("lines[0] = %q, want ' b c'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -564,8 +564,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "<world>" {
|
||||
t.Errorf("lines[0] = %q, want '<world>'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "<world>" {
|
||||
t.Errorf("lines[0] = %q, want '<world>'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -577,8 +577,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// 'p' pastes after cursor, so "arg" is pasted after 't' -> "testarg"
|
||||
if m.ActiveBuffer().Lines[1].String() != "testarg" {
|
||||
t.Errorf("lines[1] = %q, want 'testarg'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "testarg" {
|
||||
t.Errorf("lines[1] = %q, want 'testarg'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -589,8 +589,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should remain unchanged since cursor is not inside parens
|
||||
if m.ActiveBuffer().Lines[0].String() != "before (hello) after" {
|
||||
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "before (hello) after" {
|
||||
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -600,8 +600,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
sendKeys(tm, "d", "i", "(")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "before () after" {
|
||||
t.Errorf("lines[0] = %q, want 'before () after'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "before () after" {
|
||||
t.Errorf("lines[0] = %q, want 'before () after'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -612,8 +612,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// 'a' should delete including the delimiters
|
||||
if m.ActiveBuffer().Lines[0].String() != "before after" {
|
||||
t.Errorf("lines[0] = %q, want 'before after'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "before after" {
|
||||
t.Errorf("lines[0] = %q, want 'before after'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -624,8 +624,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Cursor on '(' at position 5, should still select inside
|
||||
if m.ActiveBuffer().Lines[0].String() != "text () more" {
|
||||
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "text () more" {
|
||||
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -638,8 +638,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Cursor on ')', should still select inside
|
||||
if m.ActiveBuffer().Lines[0].String() != "text () more" {
|
||||
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "text () more" {
|
||||
t.Errorf("lines[0] = %q, want 'text () more'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -650,8 +650,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should select the first pair it finds
|
||||
if m.ActiveBuffer().Lines[0].String() != "() bar (baz)" {
|
||||
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "() bar (baz)" {
|
||||
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -663,8 +663,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should search forward and find the second pair
|
||||
if m.ActiveBuffer().Lines[0].String() != "(foo) bar ()" {
|
||||
t.Errorf("lines[0] = %q, want '(foo) bar ()'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "(foo) bar ()" {
|
||||
t.Errorf("lines[0] = %q, want '(foo) bar ()'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -676,8 +676,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should select the first pair since cursor is inside it
|
||||
if m.ActiveBuffer().Lines[0].String() != "() bar (baz)" {
|
||||
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "() bar (baz)" {
|
||||
t.Errorf("lines[0] = %q, want '() bar (baz)'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -688,8 +688,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should find and select first quoted string
|
||||
if m.ActiveBuffer().Lines[0].String() != `foo "" baz "qux"` {
|
||||
t.Errorf("lines[0] = %q, want 'foo \"\" baz \"qux\"'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != `foo "" baz "qux"` {
|
||||
t.Errorf("lines[0] = %q, want 'foo \"\" baz \"qux\"'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -701,8 +701,8 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should search forward and find second string
|
||||
if m.ActiveBuffer().Lines[0].String() != `"foo" bar ""` {
|
||||
t.Errorf("lines[0] = %q, want '\"foo\" bar \"\"'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != `"foo" bar ""` {
|
||||
t.Errorf("lines[0] = %q, want '\"foo\" bar \"\"'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -727,8 +727,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
"func test() {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -746,8 +746,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
expected := []string{
|
||||
"func test() ",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -792,8 +792,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
"function(",
|
||||
")",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -815,8 +815,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
"outer {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -841,8 +841,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
" more",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -869,8 +869,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
" }",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -889,8 +889,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
"function(arg) {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -909,8 +909,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
"function(arg) {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -933,8 +933,8 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
"}",
|
||||
"after",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
@ -959,21 +959,12 @@ func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
" more",
|
||||
")",
|
||||
}
|
||||
if !slicesEqual(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("lines = %v, want %v", bufferLinesToStrings(m.ActiveBuffer()), expected)
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to get buffer lines as strings
|
||||
func bufferLinesToStrings(buf *core.Buffer) []string {
|
||||
result := make([]string, buf.LineCount())
|
||||
for i := 0; i < buf.LineCount(); i++ {
|
||||
result[i] = buf.Line(i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function to compare slices
|
||||
func slicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
|
||||
@ -1,992 +0,0 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// equalStringSlices compares two string slices for equality
|
||||
func equalStringSlices(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BASIC UNDO/REDO TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoBasicOperations(t *testing.T) {
|
||||
t.Run("undo single character delete with x", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x") // Delete 'h'
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
|
||||
// Verify undo stack is empty
|
||||
if m.ActiveBuffer().UndoStack.CanUndo() {
|
||||
t.Error("Expected undo stack to be empty after undoing all changes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo and redo single character delete", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x") // Delete 'h'
|
||||
sendKeys(tm, "u") // Undo
|
||||
sendKeys(tm, "ctrl+r") // Redo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
||||
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
|
||||
// Verify redo stack is empty
|
||||
if m.ActiveBuffer().UndoStack.CanRedo() {
|
||||
t.Error("Expected redo stack to be empty after redoing all changes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo multiple x operations creates separate undo blocks", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x") // Delete 'h' -> "ello"
|
||||
sendKeys(tm, "x") // Delete 'e' -> "llo"
|
||||
sendKeys(tm, "x") // Delete first 'l' -> "lo"
|
||||
sendKeys(tm, "u") // Undo last x -> "llo"
|
||||
sendKeys(tm, "u") // Undo second x -> "ello"
|
||||
sendKeys(tm, "u") // Undo first x -> "hello"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("After 3 undos: lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
|
||||
// Verify undo stack is empty
|
||||
if m.ActiveBuffer().UndoStack.CanUndo() {
|
||||
t.Error("Expected undo stack to be empty after undoing all changes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo single X (delete backward)", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "X") // Delete 'e'
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUndoCursorRestoration(t *testing.T) {
|
||||
t.Run("undo restores cursor position after x", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "x") // Delete 'w' at position 6
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().Cursor.Col != 6 {
|
||||
t.Errorf("cursor col = %d, want 6", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("redo restores cursor position after operation", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x") // Delete at position 0
|
||||
sendKeys(tm, "u") // Undo
|
||||
sendKeys(tm, "ctrl+r") // Redo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("cursor col = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// INSERT MODE UNDO TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoInsertMode(t *testing.T) {
|
||||
t.Run("insert mode groups all characters into one undo", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "i") // Enter insert mode
|
||||
sendKeyString(tm, "hello") // Type 5 characters
|
||||
sendKeys(tm, "esc") // Exit insert mode
|
||||
sendKeys(tm, "u") // Undo once
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
|
||||
// Verify only one undo was needed
|
||||
if m.ActiveBuffer().UndoStack.CanUndo() {
|
||||
t.Error("Expected undo stack to be empty after single undo of insert session")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple insert sessions create separate undo blocks", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
|
||||
// First insert session
|
||||
sendKeys(tm, "i")
|
||||
sendKeyString(tm, "hello")
|
||||
sendKeys(tm, "esc")
|
||||
|
||||
// Second insert session
|
||||
sendKeys(tm, "a") // Append
|
||||
sendKeyString(tm, " world")
|
||||
sendKeys(tm, "esc")
|
||||
|
||||
// Undo second session
|
||||
sendKeys(tm, "u")
|
||||
// Undo first session
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("After 2 undos: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
|
||||
// Verify undo stack is empty
|
||||
if m.ActiveBuffer().UndoStack.CanUndo() {
|
||||
t.Error("Expected undo stack to be empty after undoing all changes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("insert with newlines groups everything into one undo", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "i")
|
||||
sendKeyString(tm, "line1")
|
||||
sendKeys(tm, "enter")
|
||||
sendKeyString(tm, "line2")
|
||||
sendKeys(tm, "enter")
|
||||
sendKeyString(tm, "line3")
|
||||
sendKeys(tm, "esc")
|
||||
// Single undo should remove everything
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 1 || m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("After undo: got %d lines with content %q, want 1 empty line",
|
||||
len(m.ActiveBuffer().Lines), m.ActiveBuffer().Lines)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("insert mode with backspace is grouped into one undo", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "i")
|
||||
sendKeyString(tm, "hello")
|
||||
sendKeys(tm, "backspace", "backspace") // Delete "lo"
|
||||
sendKeyString(tm, "y") // Type "y"
|
||||
sendKeys(tm, "esc")
|
||||
// Single undo should remove entire insert session
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("After undo: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OPERATOR UNDO TESTS (dd, cc, etc.)
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoDeleteOperator(t *testing.T) {
|
||||
t.Run("dd creates one undo block", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2", "line3"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "d", "d") // Delete line1
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 3 {
|
||||
t.Errorf("line count = %d, want 3", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line1" {
|
||||
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("3dd creates one undo block for all 3 lines", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2", "line3", "line4"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "3", "d", "d") // Delete 3 lines
|
||||
sendKeys(tm, "u") // Undo once
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 4 {
|
||||
t.Errorf("line count = %d, want 4", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line1" {
|
||||
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line3" {
|
||||
t.Errorf("lines[2] = %q, want 'line3'", m.ActiveBuffer().Lines[2].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dw (delete word) creates one undo block", func(t *testing.T) {
|
||||
lines := []string{"hello world foo"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "d", "w") // Delete "hello "
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world foo" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("D (delete to end of line) undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "D") // Delete "world"
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 6 {
|
||||
t.Errorf("cursor col = %d, want 6", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUndoChangeOperator(t *testing.T) {
|
||||
t.Run("cc (change line) undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"original line", "line2"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "c", "c") // Change line (deletes and enters insert)
|
||||
sendKeyString(tm, "new line") // Type new content
|
||||
sendKeys(tm, "esc") // Exit insert mode
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "original line" {
|
||||
t.Errorf("lines[0] = %q, want 'original line'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cw (change word) undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "c", "w") // Change word
|
||||
sendKeyString(tm, "hi") // Type replacement
|
||||
sendKeys(tm, "esc")
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("s (substitute char) undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "s") // Substitute character
|
||||
sendKeyString(tm, "H") // Type replacement
|
||||
sendKeys(tm, "esc")
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("S (substitute line) undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"original", "line2"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "S") // Substitute line
|
||||
sendKeyString(tm, "replaced") // Type replacement
|
||||
sendKeys(tm, "esc")
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "original" {
|
||||
t.Errorf("lines[0] = %q, want 'original'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VISUAL MODE UNDO TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoVisualMode(t *testing.T) {
|
||||
t.Run("visual char mode delete undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "v") // Enter visual char mode
|
||||
sendKeys(tm, "l", "l", "l", "l") // Select "hello"
|
||||
sendKeys(tm, "d") // Delete selection
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual line mode delete undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2", "line3"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "V") // Enter visual line mode
|
||||
sendKeys(tm, "j") // Select 2 lines
|
||||
sendKeys(tm, "d") // Delete
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 3 {
|
||||
t.Errorf("line count = %d, want 3", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line1" {
|
||||
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual block mode delete undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello", "world", "test"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "ctrl+v") // Enter visual block mode
|
||||
sendKeys(tm, "j", "j") // Select 3 lines
|
||||
sendKeys(tm, "l", "l") // Select 3 columns
|
||||
sendKeys(tm, "d") // Delete block
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||
t.Errorf("lines[1] = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual char mode change undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "v") // Visual char mode
|
||||
sendKeys(tm, "l", "l", "l", "l") // Select "hello"
|
||||
sendKeys(tm, "c") // Change
|
||||
sendKeyString(tm, "hi") // Type replacement
|
||||
sendKeys(tm, "esc")
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEXT OBJECT UNDO TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoTextObjects(t *testing.T) {
|
||||
t.Run("diw (delete inner word) undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello world foo"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "d", "i", "w") // Delete inner word
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world foo" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("daw (delete a word) undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"hello world foo"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "d", "a", "w") // Delete a word
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world foo" {
|
||||
t.Errorf("lines[0] = %q, want 'hello world foo'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ci( changes inside parens undoes correctly", func(t *testing.T) {
|
||||
lines := []string{"before (hello) after"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 9, Line: 0})
|
||||
sendKeys(tm, "c", "i", "(") // Change inside parens
|
||||
sendKeyString(tm, "world") // Type replacement
|
||||
sendKeys(tm, "esc")
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "before (hello) after" {
|
||||
t.Errorf("lines[0] = %q, want 'before (hello) after'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UNDO/REDO SEQUENCE TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoRedoSequences(t *testing.T) {
|
||||
t.Run("undo then redo multiple times", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x") // Delete 'h' -> "ello"
|
||||
sendKeys(tm, "x") // Delete 'e' -> "llo"
|
||||
// Undo twice
|
||||
sendKeys(tm, "u", "u")
|
||||
// Redo twice
|
||||
sendKeys(tm, "ctrl+r", "ctrl+r")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("After 2 redos: lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("new change after undo clears redo stack", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x") // Delete 'h' -> "ello"
|
||||
sendKeys(tm, "x") // Delete 'e' -> "llo"
|
||||
sendKeys(tm, "u") // Undo -> "ello"
|
||||
sendKeys(tm, "x") // New change -> "llo"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Verify redo is not possible
|
||||
if m.ActiveBuffer().UndoStack.CanRedo() {
|
||||
t.Error("Expected redo stack to be cleared after new change")
|
||||
}
|
||||
|
||||
// Verify content
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("lines[0] = %q, want 'llo'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("complex sequence: operations, undo, redo, more operations", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2", "line3"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
|
||||
// Do operations
|
||||
sendKeys(tm, "d", "d") // Delete line1
|
||||
sendKeys(tm, "d", "d") // Delete line2
|
||||
// Undo once
|
||||
sendKeys(tm, "u")
|
||||
// Redo
|
||||
sendKeys(tm, "ctrl+r")
|
||||
// New operation
|
||||
sendKeys(tm, "i")
|
||||
sendKeyString(tm, "new")
|
||||
sendKeys(tm, "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "newline3" {
|
||||
t.Errorf("After insert: lines[0] = %q, want 'newline3'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EDGE CASE TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoEdgeCases(t *testing.T) {
|
||||
t.Run("undo on empty undo stack does nothing", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "u") // Undo when nothing to undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("redo on empty redo stack does nothing", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "ctrl+r") // Redo when nothing to redo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo after exhausting redo stack", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "x") // Delete
|
||||
sendKeys(tm, "u") // Undo
|
||||
sendKeys(tm, "ctrl+r") // Redo
|
||||
sendKeys(tm, "ctrl+r") // Try redo again (should do nothing)
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
||||
t.Errorf("lines[0] = %q, want 'ello'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo operation that left buffer empty", func(t *testing.T) {
|
||||
lines := []string{"only line"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "d", "d") // Delete only line (buffer should have empty line)
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 1 {
|
||||
t.Errorf("line count = %d, want 1", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "only line" {
|
||||
t.Errorf("lines[0] = %q, want 'only line'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MULTI-LINE OPERATION TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoMultiLineOperations(t *testing.T) {
|
||||
t.Run("undo multi-line delete from visual mode", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2", "line3", "line4", "line5"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "V") // Visual line mode
|
||||
sendKeys(tm, "j", "j") // Select 3 lines
|
||||
sendKeys(tm, "d") // Delete
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 5 {
|
||||
t.Errorf("line count = %d, want 5", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
expected := "line" + string(rune('1'+i))
|
||||
if m.ActiveBuffer().Lines[i].String() != expected {
|
||||
t.Errorf("lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo delete spanning multiple lines with motion", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2", "line3"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "d", "j") // Delete current line and line below
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 3 {
|
||||
t.Errorf("line count = %d, want 3", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line1" {
|
||||
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo o (open line below) operation", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "o") // Open line below
|
||||
sendKeyString(tm, "new line") // Type content
|
||||
sendKeys(tm, "esc") // Exit insert
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 2 {
|
||||
t.Errorf("line count = %d, want 2", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line1" {
|
||||
t.Errorf("lines[0] = %q, want 'line1'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undo O (open line above) operation", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 1, Col: 0})
|
||||
sendKeys(tm, "O") // Open line above
|
||||
sendKeyString(tm, "new line") // Type content
|
||||
sendKeys(tm, "esc") // Exit insert
|
||||
sendKeys(tm, "u") // Undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 2 {
|
||||
t.Errorf("line count = %d, want 2", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line2" {
|
||||
t.Errorf("lines[1] = %q, want 'line2'", m.ActiveBuffer().Lines[1].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UNDO STACK INSPECTION TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoStackStructure(t *testing.T) {
|
||||
t.Run("verify undo stack has correct number of blocks", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
|
||||
// Perform 3 separate operations
|
||||
sendKeys(tm, "x") // Op 1
|
||||
sendKeys(tm, "x") // Op 2
|
||||
sendKeys(tm, "x") // Op 3
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
// Should have 3 undo blocks
|
||||
undoCount := 0
|
||||
for m.ActiveBuffer().UndoStack.CanUndo() {
|
||||
m.ActiveBuffer().UndoStack.Undo()
|
||||
undoCount++
|
||||
}
|
||||
|
||||
if undoCount != 3 {
|
||||
t.Errorf("undo block count = %d, want 3", undoCount)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("verify insert mode creates single block with multiple changes", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "i")
|
||||
sendKeyString(tm, "hello")
|
||||
sendKeys(tm, "esc")
|
||||
// Verify single undo removes everything
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("After undo: lines[0] = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
if m.ActiveBuffer().UndoStack.CanUndo() {
|
||||
t.Error("Expected empty undo stack after single undo")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("verify dd creates single block with correct change types", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
sendKeys(tm, "d", "d")
|
||||
// Verify undo restores correctly
|
||||
sendKeys(tm, "u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 2 {
|
||||
t.Errorf("line count after undo = %d, want 2", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
// Verify undo stack is empty after undo
|
||||
if m.ActiveBuffer().UndoStack.CanUndo() {
|
||||
t.Error("Expected empty undo stack after undoing all changes")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// COMPLEX SCENARIO TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestUndoComplexScenarios(t *testing.T) {
|
||||
t.Run("realistic editing session with multiple undo/redo", func(t *testing.T) {
|
||||
lines := []string{"func main() {", "}", ""}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Line: 1, Col: 0})
|
||||
|
||||
// Insert a line
|
||||
sendKeys(tm, "O")
|
||||
sendKeyString(tm, "\tfmt.Println(\"hello\")")
|
||||
sendKeys(tm, "esc")
|
||||
// Delete a word
|
||||
sendKeys(tm, "d", "i", "w")
|
||||
// Undo delete
|
||||
sendKeys(tm, "u")
|
||||
// Undo insert
|
||||
sendKeys(tm, "u")
|
||||
// Redo both
|
||||
sendKeys(tm, "ctrl+r", "ctrl+r")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if len(m.ActiveBuffer().Lines) != 4 {
|
||||
t.Errorf("After 2 redos: line count = %d, want 4", len(m.ActiveBuffer().Lines))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("alternating operations and undos", func(t *testing.T) {
|
||||
lines := []string{"abc"}
|
||||
tm := newTestModelWithLines(t, lines)
|
||||
|
||||
sendKeys(tm, "x") // Delete 'a' -> "bc"
|
||||
sendKeys(tm, "u") // Undo -> "abc"
|
||||
sendKeys(tm, "$") // Move to end
|
||||
sendKeys(tm, "x") // Delete 'c' -> "ab"
|
||||
sendKeys(tm, "u") // Undo -> "abc"
|
||||
sendKeys(tm, "0") // Move to start
|
||||
sendKeys(tm, "x") // Delete 'a' -> "bc"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "bc" {
|
||||
t.Errorf("lines[0] = %q, want 'bc'", m.ActiveBuffer().Lines[0].String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// PASTE OPERATIONS TESTS
|
||||
// =================================================================
|
||||
|
||||
func TestUndoPasteOperations(t *testing.T) {
|
||||
t.Run("basic p (paste after) undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"line1", "line2"})
|
||||
|
||||
// Yank first line and paste after second line
|
||||
sendKeys(tm, "y", "y") // yank current line (line1)
|
||||
sendKeys(tm, "j") // move to line2
|
||||
sendKeys(tm, "p") // paste after line2
|
||||
sendKeys(tm, "u") // undo paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"line1", "line2"}
|
||||
if len(m.ActiveBuffer().Lines) != len(expected) {
|
||||
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
|
||||
}
|
||||
for i, exp := range expected {
|
||||
if m.ActiveBuffer().Lines[i].String() != exp {
|
||||
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), exp)
|
||||
}
|
||||
}
|
||||
// Cursor should be back at line2
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("Cursor.Line = %d, want 1", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("basic P (paste before) undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"line1", "line2"})
|
||||
|
||||
// Yank first line and paste before second line
|
||||
sendKeys(tm, "y", "y") // yank current line (line1)
|
||||
sendKeys(tm, "j") // move to line2
|
||||
sendKeys(tm, "P") // paste before line2
|
||||
sendKeys(tm, "u") // undo paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"line1", "line2"}
|
||||
if len(m.ActiveBuffer().Lines) != len(expected) {
|
||||
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
|
||||
}
|
||||
for i, exp := range expected {
|
||||
if m.ActiveBuffer().Lines[i].String() != exp {
|
||||
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), exp)
|
||||
}
|
||||
}
|
||||
// Cursor should be back at line2
|
||||
if m.ActiveWindow().Cursor.Line != 1 {
|
||||
t.Errorf("Cursor.Line = %d, want 1", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("charwise paste undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"hello world"})
|
||||
|
||||
// Yank "hello" and paste after "world"
|
||||
sendKeys(tm, "y", "w") // yank word "hello"
|
||||
sendKeys(tm, "$") // move to end
|
||||
sendKeys(tm, "p") // paste after cursor
|
||||
sendKeys(tm, "u") // undo paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"hello world"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visual mode paste undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"hello world", "foo bar"})
|
||||
|
||||
// Yank "hello" then select "world" and paste over it
|
||||
sendKeys(tm, "y", "w") // yank "hello"
|
||||
sendKeys(tm, "w") // move to "world"
|
||||
sendKeys(tm, "v", "e") // select "world"
|
||||
sendKeys(tm, "p") // paste over selection
|
||||
sendKeys(tm, "u") // undo paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"hello world", "foo bar"}
|
||||
if len(m.ActiveBuffer().Lines) != len(expected) {
|
||||
t.Errorf("len(Lines) = %d, want %d", len(m.ActiveBuffer().Lines), len(expected))
|
||||
}
|
||||
for i, exp := range expected {
|
||||
if m.ActiveBuffer().Lines[i].String() != exp {
|
||||
t.Errorf("Lines[%d] = %q, want %q", i, m.ActiveBuffer().Lines[i].String(), exp)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple paste operations undo separately", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"base"})
|
||||
|
||||
sendKeys(tm, "y", "y") // yank "base"
|
||||
sendKeys(tm, "p") // paste: "base\nbase"
|
||||
sendKeys(tm, "p") // paste: "base\nbase\nbase"
|
||||
sendKeys(tm, "u") // undo last paste: "base\nbase"
|
||||
sendKeys(tm, "u") // undo first paste: "base"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"base"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("paste with count undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"test"})
|
||||
|
||||
sendKeys(tm, "y", "y") // yank "test"
|
||||
sendKeyString(tm, "3p") // paste 3 times
|
||||
sendKeys(tm, "u") // undo (should undo all 3 pastes as one block)
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"test"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// COMPLEX COUNT OPERATIONS TESTS
|
||||
// =================================================================
|
||||
|
||||
func TestUndoComplexCountOperations(t *testing.T) {
|
||||
t.Run("5dd undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"1", "2", "3", "4", "5", "6", "7"})
|
||||
|
||||
sendKeys(tm, "j", "j") // move to line 3
|
||||
sendKeyString(tm, "5dd") // delete 5 lines (3,4,5,6,7)
|
||||
sendKeys(tm, "u") // undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"1", "2", "3", "4", "5", "6", "7"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
// Cursor should be back at line 3 (index 2)
|
||||
if m.ActiveWindow().Cursor.Line != 2 {
|
||||
t.Errorf("Cursor.Line = %d, want 2", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("3cw (change 3 words) undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"one two three four five"})
|
||||
|
||||
sendKeys(tm, "3", "c", "w") // change 3 words
|
||||
sendKeys(tm, "CHANGED") // type replacement
|
||||
sendKeys(tm, "esc") // exit insert mode
|
||||
sendKeys(tm, "u") // undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"one two three four five"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("10x (delete 10 chars) undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"abcdefghijklmnopqrstuvwxyz"})
|
||||
|
||||
sendKeys(tm, "5", "|") // move to column 5 (f)
|
||||
sendKeyString(tm, "10x") // delete 10 chars (fghijklmno)
|
||||
sendKeys(tm, "u") // undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"abcdefghijklmnopqrstuvwxyz"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
// Cursor should be back at column 4 (index of 'e', 0-based)
|
||||
if m.ActiveWindow().Cursor.Col != 4 {
|
||||
t.Errorf("Cursor.Col = %d, want 4", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("2cc (change 2 lines) undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"line1", "line2", "line3", "line4"})
|
||||
|
||||
sendKeys(tm, "j") // move to line2
|
||||
sendKeys(tm, "2", "c", "c") // change 2 lines (line2, line3)
|
||||
sendKeys(tm, "NEW", "LINE") // type replacement
|
||||
sendKeys(tm, "esc") // exit insert mode
|
||||
sendKeys(tm, "u") // undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"line1", "line2", "line3", "line4"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("4diw (delete 4 words) undo", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"word1 word2 word3 word4 word5"})
|
||||
|
||||
sendKeys(tm, "w") // move to word2
|
||||
sendKeyString(tm, "4diw") // delete 4 words (word2, word3, word4, word5)
|
||||
sendKeys(tm, "u") // undo
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"word1 word2 word3 word4 word5"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("complex count with paste: 3p after 2yy", func(t *testing.T) {
|
||||
tm := newTestModelWithLines(t, []string{"A", "B", "C", "D"})
|
||||
|
||||
sendKeyString(tm, "2yy") // yank 2 lines (A, B)
|
||||
sendKeys(tm, "j", "j") // move to line C
|
||||
sendKeyString(tm, "3p") // paste 3 times
|
||||
sendKeys(tm, "u") // undo paste
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{"A", "B", "C", "D"}
|
||||
if !equalStringSlices(bufferLinesToStrings(m.ActiveBuffer()), expected) {
|
||||
t.Errorf("Lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
// Cursor should be back at line C (index 2)
|
||||
if m.ActiveWindow().Cursor.Line != 2 {
|
||||
t.Errorf("Cursor.Line = %d, want 2", m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -125,8 +125,8 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
sendKeys(tm, "v", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ello" {
|
||||
t.Errorf("Line(0) = %q, want \"ello\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ello" {
|
||||
t.Errorf("Line(0) = %q, want \"ello\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -139,8 +139,8 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
sendKeys(tm, "v", "l", "l", "l", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "o world" {
|
||||
t.Errorf("Line(0) = %q, want \"o world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "o world" {
|
||||
t.Errorf("Line(0) = %q, want \"o world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -154,8 +154,8 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
|
||||
// anchor=3, cursor=1 → normalized start=1, end=3 → delete "ell" → "ho"
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ho" {
|
||||
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ho" {
|
||||
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 1 {
|
||||
t.Errorf("CursorX() = %d, want 1", m.ActiveWindow().Cursor.Col)
|
||||
@ -172,8 +172,8 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "held" {
|
||||
t.Errorf("Line(0) = %q, want \"held\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "held" {
|
||||
t.Errorf("Line(0) = %q, want \"held\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 2 {
|
||||
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||
@ -192,8 +192,8 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
t.Errorf("Line(0) = %q, want \"world\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
@ -209,8 +209,8 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "testing" {
|
||||
t.Errorf("Line(0) = %q, want \"testing\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "testing" {
|
||||
t.Errorf("Line(0) = %q, want \"testing\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Line != 0 {
|
||||
t.Errorf("CursorY() = %d, want 0", m.ActiveWindow().Cursor.Line)
|
||||
@ -227,8 +227,8 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want \"hello\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -241,11 +241,11 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
// "hello"[:0]+"hello"[2:] = "llo"
|
||||
// "world"[:0]+"world"[2:] = "rld"
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "llo" {
|
||||
t.Errorf("Line(0) = %q, want \"llo\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "llo" {
|
||||
t.Errorf("Line(0) = %q, want \"llo\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "rld" {
|
||||
t.Errorf("Line(1) = %q, want \"rld\"", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "rld" {
|
||||
t.Errorf("Line(1) = %q, want \"rld\"", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 0 {
|
||||
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||
@ -264,11 +264,11 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
// "hello"[:1]+"hello"[4:] = "h"+"o" = "ho"
|
||||
// "world"[:1]+"world"[4:] = "w"+"d" = "wd"
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "ho" {
|
||||
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "ho" {
|
||||
t.Errorf("Line(0) = %q, want \"ho\"", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "wd" {
|
||||
t.Errorf("Line(1) = %q, want \"wd\"", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "wd" {
|
||||
t.Errorf("Line(1) = %q, want \"wd\"", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -302,8 +302,8 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Deletes from 0 to 6 inclusive = "hello w", leaves "orld"
|
||||
if m.ActiveBuffer().Lines[0].String() != "orld" {
|
||||
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "orld" {
|
||||
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -333,8 +333,8 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Deletes "hello"
|
||||
if m.ActiveBuffer().Lines[0].String() != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
t.Errorf("Line(0) = %q, want ' world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -364,8 +364,8 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Deletes from "h" (0) to "w" (6) inclusive
|
||||
if m.ActiveBuffer().Lines[0].String() != "orld" {
|
||||
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "orld" {
|
||||
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -398,8 +398,9 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
if m.ActiveWindow().Anchor.Col != 0 {
|
||||
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
||||
}
|
||||
if m.ActiveWindow().Cursor.Col != 10 {
|
||||
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||
// $ moves past end of line
|
||||
if m.ActiveWindow().Cursor.Col != 11 {
|
||||
t.Errorf("CursorX() = %d, want 11", m.ActiveWindow().Cursor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
@ -411,8 +412,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
sendKeys(tm, "v", "$", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -441,8 +442,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Deletes from 'h' (0) to 'w' (6) inclusive
|
||||
if m.ActiveBuffer().Lines[0].String() != "orld" {
|
||||
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "orld" {
|
||||
t.Errorf("Line(0) = %q, want 'orld'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -492,8 +493,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "lin 3" {
|
||||
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lin 3" {
|
||||
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -526,8 +527,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "lin 3" {
|
||||
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "lin 3" {
|
||||
t.Errorf("Line(0) = %q, want 'lin 3'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -563,8 +564,8 @@ func TestVisualLineModeJumpMotions(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
t.Errorf("LineCount() = %d, want 1", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -46,14 +46,14 @@ func TestYankLineBasic(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 1" {
|
||||
t.Errorf("Line(0) = %q, want 'line 1'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 2" {
|
||||
t.Errorf("Line(1) = %q, want 'line 2'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -583,8 +583,8 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
sendKeys(tm, "y", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -659,8 +659,8 @@ func TestYankVisualCharwise(t *testing.T) {
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "y")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello world" {
|
||||
t.Errorf("Line(0) = %q, want 'hello world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -904,8 +904,8 @@ func TestYankRegisterBehavior(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "to copy" {
|
||||
t.Errorf("Line(1) = %q, want 'to copy'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "to copy" {
|
||||
t.Errorf("Line(1) = %q, want 'to copy'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1053,8 +1053,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "y", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello worldhello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello worldhello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1067,8 +1067,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
sendKeys(tm, "v", "$", "y", "0", "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "worldhello world" {
|
||||
t.Errorf("Line(0) = %q, want 'worldhello world'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "worldhello world" {
|
||||
t.Errorf("Line(0) = %q, want 'worldhello world'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1084,8 +1084,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 4 {
|
||||
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line 1" {
|
||||
t.Errorf("Line(2) = %q, want 'line 1'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line 1" {
|
||||
t.Errorf("Line(2) = %q, want 'line 1'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1101,11 +1101,11 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 6 {
|
||||
t.Errorf("LineCount() = %d, want 6", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[4].String() != "line 1" {
|
||||
t.Errorf("Line(4) = %q, want 'line 1'", m.ActiveBuffer().Lines[4].String())
|
||||
if m.ActiveBuffer().Lines[4] != "line 1" {
|
||||
t.Errorf("Line(4) = %q, want 'line 1'", m.ActiveBuffer().Lines[4])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[5].String() != "line 2" {
|
||||
t.Errorf("Line(5) = %q, want 'line 2'", m.ActiveBuffer().Lines[5].String())
|
||||
if m.ActiveBuffer().Lines[5] != "line 2" {
|
||||
t.Errorf("Line(5) = %q, want 'line 2'", m.ActiveBuffer().Lines[5])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1121,11 +1121,11 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 4 {
|
||||
t.Errorf("LineCount() = %d, want 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 3" {
|
||||
t.Errorf("Line(0) = %q, want 'line 3'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1140,14 +1140,14 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "original" {
|
||||
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "original" {
|
||||
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "original" {
|
||||
t.Errorf("Line(1) = %q, want 'original'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "original" {
|
||||
t.Errorf("Line(1) = %q, want 'original'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "other" {
|
||||
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "other" {
|
||||
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1162,14 +1162,14 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "original" {
|
||||
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "original" {
|
||||
t.Errorf("Line(0) = %q, want 'original'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "other" {
|
||||
t.Errorf("Line(1) = %q, want 'other'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "other" {
|
||||
t.Errorf("Line(1) = %q, want 'other'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "other" {
|
||||
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "other" {
|
||||
t.Errorf("Line(2) = %q, want 'other'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1182,8 +1182,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
sendKeys(tm, "y", "w", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello worldhello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello '", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello worldhello " {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello '", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1196,8 +1196,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
sendKeys(tm, "y", "e", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "hello worldhello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "hello worldhello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello worldhello'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1210,8 +1210,8 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
sendKeys(tm, "v", "l", "l", "y", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0].String() != "abcdefghcde" {
|
||||
t.Errorf("Line(0) = %q, want 'abcdefghcde'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "abcdefghcde" {
|
||||
t.Errorf("Line(0) = %q, want 'abcdefghcde'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1246,14 +1246,14 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0].String() != "line 2" {
|
||||
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0].String())
|
||||
if m.ActiveBuffer().Lines[0] != "line 2" {
|
||||
t.Errorf("Line(0) = %q, want 'line 2'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line 3" {
|
||||
t.Errorf("Line(2) = %q, want 'line 3'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
@ -1270,17 +1270,17 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Errorf("LineCount() = %d, want 7", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
// Original + 2 copies of 2 lines = 3 + 4 = 7
|
||||
if m.ActiveBuffer().Lines[1].String() != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1].String())
|
||||
if m.ActiveBuffer().Lines[1] != "line 1" {
|
||||
t.Errorf("Line(1) = %q, want 'line 1'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2].String() != "line 2" {
|
||||
t.Errorf("Line(2) = %q, want 'line 2'", m.ActiveBuffer().Lines[2].String())
|
||||
if m.ActiveBuffer().Lines[2] != "line 2" {
|
||||
t.Errorf("Line(2) = %q, want 'line 2'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[3].String() != "line 1" {
|
||||
t.Errorf("Line(3) = %q, want 'line 1'", m.ActiveBuffer().Lines[3].String())
|
||||
if m.ActiveBuffer().Lines[3] != "line 1" {
|
||||
t.Errorf("Line(3) = %q, want 'line 1'", m.ActiveBuffer().Lines[3])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[4].String() != "line 2" {
|
||||
t.Errorf("Line(4) = %q, want 'line 2'", m.ActiveBuffer().Lines[4].String())
|
||||
if m.ActiveBuffer().Lines[4] != "line 2" {
|
||||
t.Errorf("Line(4) = %q, want 'line 2'", m.ActiveBuffer().Lines[4])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -51,9 +51,6 @@ type Model struct {
|
||||
|
||||
// Visual styles
|
||||
styles style.Styles
|
||||
|
||||
// Dot operator state
|
||||
lastChangeKeys []string
|
||||
}
|
||||
|
||||
// Model.Init: Initialize the model and start any commands that may need to run. Required
|
||||
@ -123,25 +120,6 @@ func (m *Model) GetLastFind() *core.LastFindCommand {
|
||||
return &m.lastFind
|
||||
}
|
||||
|
||||
// Does update the '.' register
|
||||
func (m *Model) SetLastChangeKeys(keys []string) {
|
||||
m.lastChangeKeys = keys
|
||||
|
||||
m.SetRegister('.', core.CharwiseRegister, []string{strings.Join(keys, "")})
|
||||
}
|
||||
|
||||
func (m *Model) LastChangeKeys() []string {
|
||||
return m.lastChangeKeys
|
||||
}
|
||||
|
||||
func (m *Model) ClearLastChangeKeys() {
|
||||
m.lastChangeKeys = []string{}
|
||||
}
|
||||
|
||||
func (m *Model) HandleKey(key string) tea.Cmd {
|
||||
return m.input.Handle(m, key)
|
||||
}
|
||||
|
||||
func (m *Model) ExitInsertMode() {
|
||||
win := m.ActiveWindow()
|
||||
if m.insertCount > 1 {
|
||||
|
||||
@ -11,7 +11,76 @@ type ModelBuilder struct {
|
||||
model Model
|
||||
}
|
||||
|
||||
// NewModelBuilder: Builds and returns a new model, using the default color scheme (kanagawa-wave).
|
||||
// RPGLE
|
||||
// abap
|
||||
// algol
|
||||
// algol_nu
|
||||
// arduino
|
||||
// ashen
|
||||
// aura-theme-dark
|
||||
// aura-theme-dark-soft
|
||||
// autumn
|
||||
// average
|
||||
// base16-snazzy
|
||||
// borland
|
||||
// bw
|
||||
// catppuccin-frappe
|
||||
// catppuccin-latte
|
||||
// catppuccin-macchiato
|
||||
// catppuccin-mocha
|
||||
// colorful
|
||||
// doom-one
|
||||
// doom-one2
|
||||
// dracula
|
||||
// emacs
|
||||
// evergarden
|
||||
// friendly
|
||||
// fruity
|
||||
// github
|
||||
// github-dark
|
||||
// gruvbox
|
||||
// gruvbox-light
|
||||
// hr_high_contrast
|
||||
// hrdark
|
||||
// igor
|
||||
// lovelace
|
||||
// manni
|
||||
// modus-operandi
|
||||
// modus-vivendi
|
||||
// monokai
|
||||
// monokailight
|
||||
// murphy
|
||||
// native
|
||||
// nord
|
||||
// nordic
|
||||
// onedark
|
||||
// onesenterprise
|
||||
// paraiso-dark
|
||||
// paraiso-light
|
||||
// pastie
|
||||
// perldoc
|
||||
// pygments
|
||||
// rainbow_dash
|
||||
// rose-pine
|
||||
// rose-pine-dawn
|
||||
// rose-pine-moon
|
||||
// rrt
|
||||
// solarized-dark
|
||||
// solarized-dark256
|
||||
// solarized-light
|
||||
// swapoff
|
||||
// tango
|
||||
// tokyonight-day
|
||||
// tokyonight-moon
|
||||
// tokyonight-night
|
||||
// tokyonight-storm
|
||||
// trac
|
||||
// vim
|
||||
// vs
|
||||
// vulcan
|
||||
// witchhazel
|
||||
// xcode
|
||||
// xcode-dark
|
||||
func NewModelBuilder() *ModelBuilder {
|
||||
chromaStyle := styles.Get("kanagawa-wave")
|
||||
|
||||
|
||||
@ -1,95 +1,83 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/motion"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Model.Update: Handles BubbleTea messages including window resizes and key
|
||||
// presses. Routes input to the handler and adjusts scroll after updates.
|
||||
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
switch msg := msg.(type) {
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.termHeight = msg.Height
|
||||
m.termWidth = msg.Width
|
||||
case tea.WindowSizeMsg:
|
||||
m.termHeight = msg.Height
|
||||
m.termWidth = msg.Width
|
||||
|
||||
// TODO: Implement a layout method that handles this
|
||||
//
|
||||
// func (m *Model) layoutWindows() {
|
||||
// if len(m.windows) == 0 {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if len(m.windows) == 1 {
|
||||
// // Single window - full screen
|
||||
// m.windows[0].Width = m.termWidth
|
||||
// m.windows[0].Height = m.termHeight
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // Multiple windows - distribute space
|
||||
// // This is where you'd implement split layout logic
|
||||
// // For example, horizontal split:
|
||||
// halfHeight := m.termHeight / 2
|
||||
// for i, win := range m.windows {
|
||||
// win.Width = m.termWidth
|
||||
// if i < len(m.windows)-1 {
|
||||
// win.Height = halfHeight
|
||||
// } else {
|
||||
// // Last window gets remainder
|
||||
// win.Height = m.termHeight - (halfHeight * (len(m.windows) - 1))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
for i := range m.windows {
|
||||
m.windows[i].Height = msg.Height
|
||||
m.windows[i].Width = msg.Width
|
||||
}
|
||||
// TODO: Implement a layout method that handles this
|
||||
//
|
||||
// func (m *Model) layoutWindows() {
|
||||
// if len(m.windows) == 0 {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if len(m.windows) == 1 {
|
||||
// // Single window - full screen
|
||||
// m.windows[0].Width = m.termWidth
|
||||
// m.windows[0].Height = m.termHeight
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // Multiple windows - distribute space
|
||||
// // This is where you'd implement split layout logic
|
||||
// // For example, horizontal split:
|
||||
// halfHeight := m.termHeight / 2
|
||||
// for i, win := range m.windows {
|
||||
// win.Width = m.termWidth
|
||||
// if i < len(m.windows)-1 {
|
||||
// win.Height = halfHeight
|
||||
// } else {
|
||||
// // Last window gets remainder
|
||||
// win.Height = m.termHeight - (halfHeight * (len(m.windows) - 1))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
for i := range m.windows {
|
||||
m.windows[i].Height = msg.Height
|
||||
m.windows[i].Width = msg.Width
|
||||
}
|
||||
|
||||
// TODO: This is not great, totally temporary. But I don't like vim's handling, so this is up to me
|
||||
case tea.MouseMsg:
|
||||
switch msg.Button {
|
||||
case tea.MouseButtonWheelUp:
|
||||
scrollAction := motion.ScrollUpPage{Divisor: 4} // Quarter page
|
||||
cmd = scrollAction.Execute(m)
|
||||
case tea.MouseButtonWheelDown:
|
||||
scrollAction := motion.ScrollDownPage{Divisor: 4} // Quarter page
|
||||
cmd = scrollAction.Execute(m)
|
||||
}
|
||||
case tea.KeyMsg:
|
||||
// TODO: This needs to be removed, but for now its required for the tests.
|
||||
// Ctrl+C always quits regardless of mode
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
case tea.KeyMsg:
|
||||
// TODO: This needs to be removed, but for now its required for the tests.
|
||||
// Ctrl+C always quits regardless of mode
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
return m, tea.Quit
|
||||
}
|
||||
// TODO: This is not great
|
||||
// TODO: Any vim action should exit also
|
||||
// Simple override for command output mode for now
|
||||
if m.Mode() == core.CommandOutputMode {
|
||||
// TODO: Implement g/G/d/u
|
||||
switch msg.String() {
|
||||
case "enter":
|
||||
m.SetMode(core.NormalMode)
|
||||
m.SetCommandOutput(&core.CommandOutput{})
|
||||
case "j":
|
||||
m.CommandOutput().ScrollDown(m.termHeight)
|
||||
case "k":
|
||||
m.CommandOutput().ScrollUp()
|
||||
}
|
||||
} else {
|
||||
cmd = m.input.Handle(m, msg.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is not great
|
||||
// TODO: Any vim action should exit also
|
||||
// Simple override for command output mode for now
|
||||
if m.Mode() == core.CommandOutputMode {
|
||||
// TODO: Implement g/G/d/u
|
||||
switch msg.String() {
|
||||
case "enter":
|
||||
m.SetMode(core.NormalMode)
|
||||
m.SetCommandOutput(&core.CommandOutput{})
|
||||
case "j":
|
||||
m.CommandOutput().ScrollDown(m.termHeight)
|
||||
case "k":
|
||||
m.CommandOutput().ScrollUp()
|
||||
}
|
||||
} else {
|
||||
cmd = m.input.Handle(m, msg.String())
|
||||
}
|
||||
}
|
||||
// Keep cursor in view after any update
|
||||
win := m.ActiveWindow()
|
||||
win.AdjustScroll()
|
||||
|
||||
// Keep cursor in view after any update
|
||||
win := m.ActiveWindow()
|
||||
win.AdjustScroll()
|
||||
|
||||
return m, cmd
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
@ -2,11 +2,12 @@ package editor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
@ -23,10 +24,6 @@ func (m Model) View() string {
|
||||
styles := m.Styles()
|
||||
options := win.Options
|
||||
|
||||
// Adjust gutter to fit line len
|
||||
maxLineLen := len(strconv.Itoa(win.Buffer.LineCount()))
|
||||
options.GutterSize = max(options.GutterSize, maxLineLen+2)
|
||||
|
||||
// Draw window
|
||||
view := viewWindow(win, styles, options, m.Mode())
|
||||
|
||||
@ -55,7 +52,12 @@ func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mo
|
||||
end := w.ScrollY + w.ViewportHeight()
|
||||
|
||||
// Chroma stuff
|
||||
lexer := style.GetLexer(buf)
|
||||
name := strings.ReplaceAll(buf.Filetype, ".", "")
|
||||
lexer := lexers.Get(name)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
lexer = chroma.Coalesce(lexer) // Merge tokens together
|
||||
|
||||
// Draw buffer lines
|
||||
for lineNum := start; lineNum < end; lineNum++ {
|
||||
@ -339,11 +341,7 @@ func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles sty
|
||||
content := styles.LineStyle.Render(strings.ReplaceAll(l, "\n", "\\n"))
|
||||
overlay = append(overlay, content)
|
||||
}
|
||||
msg := core.CommandOutputExitMessage
|
||||
if len(cmd.Lines) > len(cmd.Viewport(termHeight)) {
|
||||
msg += ". " + core.CommandOutputScrollMessage
|
||||
}
|
||||
overlay = append(overlay, styles.CommandContinueMessage.Render(msg))
|
||||
overlay = append(overlay, styles.CommandContinueMessage.Render(core.CommandOutputExitMessage))
|
||||
|
||||
// NOTE: strings.Split on "\n" is safe as long as no style uses .Width()/.Height()/.Padding()/.Margin(),
|
||||
// which would cause Lipgloss to embed newlines internally and corrupt the line count.
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/operator"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
@ -34,14 +31,10 @@ type Handler struct {
|
||||
charMotionType string // which char motion is waiting: "f", "t", "F", or "T"
|
||||
modifier string // which modifier used for text object: "i" or "a"
|
||||
|
||||
// Dot operator - accumulate keys for current operation
|
||||
recordingKeys []string
|
||||
|
||||
// Keymaps
|
||||
normalKeymap *Keymap
|
||||
visualKeymap *Keymap
|
||||
insertKeymap *Keymap
|
||||
replaceKeymap *Keymap
|
||||
commandKeymap *Keymap
|
||||
|
||||
currentKeymap *Keymap
|
||||
@ -54,7 +47,6 @@ func NewHandler() *Handler {
|
||||
normalKeymap: NewNormalKeymap(),
|
||||
visualKeymap: NewVisualKeymap(),
|
||||
insertKeymap: NewInsertKeymap(),
|
||||
replaceKeymap: NewReplaceKeymap(),
|
||||
commandKeymap: NewCommandKeymap(),
|
||||
currentKeymap: nil,
|
||||
}
|
||||
@ -63,29 +55,10 @@ func NewHandler() *Handler {
|
||||
// Handler.Handle: Main entry point for processing a keypress. Routes to appropriate
|
||||
// handler based on current mode and state.
|
||||
func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
||||
ignoreKeys := []string{".", "u", "ctrl+r"}
|
||||
|
||||
// Record key for dot operator (except in insert/command mode which handle separately)
|
||||
if m.Mode() != core.InsertMode && m.Mode() != core.CommandMode && !slices.Contains(ignoreKeys, key) {
|
||||
h.recordingKeys = append(h.recordingKeys, key)
|
||||
}
|
||||
|
||||
// ESC always resets everything
|
||||
if key == "esc" {
|
||||
// If insert mode, keep the escape
|
||||
if m.Mode() == core.InsertMode {
|
||||
m.SetLastChangeKeys(append(m.LastChangeKeys(), key))
|
||||
}
|
||||
|
||||
h.recordingKeys = []string{} // Clear recording on ESC
|
||||
h.Reset()
|
||||
if m.Mode() == core.InsertMode || m.Mode() == core.ReplaceMode {
|
||||
// Before exiting insert mode, end the block in the undo stack
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
if buf.UndoStack != nil {
|
||||
buf.UndoStack.EndBlock(win.Cursor)
|
||||
}
|
||||
if m.Mode() == core.InsertMode {
|
||||
m.ExitInsertMode()
|
||||
} else {
|
||||
m.SetMode(core.NormalMode)
|
||||
@ -97,8 +70,6 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
||||
switch m.Mode() {
|
||||
case core.InsertMode:
|
||||
return h.handleInsertKey(m, key)
|
||||
case core.ReplaceMode:
|
||||
return h.handleReplaceKey(m, key)
|
||||
case core.CommandMode:
|
||||
return h.handleCommandKey(m, key)
|
||||
}
|
||||
@ -176,9 +147,6 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
||||
func (h *Handler) dispatch(m action.Model, kind string, binding any, key string) tea.Cmd {
|
||||
// Handle character motions (f/t/F/T) - transition to waiting state
|
||||
if kind == "char_motion" {
|
||||
if key == "r" {
|
||||
m.SetMode(core.WaitingMode)
|
||||
}
|
||||
h.charMotionType = key
|
||||
h.state = StateWaitingForChar
|
||||
return nil
|
||||
@ -212,13 +180,7 @@ func (h *Handler) handleInitial(m action.Model, kind string, binding any, key st
|
||||
if res, ok := mot.(action.Resolvable); ok {
|
||||
mot = res.Resolve(m)
|
||||
}
|
||||
cmd := h.executeMotion(m, mot)
|
||||
|
||||
// Only clear recording for pure motions in normal mode
|
||||
// In visual mode, motions are part of building the selection
|
||||
if !m.Mode().IsVisualMode() {
|
||||
h.recordingKeys = []string{}
|
||||
}
|
||||
cmd := mot.Execute(m)
|
||||
h.Reset()
|
||||
return cmd
|
||||
|
||||
@ -232,12 +194,12 @@ func (h *Handler) handleInitial(m action.Model, kind string, binding any, key st
|
||||
if m.Mode() == core.VisualLineMode {
|
||||
mtype = core.Linewise
|
||||
}
|
||||
cmd := h.executeOperator(m, op, start, end, mtype)
|
||||
cmd := op.Operate(m, start, end, mtype)
|
||||
// Only reset to normal mode if operator didn't enter insert mode
|
||||
if m.Mode() != core.InsertMode {
|
||||
m.SetMode(core.NormalMode)
|
||||
}
|
||||
h.RecordAndReset(m)
|
||||
h.Reset()
|
||||
return cmd
|
||||
}
|
||||
// In normal mode, wait for a motion to define the range
|
||||
@ -251,13 +213,8 @@ func (h *Handler) handleInitial(m action.Model, kind string, binding any, key st
|
||||
if r, ok := act.(action.Repeatable); ok {
|
||||
act = r.WithCount(count)
|
||||
}
|
||||
cmd := h.executeAction(m, act)
|
||||
// Only record if we're not entering visual mode (visual ops record when they complete)
|
||||
if m.Mode().IsVisualMode() {
|
||||
h.Reset() // In visual mode now, don't save yet
|
||||
} else {
|
||||
h.RecordAndReset(m)
|
||||
}
|
||||
cmd := act.Execute(m)
|
||||
h.Reset()
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -275,8 +232,8 @@ func (h *Handler) handleAfterOperator(m action.Model, kind string, binding any,
|
||||
if kind == "operator" && key == h.operatorKey {
|
||||
// Only call DoublePress if the operator supports it
|
||||
if dp, ok := h.operator.(action.DoublePresser); ok {
|
||||
cmd := h.executeDoublePress(m, dp, count)
|
||||
h.RecordAndReset(m)
|
||||
cmd := dp.DoublePress(m, count)
|
||||
h.Reset()
|
||||
return cmd
|
||||
}
|
||||
h.Reset()
|
||||
@ -301,10 +258,10 @@ func (h *Handler) handleAfterOperator(m action.Model, kind string, binding any,
|
||||
}
|
||||
// Get range and motion type
|
||||
start := win.Cursor
|
||||
h.executeMotion(m, mot)
|
||||
mot.Execute(m)
|
||||
end := win.Cursor
|
||||
cmd := h.executeOperator(m, h.operator, start, end, mot.Type())
|
||||
h.RecordAndReset(m)
|
||||
cmd := h.operator.Operate(m, start, end, mot.Type())
|
||||
h.Reset()
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -369,34 +326,23 @@ func (h *Handler) handleCharMotion(m action.Model, key string) tea.Cmd {
|
||||
|
||||
// Apply count if supported
|
||||
if r, ok := mot.(action.Repeatable); ok {
|
||||
result := r.WithCount(count)
|
||||
// WithCount returns Action, but char motions still implement Motion
|
||||
if m, ok := result.(action.Motion); ok {
|
||||
mot = m
|
||||
}
|
||||
mot = r.WithCount(count).(action.Motion)
|
||||
}
|
||||
|
||||
// If operator pending (e.g., "df{char}"), get range and operate
|
||||
if h.operator != nil {
|
||||
win := m.ActiveWindow()
|
||||
start := win.Cursor
|
||||
h.executeMotion(m, mot)
|
||||
mot.Execute(m)
|
||||
end := win.Cursor
|
||||
cmd := h.executeOperator(m, h.operator, start, end, mot.Type())
|
||||
h.RecordAndReset(m)
|
||||
cmd := h.operator.Operate(m, start, end, mot.Type())
|
||||
h.Reset()
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Otherwise just execute the motion
|
||||
cmd := h.executeMotion(m, mot)
|
||||
|
||||
// ReplaceChar modifies the buffer, so it should be repeatable with '.'
|
||||
// (unlike f/t/F/T which are pure motions)
|
||||
if _, isReplace := mot.(action.ReplaceChar); isReplace {
|
||||
h.RecordAndReset(m)
|
||||
} else {
|
||||
h.Reset()
|
||||
}
|
||||
cmd := mot.Execute(m)
|
||||
h.Reset()
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -420,8 +366,8 @@ func (h *Handler) handleTextObject(m action.Model, kind string, binding any, key
|
||||
|
||||
// If we have an operator pending (e.g., "diw")
|
||||
if h.operator != nil {
|
||||
cmd := h.executeOperator(m, h.operator, start, end, mtype)
|
||||
h.RecordAndReset(m)
|
||||
cmd := h.operator.Operate(m, start, end, mtype)
|
||||
h.Reset()
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -503,7 +449,6 @@ func (h *Handler) effectiveCount() int {
|
||||
}
|
||||
|
||||
// Handler.Reset: Clears all handler state including counts, operators, and buffers.
|
||||
// Does NOT clear recordingKeys - those accumulate across an operation.
|
||||
func (h *Handler) Reset() {
|
||||
h.state = StateReady
|
||||
h.count1 = 0
|
||||
@ -514,28 +459,6 @@ func (h *Handler) Reset() {
|
||||
h.pending = ""
|
||||
h.charMotionType = ""
|
||||
h.modifier = ""
|
||||
// NOTE: recordingKeys is NOT cleared here - it accumulates across the operation
|
||||
}
|
||||
|
||||
func (h *Handler) RecordAndReset(m action.Model) {
|
||||
// Save the recorded keys to the model for dot operator
|
||||
// Filter out mode-switch keys that don't modify the buffer
|
||||
ignoreStates := []string{":", "v", "V", "."}
|
||||
|
||||
if len(h.recordingKeys) > 0 {
|
||||
// Check if the entire sequence is just a mode switch
|
||||
shouldRecord := true
|
||||
if len(h.recordingKeys) == 1 && slices.Contains(ignoreStates, h.recordingKeys[0]) {
|
||||
shouldRecord = false
|
||||
}
|
||||
|
||||
if shouldRecord {
|
||||
m.SetLastChangeKeys(h.recordingKeys)
|
||||
}
|
||||
}
|
||||
|
||||
h.recordingKeys = []string{} // Clear recording after saving
|
||||
h.Reset()
|
||||
}
|
||||
|
||||
// Handler.Pending: Returns the accumulated input buffer for display.
|
||||
@ -545,21 +468,9 @@ func (h *Handler) Pending() string {
|
||||
|
||||
// Handler.handleInsertKey: Processes a keypress in insert mode, recording it
|
||||
// for count replay and executing it as an action or character insertion.
|
||||
//
|
||||
// This function does not make use of the execute abstractions, to prevent each
|
||||
// key inserted from creating a new block in the undo stack.
|
||||
func (h *Handler) handleInsertKey(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.insertKeymap.Lookup(key)
|
||||
@ -574,35 +485,8 @@ func (h *Handler) handleInsertKey(m action.Model, key string) tea.Cmd {
|
||||
return action.InsertChar{Char: key}.Execute(m)
|
||||
}
|
||||
|
||||
func (h *Handler) handleReplaceKey(m action.Model, key string) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
win := m.ActiveWindow()
|
||||
|
||||
// Start undo block on first insert key
|
||||
if buf.UndoStack != nil && !buf.UndoStack.Recording() {
|
||||
buf.UndoStack.BeginBlock(win.Cursor)
|
||||
}
|
||||
|
||||
// Record the key for count replay (e.g. 5i...)
|
||||
m.SetInsertKeys(append(m.InsertKeys(), key))
|
||||
m.SetLastChangeKeys(append(m.LastChangeKeys(), key))
|
||||
|
||||
// Check the insert keymap first
|
||||
kind, binding := h.replaceKeymap.Lookup(key)
|
||||
switch kind {
|
||||
case "action":
|
||||
return binding.(action.Action).Execute(m)
|
||||
case "motion":
|
||||
return binding.(action.Motion).Execute(m)
|
||||
}
|
||||
|
||||
// Fallback: treat as a regular character to "insert"
|
||||
return action.ReplaceModeChar{Char: key}.Execute(m)
|
||||
}
|
||||
|
||||
// Handler.handleCommandKey: Processes a keypress in command mode, executing
|
||||
// it as an action or inserting it into the command line. This does not record
|
||||
// anything into the undo stack.
|
||||
// it as an action or inserting it into the command line.
|
||||
func (h *Handler) handleCommandKey(m action.Model, key string) tea.Cmd {
|
||||
kind, binding := h.commandKeymap.Lookup(key)
|
||||
switch kind {
|
||||
@ -627,83 +511,3 @@ func normalizeVisualSelection(m action.Model) (core.Position, core.Position) {
|
||||
}
|
||||
return c, a
|
||||
}
|
||||
|
||||
func (h *Handler) executeAction(m action.Model, act action.Action) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
if buf.UndoStack != nil {
|
||||
buf.UndoStack.BeginBlock(win.Cursor)
|
||||
}
|
||||
|
||||
cmd := act.Execute(m)
|
||||
|
||||
// If the action one that includes insert mode, we should not end the block, we want to
|
||||
// include the text from the insert mode in the block.
|
||||
_, O := act.(action.OpenLineAbove)
|
||||
_, o := act.(action.OpenLineBelow)
|
||||
_, s := act.(action.SubstituteChar)
|
||||
_, S := act.(action.SubstituteLine)
|
||||
_, C := act.(action.ChangeToEndOfLine)
|
||||
if o || O || s || S || C {
|
||||
return nil
|
||||
}
|
||||
|
||||
if buf.UndoStack != nil {
|
||||
buf.UndoStack.EndBlock(win.Cursor)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (h *Handler) executeMotion(m action.Model, mot action.Motion) tea.Cmd {
|
||||
// These do not change the buffer, so no need to record anything
|
||||
return mot.Execute(m)
|
||||
}
|
||||
|
||||
func (h *Handler) executeOperator(m action.Model, op action.Operator, start, end core.Position, mtype core.MotionType) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
win := m.ActiveWindow()
|
||||
|
||||
if buf.UndoStack != nil {
|
||||
buf.UndoStack.BeginBlock(win.Cursor)
|
||||
}
|
||||
|
||||
cmd := op.Operate(m, start, end, mtype)
|
||||
|
||||
// If operator is one that enters insert mode, we do not want to end the block.
|
||||
_, c := op.(operator.ChangeOperator)
|
||||
if c {
|
||||
return cmd
|
||||
}
|
||||
|
||||
if buf.UndoStack != nil {
|
||||
buf.UndoStack.EndBlock(win.Cursor)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (h *Handler) executeDoublePress(m action.Model, dp action.DoublePresser, count int) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
win := m.ActiveWindow()
|
||||
|
||||
if buf.UndoStack != nil {
|
||||
buf.UndoStack.BeginBlock(win.Cursor)
|
||||
}
|
||||
|
||||
cmd := dp.DoublePress(m, count)
|
||||
|
||||
// If operator being double pressed is one that enters insert mode, we do not
|
||||
// want to end the block.
|
||||
_, c := dp.(operator.ChangeOperator)
|
||||
if c {
|
||||
return cmd
|
||||
}
|
||||
|
||||
if buf.UndoStack != nil {
|
||||
buf.UndoStack.EndBlock(win.Cursor)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -38,15 +38,10 @@ func NewNormalKeymap() *Keymap {
|
||||
"e": motion.MoveForwardWordEnd{Count: 1},
|
||||
"E": motion.MoveForwardWORDEnd{Count: 1},
|
||||
"b": motion.MoveBackwardWord{Count: 1},
|
||||
"B": motion.MoveBackwardWORD{Count: 1},
|
||||
"ge": motion.MoveBackwardWordEnd{Count: 1},
|
||||
"gE": motion.MoveBackwardWORDEnd{Count: 1},
|
||||
"ctrl+u": motion.ScrollUpPage{Divisor: 2},
|
||||
"ctrl+d": motion.ScrollDownPage{Divisor: 2},
|
||||
"ctrl+b": motion.ScrollUpPage{Divisor: 1},
|
||||
"ctrl+f": motion.ScrollDownPage{Divisor: 1},
|
||||
"ctrl+u": motion.ScrollUpHalfPage{},
|
||||
"ctrl+d": motion.ScrollDownHalfPage{},
|
||||
";": action.RepeatFind{Count: 1, Reverse: false},
|
||||
",": action.RepeatFind{Count: 1, Reverse: true},
|
||||
".": action.RepeatFind{Count: 1, Reverse: true},
|
||||
},
|
||||
operators: map[string]action.Operator{
|
||||
"d": operator.DeleteOperator{},
|
||||
@ -74,17 +69,12 @@ func NewNormalKeymap() *Keymap {
|
||||
"S": action.SubstituteLine{Count: 1},
|
||||
"p": action.Paste{Count: 1},
|
||||
"P": action.PasteBefore{Count: 1},
|
||||
"u": action.Undo{},
|
||||
"ctrl+r": action.Redo{},
|
||||
".": action.Repeat{Count: 1},
|
||||
"R": action.EnterReplace{},
|
||||
},
|
||||
charMotions: map[string]action.Motion{
|
||||
"f": action.FindChar{Forward: true, Inclusive: true, Repeated: false},
|
||||
"F": action.FindChar{Forward: false, Inclusive: true, Repeated: false},
|
||||
"t": action.FindChar{Forward: true, Inclusive: false, Repeated: false},
|
||||
"T": action.FindChar{Forward: false, Inclusive: false, Repeated: false},
|
||||
"r": action.ReplaceChar{Count: 1},
|
||||
},
|
||||
modifiers: map[string]any{
|
||||
"i": nil,
|
||||
@ -115,32 +105,22 @@ func NewNormalKeymap() *Keymap {
|
||||
func NewVisualKeymap() *Keymap {
|
||||
return &Keymap{
|
||||
motions: map[string]action.Motion{
|
||||
"j": motion.MoveDown{Count: 1},
|
||||
"k": motion.MoveUp{Count: 1},
|
||||
"h": motion.MoveLeft{Count: 1},
|
||||
"l": motion.MoveRight{Count: 1},
|
||||
"G": motion.MoveToBottom{},
|
||||
"gg": motion.MoveToTop{},
|
||||
"0": motion.MoveToLineStart{},
|
||||
"$": motion.MoveToLineEnd{},
|
||||
"_": motion.MoveToLineContentStart{},
|
||||
"^": motion.MoveToLineContentStart{},
|
||||
"|": motion.MoveToColumn{Count: 0},
|
||||
"w": motion.MoveForwardWord{Count: 1},
|
||||
"W": motion.MoveForwardWORD{Count: 1},
|
||||
"e": motion.MoveForwardWordEnd{Count: 1},
|
||||
"E": motion.MoveForwardWORDEnd{Count: 1},
|
||||
"b": motion.MoveBackwardWord{Count: 1},
|
||||
"B": motion.MoveBackwardWORD{Count: 1},
|
||||
"ge": motion.MoveBackwardWordEnd{Count: 1},
|
||||
"gE": motion.MoveBackwardWORDEnd{Count: 1},
|
||||
"ctrl+u": motion.ScrollUpPage{Divisor: 2},
|
||||
"ctrl+d": motion.ScrollDownPage{Divisor: 2},
|
||||
"ctrl+b": motion.ScrollUpPage{Divisor: 1},
|
||||
"ctrl+f": motion.ScrollDownPage{Divisor: 1},
|
||||
";": action.RepeatFind{Count: 1, Reverse: false},
|
||||
",": action.RepeatFind{Count: 1, Reverse: true},
|
||||
// TODO: O and o. These are fun ones! Should be simple too
|
||||
"j": motion.MoveDown{Count: 1},
|
||||
"k": motion.MoveUp{Count: 1},
|
||||
"h": motion.MoveLeft{Count: 1},
|
||||
"l": motion.MoveRight{Count: 1},
|
||||
"G": motion.MoveToBottom{},
|
||||
"gg": motion.MoveToTop{},
|
||||
"0": motion.MoveToLineStart{},
|
||||
"$": motion.MoveToLineEnd{},
|
||||
"_": motion.MoveToLineContentStart{},
|
||||
"^": motion.MoveToLineContentStart{},
|
||||
"|": motion.MoveToColumn{Count: 0},
|
||||
"w": motion.MoveForwardWord{Count: 1},
|
||||
"W": motion.MoveForwardWORD{Count: 1},
|
||||
"e": motion.MoveForwardWordEnd{Count: 1},
|
||||
"E": motion.MoveForwardWORDEnd{Count: 1},
|
||||
"b": motion.MoveBackwardWord{Count: 1},
|
||||
},
|
||||
operators: map[string]action.Operator{
|
||||
"d": operator.DeleteOperator{},
|
||||
@ -148,13 +128,9 @@ func NewVisualKeymap() *Keymap {
|
||||
"X": operator.DeleteOperator{},
|
||||
"y": operator.YankOperator{},
|
||||
"c": operator.ChangeOperator{},
|
||||
"s": operator.ChangeOperator{}, // Same as c in visual mode
|
||||
"R": operator.ChangeOperator{}, // Seems to do the same thing
|
||||
},
|
||||
actions: map[string]action.Action{
|
||||
"p": action.VisualPaste{Count: 1, Replace: true},
|
||||
"P": action.VisualPaste{Count: 1, Replace: false},
|
||||
".": action.Repeat{Count: 1},
|
||||
"p": action.VisualPaste{Count: 1},
|
||||
// ":": action.EnterComandMode{}, // Different OP
|
||||
},
|
||||
charMotions: map[string]action.Motion{
|
||||
@ -206,26 +182,7 @@ func NewInsertKeymap() *Keymap {
|
||||
"ctrl+w": action.InsertDeletePreviousWord{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewReplaceKeymap: Creates a keymap for replace mode with editing actions. All actions
|
||||
func NewReplaceKeymap() *Keymap {
|
||||
return &Keymap{
|
||||
motions: map[string]action.Motion{
|
||||
"down": motion.MoveDown{Count: 1},
|
||||
"up": motion.MoveUp{Count: 1},
|
||||
"left": motion.MoveLeft{Count: 1},
|
||||
"right": motion.MoveRight{Count: 1},
|
||||
},
|
||||
operators: map[string]action.Operator{}, // this will likely be empty
|
||||
actions: map[string]action.Action{
|
||||
"enter": action.ReplaceNewline{},
|
||||
"backspace": action.InsertBackspace{},
|
||||
"delete": action.InsertDelete{},
|
||||
"tab": action.ReplaceTab{}, // TODO: This needs replacing
|
||||
"ctrl+w": action.InsertDeletePreviousWord{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewCommandKeymap: Creates a keymap for command mode with command line editing.
|
||||
|
||||
@ -80,7 +80,7 @@ type MoveRight struct {
|
||||
func (a MoveRight) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
lineLen := buf.Lines[win.Cursor.Line].Len()
|
||||
lineLen := len(buf.Lines[win.Cursor.Line])
|
||||
for i := 0; i < a.Count && win.Cursor.Col <= lineLen; i++ {
|
||||
win.SetCursorCol(win.Cursor.Col + 1)
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ type MoveToLineEnd struct{}
|
||||
func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
win.SetCursorCol(buf.Lines[win.Cursor.Line].Len() - 1)
|
||||
win.SetCursorCol(len(buf.Lines[win.Cursor.Line]))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
line := buf.Line(win.Cursor.Line)
|
||||
line := buf.Lines[win.Cursor.Line]
|
||||
x := 0
|
||||
for x < len(line) {
|
||||
ch := line[x]
|
||||
@ -96,7 +96,7 @@ func (a MoveToColumn) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
line := buf.Line(win.Cursor.Line)
|
||||
line := buf.Lines[win.Cursor.Line]
|
||||
col := min(a.Count-1, len(line)-1)
|
||||
|
||||
win.SetCursorCol(col)
|
||||
@ -111,23 +111,21 @@ func (a MoveToColumn) WithCount(n int) action.Action {
|
||||
|
||||
// TODO: Count for these, maybe?
|
||||
|
||||
// ScrollDownPage implements Motion (ctrl+d) - linewise
|
||||
type ScrollDownPage struct {
|
||||
Divisor int
|
||||
}
|
||||
// ScrollDownHalfPage implements Motion (ctrl+d) - linewise
|
||||
type ScrollDownHalfPage struct{}
|
||||
|
||||
// ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the
|
||||
// cursor's relative position in the viewport.
|
||||
func (a ScrollDownPage) Execute(m action.Model) tea.Cmd {
|
||||
func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
viewportHeight := win.ViewportHeight()
|
||||
viewportHeight := win.Height - 2
|
||||
if viewportHeight <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
scroll := viewportHeight / a.Divisor
|
||||
scroll := viewportHeight / 2
|
||||
scrollOff := win.Options.ScrollOff
|
||||
|
||||
// Current relative position in viewport
|
||||
@ -154,24 +152,22 @@ func (a ScrollDownPage) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a ScrollDownPage) Type() core.MotionType { return core.Linewise }
|
||||
func (a ScrollDownHalfPage) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
// ScrollUpPage implements Motion (ctrl+u) - linewise
|
||||
type ScrollUpPage struct {
|
||||
Divisor int
|
||||
}
|
||||
// ScrollUpHalfPage implements Motion (ctrl+u) - linewise
|
||||
type ScrollUpHalfPage struct{}
|
||||
|
||||
// ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the
|
||||
// cursor's relative position in the viewport.
|
||||
func (a ScrollUpPage) Execute(m action.Model) tea.Cmd {
|
||||
func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
viewportHeight := win.ViewportHeight()
|
||||
viewportHeight := win.Height - 2
|
||||
if viewportHeight <= 0 {
|
||||
return nil
|
||||
}
|
||||
scroll := viewportHeight / a.Divisor
|
||||
scroll := viewportHeight / 2
|
||||
scrollOff := win.Options.ScrollOff
|
||||
|
||||
// Current relative position in viewport
|
||||
@ -197,4 +193,4 @@ func (a ScrollUpPage) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a ScrollUpPage) Type() core.MotionType { return core.Linewise }
|
||||
func (a ScrollUpHalfPage) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
@ -24,7 +24,7 @@ func isWordPunctuation(c byte) bool {
|
||||
// nextWordStart: Finds the start of the next word from position (x,y), handling
|
||||
// word boundaries and line crossing.
|
||||
func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Skip current class
|
||||
if x < len(line) {
|
||||
@ -59,7 +59,7 @@ func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
|
||||
// Move to first char of next line
|
||||
y++
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
x = 0
|
||||
|
||||
// If the first char of the new line is no whitespace, stay here!
|
||||
@ -74,7 +74,7 @@ func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
// nextWORDStart: Finds the start of the next WORD from position (x,y), treating
|
||||
// all non-whitespace as a single class.
|
||||
func nextWORDStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Skip current WORD (all non-whitespace is one class for W)
|
||||
for x < len(line) && line[x] != ' ' && line[x] != '\t' {
|
||||
@ -100,7 +100,7 @@ func nextWORDStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
|
||||
// Move to first char of next line
|
||||
y++
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
x = 0
|
||||
|
||||
// If the first char of the new line is no whitespace, stay here!
|
||||
@ -115,7 +115,7 @@ func nextWORDStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
// nextWordEnd: Finds the end of the next word from position (x,y), respecting
|
||||
// word character classes.
|
||||
func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Advance once to avoid being stuck on the current end
|
||||
x++
|
||||
@ -128,7 +128,7 @@ func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
// Otherwise, move to next line
|
||||
y++
|
||||
x = 0
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
}
|
||||
|
||||
// Skip whitespace and cross lines if needed
|
||||
@ -150,7 +150,7 @@ func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
|
||||
// Move to first char of next line
|
||||
y++
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
x = 0
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
// nextWORDEnd: Finds the end of the next WORD from position (x,y), treating
|
||||
// all non-whitespace as a single class.
|
||||
func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Advance once to avoid being stuck on the current end
|
||||
x++
|
||||
@ -187,7 +187,7 @@ func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
// Otherwise, move to next line
|
||||
y++
|
||||
x = 0
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
}
|
||||
|
||||
// Skip whitespace and cross lines if needed
|
||||
@ -209,7 +209,7 @@ func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
|
||||
// Move to first char of next line
|
||||
y++
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
x = 0
|
||||
}
|
||||
|
||||
@ -224,7 +224,7 @@ func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
// prevWordStart: Finds the start of the previous word from position (x,y),
|
||||
// moving backward through character classes.
|
||||
func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Back one to avoid being stuck on the current start
|
||||
x--
|
||||
@ -233,7 +233,7 @@ func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
return 0, 0 // beginning of file, stay put
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
x = len(line) - 1
|
||||
if x < 0 {
|
||||
return 0, y // landed on an empty line
|
||||
@ -252,7 +252,7 @@ func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
return 0, 0
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
line = buf.Lines[y]
|
||||
x = len(line) - 1
|
||||
if len(line) == 0 {
|
||||
return 0, y // empty line acts as a word boundary
|
||||
@ -412,290 +412,3 @@ func (a MoveBackwardWord) Type() core.MotionType { return core.CharwiseExclusive
|
||||
func (a MoveBackwardWord) WithCount(n int) action.Action {
|
||||
return MoveBackwardWord{Count: n}
|
||||
}
|
||||
|
||||
// prevWORDStart: Finds the start of the previous WORD from position (x,y),
|
||||
// treating all non-whitespace as a single class.
|
||||
func prevWORDStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
|
||||
// Back one to avoid being stuck on the current start
|
||||
x--
|
||||
if x < 0 {
|
||||
if y == 0 {
|
||||
return 0, 0 // beginning of file, stay put
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
if x < 0 {
|
||||
return 0, y // landed on an empty line
|
||||
}
|
||||
}
|
||||
|
||||
// Skip whitespace backward, crossing lines if needed
|
||||
for {
|
||||
for x >= 0 && (line[x] == ' ' || line[x] == '\t') {
|
||||
x--
|
||||
}
|
||||
if x >= 0 {
|
||||
break // landed on a non-whitespace char
|
||||
}
|
||||
if y == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
if len(line) == 0 {
|
||||
return 0, y // empty line acts as a word boundary
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to the start of the WORD (all non-whitespace is one class)
|
||||
for x-1 >= 0 && line[x-1] != ' ' && line[x-1] != '\t' {
|
||||
x--
|
||||
}
|
||||
|
||||
return x, y
|
||||
}
|
||||
|
||||
// prevWordEnd: Finds the end of the previous word from position (x,y),
|
||||
// respecting word character classes.
|
||||
func prevWordEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
origY := y
|
||||
|
||||
// Back one to avoid being stuck on the current end
|
||||
x--
|
||||
if x < 0 {
|
||||
if y == 0 {
|
||||
return 0, 0 // beginning of file, stay put
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
// Don't return early for empty line - we'll handle it in whitespace skip
|
||||
}
|
||||
|
||||
// Skip backward through current word class if we're on one
|
||||
// BUT: if we crossed lines in the "back one" step, we're already at the end of a word
|
||||
if y == origY && x >= 0 && line[x] != ' ' && line[x] != '\t' {
|
||||
if isWordChar(line[x]) {
|
||||
// Skip word characters
|
||||
for x >= 0 && isWordChar(line[x]) {
|
||||
x--
|
||||
if x < 0 {
|
||||
if y == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
if x < 0 {
|
||||
return 0, y
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Skip punctuation
|
||||
for x >= 0 && isWordPunctuation(line[x]) {
|
||||
x--
|
||||
if x < 0 {
|
||||
if y == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
if x < 0 {
|
||||
return 0, y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip whitespace backward, crossing lines if needed
|
||||
for {
|
||||
for x >= 0 && (line[x] == ' ' || line[x] == '\t') {
|
||||
x--
|
||||
}
|
||||
if x >= 0 {
|
||||
break // landed on a non-whitespace char, this is our word end!
|
||||
}
|
||||
if y == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
if len(line) == 0 {
|
||||
return 0, y // empty line acts as a word boundary
|
||||
}
|
||||
}
|
||||
|
||||
// Now x,y is at the start of the target word. Move forward to its end.
|
||||
if x >= 0 {
|
||||
if isWordChar(line[x]) {
|
||||
for x+1 < len(line) && isWordChar(line[x+1]) {
|
||||
x++
|
||||
}
|
||||
} else if isWordPunctuation(line[x]) {
|
||||
for x+1 < len(line) && isWordPunctuation(line[x+1]) {
|
||||
x++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return x, y
|
||||
}
|
||||
|
||||
// prevWORDEnd: Finds the end of the previous WORD from position (x,y),
|
||||
// treating all non-whitespace as a single class.
|
||||
func prevWORDEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Line(y)
|
||||
origY := y
|
||||
|
||||
// Back one to avoid being stuck on the current end
|
||||
x--
|
||||
if x < 0 {
|
||||
if y == 0 {
|
||||
return 0, 0 // beginning of file, stay put
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
// Don't return early for empty line - we'll handle it in whitespace skip
|
||||
}
|
||||
|
||||
// Skip backward through current WORD if we're on one
|
||||
// BUT: if we crossed lines in the "back one" step, we're already at the end of a WORD
|
||||
if y == origY && x >= 0 && line[x] != ' ' && line[x] != '\t' {
|
||||
for x >= 0 && line[x] != ' ' && line[x] != '\t' {
|
||||
x--
|
||||
if x < 0 {
|
||||
if y == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
if x < 0 {
|
||||
return 0, y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip whitespace backward, crossing lines if needed
|
||||
for {
|
||||
for x >= 0 && (line[x] == ' ' || line[x] == '\t') {
|
||||
x--
|
||||
}
|
||||
if x >= 0 {
|
||||
break // landed on a non-whitespace char, this is our WORD end!
|
||||
}
|
||||
if y == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
y--
|
||||
line = buf.Line(y)
|
||||
x = len(line) - 1
|
||||
if len(line) == 0 {
|
||||
return 0, y // empty line acts as a word boundary
|
||||
}
|
||||
}
|
||||
|
||||
// Now x,y is at the start of the target WORD. Move forward to its end.
|
||||
if x >= 0 {
|
||||
for x+1 < len(line) && line[x+1] != ' ' && line[x+1] != '\t' {
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
return x, y
|
||||
}
|
||||
|
||||
// MoveBackwardWORD implements Motion (B) - charwise
|
||||
type MoveBackwardWORD struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveBackwardWORD.Execute: Moves the cursor backward by Count WORDs (B motion).
|
||||
func (a MoveBackwardWORD) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x := win.Cursor.Col
|
||||
y := win.Cursor.Line
|
||||
for i := 0; i < a.Count; i++ {
|
||||
x, y = prevWORDStart(buf, x, y)
|
||||
}
|
||||
win.SetCursorCol(x)
|
||||
win.SetCursorLine(y)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveBackwardWORD.Type: Returns CharwiseExclusive for backward WORD motion.
|
||||
func (a MoveBackwardWORD) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
// MoveBackwardWORD.WithCount: Returns a new MoveBackwardWORD with the given count.
|
||||
func (a MoveBackwardWORD) WithCount(n int) action.Action {
|
||||
return MoveBackwardWORD{Count: n}
|
||||
}
|
||||
|
||||
// MoveBackwardWordEnd implements Motion (ge) - charwise
|
||||
type MoveBackwardWordEnd struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveBackwardWordEnd.Execute: Moves the cursor to the end of the previous word (ge motion).
|
||||
func (a MoveBackwardWordEnd) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x := win.Cursor.Col
|
||||
y := win.Cursor.Line
|
||||
for i := 0; i < a.Count; i++ {
|
||||
x, y = prevWordEnd(buf, x, y)
|
||||
}
|
||||
win.SetCursorCol(x)
|
||||
win.SetCursorLine(y)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveBackwardWordEnd.Type: Returns CharwiseInclusive for backward word-end motion.
|
||||
func (a MoveBackwardWordEnd) Type() core.MotionType { return core.CharwiseInclusive }
|
||||
|
||||
// MoveBackwardWordEnd.WithCount: Returns a new MoveBackwardWordEnd with the given count.
|
||||
func (a MoveBackwardWordEnd) WithCount(n int) action.Action {
|
||||
return MoveBackwardWordEnd{Count: n}
|
||||
}
|
||||
|
||||
// MoveBackwardWORDEnd implements Motion (gE) - charwise
|
||||
type MoveBackwardWORDEnd struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveBackwardWORDEnd.Execute: Moves the cursor to the end of the previous WORD (gE motion).
|
||||
func (a MoveBackwardWORDEnd) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
x := win.Cursor.Col
|
||||
y := win.Cursor.Line
|
||||
for i := 0; i < a.Count; i++ {
|
||||
x, y = prevWORDEnd(buf, x, y)
|
||||
}
|
||||
win.SetCursorCol(x)
|
||||
win.SetCursorLine(y)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveBackwardWORDEnd.Type: Returns CharwiseInclusive for backward WORD-end motion.
|
||||
func (a MoveBackwardWORDEnd) Type() core.MotionType { return core.CharwiseInclusive }
|
||||
|
||||
// MoveBackwardWORDEnd.WithCount: Returns a new MoveBackwardWORDEnd with the given count.
|
||||
func (a MoveBackwardWORDEnd) WithCount(n int) action.Action {
|
||||
return MoveBackwardWORDEnd{Count: n}
|
||||
}
|
||||
|
||||
@ -68,18 +68,18 @@ func changeCharSelection(m action.Model, start, end core.Position) {
|
||||
var deletedText string
|
||||
|
||||
if start.Line == end.Line {
|
||||
line := buf.Line(start.Line)
|
||||
line := buf.Lines[start.Line]
|
||||
endCol := min(end.Col+1, len(line))
|
||||
deletedText = line[start.Col:endCol]
|
||||
buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
|
||||
} else {
|
||||
startLine := buf.Line(start.Line)
|
||||
endLine := buf.Line(end.Line)
|
||||
startLine := buf.Lines[start.Line]
|
||||
endLine := buf.Lines[end.Line]
|
||||
|
||||
// Extract deleted text
|
||||
deletedText = startLine[start.Col:] + "\n"
|
||||
for y := start.Line + 1; y < end.Line; y++ {
|
||||
deletedText += buf.Line(y) + "\n"
|
||||
deletedText += buf.Lines[y] + "\n"
|
||||
}
|
||||
endCol := min(end.Col+1, len(endLine))
|
||||
deletedText += endLine[:endCol]
|
||||
@ -113,7 +113,7 @@ func changeLineSelection(m action.Model, start, end core.Position) {
|
||||
var lines []string
|
||||
|
||||
for i := end.Line; i >= start.Line; i-- {
|
||||
lines = append([]string{buf.Line(i)}, lines...)
|
||||
lines = append([]string{buf.Lines[i]}, lines...)
|
||||
buf.DeleteLine(i)
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ func changeBlockSelection(m action.Model, start, end core.Position) {
|
||||
endCol := max(start.Col, end.Col)
|
||||
|
||||
for y := start.Line; y <= end.Line; y++ {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
if startCol >= len(line) {
|
||||
continue
|
||||
}
|
||||
@ -168,7 +168,7 @@ func (o ChangeOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
|
||||
// Collect lines to delete (always delete at startY since lines shift up)
|
||||
for range opCount {
|
||||
lines = append(lines, buf.Line(startY))
|
||||
lines = append(lines, buf.Lines[startY])
|
||||
buf.DeleteLine(startY)
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ func (o DeleteOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
|
||||
for range opCount {
|
||||
y := win.Cursor.Line
|
||||
lines = append(lines, buf.Line(y))
|
||||
lines = append(lines, buf.Lines[y])
|
||||
|
||||
buf.DeleteLine(y)
|
||||
|
||||
@ -99,12 +99,12 @@ func deleteCharSelection(m action.Model, start, end core.Position) {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
if start.Line == end.Line {
|
||||
line := buf.Line(start.Line)
|
||||
line := buf.Lines[start.Line]
|
||||
endCol := min(end.Col+1, len(line))
|
||||
buf.SetLine(start.Line, line[:start.Col]+line[endCol:])
|
||||
} else {
|
||||
startLine := buf.Line(start.Line)
|
||||
endLine := buf.Line(end.Line)
|
||||
startLine := buf.Lines[start.Line]
|
||||
endLine := buf.Lines[end.Line]
|
||||
|
||||
prefix := startLine[:start.Col]
|
||||
suffix := ""
|
||||
@ -131,7 +131,7 @@ func deleteLineSelection(m action.Model, start, end core.Position) {
|
||||
var lines []string
|
||||
|
||||
for i := end.Line; i >= start.Line; i-- {
|
||||
lines = append(lines, buf.Line(i))
|
||||
lines = append(lines, buf.Lines[i])
|
||||
buf.DeleteLine(i)
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ func deleteBlockSelection(m action.Model, start, end core.Position) {
|
||||
endCol := max(start.Col, end.Col)
|
||||
|
||||
for y := start.Line; y <= end.Line; y++ {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
if startCol >= len(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -30,14 +30,8 @@ func (o YankOperator) Operate(m action.Model, start, end core.Position, mtype co
|
||||
})
|
||||
}
|
||||
|
||||
// Normalize so cursor is set to the earlier position (important for backward motions)
|
||||
cursorPos := start
|
||||
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
|
||||
cursorPos = end
|
||||
}
|
||||
|
||||
win.SetCursorCol(cursorPos.Col)
|
||||
win.SetCursorLine(cursorPos.Line)
|
||||
win.SetCursorCol(start.Col)
|
||||
win.SetCursorLine(start.Line)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -57,7 +51,7 @@ func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
var lines []string
|
||||
|
||||
for i := range opCount {
|
||||
lines = append(lines, buf.Line(y+i))
|
||||
lines = append(lines, buf.Lines[y+i])
|
||||
}
|
||||
|
||||
// Put her in the register!
|
||||
@ -72,7 +66,17 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
|
||||
|
||||
switch {
|
||||
case mtype.IsCharwise():
|
||||
line := buf.Line(start.Line)
|
||||
// This shouldn't happen
|
||||
// if start.Line != end.Line {
|
||||
// m.SetCommandOutput(&core.CommandOutput{
|
||||
// Lines: []string{"Start line and end line must match for charwise yank operations."},
|
||||
// Inline: true,
|
||||
// IsError: true,
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
line := buf.Lines[start.Line]
|
||||
|
||||
startX := min(start.Col, end.Col)
|
||||
endX := max(start.Col, end.Col)
|
||||
@ -87,19 +91,22 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
|
||||
cnt := line[startX:endX]
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
|
||||
|
||||
win := m.ActiveWindow()
|
||||
win.SetCursorCol(startX)
|
||||
win.SetCursorLine(start.Line)
|
||||
|
||||
case mtype == core.Linewise:
|
||||
// This shouldn't happen
|
||||
// if start.Col != end.Col {
|
||||
// m.SetCommandOutput(&core.CommandOutput{
|
||||
// Lines: []string{"Start column and end column must match for linewise yank operations."},
|
||||
// Inline: true,
|
||||
// IsError: true,
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
// These don't need to be validated, they are validated before being passed into the function
|
||||
startY := min(start.Line, end.Line)
|
||||
endY := max(start.Line, end.Line)
|
||||
|
||||
var cnt []string
|
||||
for i := startY; i <= endY; i++ {
|
||||
cnt = append(cnt, buf.Line(i))
|
||||
}
|
||||
cnt := buf.Lines[startY : endY+1]
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
|
||||
}
|
||||
}
|
||||
@ -115,7 +122,7 @@ func yankVisualMode(m action.Model, start, end core.Position) {
|
||||
|
||||
// Single line selection
|
||||
if start.Line == end.Line {
|
||||
line := buf.Line(start.Line)
|
||||
line := buf.Lines[start.Line]
|
||||
endCol := min(end.Col+1, len(line)) // +1 because visual selection is inclusive
|
||||
startCol := min(start.Col, len(line))
|
||||
cnt := line[startCol:endCol]
|
||||
@ -127,17 +134,17 @@ func yankVisualMode(m action.Model, start, end core.Position) {
|
||||
var content []string
|
||||
|
||||
// First line: from start.Col to end of line
|
||||
firstLine := buf.Line(start.Line)
|
||||
firstLine := buf.Lines[start.Line]
|
||||
startCol := min(start.Col, len(firstLine))
|
||||
content = append(content, firstLine[startCol:])
|
||||
|
||||
// Middle lines: entire lines
|
||||
for y := start.Line + 1; y < end.Line; y++ {
|
||||
content = append(content, buf.Line(y))
|
||||
content = append(content, buf.Lines[y])
|
||||
}
|
||||
|
||||
// Last line: from beginning to end.Col (inclusive)
|
||||
lastLine := buf.Line(end.Line)
|
||||
lastLine := buf.Lines[end.Line]
|
||||
endCol := min(end.Col+1, len(lastLine))
|
||||
content = append(content, lastLine[:endCol])
|
||||
|
||||
@ -162,10 +169,7 @@ func yankVisualLineMode(m action.Model, start, end core.Position) {
|
||||
startY := min(start.Line, end.Line)
|
||||
endY := max(start.Line, end.Line)
|
||||
|
||||
var cnt []string
|
||||
for i := startY; i <= endY; i++ {
|
||||
cnt = append(cnt, buf.Line(i))
|
||||
}
|
||||
cnt := buf.Lines[startY : endY+1]
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
|
||||
|
||||
}
|
||||
@ -183,7 +187,7 @@ func yankVisualBlockMode(m action.Model, start, end core.Position) {
|
||||
var content []string
|
||||
|
||||
for y := startY; y <= endY; y++ {
|
||||
line := buf.Line(y)
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Handle lines shorter than the block selection
|
||||
if startX >= len(line) {
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
package style
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
@ -15,7 +12,6 @@ type Styles struct {
|
||||
CursorNormal lipgloss.Style
|
||||
CursorInsert lipgloss.Style
|
||||
CursorCommand lipgloss.Style
|
||||
CursorReplace lipgloss.Style
|
||||
|
||||
// Gutter (line numbers)
|
||||
Gutter lipgloss.Style
|
||||
@ -48,7 +44,6 @@ func DefaultStyles() Styles {
|
||||
CursorNormal: lipgloss.NewStyle().Reverse(true),
|
||||
CursorInsert: lipgloss.NewStyle().Underline(true),
|
||||
CursorCommand: lipgloss.NewStyle().Reverse(true),
|
||||
CursorReplace: lipgloss.NewStyle().Underline(true),
|
||||
|
||||
Gutter: lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("236")).
|
||||
@ -97,17 +92,12 @@ func ChromaStyles(chromaStyle *chroma.Style) Styles {
|
||||
|
||||
CursorInsert: lipgloss.NewStyle().
|
||||
Background(lipgloss.Color(bgString)).
|
||||
Bold(true).
|
||||
Underline(true),
|
||||
|
||||
CursorCommand: lipgloss.NewStyle().
|
||||
Background(lipgloss.Color(bgString)).
|
||||
Reverse(true),
|
||||
|
||||
CursorReplace: lipgloss.NewStyle().
|
||||
Background(lipgloss.Color(bgString)).
|
||||
Underline(true),
|
||||
|
||||
Gutter: lipgloss.NewStyle().
|
||||
Background(lipgloss.Color(
|
||||
darkenColor(lineNumbers.Background, 0.9).String()),
|
||||
@ -170,8 +160,6 @@ func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
|
||||
return s.CursorInsert
|
||||
case core.CommandMode:
|
||||
return s.CursorCommand
|
||||
case core.ReplaceMode:
|
||||
return s.CursorReplace
|
||||
default:
|
||||
return s.CursorNormal
|
||||
}
|
||||
@ -186,11 +174,6 @@ func (s Styles) CursorStyle(mode core.Mode, style lipgloss.Style) lipgloss.Style
|
||||
return lipgloss.NewStyle().
|
||||
Background(style.GetForeground()).
|
||||
Foreground(style.GetBackground())
|
||||
case core.ReplaceMode, core.WaitingMode:
|
||||
return lipgloss.NewStyle().
|
||||
Background(style.GetBackground()).
|
||||
Foreground(style.GetForeground()).
|
||||
Underline(true)
|
||||
default:
|
||||
return lipgloss.NewStyle().
|
||||
Background(s.BackgroundStyle.GetBackground()).
|
||||
@ -250,33 +233,3 @@ func darkenColor(c chroma.Colour, factor float64) chroma.Colour {
|
||||
b := uint8(float64(c.Blue()) * factor)
|
||||
return chroma.NewColour(r, g, b)
|
||||
}
|
||||
|
||||
// GetLexer: Uses buffer meta data or content to pick a lexer for use in applying
|
||||
// highlights.
|
||||
func GetLexer(buf *core.Buffer) chroma.Lexer {
|
||||
var lexer chroma.Lexer
|
||||
|
||||
if buf.Filetype != "" {
|
||||
lexer = lexers.Get(strings.TrimPrefix(buf.Filetype, "."))
|
||||
}
|
||||
|
||||
if lexer == nil && buf.Filename != "" {
|
||||
lexer = lexers.Match(buf.Filename)
|
||||
}
|
||||
|
||||
if lexer == nil && len(buf.Lines) > 0 {
|
||||
// Get first few lines for content analysis
|
||||
var content strings.Builder
|
||||
for i := 0; i < min(len(buf.Lines), 10); i++ {
|
||||
content.WriteString(buf.Lines[i].String() + "\n")
|
||||
}
|
||||
lexer = lexers.Analyse(content.String())
|
||||
}
|
||||
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
|
||||
lexer = chroma.Coalesce(lexer) // Merge tokens together
|
||||
return lexer
|
||||
}
|
||||
|
||||
@ -75,14 +75,8 @@ func (to Delimiter) GetRange(m action.Model, cursor core.Position, modifier stri
|
||||
return cursor, cursor, core.CharwiseExclusive
|
||||
}
|
||||
|
||||
// Convert buffer lines to strings for delimiter finding
|
||||
var lines []string
|
||||
for i := 0; i < buf.LineCount(); i++ {
|
||||
lines = append(lines, buf.Line(i))
|
||||
}
|
||||
|
||||
// Use multi-line delimiter pair finding
|
||||
start, end, found := findMultiLineDelimiterPair(lines, startDelim, endDelim, cursor, modifier == "a")
|
||||
start, end, found := findMultiLineDelimiterPair(buf.Lines, startDelim, endDelim, cursor, modifier == "a")
|
||||
|
||||
if !found {
|
||||
return cursor, cursor, core.CharwiseExclusive
|
||||
|
||||
@ -10,7 +10,7 @@ type Word struct{}
|
||||
|
||||
func (to Word) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
|
||||
buf := m.ActiveBuffer()
|
||||
line := buf.Line(cursor.Line)
|
||||
line := buf.Lines[cursor.Line]
|
||||
|
||||
// Find word boundaries
|
||||
start := findWordStart(line, cursor.Col)
|
||||
@ -28,7 +28,7 @@ type WORD struct{}
|
||||
|
||||
func (to WORD) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
|
||||
buf := m.ActiveBuffer()
|
||||
line := buf.Line(cursor.Line)
|
||||
line := buf.Lines[cursor.Line]
|
||||
|
||||
// Find word boundaries
|
||||
start := findWORDStart(line, cursor.Col)
|
||||
|
||||
25
qodo.md
25
qodo.md
@ -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."`
|
||||
Loading…
x
Reference in New Issue
Block a user