package command import ( "fmt" "strconv" "strings" "git.gophernest.net/azpect/TextEditor/internal/action" tea "github.com/charmbracelet/bubbletea" ) // QuitMsg signals the application should quit type QuitMsg struct{} // ErrorMsg signals an error to display type ErrorMsg struct { Err error } // cmdQuit handles :quit / :q func cmdQuit(m action.Model, args []string) tea.Cmd { return func() tea.Msg { return tea.Quit() } } // cmdQuitAll handles :qall / :qa func cmdQuitAll(m action.Model, args []string) tea.Cmd { return func() tea.Msg { return tea.Quit() } } // cmdWrite handles :write / :w 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 func cmdWriteAll(m action.Model, args []string) tea.Cmd { // TODO: Implement saving all buffers return nil } // cmdWriteQuit handles :wq func cmdWriteQuit(m action.Model, args []string) tea.Cmd { // TODO: Save then quit return func() tea.Msg { return tea.Quit() } } // cmdRegisters handles :register 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] // 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 option type Setting struct { Name string ShortForm string Type SettingType Get func(s action.Settings) any Set func(m action.Model, val any) } 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 action.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 action.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 action.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 action.Settings) any { return s.ScrollOff }, Set: func(m action.Model, val any) { s := m.Settings() s.ScrollOff = val.(int) m.SetSettings(s) }, }, } 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 } 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 }