From 098641f5c01e082a0e57cc5825a79fb88f50ec61 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Thu, 5 Mar 2026 14:34:21 -0700 Subject: [PATCH] fix: cleaned up and fixed the settings that needed updating --- cmd/gim/main.go | 1 + internal/action/insert.go | 2 +- internal/action/interface.go | 5 +- internal/command/handlers.go | 191 +++++++++++++++----- internal/core/settings.go | 20 +- internal/core/window.go | 19 +- internal/editor/integration_command_test.go | 8 +- internal/editor/model.go | 8 +- internal/editor/model_builder.go | 2 +- internal/editor/view.go | 22 +-- 10 files changed, 188 insertions(+), 90 deletions(-) diff --git a/cmd/gim/main.go b/cmd/gim/main.go index 9be303b..c688fb3 100644 --- a/cmd/gim/main.go +++ b/cmd/gim/main.go @@ -16,6 +16,7 @@ func main() { win := core.NewWindowBuilder(). WithBuffer(&buf). + WithOptions(core.NewDefaultWinOptions()). Build() model := editor.NewModelBuilder(). diff --git a/internal/action/insert.go b/internal/action/insert.go index 144a13b..ee74aec 100644 --- a/internal/action/insert.go +++ b/internal/action/insert.go @@ -241,7 +241,7 @@ func (a InsertTab) Execute(m Model) tea.Cmd { x, y := win.Cursor.Col, win.Cursor.Line l := buf.Lines[y] - tabs := strings.Repeat(" ", m.Settings().TabSize) + tabs := strings.Repeat(" ", m.Settings().TabStop) if x < len(l) { buf.SetLine(y, l[:x]+tabs+l[x:]) } else { diff --git a/internal/action/interface.go b/internal/action/interface.go index 88781a9..0e57baf 100644 --- a/internal/action/interface.go +++ b/internal/action/interface.go @@ -45,8 +45,8 @@ type Model interface { Mode() core.Mode SetMode(mode core.Mode) - Settings() core.Settings - SetSettings(s core.Settings) + Settings() core.EditorSettings + SetSettings(s core.EditorSettings) // ================================================== // Registers @@ -55,7 +55,6 @@ type Model interface { GetRegister(name rune) (core.Register, bool) SetRegister(name rune, t core.RegisterType, cnt []string) error UpdateDefaultRegister(t core.RegisterType, cnt []string) - } // Action is the base interface - anything executable diff --git a/internal/command/handlers.go b/internal/command/handlers.go index 6cf1384..1a14129 100644 --- a/internal/command/handlers.go +++ b/internal/command/handlers.go @@ -110,7 +110,15 @@ type Setting struct { Name string ShortForm string Type SettingType - Get func(s core.Settings) any + Get func(s core.EditorSettings) any + Set func(m action.Model, val any) +} + +type WindowSetting struct { + Name string + ShortForm string + Type SettingType + Get func(m action.Model) any Set func(m action.Model, val any) } @@ -123,50 +131,69 @@ const ( StringSetting ) -// settingsMap defines all available settings +// settingsMap defines all available editor settings var settingsMap = []Setting{ + { + Name: "tabstop", + ShortForm: "ts", + Type: IntSetting, + Get: func(s core.EditorSettings) any { return s.TabStop }, + Set: func(m action.Model, val any) { + s := m.Settings() + s.TabStop = val.(int) + m.SetSettings(s) + }, + }, +} + +// windowSettingsMap defines all available window settings +var windowSettingsMap = []WindowSetting{ { Name: "number", ShortForm: "nu", Type: BoolSetting, - Get: func(s core.Settings) any { return s.Number }, + Get: func(m action.Model) any { return m.ActiveWindow().Options.Number }, Set: func(m action.Model, val any) { - s := m.Settings() - s.Number = val.(bool) - m.SetSettings(s) + w := m.ActiveWindow() + o := w.Options + o.Number = val.(bool) + w.SetOptions(o) }, }, { Name: "relativenumber", ShortForm: "rnu", Type: BoolSetting, - Get: func(s core.Settings) any { return s.RelativeNumber }, + Get: func(m action.Model) any { return m.ActiveWindow().Options.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) + w := m.ActiveWindow() + o := w.Options + o.RelativeNumber = val.(bool) + w.SetOptions(o) }, }, { Name: "scrolloff", ShortForm: "so", Type: IntSetting, - Get: func(s core.Settings) any { return s.ScrollOff }, + Get: func(m action.Model) any { return m.ActiveWindow().Options.ScrollOff }, Set: func(m action.Model, val any) { - s := m.Settings() - s.ScrollOff = val.(int) - m.SetSettings(s) + 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) }, }, } @@ -186,31 +213,66 @@ func lookupSetting(name string) *Setting { return nil } +// lookupWindowSetting: Finds a window setting by name, short form, or prefix. +func lookupWindowSetting(name string) *WindowSetting { + for i := range windowSettingsMap { + s := &windowSettingsMap[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 != nil { + if setting.Type == BoolSetting { + // Toggle the boolean + currentVal := setting.Get(m.Settings()).(bool) + setting.Set(m, !currentVal) + } + return nil } - if setting.Type == BoolSetting { - // Toggle the boolean - currentVal := setting.Get(m.Settings()).(bool) - setting.Set(m, !currentVal) + + windowSetting := lookupWindowSetting(name) + if windowSetting != nil { + if windowSetting.Type == BoolSetting { + // Toggle the boolean + currentVal := windowSetting.Get(m).(bool) + windowSetting.Set(m, !currentVal) + } + return nil } + return nil } // Handle disable: nooption if name, ok := strings.CutPrefix(opt, "no"); ok { setting := lookupSetting(name) - if setting == nil { + if setting != nil { + if setting.Type == BoolSetting { + setting.Set(m, false) + } return nil } - if setting.Type == BoolSetting { - setting.Set(m, false) + + windowSetting := lookupWindowSetting(name) + if windowSetting != nil { + if windowSetting.Type == BoolSetting { + windowSetting.Set(m, false) + } + return nil } + return nil } @@ -218,34 +280,63 @@ func parseSetOption(m action.Model, opt string) error { if strings.Contains(opt, "=") { parts := strings.SplitN(opt, "=", 2) name, value := parts[0], parts[1] + setting := lookupSetting(name) - if setting == nil { + 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 } - switch setting.Type { - case IntSetting: - intVal, err := strconv.Atoi(value) - if err != nil { - return err + + windowSetting := lookupWindowSetting(name) + if windowSetting != nil { + switch windowSetting.Type { + case IntSetting: + intVal, err := strconv.Atoi(value) + if err != nil { + return err + } + windowSetting.Set(m, intVal) + case StringSetting: + windowSetting.Set(m, value) + case BoolSetting: + // Handle :set option=true / :set option=false + boolVal := value == "true" || value == "1" || value == "yes" + windowSetting.Set(m, boolVal) } - 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 } + return nil } // Handle enable: option (boolean only) setting := lookupSetting(opt) - if setting == nil { + if setting != nil { + if setting.Type == BoolSetting { + setting.Set(m, true) + } return nil } - if setting.Type == BoolSetting { - setting.Set(m, true) + + windowSetting := lookupWindowSetting(opt) + if windowSetting != nil { + if windowSetting.Type == BoolSetting { + windowSetting.Set(m, true) + } + return nil } return nil diff --git a/internal/core/settings.go b/internal/core/settings.go index 28482b5..0c2b9ed 100644 --- a/internal/core/settings.go +++ b/internal/core/settings.go @@ -1,23 +1,15 @@ package core -// Settings: Configuration options for editor display and behavior. -type Settings struct { - Number bool - RelativeNumber bool - GutterSize int - TabSize int - ScrollOff int +// EditorSettings: Configuration options for editor display and behavior. +type EditorSettings struct { + TabStop int // TODO: Colors } // NewDefaultSettings: Creates a Settings struct with sensible defaults for // line numbers, gutter width, tab size, and scroll offset. -func NewDefaultSettings() Settings { - return Settings{ - Number: true, - RelativeNumber: true, - GutterSize: 5, - TabSize: 2, - ScrollOff: 8, +func NewDefaultSettings() EditorSettings { + return EditorSettings{ + TabStop: 2, } } diff --git a/internal/core/window.go b/internal/core/window.go index 8571412..a062f1e 100644 --- a/internal/core/window.go +++ b/internal/core/window.go @@ -2,12 +2,22 @@ package core // TODO: No more global settings, window-wide settings type WinOptions struct { - // Number bool + Number bool + RelativeNumber bool + GutterSize int // Wrap bool - // Relnumber bool ScrollOff int } +func NewDefaultWinOptions() WinOptions { + return WinOptions{ + Number: true, + RelativeNumber: true, + GutterSize: 5, + ScrollOff: 8, + } +} + type Window struct { Id int Number int // Ignored for now, will be used when splits come into play @@ -181,3 +191,8 @@ func (w *Window) SetDimensions(width, height int) { w.Width = width w.Height = height } + +// Window.SetOptions: Sets the options of this window. +func (w *Window) SetOptions(opts WinOptions) { + w.Options = opts +} diff --git a/internal/editor/integration_command_test.go b/internal/editor/integration_command_test.go index 7cdef8b..0a29312 100644 --- a/internal/editor/integration_command_test.go +++ b/internal/editor/integration_command_test.go @@ -102,8 +102,8 @@ func TestCommandSetInteger(t *testing.T) { sendKeys(tm, ":", "s", "e", "t", " ", "t", "a", "b", "s", "t", "o", "p", "=", "4", "enter") m := getFinalModel(t, tm) - if m.Settings().TabSize != 4 { - t.Errorf("TabSize = %d, want 4", m.Settings().TabSize) + if m.Settings().TabStop != 4 { + t.Errorf("TabSize = %d, want 4", m.Settings().TabStop) } }) @@ -114,8 +114,8 @@ func TestCommandSetInteger(t *testing.T) { sendKeys(tm, ":", "s", "e", "t", " ", "t", "s", "=", "8", "enter") m := getFinalModel(t, tm) - if m.Settings().TabSize != 8 { - t.Errorf("TabSize = %d, want 8", m.Settings().TabSize) + if m.Settings().TabStop != 8 { + t.Errorf("TabSize = %d, want 8", m.Settings().TabStop) } }) diff --git a/internal/editor/model.go b/internal/editor/model.go index 1485869..1760817 100644 --- a/internal/editor/model.go +++ b/internal/editor/model.go @@ -42,7 +42,7 @@ type Model struct { commandOutput string // Global settings (TODO: This needs to be refactored) - settings core.Settings + settings core.EditorSettings // Registers registers map[rune]core.Register // name -> register @@ -185,7 +185,7 @@ func (m *Model) processInsertKey(key string) { } case "tab": - tabs := strings.Repeat(" ", m.Settings().TabSize) + tabs := strings.Repeat(" ", m.Settings().TabStop) if col < len(l) { buf.SetLine(line, l[:col]+tabs+l[col:]) } else { @@ -282,11 +282,11 @@ func (m *Model) SetMode(mode core.Mode) { m.mode = mode } -func (m *Model) Settings() core.Settings { +func (m *Model) Settings() core.EditorSettings { return m.settings } -func (m *Model) SetSettings(s core.Settings) { +func (m *Model) SetSettings(s core.EditorSettings) { m.settings = s } diff --git a/internal/editor/model_builder.go b/internal/editor/model_builder.go index c258999..066e60c 100644 --- a/internal/editor/model_builder.go +++ b/internal/editor/model_builder.go @@ -93,7 +93,7 @@ func (mb *ModelBuilder) WithTermHeight(height int) *ModelBuilder { } // ModelBuilder.WithSettings: Set the editor settings (tabstop, scrolloff, etc). -func (mb *ModelBuilder) WithSettings(settings core.Settings) *ModelBuilder { +func (mb *ModelBuilder) WithSettings(settings core.EditorSettings) *ModelBuilder { mb.model.settings = settings return mb } diff --git a/internal/editor/view.go b/internal/editor/view.go index 063d2da..b9d4e74 100644 --- a/internal/editor/view.go +++ b/internal/editor/view.go @@ -19,10 +19,10 @@ func (m Model) View() string { // Each window has its own status bar and mode styles := m.Styles() - settings := m.Settings() + options := win.Options // Draw window - view := viewWindow(win, styles, settings, m.Mode()) + view := viewWindow(win, styles, options, m.Mode()) // Command bar is seperate cmdBar := drawCommandBar(m) @@ -31,7 +31,7 @@ func (m Model) View() string { // viewWindow: Renders a single window's content including line numbers and buffer text. // Each window has its own line numbers, gutter, and viewport dimensions. -func viewWindow(w *core.Window, styles style.Styles, settings core.Settings, mode core.Mode) string { +func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mode core.Mode) string { buf := w.Buffer var view strings.Builder @@ -42,7 +42,7 @@ func viewWindow(w *core.Window, styles style.Styles, settings core.Settings, mod // Draw buffer lines for lineNum := start; lineNum < end; lineNum++ { if lineNum < buf.LineCount() { - line := drawLine(w, styles, settings, mode, buf.Line(lineNum), lineNum) + line := drawLine(w, styles, options, mode, buf.Line(lineNum), lineNum) view.WriteString(line) } view.WriteRune('\n') @@ -57,7 +57,7 @@ func viewWindow(w *core.Window, styles style.Styles, settings core.Settings, mod // drawLine: Renders a single line with syntax highlighting, cursor, and visual selection. // Handles gutter, cursor rendering, and visual mode highlighting. -func drawLine(w *core.Window, styles style.Styles, settings core.Settings, mode core.Mode, line string, lineNumber int) string { +func drawLine(w *core.Window, styles style.Styles, options core.WinOptions, mode core.Mode, line string, lineNumber int) string { runes := []rune(line) curStyle := styles.CursorStyle(mode) @@ -66,7 +66,7 @@ func drawLine(w *core.Window, styles style.Styles, settings core.Settings, mode var view strings.Builder // Draw gutter first - gutter := drawGutter(w, styles, settings, lineNumber) + gutter := drawGutter(w, styles, options, lineNumber) view.WriteString(gutter) // Now draw the line content @@ -97,15 +97,15 @@ func drawLine(w *core.Window, styles style.Styles, settings core.Settings, mode // drawGutter: Renders the line number gutter with support for both absolute and // relative line numbers, highlighting the current line differently. -func drawGutter(w *core.Window, styles style.Styles, settings core.Settings, curLine int) string { - if !(settings.Number || settings.RelativeNumber) { +func drawGutter(w *core.Window, styles style.Styles, options core.WinOptions, curLine int) string { + if !(options.Number || options.RelativeNumber) { return "" } // Required vars var ( view strings.Builder - gutSize int = settings.GutterSize - 1 // -1 is for padding + gutSize int = options.GutterSize - 1 // -1 is for padding currentLine bool = curLine == w.Cursor.Line lineNumber int @@ -115,7 +115,7 @@ func drawGutter(w *core.Window, styles style.Styles, settings core.Settings, cur ) // If we have relative setting, set the numbers relatively - if settings.RelativeNumber { + if options.RelativeNumber { if curLine > w.Cursor.Line { lineNumber = curLine - w.Cursor.Line } @@ -126,7 +126,7 @@ func drawGutter(w *core.Window, styles style.Styles, settings core.Settings, cur } // If we have number setting AND not relative setting OR we are on current line, use current line number - if (settings.Number && !settings.RelativeNumber) || (settings.Number && currentLine) { + if (options.Number && !options.RelativeNumber) || (options.Number && currentLine) { lineNumber = curLine + 1 }