Gim/internal/command/handlers.go
Hayden Hargreaves ccb061989a refactor: huge refactor, this looks amazing.
Lots of comments from the AI. Some tests are not passing though
2026-03-04 21:45:47 -07:00

253 lines
5.8 KiB
Go

package command
import (
"fmt"
"strconv"
"strings"
"git.gophernest.net/azpect/TextEditor/internal/action"
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
// QuitMsg: Message signaling the application should quit.
type QuitMsg struct{}
// ErrorMsg: Message signaling an error to display.
type ErrorMsg struct {
Err error
}
// cmdQuit: Handles :quit / :q command.
func cmdQuit(m action.Model, args []string) tea.Cmd {
return func() tea.Msg {
return tea.Quit()
}
}
// cmdQuitAll: Handles :qall / :qa command.
func cmdQuitAll(m action.Model, args []string) tea.Cmd {
return func() tea.Msg {
return tea.Quit()
}
}
// cmdWrite: Handles :write / :w command (TODO: implement file saving).
func cmdWrite(m action.Model, args []string) tea.Cmd {
// TODO: Implement file saving
// If args provided, save to that filename
// Otherwise save to current file
return nil
}
// cmdWriteAll: Handles :wall / :wa command (TODO: implement saving all buffers).
func cmdWriteAll(m action.Model, args []string) tea.Cmd {
// TODO: Implement saving all buffers
return nil
}
// cmdWriteQuit: Handles :wq command (TODO: save then quit).
func cmdWriteQuit(m action.Model, args []string) tea.Cmd {
// TODO: Save then quit
return func() tea.Msg {
return tea.Quit()
}
}
// cmdRegisters: Handles :register command (debug - displays register content).
func cmdRegisters(m action.Model, args []string) tea.Cmd {
// TODO: This is temporary, for debugging
if len(args) < 1 {
m.SetCommandError(fmt.Errorf("Please provide a name. Register dump not yet implemented."))
return nil
}
if len(args[0]) != 1 {
m.SetCommandError(fmt.Errorf("Name should be a single character."))
return nil
}
name := rune(args[0][0])
reg, found := m.GetRegister(name)
if !found {
m.SetCommandError(fmt.Errorf("Could not find register '%c'.", name))
return nil
}
content := strings.Join(reg.Content, "\\n")
t := reg.Type
m.SetCommandOutput(fmt.Sprintf("Type: %d Name: \"%c Content: %s", t, name, content))
return nil
}
// cmdSet: Handles :set option[=value] command for configuring editor settings.
// Examples:
//
// :set number - enable number
// :set nonumber - disable number
// :set number! - toggle number
// :set tabstop=4 - set tabstop to 4
// :set ts=4 - set tabstop to 4 (abbreviation)
func cmdSet(m action.Model, args []string) tea.Cmd {
if len(args) == 0 {
out := fmt.Sprintf("%+v", m.Settings())
m.SetCommandOutput(out)
return nil
}
for _, arg := range args {
if err := parseSetOption(m, arg); err != nil {
m.SetCommandError(err)
return nil
}
}
return nil
}
// Setting: Represents a configurable editor option.
type Setting struct {
Name string
ShortForm string
Type SettingType
Get func(s core.Settings) any
Set func(m action.Model, val any)
}
// SettingType: Enumeration of setting value types.
type SettingType int
const (
BoolSetting SettingType = iota
IntSetting
StringSetting
)
// settingsMap defines all available settings
var settingsMap = []Setting{
{
Name: "number",
ShortForm: "nu",
Type: BoolSetting,
Get: func(s core.Settings) any { return s.Number },
Set: func(m action.Model, val any) {
s := m.Settings()
s.Number = val.(bool)
m.SetSettings(s)
},
},
{
Name: "relativenumber",
ShortForm: "rnu",
Type: BoolSetting,
Get: func(s core.Settings) any { return s.RelativeNumber },
Set: func(m action.Model, val any) {
s := m.Settings()
s.RelativeNumber = val.(bool)
m.SetSettings(s)
},
},
{
Name: "tabstop",
ShortForm: "ts",
Type: IntSetting,
Get: func(s core.Settings) any { return s.TabSize },
Set: func(m action.Model, val any) {
s := m.Settings()
s.TabSize = val.(int)
m.SetSettings(s)
},
},
{
Name: "scrolloff",
ShortForm: "so",
Type: IntSetting,
Get: func(s core.Settings) any { return s.ScrollOff },
Set: func(m action.Model, val any) {
s := m.Settings()
s.ScrollOff = val.(int)
m.SetSettings(s)
},
},
}
// lookupSetting: Finds a setting by name, short form, or prefix.
func lookupSetting(name string) *Setting {
for i := range settingsMap {
s := &settingsMap[i]
if name == s.Name || name == s.ShortForm {
return s
}
// Prefix matching
if len(name) >= len(s.ShortForm) && strings.HasPrefix(s.Name, name) {
return s
}
}
return nil
}
// parseSetOption: Parses and applies a single :set option.
func parseSetOption(m action.Model, opt string) error {
// Handle toggle: option!
if name, ok := strings.CutSuffix(opt, "!"); ok {
setting := lookupSetting(name)
if setting == nil {
return nil // Unknown setting
}
if setting.Type == BoolSetting {
// Toggle the boolean
currentVal := setting.Get(m.Settings()).(bool)
setting.Set(m, !currentVal)
}
return nil
}
// Handle disable: nooption
if name, ok := strings.CutPrefix(opt, "no"); ok {
setting := lookupSetting(name)
if setting == nil {
return nil
}
if setting.Type == BoolSetting {
setting.Set(m, false)
}
return nil
}
// Handle assignment: option=value
if strings.Contains(opt, "=") {
parts := strings.SplitN(opt, "=", 2)
name, value := parts[0], parts[1]
setting := lookupSetting(name)
if setting == nil {
return nil
}
switch setting.Type {
case IntSetting:
intVal, err := strconv.Atoi(value)
if err != nil {
return err
}
setting.Set(m, intVal)
case StringSetting:
setting.Set(m, value)
case BoolSetting:
// Handle :set option=true / :set option=false
boolVal := value == "true" || value == "1" || value == "yes"
setting.Set(m, boolVal)
}
return nil
}
// Handle enable: option (boolean only)
setting := lookupSetting(opt)
if setting == nil {
return nil
}
if setting.Type == BoolSetting {
setting.Set(m, true)
}
return nil
}