All checks were successful
Run Test Suite / test (push) Successful in 17s
Also added tons of tests for the actual command mode, since that was all untested...
183 lines
4.5 KiB
Go
183 lines
4.5 KiB
Go
package action
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"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)
|
|
|
|
// try to parse entire thing as a number
|
|
num, err := strconv.Atoi(cmdLine)
|
|
if err == nil {
|
|
return cmdGoToLine(m, num)
|
|
}
|
|
|
|
cmd, err := a.Registry.Execute(m, cmdLine)
|
|
if err != nil {
|
|
out := core.CommandOutput{
|
|
Lines: []string{err.Error()},
|
|
Inline: true,
|
|
IsError: true,
|
|
}
|
|
m.SetCommandOutput(&out)
|
|
return nil
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
// cmdGoToLine: DOES NOT implement command.Command. Instead, is called directly
|
|
// by CommandExecture.Execute(). Jumps the cursor to the line provided
|
|
func cmdGoToLine(m Model, line int) tea.Cmd {
|
|
// number below 0 just goes back that many
|
|
|
|
win := m.ActiveWindow()
|
|
buf := m.ActiveBuffer()
|
|
|
|
if line <= 0 {
|
|
newLine := max(0, win.Cursor.Line+line)
|
|
win.SetCursorLine(newLine)
|
|
return nil
|
|
}
|
|
|
|
newLine := min(line-1, buf.LineCount())
|
|
win.SetCursorLine(newLine)
|
|
|
|
return nil
|
|
}
|