Compare commits
2 Commits
5ff473d0d9
...
a01369f407
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01369f407 | ||
|
|
3c98dca777 |
@ -11,6 +11,7 @@ type ExitCommandMode struct{}
|
|||||||
// ExitCommandMode.Execute: Exits command mode and returns to normal mode (Esc key).
|
// ExitCommandMode.Execute: Exits command mode and returns to normal mode (Esc key).
|
||||||
func (a ExitCommandMode) Execute(m Model) tea.Cmd {
|
func (a ExitCommandMode) Execute(m Model) tea.Cmd {
|
||||||
m.SetCommandCursor(0)
|
m.SetCommandCursor(0)
|
||||||
|
m.SetCommandHistoryCursor(0)
|
||||||
m.SetCommand("")
|
m.SetCommand("")
|
||||||
m.SetCommandOutput(&core.CommandOutput{})
|
m.SetCommandOutput(&core.CommandOutput{})
|
||||||
m.SetMode(core.NormalMode)
|
m.SetMode(core.NormalMode)
|
||||||
@ -127,12 +128,17 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
|||||||
|
|
||||||
// Clear command state and return to normal mode
|
// Clear command state and return to normal mode
|
||||||
m.SetCommandCursor(0)
|
m.SetCommandCursor(0)
|
||||||
|
m.SetCommandHistoryCursor(0)
|
||||||
m.SetMode(core.NormalMode)
|
m.SetMode(core.NormalMode)
|
||||||
|
|
||||||
if a.Registry == nil || cmdLine == "" {
|
if a.Registry == nil || cmdLine == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
history := append([]string{cmdLine}, m.CommandHistory()...)
|
||||||
|
// history = append([]string{cmdLine}, history...)
|
||||||
|
m.SetCommandHistory(history)
|
||||||
|
|
||||||
cmd, err := a.Registry.Execute(m, cmdLine)
|
cmd, err := a.Registry.Execute(m, cmdLine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out := core.CommandOutput{
|
out := core.CommandOutput{
|
||||||
|
|||||||
133
internal/action/command_history_test.go
Normal file
133
internal/action/command_history_test.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mockRegistry for testing command execution without actual command handlers
|
||||||
|
type mockRegistry struct{}
|
||||||
|
|
||||||
|
func (r *mockRegistry) Execute(m Model, cmdLine string) (tea.Cmd, error) {
|
||||||
|
// Mock implementation - just returns nil
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCommandExecuteUpdatesHistory tests that executing commands adds them to history
|
||||||
|
func TestCommandExecuteUpdatesHistory(t *testing.T) {
|
||||||
|
t.Run("first command added to empty history", func(t *testing.T) {
|
||||||
|
m := NewMockModel()
|
||||||
|
m.ModeVal = core.CommandMode
|
||||||
|
m.CommandVal = "w test.txt"
|
||||||
|
m.CommandHistoryList = []string{}
|
||||||
|
m.CommandHistoryCur = 0
|
||||||
|
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
action := CommandExecute{Registry: registry}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
history := m.CommandHistory()
|
||||||
|
if len(history) != 1 {
|
||||||
|
t.Errorf("History length = %d, want 1", len(history))
|
||||||
|
}
|
||||||
|
|
||||||
|
if history[0] != "w test.txt" {
|
||||||
|
t.Errorf("History[0] = %q, want %q", history[0], "w test.txt")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple commands prepended to history", func(t *testing.T) {
|
||||||
|
m := NewMockModel()
|
||||||
|
m.ModeVal = core.CommandMode
|
||||||
|
m.CommandVal = "w file1.txt"
|
||||||
|
m.CommandHistoryList = []string{}
|
||||||
|
m.CommandHistoryCur = 0
|
||||||
|
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
action := CommandExecute{Registry: registry}
|
||||||
|
|
||||||
|
// Execute first command
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Execute second command
|
||||||
|
m.CommandVal = "q"
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Execute third command
|
||||||
|
m.CommandVal = "set number"
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
history := m.CommandHistory()
|
||||||
|
if len(history) != 3 {
|
||||||
|
t.Errorf("History length = %d, want 3", len(history))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most recent should be first (prepended)
|
||||||
|
want := []string{"set number", "q", "w file1.txt"}
|
||||||
|
for i, cmd := range want {
|
||||||
|
if history[i] != cmd {
|
||||||
|
t.Errorf("History[%d] = %q, want %q", i, history[i], cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty command not added to history", func(t *testing.T) {
|
||||||
|
m := NewMockModel()
|
||||||
|
m.ModeVal = core.CommandMode
|
||||||
|
m.CommandVal = ""
|
||||||
|
m.CommandHistoryList = []string{"previous command"}
|
||||||
|
m.CommandHistoryCur = 0
|
||||||
|
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
action := CommandExecute{Registry: registry}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
history := m.CommandHistory()
|
||||||
|
if len(history) != 1 {
|
||||||
|
t.Errorf("History length = %d, want 1 (empty command should not be added)", len(history))
|
||||||
|
}
|
||||||
|
|
||||||
|
if history[0] != "previous command" {
|
||||||
|
t.Errorf("History[0] = %q, want %q", history[0], "previous command")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("history cursor resets on execute", func(t *testing.T) {
|
||||||
|
m := NewMockModel()
|
||||||
|
m.ModeVal = core.CommandMode
|
||||||
|
m.CommandVal = "w"
|
||||||
|
m.CommandHistoryList = []string{}
|
||||||
|
m.CommandHistoryCur = 5 // Set to non-zero
|
||||||
|
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
action := CommandExecute{Registry: registry}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 0 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 0 after execute", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("duplicate commands are added to history", func(t *testing.T) {
|
||||||
|
m := NewMockModel()
|
||||||
|
m.ModeVal = core.CommandMode
|
||||||
|
m.CommandVal = "w"
|
||||||
|
m.CommandHistoryList = []string{"w"}
|
||||||
|
m.CommandHistoryCur = 0
|
||||||
|
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
action := CommandExecute{Registry: registry}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
history := m.CommandHistory()
|
||||||
|
if len(history) != 2 {
|
||||||
|
t.Errorf("History length = %d, want 2 (duplicates should be added)", len(history))
|
||||||
|
}
|
||||||
|
|
||||||
|
if history[0] != "w" || history[1] != "w" {
|
||||||
|
t.Errorf("History = %v, want ['w', 'w']", history)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -4,124 +4,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ==================================================
|
|
||||||
// Mock Model Implementation
|
|
||||||
// ==================================================
|
|
||||||
|
|
||||||
type mockModel struct {
|
|
||||||
windows []*core.Window
|
|
||||||
activeWindow *core.Window
|
|
||||||
buffers []*core.Buffer
|
|
||||||
settings core.EditorSettings
|
|
||||||
mode core.Mode
|
|
||||||
registers map[rune]core.Register
|
|
||||||
insertKeys []string
|
|
||||||
command string
|
|
||||||
commandCursor int
|
|
||||||
commandOutput *core.CommandOutput
|
|
||||||
lastFind core.LastFindCommand
|
|
||||||
styles style.Styles
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockModel() *mockModel {
|
|
||||||
buf := core.NewBufferBuilder().
|
|
||||||
WithLines([]string{""}).
|
|
||||||
Build()
|
|
||||||
|
|
||||||
win := core.NewWindowBuilder().
|
|
||||||
WithBuffer(&buf).
|
|
||||||
WithHeight(24).
|
|
||||||
WithWidth(80).
|
|
||||||
Build()
|
|
||||||
|
|
||||||
return &mockModel{
|
|
||||||
windows: []*core.Window{&win},
|
|
||||||
activeWindow: &win,
|
|
||||||
buffers: []*core.Buffer{&buf},
|
|
||||||
settings: core.NewDefaultSettings(),
|
|
||||||
mode: core.NormalMode,
|
|
||||||
registers: core.DefaultRegisters(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockModelWithBuffer(buf *core.Buffer) *mockModel {
|
|
||||||
win := core.NewWindowBuilder().
|
|
||||||
WithBuffer(buf).
|
|
||||||
WithHeight(24).
|
|
||||||
WithWidth(80).
|
|
||||||
Build()
|
|
||||||
|
|
||||||
return &mockModel{
|
|
||||||
windows: []*core.Window{&win},
|
|
||||||
activeWindow: &win,
|
|
||||||
buffers: []*core.Buffer{buf},
|
|
||||||
settings: core.NewDefaultSettings(),
|
|
||||||
mode: core.NormalMode,
|
|
||||||
registers: core.DefaultRegisters(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockModelWithWindow(win *core.Window) *mockModel {
|
|
||||||
return &mockModel{
|
|
||||||
windows: []*core.Window{win},
|
|
||||||
activeWindow: win,
|
|
||||||
buffers: []*core.Buffer{win.Buffer},
|
|
||||||
settings: core.NewDefaultSettings(),
|
|
||||||
mode: core.NormalMode,
|
|
||||||
registers: core.DefaultRegisters(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core Data Access
|
|
||||||
func (m *mockModel) Windows() []*core.Window { return m.windows }
|
|
||||||
func (m *mockModel) ActiveWindow() *core.Window { return m.activeWindow }
|
|
||||||
func (m *mockModel) Buffers() []*core.Buffer { return m.buffers }
|
|
||||||
func (m *mockModel) SetBuffers(bufs []*core.Buffer) { m.buffers = bufs }
|
|
||||||
func (m *mockModel) ActiveBuffer() *core.Buffer { return m.activeWindow.Buffer }
|
|
||||||
|
|
||||||
// Insert Mode State
|
|
||||||
func (m *mockModel) InsertKeys() []string { return m.insertKeys }
|
|
||||||
func (m *mockModel) SetInsertKeys(keys []string) { m.insertKeys = keys }
|
|
||||||
func (m *mockModel) SetInsertRecording(count int, action Action) {}
|
|
||||||
func (m *mockModel) ExitInsertMode() {}
|
|
||||||
func (m *mockModel) SetLastFind(char string, forward, inclusive bool) {
|
|
||||||
m.lastFind = core.LastFindCommand{Char: char, Forward: forward, Inclusive: inclusive}
|
|
||||||
}
|
|
||||||
func (m *mockModel) GetLastFind() *core.LastFindCommand { return &m.lastFind }
|
|
||||||
|
|
||||||
// Command Mode State
|
|
||||||
func (m *mockModel) Command() string { return m.command }
|
|
||||||
func (m *mockModel) SetCommand(cmd string) { m.command = cmd }
|
|
||||||
func (m *mockModel) CommandCursor() int { return m.commandCursor }
|
|
||||||
func (m *mockModel) SetCommandCursor(cur int) { m.commandCursor = cur }
|
|
||||||
func (m *mockModel) CommandOutput() *core.CommandOutput { return m.commandOutput }
|
|
||||||
func (m *mockModel) SetCommandOutput(out *core.CommandOutput) { m.commandOutput = out }
|
|
||||||
|
|
||||||
// Editor-wide State
|
|
||||||
func (m *mockModel) Mode() core.Mode { return m.mode }
|
|
||||||
func (m *mockModel) SetMode(mode core.Mode) { m.mode = mode }
|
|
||||||
func (m *mockModel) Settings() core.EditorSettings { return m.settings }
|
|
||||||
func (m *mockModel) SetSettings(s core.EditorSettings) { m.settings = s }
|
|
||||||
func (m *mockModel) Styles() style.Styles { return m.styles }
|
|
||||||
func (m *mockModel) SetStyles(s style.Styles) { m.styles = s }
|
|
||||||
|
|
||||||
// Registers
|
|
||||||
func (m *mockModel) Registers() map[rune]core.Register { return m.registers }
|
|
||||||
func (m *mockModel) GetRegister(name rune) (core.Register, bool) {
|
|
||||||
reg, ok := m.registers[name]
|
|
||||||
return reg, ok
|
|
||||||
}
|
|
||||||
func (m *mockModel) SetRegister(name rune, t core.RegisterType, cnt []string) error {
|
|
||||||
m.registers[name] = core.Register{Type: t, Content: cnt}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (m *mockModel) UpdateDefaultRegister(t core.RegisterType, cnt []string) {
|
|
||||||
m.registers['"'] = core.Register{Type: t, Content: cnt}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// f (find forward inclusive) Tests
|
// f (find forward inclusive) Tests
|
||||||
// ==================================================
|
// ==================================================
|
||||||
@ -137,7 +21,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0). // At 'h'
|
WithCursorPos(0, 0). // At 'h'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -164,7 +48,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 4). // At first 'o'
|
WithCursorPos(0, 4). // At first 'o'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -191,7 +75,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "x",
|
Char: "x",
|
||||||
@ -218,7 +102,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "d",
|
Char: "d",
|
||||||
@ -245,7 +129,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 5). // At space after 'hello'
|
WithCursorPos(0, 5). // At space after 'hello'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -272,7 +156,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -299,7 +183,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -326,7 +210,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: " ",
|
Char: " ",
|
||||||
@ -353,7 +237,7 @@ func TestFindCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "b",
|
Char: "b",
|
||||||
@ -386,7 +270,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10). // At 'd'
|
WithCursorPos(0, 10). // At 'd'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -413,7 +297,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 7). // At 'o' in 'world'
|
WithCursorPos(0, 7). // At 'o' in 'world'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -440,7 +324,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "x",
|
Char: "x",
|
||||||
@ -467,7 +351,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "h",
|
Char: "h",
|
||||||
@ -494,7 +378,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 6). // At 'w'
|
WithCursorPos(0, 6). // At 'w'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -521,7 +405,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -548,7 +432,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -575,7 +459,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: " ",
|
Char: " ",
|
||||||
@ -602,7 +486,7 @@ func TestFindCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 9). // At last 'b'
|
WithCursorPos(0, 9). // At last 'b'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -635,7 +519,7 @@ func TestTillCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0). // At 'h'
|
WithCursorPos(0, 0). // At 'h'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -662,7 +546,7 @@ func TestTillCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "x",
|
Char: "x",
|
||||||
@ -689,7 +573,7 @@ func TestTillCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0). // At 'a'
|
WithCursorPos(0, 0). // At 'a'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "b",
|
Char: "b",
|
||||||
@ -717,7 +601,7 @@ func TestTillCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "d",
|
Char: "d",
|
||||||
@ -744,7 +628,7 @@ func TestTillCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -771,7 +655,7 @@ func TestTillCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: " ",
|
Char: " ",
|
||||||
@ -798,7 +682,7 @@ func TestTillCharForward(t *testing.T) {
|
|||||||
WithCursorPos(0, 5). // At space
|
WithCursorPos(0, 5). // At space
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -831,7 +715,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10). // At 'd'
|
WithCursorPos(0, 10). // At 'd'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -858,7 +742,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "x",
|
Char: "x",
|
||||||
@ -885,7 +769,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 1). // At 'b'
|
WithCursorPos(0, 1). // At 'b'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -913,7 +797,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "h",
|
Char: "h",
|
||||||
@ -940,7 +824,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -967,7 +851,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: " ",
|
Char: " ",
|
||||||
@ -994,7 +878,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 6). // At 'w'
|
WithCursorPos(0, 6). // At 'w'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "o",
|
Char: "o",
|
||||||
@ -1021,7 +905,7 @@ func TestTillCharBackward(t *testing.T) {
|
|||||||
WithCursorPos(0, 9). // At last 'b'
|
WithCursorPos(0, 9). // At last 'b'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -1054,7 +938,7 @@ func TestFindCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0). // At 'h'
|
WithCursorPos(0, 0). // At 'h'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1085,7 +969,7 @@ func TestFindCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1116,7 +1000,7 @@ func TestFindCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1143,7 +1027,7 @@ func TestFindCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "w",
|
Char: "w",
|
||||||
@ -1170,7 +1054,7 @@ func TestFindCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -1198,7 +1082,7 @@ func TestFindCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 4). // At first 'b'
|
WithCursorPos(0, 4). // At first 'b'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -1235,7 +1119,7 @@ func TestFindCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10). // At 'd'
|
WithCursorPos(0, 10). // At 'd'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1266,7 +1150,7 @@ func TestFindCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1298,7 +1182,7 @@ func TestFindCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1325,7 +1209,7 @@ func TestFindCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10). // At last 'b'
|
WithCursorPos(0, 10). // At last 'b'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "b",
|
Char: "b",
|
||||||
@ -1353,7 +1237,7 @@ func TestFindCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 6). // At middle 'b'
|
WithCursorPos(0, 6). // At middle 'b'
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -1390,7 +1274,7 @@ func TestTillCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1417,7 +1301,7 @@ func TestTillCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1444,7 +1328,7 @@ func TestTillCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1471,7 +1355,7 @@ func TestTillCharForwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 0).
|
WithCursorPos(0, 0).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "a",
|
Char: "a",
|
||||||
@ -1505,7 +1389,7 @@ func TestTillCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1532,7 +1416,7 @@ func TestTillCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1559,7 +1443,7 @@ func TestTillCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "l",
|
Char: "l",
|
||||||
@ -1586,7 +1470,7 @@ func TestTillCharBackwardWithCount(t *testing.T) {
|
|||||||
WithCursorPos(0, 10).
|
WithCursorPos(0, 10).
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
|
|
||||||
action := FindChar{
|
action := FindChar{
|
||||||
Char: "b",
|
Char: "b",
|
||||||
@ -1725,7 +1609,7 @@ func TestRepeatFind_Semicolon_After_f(t *testing.T) {
|
|||||||
t.Run("basic: lands on next inclusive match", func(t *testing.T) {
|
t.Run("basic: lands on next inclusive match", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1739,7 +1623,7 @@ func TestRepeatFind_Semicolon_After_f(t *testing.T) {
|
|||||||
t.Run("no further match: cursor stays", func(t *testing.T) {
|
t.Run("no further match: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1753,7 +1637,7 @@ func TestRepeatFind_Semicolon_After_f(t *testing.T) {
|
|||||||
t.Run("cursor at end of line: no move", func(t *testing.T) {
|
t.Run("cursor at end of line: no move", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 10).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 10).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1769,7 +1653,7 @@ func TestRepeatFind_Semicolon_After_f(t *testing.T) {
|
|||||||
t.Run("count=2 skips first match lands on second", func(t *testing.T) {
|
t.Run("count=2 skips first match lands on second", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"aXbXcX"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"aXbXcX"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("X", true, true)
|
m.SetLastFind("X", true, true)
|
||||||
|
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 2, Repeated: true}.Execute(m)
|
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 2, Repeated: true}.Execute(m)
|
||||||
@ -1783,7 +1667,7 @@ func TestRepeatFind_Semicolon_After_f(t *testing.T) {
|
|||||||
t.Run("does not overwrite lastFind when Repeated", func(t *testing.T) {
|
t.Run("does not overwrite lastFind when Repeated", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1804,7 +1688,7 @@ func TestRepeatFind_Semicolon_After_F(t *testing.T) {
|
|||||||
t.Run("basic: lands on previous inclusive match", func(t *testing.T) {
|
t.Run("basic: lands on previous inclusive match", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, true)
|
m.SetLastFind("o", false, true)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1818,7 +1702,7 @@ func TestRepeatFind_Semicolon_After_F(t *testing.T) {
|
|||||||
t.Run("no earlier match: cursor stays", func(t *testing.T) {
|
t.Run("no earlier match: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, true)
|
m.SetLastFind("o", false, true)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1832,7 +1716,7 @@ func TestRepeatFind_Semicolon_After_F(t *testing.T) {
|
|||||||
t.Run("cursor at start of line: no move", func(t *testing.T) {
|
t.Run("cursor at start of line: no move", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, true)
|
m.SetLastFind("o", false, true)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1847,7 +1731,7 @@ func TestRepeatFind_Semicolon_After_F(t *testing.T) {
|
|||||||
t.Run("count=2 backward skips one lands on second", func(t *testing.T) {
|
t.Run("count=2 backward skips one lands on second", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"XaXbX"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"XaXbX"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("X", false, true)
|
m.SetLastFind("X", false, true)
|
||||||
|
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 2, Repeated: true}.Execute(m)
|
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 2, Repeated: true}.Execute(m)
|
||||||
@ -1870,7 +1754,7 @@ func TestRepeatFind_Semicolon_After_t(t *testing.T) {
|
|||||||
t.Run("basic: skips adjacent target, lands before next", func(t *testing.T) {
|
t.Run("basic: skips adjacent target, lands before next", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 3).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 3).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, false)
|
m.SetLastFind("o", true, false)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1885,7 +1769,7 @@ func TestRepeatFind_Semicolon_After_t(t *testing.T) {
|
|||||||
t.Run("no further match after second repeat: cursor stays", func(t *testing.T) {
|
t.Run("no further match after second repeat: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, false)
|
m.SetLastFind("o", true, false)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1901,7 +1785,7 @@ func TestRepeatFind_Semicolon_After_t(t *testing.T) {
|
|||||||
t.Run("col+2 out of bounds: no move", func(t *testing.T) {
|
t.Run("col+2 out of bounds: no move", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"Xo"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"Xo"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, false)
|
m.SetLastFind("o", true, false)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1918,7 +1802,7 @@ func TestRepeatFind_Semicolon_After_t(t *testing.T) {
|
|||||||
t.Run("three chained repeats advance correctly", func(t *testing.T) {
|
t.Run("three chained repeats advance correctly", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"aXbXcXd"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"aXbXcXd"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("X", true, false)
|
m.SetLastFind("X", true, false)
|
||||||
|
|
||||||
// First repeat
|
// First repeat
|
||||||
@ -1946,7 +1830,7 @@ func TestRepeatFind_Semicolon_After_t(t *testing.T) {
|
|||||||
t.Run("count=2 repeated exclusive forward", func(t *testing.T) {
|
t.Run("count=2 repeated exclusive forward", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"aXbXcX"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"aXbXcX"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("X", true, false)
|
m.SetLastFind("X", true, false)
|
||||||
|
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 2, Repeated: true}.Execute(m)
|
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 2, Repeated: true}.Execute(m)
|
||||||
@ -1968,7 +1852,7 @@ func TestRepeatFind_Semicolon_After_T(t *testing.T) {
|
|||||||
t.Run("basic: skips adjacent target, lands after previous", func(t *testing.T) {
|
t.Run("basic: skips adjacent target, lands after previous", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, false)
|
m.SetLastFind("o", false, false)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1982,7 +1866,7 @@ func TestRepeatFind_Semicolon_After_T(t *testing.T) {
|
|||||||
t.Run("no earlier match after second repeat: cursor stays", func(t *testing.T) {
|
t.Run("no earlier match after second repeat: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 5).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 5).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, false)
|
m.SetLastFind("o", false, false)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -1997,7 +1881,7 @@ func TestRepeatFind_Semicolon_After_T(t *testing.T) {
|
|||||||
buf := core.NewBufferBuilder().WithLines([]string{"oX"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"oX"}).Build()
|
||||||
// Cursor at col 1 (as if `TX` landed at x+1=1 where x=0).
|
// Cursor at col 1 (as if `TX` landed at x+1=1 where x=0).
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("X", false, false)
|
m.SetLastFind("X", false, false)
|
||||||
|
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -2015,7 +1899,7 @@ func TestRepeatFind_Semicolon_After_T(t *testing.T) {
|
|||||||
t.Run("three chained repeats advance correctly backward", func(t *testing.T) {
|
t.Run("three chained repeats advance correctly backward", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"dXcXbXa"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"dXcXbXa"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("X", false, false)
|
m.SetLastFind("X", false, false)
|
||||||
|
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -2041,7 +1925,7 @@ func TestRepeatFind_Semicolon_After_T(t *testing.T) {
|
|||||||
t.Run("count=2 repeated exclusive backward", func(t *testing.T) {
|
t.Run("count=2 repeated exclusive backward", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"dXcXbXa"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"dXcXbXa"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("X", false, false)
|
m.SetLastFind("X", false, false)
|
||||||
|
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 2, Repeated: true}.Execute(m)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 2, Repeated: true}.Execute(m)
|
||||||
@ -2084,7 +1968,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
|
|
||||||
// Simulate with two sequential ; presses
|
// Simulate with two sequential ; presses
|
||||||
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
||||||
m1 := newMockModelWithWindow(&win1)
|
m1 := NewMockModelWithWindow(&win1)
|
||||||
m1.SetLastFind("X", true, true)
|
m1.SetLastFind("X", true, true)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
||||||
@ -2092,7 +1976,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
|
|
||||||
// Simulate with a single 2; (Count=2)
|
// Simulate with a single 2; (Count=2)
|
||||||
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
||||||
m2 := newMockModelWithWindow(&win2)
|
m2 := NewMockModelWithWindow(&win2)
|
||||||
m2.SetLastFind("X", true, true)
|
m2.SetLastFind("X", true, true)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 2, Repeated: true}.Execute(m2)
|
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 2, Repeated: true}.Execute(m2)
|
||||||
counted := m2.ActiveWindow().Cursor.Col
|
counted := m2.ActiveWindow().Cursor.Col
|
||||||
@ -2107,7 +1991,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
||||||
|
|
||||||
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
||||||
m1 := newMockModelWithWindow(&win1)
|
m1 := NewMockModelWithWindow(&win1)
|
||||||
m1.SetLastFind("X", true, true)
|
m1.SetLastFind("X", true, true)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
||||||
@ -2115,7 +1999,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
threeSemicolons := m1.ActiveWindow().Cursor.Col
|
threeSemicolons := m1.ActiveWindow().Cursor.Col
|
||||||
|
|
||||||
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 1).Build()
|
||||||
m2 := newMockModelWithWindow(&win2)
|
m2 := NewMockModelWithWindow(&win2)
|
||||||
m2.SetLastFind("X", true, true)
|
m2.SetLastFind("X", true, true)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 3, Repeated: true}.Execute(m2)
|
FindChar{Char: "X", Forward: true, Inclusive: true, Count: 3, Repeated: true}.Execute(m2)
|
||||||
counted := m2.ActiveWindow().Cursor.Col
|
counted := m2.ActiveWindow().Cursor.Col
|
||||||
@ -2135,14 +2019,14 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
||||||
|
|
||||||
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
||||||
m1 := newMockModelWithWindow(&win1)
|
m1 := NewMockModelWithWindow(&win1)
|
||||||
m1.SetLastFind("X", false, true)
|
m1.SetLastFind("X", false, true)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 1, Repeated: true}.Execute(m1)
|
||||||
twoSemicolons := m1.ActiveWindow().Cursor.Col
|
twoSemicolons := m1.ActiveWindow().Cursor.Col
|
||||||
|
|
||||||
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
||||||
m2 := newMockModelWithWindow(&win2)
|
m2 := NewMockModelWithWindow(&win2)
|
||||||
m2.SetLastFind("X", false, true)
|
m2.SetLastFind("X", false, true)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 2, Repeated: true}.Execute(m2)
|
FindChar{Char: "X", Forward: false, Inclusive: true, Count: 2, Repeated: true}.Execute(m2)
|
||||||
counted := m2.ActiveWindow().Cursor.Col
|
counted := m2.ActiveWindow().Cursor.Col
|
||||||
@ -2163,14 +2047,14 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
||||||
|
|
||||||
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m1 := newMockModelWithWindow(&win1)
|
m1 := NewMockModelWithWindow(&win1)
|
||||||
m1.SetLastFind("X", true, false)
|
m1.SetLastFind("X", true, false)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
twoSemicolons := m1.ActiveWindow().Cursor.Col
|
twoSemicolons := m1.ActiveWindow().Cursor.Col
|
||||||
|
|
||||||
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m2 := newMockModelWithWindow(&win2)
|
m2 := NewMockModelWithWindow(&win2)
|
||||||
m2.SetLastFind("X", true, false)
|
m2.SetLastFind("X", true, false)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 2, Repeated: true}.Execute(m2)
|
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 2, Repeated: true}.Execute(m2)
|
||||||
counted := m2.ActiveWindow().Cursor.Col
|
counted := m2.ActiveWindow().Cursor.Col
|
||||||
@ -2185,7 +2069,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
||||||
|
|
||||||
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m1 := newMockModelWithWindow(&win1)
|
m1 := NewMockModelWithWindow(&win1)
|
||||||
m1.SetLastFind("X", true, false)
|
m1.SetLastFind("X", true, false)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
@ -2193,7 +2077,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
threeSemicolons := m1.ActiveWindow().Cursor.Col
|
threeSemicolons := m1.ActiveWindow().Cursor.Col
|
||||||
|
|
||||||
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 0).Build()
|
||||||
m2 := newMockModelWithWindow(&win2)
|
m2 := NewMockModelWithWindow(&win2)
|
||||||
m2.SetLastFind("X", true, false)
|
m2.SetLastFind("X", true, false)
|
||||||
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 3, Repeated: true}.Execute(m2)
|
FindChar{Char: "X", Forward: true, Inclusive: false, Count: 3, Repeated: true}.Execute(m2)
|
||||||
counted := m2.ActiveWindow().Cursor.Col
|
counted := m2.ActiveWindow().Cursor.Col
|
||||||
@ -2213,14 +2097,14 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
||||||
|
|
||||||
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
||||||
m1 := newMockModelWithWindow(&win1)
|
m1 := NewMockModelWithWindow(&win1)
|
||||||
m1.SetLastFind("X", false, false)
|
m1.SetLastFind("X", false, false)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
twoSemicolons := m1.ActiveWindow().Cursor.Col
|
twoSemicolons := m1.ActiveWindow().Cursor.Col
|
||||||
|
|
||||||
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
||||||
m2 := newMockModelWithWindow(&win2)
|
m2 := NewMockModelWithWindow(&win2)
|
||||||
m2.SetLastFind("X", false, false)
|
m2.SetLastFind("X", false, false)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 2, Repeated: true}.Execute(m2)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 2, Repeated: true}.Execute(m2)
|
||||||
counted := m2.ActiveWindow().Cursor.Col
|
counted := m2.ActiveWindow().Cursor.Col
|
||||||
@ -2235,7 +2119,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{line}).Build()
|
||||||
|
|
||||||
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
win1 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
||||||
m1 := newMockModelWithWindow(&win1)
|
m1 := NewMockModelWithWindow(&win1)
|
||||||
m1.SetLastFind("X", false, false)
|
m1.SetLastFind("X", false, false)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m1)
|
||||||
@ -2243,7 +2127,7 @@ func TestRepeatFind_CountedRepeats(t *testing.T) {
|
|||||||
threeSemicolons := m1.ActiveWindow().Cursor.Col
|
threeSemicolons := m1.ActiveWindow().Cursor.Col
|
||||||
|
|
||||||
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
win2 := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
||||||
m2 := newMockModelWithWindow(&win2)
|
m2 := NewMockModelWithWindow(&win2)
|
||||||
m2.SetLastFind("X", false, false)
|
m2.SetLastFind("X", false, false)
|
||||||
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 3, Repeated: true}.Execute(m2)
|
FindChar{Char: "X", Forward: false, Inclusive: false, Count: 3, Repeated: true}.Execute(m2)
|
||||||
counted := m2.ActiveWindow().Cursor.Col
|
counted := m2.ActiveWindow().Cursor.Col
|
||||||
@ -2270,7 +2154,7 @@ func TestRepeatFind_Comma_After_f(t *testing.T) {
|
|||||||
t.Run("no previous match after fo: cursor stays", func(t *testing.T) {
|
t.Run("no previous match after fo: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
// Simulate: lastFind was set by `fo`
|
// Simulate: lastFind was set by `fo`
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
@ -2287,7 +2171,7 @@ func TestRepeatFind_Comma_After_f(t *testing.T) {
|
|||||||
t.Run("after ;, comma returns to previous match", func(t *testing.T) {
|
t.Run("after ;, comma returns to previous match", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
// , reversed → backward inclusive from col 7, start at col 6: finds 'o' at 4
|
// , reversed → backward inclusive from col 7, start at col 6: finds 'o' at 4
|
||||||
@ -2309,7 +2193,7 @@ func TestRepeatFind_Comma_After_F(t *testing.T) {
|
|||||||
t.Run("no further match forward: cursor stays", func(t *testing.T) {
|
t.Run("no further match forward: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, true)
|
m.SetLastFind("o", false, true)
|
||||||
|
|
||||||
// , reversed → forward inclusive
|
// , reversed → forward inclusive
|
||||||
@ -2324,7 +2208,7 @@ func TestRepeatFind_Comma_After_F(t *testing.T) {
|
|||||||
t.Run("after ;, comma returns forward to next match", func(t *testing.T) {
|
t.Run("after ;, comma returns forward to next match", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, true)
|
m.SetLastFind("o", false, true)
|
||||||
|
|
||||||
// , reversed → forward inclusive from col 4, start at col 5: finds 'o' at 7
|
// , reversed → forward inclusive from col 4, start at col 5: finds 'o' at 7
|
||||||
@ -2347,7 +2231,7 @@ func TestRepeatFind_Comma_After_t(t *testing.T) {
|
|||||||
t.Run("no previous exclusive match: cursor stays", func(t *testing.T) {
|
t.Run("no previous exclusive match: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 3).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 3).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, false)
|
m.SetLastFind("o", true, false)
|
||||||
|
|
||||||
// , reversed → backward exclusive, repeated
|
// , reversed → backward exclusive, repeated
|
||||||
@ -2363,7 +2247,7 @@ func TestRepeatFind_Comma_After_t(t *testing.T) {
|
|||||||
t.Run("after ;, comma goes backward exclusive", func(t *testing.T) {
|
t.Run("after ;, comma goes backward exclusive", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 6).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, false)
|
m.SetLastFind("o", true, false)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: false, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -2385,7 +2269,7 @@ func TestRepeatFind_Comma_After_T(t *testing.T) {
|
|||||||
t.Run("no further exclusive match forward: cursor stays", func(t *testing.T) {
|
t.Run("no further exclusive match forward: cursor stays", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, false)
|
m.SetLastFind("o", false, false)
|
||||||
|
|
||||||
// , reversed → forward exclusive, repeated
|
// , reversed → forward exclusive, repeated
|
||||||
@ -2401,7 +2285,7 @@ func TestRepeatFind_Comma_After_T(t *testing.T) {
|
|||||||
t.Run("after ;, comma goes forward exclusive", func(t *testing.T) {
|
t.Run("after ;, comma goes forward exclusive", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 5).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 5).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, false)
|
m.SetLastFind("o", false, false)
|
||||||
|
|
||||||
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
FindChar{Char: "o", Forward: true, Inclusive: false, Count: 1, Repeated: true}.Execute(m)
|
||||||
@ -2417,10 +2301,10 @@ func TestRepeatFind_Comma_After_T(t *testing.T) {
|
|||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
func TestRepeatFind_Resolve(t *testing.T) {
|
func TestRepeatFind_Resolve(t *testing.T) {
|
||||||
makeMock := func(char string, forward, inclusive bool) *mockModel {
|
makeMock := func(char string, forward, inclusive bool) *MockModel {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"x"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"x"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind(char, forward, inclusive)
|
m.SetLastFind(char, forward, inclusive)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@ -2542,7 +2426,7 @@ func TestRepeatFind_Execute(t *testing.T) {
|
|||||||
t.Run("Execute via ; after f moves cursor correctly", func(t *testing.T) {
|
t.Run("Execute via ; after f moves cursor correctly", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 4).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
// ; = RepeatFind{Reverse: false}
|
// ; = RepeatFind{Reverse: false}
|
||||||
@ -2556,7 +2440,7 @@ func TestRepeatFind_Execute(t *testing.T) {
|
|||||||
t.Run("Execute via , after f reverses and moves cursor correctly", func(t *testing.T) {
|
t.Run("Execute via , after f reverses and moves cursor correctly", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 7).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, true)
|
m.SetLastFind("o", true, true)
|
||||||
|
|
||||||
// , = RepeatFind{Reverse: true}
|
// , = RepeatFind{Reverse: true}
|
||||||
@ -2570,7 +2454,7 @@ func TestRepeatFind_Execute(t *testing.T) {
|
|||||||
t.Run("Execute via ; after t skips adjacent and moves cursor", func(t *testing.T) {
|
t.Run("Execute via ; after t skips adjacent and moves cursor", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 3).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 3).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", true, false)
|
m.SetLastFind("o", true, false)
|
||||||
|
|
||||||
RepeatFind{Count: 1, Reverse: false}.Execute(m)
|
RepeatFind{Count: 1, Reverse: false}.Execute(m)
|
||||||
@ -2583,7 +2467,7 @@ func TestRepeatFind_Execute(t *testing.T) {
|
|||||||
t.Run("Execute via ; after T skips adjacent backward and moves cursor", func(t *testing.T) {
|
t.Run("Execute via ; after T skips adjacent backward and moves cursor", func(t *testing.T) {
|
||||||
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
buf := core.NewBufferBuilder().WithLines([]string{"hello world"}).Build()
|
||||||
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
win := core.NewWindowBuilder().WithBuffer(&buf).WithCursorPos(0, 8).Build()
|
||||||
m := newMockModelWithWindow(&win)
|
m := NewMockModelWithWindow(&win)
|
||||||
m.SetLastFind("o", false, false)
|
m.SetLastFind("o", false, false)
|
||||||
|
|
||||||
RepeatFind{Count: 1, Reverse: false}.Execute(m)
|
RepeatFind{Count: 1, Reverse: false}.Execute(m)
|
||||||
|
|||||||
@ -41,6 +41,10 @@ type Model interface {
|
|||||||
CommandOutput() *core.CommandOutput
|
CommandOutput() *core.CommandOutput
|
||||||
// DO NOT FORGET TO CALL SetMode()
|
// DO NOT FORGET TO CALL SetMode()
|
||||||
SetCommandOutput(out *core.CommandOutput)
|
SetCommandOutput(out *core.CommandOutput)
|
||||||
|
CommandHistory() []string
|
||||||
|
SetCommandHistory(history []string)
|
||||||
|
CommandHistoryCursor() int
|
||||||
|
SetCommandHistoryCursor(cur int)
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// Editor-wide State
|
// Editor-wide State
|
||||||
|
|||||||
133
internal/action/mock.go
Normal file
133
internal/action/mock.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockModel is a shared test implementation of the Model interface.
|
||||||
|
// Used by test files across multiple packages to avoid duplication.
|
||||||
|
// All fields are exported to allow direct manipulation in tests.
|
||||||
|
type MockModel struct {
|
||||||
|
WindowsList []*core.Window
|
||||||
|
ActiveWindowVal *core.Window
|
||||||
|
BuffersList []*core.Buffer
|
||||||
|
SettingsVal core.EditorSettings
|
||||||
|
ModeVal core.Mode
|
||||||
|
RegistersMap map[rune]core.Register
|
||||||
|
InsertKeysList []string
|
||||||
|
CommandVal string
|
||||||
|
CommandCursorVal int
|
||||||
|
CommandOutputVal *core.CommandOutput
|
||||||
|
CommandHistoryList []string
|
||||||
|
CommandHistoryCur int
|
||||||
|
LastFindVal core.LastFindCommand
|
||||||
|
StylesVal style.Styles
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockModel creates a mock with an empty buffer and 24x80 window.
|
||||||
|
func NewMockModel() *MockModel {
|
||||||
|
buf := core.NewBufferBuilder().
|
||||||
|
WithLines([]string{""}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
win := core.NewWindowBuilder().
|
||||||
|
WithBuffer(&buf).
|
||||||
|
WithHeight(24).
|
||||||
|
WithWidth(80).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
return &MockModel{
|
||||||
|
WindowsList: []*core.Window{&win},
|
||||||
|
ActiveWindowVal: &win,
|
||||||
|
BuffersList: []*core.Buffer{&buf},
|
||||||
|
SettingsVal: core.NewDefaultSettings(),
|
||||||
|
ModeVal: core.NormalMode,
|
||||||
|
RegistersMap: core.DefaultRegisters(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockModelWithBuffer creates a mock with a custom buffer.
|
||||||
|
func NewMockModelWithBuffer(buf *core.Buffer) *MockModel {
|
||||||
|
win := core.NewWindowBuilder().
|
||||||
|
WithBuffer(buf).
|
||||||
|
WithHeight(24).
|
||||||
|
WithWidth(80).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
return &MockModel{
|
||||||
|
WindowsList: []*core.Window{&win},
|
||||||
|
ActiveWindowVal: &win,
|
||||||
|
BuffersList: []*core.Buffer{buf},
|
||||||
|
SettingsVal: core.NewDefaultSettings(),
|
||||||
|
ModeVal: core.NormalMode,
|
||||||
|
RegistersMap: core.DefaultRegisters(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockModelWithWindow creates a mock with a custom window.
|
||||||
|
func NewMockModelWithWindow(win *core.Window) *MockModel {
|
||||||
|
return &MockModel{
|
||||||
|
WindowsList: []*core.Window{win},
|
||||||
|
ActiveWindowVal: win,
|
||||||
|
BuffersList: []*core.Buffer{win.Buffer},
|
||||||
|
SettingsVal: core.NewDefaultSettings(),
|
||||||
|
ModeVal: core.NormalMode,
|
||||||
|
RegistersMap: core.DefaultRegisters(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// Model Interface Implementation
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
// Core Data Access
|
||||||
|
func (m *MockModel) Windows() []*core.Window { return m.WindowsList }
|
||||||
|
func (m *MockModel) ActiveWindow() *core.Window { return m.ActiveWindowVal }
|
||||||
|
func (m *MockModel) Buffers() []*core.Buffer { return m.BuffersList }
|
||||||
|
func (m *MockModel) SetBuffers(bufs []*core.Buffer) { m.BuffersList = bufs }
|
||||||
|
func (m *MockModel) ActiveBuffer() *core.Buffer { return m.ActiveWindowVal.Buffer }
|
||||||
|
|
||||||
|
// Insert Mode State
|
||||||
|
func (m *MockModel) InsertKeys() []string { return m.InsertKeysList }
|
||||||
|
func (m *MockModel) SetInsertKeys(keys []string) { m.InsertKeysList = keys }
|
||||||
|
func (m *MockModel) SetInsertRecording(count int, a Action) {}
|
||||||
|
func (m *MockModel) ExitInsertMode() {}
|
||||||
|
func (m *MockModel) SetLastFind(char string, forward, inclusive bool) {
|
||||||
|
m.LastFindVal = core.LastFindCommand{Char: char, Forward: forward, Inclusive: inclusive}
|
||||||
|
}
|
||||||
|
func (m *MockModel) GetLastFind() *core.LastFindCommand { return &m.LastFindVal }
|
||||||
|
|
||||||
|
// Command Mode State
|
||||||
|
func (m *MockModel) Command() string { return m.CommandVal }
|
||||||
|
func (m *MockModel) SetCommand(cmd string) { m.CommandVal = cmd }
|
||||||
|
func (m *MockModel) CommandCursor() int { return m.CommandCursorVal }
|
||||||
|
func (m *MockModel) SetCommandCursor(cur int) { m.CommandCursorVal = cur }
|
||||||
|
func (m *MockModel) CommandOutput() *core.CommandOutput { return m.CommandOutputVal }
|
||||||
|
func (m *MockModel) SetCommandOutput(out *core.CommandOutput) { m.CommandOutputVal = out }
|
||||||
|
func (m *MockModel) CommandHistory() []string { return m.CommandHistoryList }
|
||||||
|
func (m *MockModel) SetCommandHistory(history []string) { m.CommandHistoryList = history }
|
||||||
|
func (m *MockModel) CommandHistoryCursor() int { return m.CommandHistoryCur }
|
||||||
|
func (m *MockModel) SetCommandHistoryCursor(cur int) { m.CommandHistoryCur = cur }
|
||||||
|
|
||||||
|
// Editor-wide State
|
||||||
|
func (m *MockModel) Mode() core.Mode { return m.ModeVal }
|
||||||
|
func (m *MockModel) SetMode(mode core.Mode) { m.ModeVal = mode }
|
||||||
|
func (m *MockModel) Settings() core.EditorSettings { return m.SettingsVal }
|
||||||
|
func (m *MockModel) SetSettings(s core.EditorSettings) { m.SettingsVal = s }
|
||||||
|
func (m *MockModel) Styles() style.Styles { return m.StylesVal }
|
||||||
|
func (m *MockModel) SetStyles(s style.Styles) { m.StylesVal = s }
|
||||||
|
|
||||||
|
// Registers
|
||||||
|
func (m *MockModel) Registers() map[rune]core.Register { return m.RegistersMap }
|
||||||
|
func (m *MockModel) GetRegister(name rune) (core.Register, bool) {
|
||||||
|
reg, ok := m.RegistersMap[name]
|
||||||
|
return reg, ok
|
||||||
|
}
|
||||||
|
func (m *MockModel) SetRegister(name rune, t core.RegisterType, cnt []string) error {
|
||||||
|
m.RegistersMap[name] = core.Register{Type: t, Content: cnt}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *MockModel) UpdateDefaultRegister(t core.RegisterType, cnt []string) {
|
||||||
|
m.RegistersMap['"'] = core.Register{Type: t, Content: cnt}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -309,6 +310,27 @@ func cmdRegisters(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// History Commands
|
||||||
|
// --------------------------------------------------
|
||||||
|
func cmdHistory(m action.Model, args []string, force bool) tea.Cmd {
|
||||||
|
_, _ = args, force
|
||||||
|
|
||||||
|
history := m.CommandHistory()
|
||||||
|
reversed := slices.Clone(history)
|
||||||
|
slices.Reverse(reversed)
|
||||||
|
|
||||||
|
m.SetMode(core.CommandOutputMode)
|
||||||
|
m.SetCommandOutput(&core.CommandOutput{
|
||||||
|
Title: ":history",
|
||||||
|
Lines: reversed,
|
||||||
|
Inline: false,
|
||||||
|
IsError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// Buffer Commands
|
// Buffer Commands
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -162,6 +162,13 @@ func (r *Registry) registerDefaults() {
|
|||||||
Handler: cmdRegisters,
|
Handler: cmdRegisters,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// History commands
|
||||||
|
r.Register(Command{
|
||||||
|
Name: "history",
|
||||||
|
ShortForm: "his",
|
||||||
|
Handler: cmdHistory,
|
||||||
|
})
|
||||||
|
|
||||||
// Buffer commands
|
// Buffer commands
|
||||||
r.Register(Command{
|
r.Register(Command{
|
||||||
Name: "buffers",
|
Name: "buffers",
|
||||||
@ -224,6 +231,7 @@ func (r *Registry) registerDefaults() {
|
|||||||
ShortForm: "colo",
|
ShortForm: "colo",
|
||||||
Handler: cmdColorscheme,
|
Handler: cmdColorscheme,
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Register(Command{
|
r.Register(Command{
|
||||||
Name: "colorschemes",
|
Name: "colorschemes",
|
||||||
ShortForm: "colorschemes",
|
ShortForm: "colorschemes",
|
||||||
|
|||||||
@ -40,6 +40,8 @@ type Model struct {
|
|||||||
command string
|
command string
|
||||||
commandCursor int
|
commandCursor int
|
||||||
commandOutput *core.CommandOutput
|
commandOutput *core.CommandOutput
|
||||||
|
commandHistory []string
|
||||||
|
commandHistoryCursor int
|
||||||
|
|
||||||
// Global settings
|
// Global settings
|
||||||
settings core.EditorSettings
|
settings core.EditorSettings
|
||||||
@ -279,6 +281,22 @@ func (m *Model) SetCommandOutput(out *core.CommandOutput) {
|
|||||||
m.commandOutput = out
|
m.commandOutput = out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) CommandHistory() []string {
|
||||||
|
return m.commandHistory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) SetCommandHistory(history []string) {
|
||||||
|
m.commandHistory = history
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) CommandHistoryCursor() int {
|
||||||
|
return m.commandHistoryCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) SetCommandHistoryCursor(cur int) {
|
||||||
|
m.commandHistoryCursor = cur
|
||||||
|
}
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// Editor-wide State
|
// Editor-wide State
|
||||||
// ==================================================
|
// ==================================================
|
||||||
|
|||||||
@ -60,6 +60,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
// TODO: Any vim action should exit also
|
// TODO: Any vim action should exit also
|
||||||
// Simple override for command output mode for now
|
// Simple override for command output mode for now
|
||||||
if m.Mode() == core.CommandOutputMode {
|
if m.Mode() == core.CommandOutputMode {
|
||||||
|
// TODO: Implement g/G/d/u
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "enter":
|
case "enter":
|
||||||
m.SetMode(core.NormalMode)
|
m.SetMode(core.NormalMode)
|
||||||
|
|||||||
@ -142,6 +142,8 @@ func NewCommandKeymap() *Keymap {
|
|||||||
motions: map[string]action.Motion{
|
motions: map[string]action.Motion{
|
||||||
"left": motion.MoveCommandLeft{},
|
"left": motion.MoveCommandLeft{},
|
||||||
"right": motion.MoveCommandRight{},
|
"right": motion.MoveCommandRight{},
|
||||||
|
"up": motion.MoveCommandHistoryUp{},
|
||||||
|
"down": motion.MoveCommandHistoryDown{},
|
||||||
},
|
},
|
||||||
operators: map[string]action.Operator{}, // this will likely be empty
|
operators: map[string]action.Operator{}, // this will likely be empty
|
||||||
actions: map[string]action.Action{
|
actions: map[string]action.Action{
|
||||||
|
|||||||
@ -31,3 +31,54 @@ func (a MoveCommandRight) Execute(m action.Model) tea.Cmd {
|
|||||||
|
|
||||||
// MoveCommandRight.Type: Returns CharwiseExclusive for command line motion.
|
// MoveCommandRight.Type: Returns CharwiseExclusive for command line motion.
|
||||||
func (a MoveCommandRight) Type() core.MotionType { return core.CharwiseExclusive }
|
func (a MoveCommandRight) Type() core.MotionType { return core.CharwiseExclusive }
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// Command History Motions
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
// MoveCommandHistoryUp implements Motion - moves cursor right in command line.
|
||||||
|
type MoveCommandHistoryUp struct{}
|
||||||
|
|
||||||
|
// MoveCommandHistoryUp.Execute: Moves the command history cursor up (if possible).
|
||||||
|
func (a MoveCommandHistoryUp) Execute(m action.Model) tea.Cmd {
|
||||||
|
cur := m.CommandHistoryCursor() // always +1 (bascially indexed at 1)
|
||||||
|
history := m.CommandHistory()
|
||||||
|
|
||||||
|
if cur < len(history) {
|
||||||
|
cmd := history[cur]
|
||||||
|
m.SetCommand(cmd)
|
||||||
|
m.SetCommandCursor(len(cmd))
|
||||||
|
|
||||||
|
// Only go up if we can
|
||||||
|
m.SetCommandHistoryCursor(cur + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveCommandHistoryUp.Type: Returns Linewise for command line motion.
|
||||||
|
func (a MoveCommandHistoryUp) Type() core.MotionType { return core.Linewise }
|
||||||
|
|
||||||
|
// MoveCommandHistoryDown implements Motion - moves cursor right in command line.
|
||||||
|
type MoveCommandHistoryDown struct{}
|
||||||
|
|
||||||
|
// MoveCommandHistoryDown.Execute: Moves the command history cursor down (if possible).
|
||||||
|
func (a MoveCommandHistoryDown) Execute(m action.Model) tea.Cmd {
|
||||||
|
cur := m.CommandHistoryCursor() // always +1 (bascially indexed at 1)
|
||||||
|
history := m.CommandHistory()
|
||||||
|
|
||||||
|
if cur > 1 {
|
||||||
|
cmd := history[cur-2]
|
||||||
|
m.SetCommand(cmd)
|
||||||
|
m.SetCommandCursor(len(cmd))
|
||||||
|
} else {
|
||||||
|
m.SetCommand("") // BUG: We should probably keep the original in state
|
||||||
|
m.SetCommandCursor(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SetCommandHistoryCursor(max(0, cur-1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveCommandHistoryDown.Type: Returns Linewise for command line motion.
|
||||||
|
func (a MoveCommandHistoryDown) Type() core.MotionType { return core.Linewise }
|
||||||
|
|||||||
376
internal/motion/command_test.go
Normal file
376
internal/motion/command_test.go
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
package motion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// MoveCommandHistoryUp Tests
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
func TestMoveCommandHistoryUp(t *testing.T) {
|
||||||
|
t.Run("navigate up from start", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"},
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryUp{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Should load first command in history
|
||||||
|
if m.Command() != "cmd3" {
|
||||||
|
t.Errorf("Command = %q, want %q", m.Command(), "cmd3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor should move to end of command
|
||||||
|
if m.CommandCursor() != len("cmd3") {
|
||||||
|
t.Errorf("CommandCursor = %d, want %d", m.CommandCursor(), len("cmd3"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// History cursor should advance
|
||||||
|
if m.CommandHistoryCursor() != 1 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 1", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("navigate up multiple times", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"},
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryUp{}
|
||||||
|
|
||||||
|
// First up
|
||||||
|
action.Execute(m)
|
||||||
|
if m.Command() != "cmd3" {
|
||||||
|
t.Errorf("After 1st up: Command = %q, want 'cmd3'", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second up
|
||||||
|
action.Execute(m)
|
||||||
|
if m.Command() != "cmd2" {
|
||||||
|
t.Errorf("After 2nd up: Command = %q, want 'cmd2'", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third up
|
||||||
|
action.Execute(m)
|
||||||
|
if m.Command() != "cmd1" {
|
||||||
|
t.Errorf("After 3rd up: Command = %q, want 'cmd1'", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 3 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 3", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot navigate past end of history", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"},
|
||||||
|
CommandHistoryCur: 3, // Already at end
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryUp{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Should not change command (still showing cmd1)
|
||||||
|
if m.Command() != "" {
|
||||||
|
t.Errorf("Command = %q, want empty (should not go past end)", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor should not advance
|
||||||
|
if m.CommandHistoryCursor() != 3 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 3 (should not advance)", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty history does nothing", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "current",
|
||||||
|
CommandHistoryList: []string{},
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryUp{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Command should not change
|
||||||
|
if m.Command() != "current" {
|
||||||
|
t.Errorf("Command = %q, want 'current' (no history to navigate)", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 0 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 0", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("command cursor moves to end", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandCursorVal: 0,
|
||||||
|
CommandHistoryList: []string{"long command here"},
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryUp{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
expectedLen := len("long command here")
|
||||||
|
if m.CommandCursor() != expectedLen {
|
||||||
|
t.Errorf("CommandCursor = %d, want %d (end of command)", m.CommandCursor(), expectedLen)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// MoveCommandHistoryDown Tests
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
func TestMoveCommandHistoryDown(t *testing.T) {
|
||||||
|
t.Run("navigate down from middle of history", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "cmd2",
|
||||||
|
CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"},
|
||||||
|
CommandHistoryCur: 2, // At cmd2
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryDown{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Should load cmd3 (more recent)
|
||||||
|
if m.Command() != "cmd3" {
|
||||||
|
t.Errorf("Command = %q, want 'cmd3'", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor should be at 1
|
||||||
|
if m.CommandHistoryCursor() != 1 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 1", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("navigate down to empty command", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "cmd3",
|
||||||
|
CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"},
|
||||||
|
CommandHistoryCur: 1, // At first command
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryDown{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Should clear command (back to current input)
|
||||||
|
if m.Command() != "" {
|
||||||
|
t.Errorf("Command = %q, want empty (back to current)", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.CommandCursor() != 0 {
|
||||||
|
t.Errorf("CommandCursor = %d, want 0", m.CommandCursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 0 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 0", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("navigate down from cursor 0 does nothing", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"},
|
||||||
|
CommandHistoryCur: 0, // Already at bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryDown{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
// Should clear command
|
||||||
|
if m.Command() != "" {
|
||||||
|
t.Errorf("Command = %q, want empty", m.Command())
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 0 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 0", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("command cursor moves to end", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandCursorVal: 0,
|
||||||
|
CommandHistoryList: []string{"cmd3", "long command here"},
|
||||||
|
CommandHistoryCur: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
action := MoveCommandHistoryDown{}
|
||||||
|
action.Execute(m)
|
||||||
|
|
||||||
|
expectedLen := len("cmd3")
|
||||||
|
if m.CommandCursor() != expectedLen {
|
||||||
|
t.Errorf("CommandCursor = %d, want %d (end of command)", m.CommandCursor(), expectedLen)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// Integration Tests
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
func TestCommandHistoryIntegration(t *testing.T) {
|
||||||
|
t.Run("up down up sequence", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: []string{"cmd3", "cmd2", "cmd1"},
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
upAction := MoveCommandHistoryUp{}
|
||||||
|
downAction := MoveCommandHistoryDown{}
|
||||||
|
|
||||||
|
// Up twice
|
||||||
|
upAction.Execute(m) // cursor=1, cmd=cmd3
|
||||||
|
upAction.Execute(m) // cursor=2, cmd=cmd2
|
||||||
|
|
||||||
|
// Down once
|
||||||
|
downAction.Execute(m) // cursor=1, cmd=cmd3
|
||||||
|
|
||||||
|
// Up again
|
||||||
|
upAction.Execute(m) // cursor=2, cmd=cmd2
|
||||||
|
|
||||||
|
if m.Command() != "cmd2" {
|
||||||
|
t.Errorf("Command = %q, want 'cmd2'", m.Command())
|
||||||
|
}
|
||||||
|
if m.CommandHistoryCursor() != 2 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 2", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("navigate up past end does not crash", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: []string{"cmd1", "cmd2"},
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
upAction := MoveCommandHistoryUp{}
|
||||||
|
|
||||||
|
// Navigate to end
|
||||||
|
upAction.Execute(m) // cursor = 1, cmd = cmd1
|
||||||
|
upAction.Execute(m) // cursor = 2, cmd = cmd2
|
||||||
|
|
||||||
|
// Try to go past end
|
||||||
|
upAction.Execute(m) // Should do nothing
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 2 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 2 (should stay at end)", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("navigate down past bottom does not crash", func(t *testing.T) {
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: []string{"cmd1"},
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
downAction := MoveCommandHistoryDown{}
|
||||||
|
|
||||||
|
// Try to go down from bottom
|
||||||
|
downAction.Execute(m)
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 0 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 0 (should stay at bottom)", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try again
|
||||||
|
downAction.Execute(m)
|
||||||
|
|
||||||
|
if m.CommandHistoryCursor() != 0 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 0 (should stay at bottom)", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// Long History Tests
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
func TestCommandHistoryWithLongHistory(t *testing.T) {
|
||||||
|
t.Run("navigate through 20 commands", func(t *testing.T) {
|
||||||
|
history := make([]string, 20)
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
history[i] = string(rune('A' + i))
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: history,
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
upAction := MoveCommandHistoryUp{}
|
||||||
|
|
||||||
|
// Navigate to 10th command
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
upAction.Execute(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := string(rune('A' + 9))
|
||||||
|
if m.Command() != want {
|
||||||
|
t.Errorf("Command after 10 ups = %q, want %q", m.Command(), want)
|
||||||
|
}
|
||||||
|
if m.CommandHistoryCursor() != 10 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 10", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("navigate to very end of long history", func(t *testing.T) {
|
||||||
|
history := make([]string, 50)
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
history[i] = string(rune('0' + (i % 10)))
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &action.MockModel{
|
||||||
|
ModeVal: core.CommandMode,
|
||||||
|
CommandVal: "",
|
||||||
|
CommandHistoryList: history,
|
||||||
|
CommandHistoryCur: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
upAction := MoveCommandHistoryUp{}
|
||||||
|
|
||||||
|
// Navigate all the way to the end
|
||||||
|
for i := 0; i < 100; i++ { // Try to go past end
|
||||||
|
upAction.Execute(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be at position 50 (end of 50-item array)
|
||||||
|
if m.CommandHistoryCursor() != 50 {
|
||||||
|
t.Errorf("CommandHistoryCursor = %d, want 50 (at end)", m.CommandHistoryCursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be showing last command
|
||||||
|
want := string(rune('0' + 49%10))
|
||||||
|
if m.Command() != want {
|
||||||
|
t.Errorf("Command = %q, want %q (last in history)", m.Command(), want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user