package command import ( "fmt" "strconv" "strings" "git.gophernest.net/azpect/TextEditor/internal/action" 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(m action.Model) 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 (both global and window-local) var settingsMap = []Setting{ // Global editor settings { Name: "tabstop", ShortForm: "ts", Type: IntSetting, Get: func(m action.Model) any { return m.Settings().TabStop }, Set: func(m action.Model, val any) { s := m.Settings() s.TabStop = val.(int) m.SetSettings(s) }, }, // Window-local settings { Name: "number", ShortForm: "nu", Type: BoolSetting, Get: func(m action.Model) any { return m.ActiveWindow().Options.Number }, Set: func(m action.Model, val any) { w := m.ActiveWindow() o := w.Options o.Number = val.(bool) w.SetOptions(o) }, }, { Name: "relativenumber", ShortForm: "rnu", Type: BoolSetting, Get: func(m action.Model) any { return m.ActiveWindow().Options.RelativeNumber }, Set: func(m action.Model, val any) { w := m.ActiveWindow() o := w.Options o.RelativeNumber = val.(bool) w.SetOptions(o) }, }, { Name: "scrolloff", ShortForm: "so", Type: IntSetting, Get: func(m action.Model) any { return m.ActiveWindow().Options.ScrollOff }, Set: func(m action.Model, val any) { w := m.ActiveWindow() o := w.Options o.ScrollOff = val.(int) w.SetOptions(o) }, }, { Name: "guttersize", ShortForm: "gu", Type: IntSetting, Get: func(m action.Model) any { return m.ActiveWindow().Options.GutterSize }, Set: func(m action.Model, val any) { w := m.ActiveWindow() o := w.Options o.GutterSize = val.(int) w.SetOptions(o) }, }, } // 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 && setting.Type == BoolSetting { currentVal := setting.Get(m).(bool) setting.Set(m, !currentVal) } return nil } // Handle disable: nooption if name, ok := strings.CutPrefix(opt, "no"); ok { setting := lookupSetting(name) if setting != nil && 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 { 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 && setting.Type == BoolSetting { setting.Set(m, true) } return nil }