Gim/internal/action/command.go
Hayden Hargreaves 3c98dca777 feat: implement command history, tested
The tests are starting to get messy, lots of duplication. Going to
resolve that. Lots of this is due to AI generation of tests.
2026-03-19 15:23:44 -07:00

155 lines
3.9 KiB
Go

package action
import (
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
// ExitCommandMode implements Action - exits command mode and returns to normal mode.
type ExitCommandMode struct{}
// ExitCommandMode.Execute: Exits command mode and returns to normal mode (Esc key).
func (a ExitCommandMode) Execute(m Model) tea.Cmd {
m.SetCommandCursor(0)
m.SetCommandHistoryCursor(0)
m.SetCommand("")
m.SetCommandOutput(&core.CommandOutput{})
m.SetMode(core.NormalMode)
return nil
}
// InsertCommandChar implements Action - inserts a character in command mode.
type InsertCommandChar struct {
Char string
}
// InsertCommandChar.Execute: Inserts a character at the command cursor position.
func (a InsertCommandChar) Execute(m Model) tea.Cmd {
cur := m.CommandCursor()
cmd := m.Command()
m.SetCommand(cmd[:cur] + a.Char + cmd[cur:])
m.SetCommandCursor(cur + 1)
return nil
}
// CommandBackspace implements Action - deletes character before cursor in command mode.
type CommandBackspace struct{}
// CommandBackspace.Execute: Deletes the character before the command cursor (Backspace key).
func (a CommandBackspace) Execute(m Model) tea.Cmd {
cur := m.CommandCursor()
cmd := m.Command()
if cur > 0 {
m.SetCommand(cmd[:cur-1] + cmd[cur:])
m.SetCommandCursor(cur - 1)
}
return nil
}
// CommandDelete implements Action - deletes character at cursor in command mode.
type CommandDelete struct{}
// CommandDelete.Execute: Deletes the character at the command cursor (Delete key).
func (a CommandDelete) Execute(m Model) tea.Cmd {
cur := m.CommandCursor()
cmd := m.Command()
if cur < len(cmd)-1 {
m.SetCommand(cmd[:cur+1] + cmd[cur+2:])
} else if cur == len(cmd)-1 {
// last text char, delete it
m.SetCommand(cmd[:cur] + cmd[cur+1:])
} else if cur == len(cmd) && cur > 0 {
// if at end, we do backspace op
m.SetCommand(cmd[:cur-1] + cmd[cur:])
m.SetCommandCursor(cur - 1)
}
return nil
}
// CommandDeletePreviousWord implements Action - deletes word before cursor in command mode.
type CommandDeletePreviousWord struct{}
// CommandDeletePreviousWord.Execute: Deletes the word before the command cursor (Ctrl+W).
func (a CommandDeletePreviousWord) Execute(m Model) tea.Cmd {
cur := m.CommandCursor()
cmd := m.Command()
if cur > 0 {
newCur := cur
// If we are on puncuation, we should just skip them all and quit
if isPunctuation(cmd[newCur-1]) {
for newCur > 0 && isPunctuation(cmd[newCur-1]) {
newCur--
}
m.SetCommand(cmd[:newCur] + cmd[cur:])
m.SetCommandCursor(newCur)
return nil
}
// Skip whitespace immediately before the cursor
for newCur > 0 && (cmd[newCur-1] == ' ' || cmd[newCur-1] == '\t') {
newCur--
}
// Skip the word characters before the cursor
for newCur > 0 && isWordChar(cmd[newCur-1]) {
newCur--
}
// Delete everything from newCur up to cur in one operation
m.SetCommand(cmd[:newCur] + cmd[cur:])
m.SetCommandCursor(newCur)
}
return nil
}
// CommandExecute implements Action - executes the command line.
type CommandExecute struct {
Registry CommandRegistry
}
// CommandRegistry: Interface for executing commands.
type CommandRegistry interface {
Execute(m Model, cmdLine string) (tea.Cmd, error)
}
// CommandExecute.Execute: Executes the command line (Enter key).
func (a CommandExecute) Execute(m Model) tea.Cmd {
cmdLine := m.Command()
// Clear command state and return to normal mode
m.SetCommandCursor(0)
m.SetCommandHistoryCursor(0)
m.SetMode(core.NormalMode)
if a.Registry == nil || cmdLine == "" {
return nil
}
history := append([]string{cmdLine}, m.CommandHistory()...)
// history = append([]string{cmdLine}, history...)
m.SetCommandHistory(history)
cmd, err := a.Registry.Execute(m, cmdLine)
if err != nil {
out := core.CommandOutput{
Lines: []string{err.Error()},
Inline: true,
IsError: true,
}
m.SetCommandOutput(&out)
return nil
}
return cmd
}