From 00e568640c781092d37a7fe5d8ebab8faeebed1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:25:10 +0100 Subject: [PATCH 1/6] buffer: Fix `ReloadSettings(true)` for volatile `filetype` We shall not overwrite a volatile set `filetype` provided as argument for micro: `micro -filetype shell foo` --- internal/buffer/settings.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/buffer/settings.go b/internal/buffer/settings.go index 3bb3d0c0..fe794550 100644 --- a/internal/buffer/settings.go +++ b/internal/buffer/settings.go @@ -13,7 +13,9 @@ import ( func (b *Buffer) ReloadSettings(reloadFiletype bool) { settings := config.ParsedSettings() - if _, ok := b.LocalSettings["filetype"]; !ok && reloadFiletype { + _, local := b.LocalSettings["filetype"] + _, volatile := config.VolatileSettings["filetype"] + if reloadFiletype && !local && !volatile { // need to update filetype before updating other settings based on it b.Settings["filetype"] = "unknown" if v, ok := settings["filetype"]; ok { From 930fbea74d12f7585a8fd38827f04011e9445a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:11:14 +0100 Subject: [PATCH 2/6] config: Split up `InitLocalSettings()` into two dedicated functions * `UpdatePathGlobLocals()` * to apply the settings provided within e.g. "/etc/*": {} * `UpdateFileTypeLocals()` * to apply the settings provided within e.g. "ft:shell": {} We don't need to call `InitLocalSettings()` twice any longer. --- internal/buffer/buffer.go | 21 +++++---------------- internal/buffer/settings.go | 4 ++-- internal/config/settings.go | 37 ++++++++++++++++++++++--------------- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 1172dac8..092a5dcc 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -325,28 +325,17 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT b.AbsPath = absPath b.Path = path - // this is a little messy since we need to know some settings to read - // the file properly, but some settings depend on the filetype, which - // we don't know until reading the file. We first read the settings - // into a local variable and then use that to determine the encoding, - // readonly, and fileformat necessary for reading the file and - // assigning the filetype. - settings := config.DefaultCommonSettings() b.Settings = config.DefaultCommonSettings() b.LocalSettings = make(map[string]bool) for k, v := range config.GlobalSettings { if _, ok := config.DefaultGlobalOnlySettings[k]; !ok { // make sure setting is not global-only - settings[k] = v b.Settings[k] = v } } - config.InitLocalSettings(settings, absPath) - b.Settings["readonly"] = settings["readonly"] - b.Settings["filetype"] = settings["filetype"] - b.Settings["syntax"] = settings["syntax"] + config.UpdatePathGlobLocals(b.Settings, absPath) - enc, err := htmlindex.Get(settings["encoding"].(string)) + enc, err := htmlindex.Get(b.Settings["encoding"].(string)) if err != nil { enc = unicode.UTF8 b.Settings["encoding"] = "utf-8" @@ -366,7 +355,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT if size == 0 { // for empty files, use the fileformat setting instead of // autodetection - switch settings["fileformat"] { + switch b.Settings["fileformat"] { case "unix": ff = FFUnix case "dos": @@ -397,8 +386,8 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT } b.UpdateRules() - // init local settings again now that we know the filetype - config.InitLocalSettings(b.Settings, b.Path) + // we know the filetype now, so update per-filetype settings + config.UpdateFileTypeLocals(b.Settings, b.Settings["filetype"].(string)) if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) { os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm) diff --git a/internal/buffer/settings.go b/internal/buffer/settings.go index fe794550..2e9005b2 100644 --- a/internal/buffer/settings.go +++ b/internal/buffer/settings.go @@ -25,9 +25,9 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) { // update syntax rules, which will also update filetype if needed b.UpdateRules() - settings["filetype"] = b.Settings["filetype"] - config.InitLocalSettings(settings, b.Path) + config.UpdatePathGlobLocals(settings, b.AbsPath) + config.UpdateFileTypeLocals(settings, b.Settings["filetype"].(string)) for k, v := range config.DefaultCommonSettings() { if k == "filetype" { // prevent recursion diff --git a/internal/config/settings.go b/internal/config/settings.go index a72f5025..ddffb72f 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -288,24 +288,31 @@ func InitGlobalSettings() error { return err } -// InitLocalSettings scans the json in settings.json and sets the options locally based -// on whether the filetype or path matches ft or glob local settings +// UpdatePathGlobLocals scans the already parsed settings and sets the options locally +// based on whether the path matches a glob // Must be called after ReadSettings -func InitLocalSettings(settings map[string]interface{}, path string) { +func UpdatePathGlobLocals(settings map[string]interface{}, path string) { for k, v := range parsedSettings { - if strings.HasPrefix(reflect.TypeOf(v).String(), "map") { - if strings.HasPrefix(k, "ft:") { - if settings["filetype"].(string) == k[3:] { - for k1, v1 := range v.(map[string]interface{}) { - settings[k1] = v1 - } + if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") { + g, _ := glob.Compile(k) + if g.MatchString(path) { + for k1, v1 := range v.(map[string]interface{}) { + settings[k1] = v1 } - } else { - g, _ := glob.Compile(k) - if g.MatchString(path) { - for k1, v1 := range v.(map[string]interface{}) { - settings[k1] = v1 - } + } + } + } +} + +// UpdateFileTypeLocals scans the already parsed settings and sets the options locally +// based on whether the filetype matches to "ft:" +// Must be called after ReadSettings +func UpdateFileTypeLocals(settings map[string]interface{}, filetype string) { + for k, v := range parsedSettings { + if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && strings.HasPrefix(k, "ft:") { + if filetype == k[3:] { + for k1, v1 := range v.(map[string]interface{}) { + settings[k1] = v1 } } } From 982a4fe065510357709c2a8d40443ed5e8137536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:17:30 +0100 Subject: [PATCH 3/6] config: Prevent the update of `filetype` by `UpdateFileTypeLocals()` This shall prevent unpredictable results caused by such a user configuration: ``` "ft:go" { "filetype": "c" } ``` --- internal/config/settings.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/config/settings.go b/internal/config/settings.go index ddffb72f..7630915f 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -312,7 +312,9 @@ func UpdateFileTypeLocals(settings map[string]interface{}, filetype string) { if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && strings.HasPrefix(k, "ft:") { if filetype == k[3:] { for k1, v1 := range v.(map[string]interface{}) { - settings[k1] = v1 + if k1 != "filetype" { + settings[k1] = v1 + } } } } From 4a9058c3bd9b46c69cf1cd60599b89b83e28c60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:57:11 +0100 Subject: [PATCH 4/6] buffer: Move `UpdatePathGlobLocals()` before updating the `filetype` Like in NewBuffer(), we need to update glob-based local settings before updating the filetype, since the filetype itself may be among those glob-based local settings. --- internal/buffer/settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/buffer/settings.go b/internal/buffer/settings.go index 2e9005b2..661ba3fd 100644 --- a/internal/buffer/settings.go +++ b/internal/buffer/settings.go @@ -12,6 +12,7 @@ import ( func (b *Buffer) ReloadSettings(reloadFiletype bool) { settings := config.ParsedSettings() + config.UpdatePathGlobLocals(settings, b.AbsPath) _, local := b.LocalSettings["filetype"] _, volatile := config.VolatileSettings["filetype"] @@ -26,7 +27,6 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) { // update syntax rules, which will also update filetype if needed b.UpdateRules() - config.UpdatePathGlobLocals(settings, b.AbsPath) config.UpdateFileTypeLocals(settings, b.Settings["filetype"].(string)) for k, v := range config.DefaultCommonSettings() { if k == "filetype" { From 2e9423590509e8a5f29fbb2fec1f9ca33b940241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:43:59 +0100 Subject: [PATCH 5/6] buffer: Perform `filetype` callbacks on `ReloadSettings()` In `ReloadSettings()` the `filetype` can change upon globbed path given by the `settings.json` or by identifying a different `filetype` based on the file name given or pattern present inside the file. To prevent further recursion caused by checking the `filetype` again, its processing stops here and isn't considered in `DoSetOptionNative()` once again where the callbacks are usually triggered. --- internal/buffer/settings.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/internal/buffer/settings.go b/internal/buffer/settings.go index 661ba3fd..3db35e97 100644 --- a/internal/buffer/settings.go +++ b/internal/buffer/settings.go @@ -14,6 +14,8 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) { settings := config.ParsedSettings() config.UpdatePathGlobLocals(settings, b.AbsPath) + oldFiletype := b.Settings["filetype"].(string) + _, local := b.LocalSettings["filetype"] _, volatile := config.VolatileSettings["filetype"] if reloadFiletype && !local && !volatile { @@ -27,7 +29,13 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) { // update syntax rules, which will also update filetype if needed b.UpdateRules() - config.UpdateFileTypeLocals(settings, b.Settings["filetype"].(string)) + curFiletype := b.Settings["filetype"].(string) + if oldFiletype != curFiletype { + b.doCallbacks("filetype", oldFiletype, curFiletype) + } + + config.UpdateFileTypeLocals(settings, curFiletype) + for k, v := range config.DefaultCommonSettings() { if k == "filetype" { // prevent recursion @@ -119,15 +127,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) { } } - if b.OptionCallback != nil { - b.OptionCallback(option, nativeValue) - } - - if err := config.RunPluginFn("onBufferOptionChanged", - luar.New(ulua.L, b), luar.New(ulua.L, option), - luar.New(ulua.L, oldValue), luar.New(ulua.L, nativeValue)); err != nil { - screen.TermMessage(err) - } + b.doCallbacks(option, oldValue, nativeValue) } func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error { @@ -154,3 +154,15 @@ func (b *Buffer) SetOption(option, value string) error { return b.SetOptionNative(option, nativeValue) } + +func (b *Buffer) doCallbacks(option string, oldValue interface{}, newValue interface{}) { + if b.OptionCallback != nil { + b.OptionCallback(option, newValue) + } + + if err := config.RunPluginFn("onBufferOptionChanged", + luar.New(ulua.L, b), luar.New(ulua.L, option), + luar.New(ulua.L, oldValue), luar.New(ulua.L, newValue)); err != nil { + screen.TermMessage(err) + } +} From ddc6051b33397f37460cbae3881c99e0932b79c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Sun, 16 Feb 2025 18:01:18 +0100 Subject: [PATCH 6/6] actions: Use `SetOptionNative()` instead of setting options directly Setting options directly in (h.)Buf.Settings without calling SetOption() or SetOptionNative() is generally not the best idea, since it may not trigger the needed side effects. In particular, after https://github.com/zyedidia/micro/pull/3343, directly setting `diffgutter` and `ruler` causes them not being tracked as locally overridden per buffer, so if we run the `reload` command, it unexpectedly replaces them with the default ones. --- internal/action/actions.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index 84a5060b..e06b7815 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1794,12 +1794,12 @@ func (h *BufPane) HalfPageDown() bool { // ToggleDiffGutter turns the diff gutter off and on func (h *BufPane) ToggleDiffGutter() bool { - if !h.Buf.Settings["diffgutter"].(bool) { - h.Buf.Settings["diffgutter"] = true + diffgutter := !h.Buf.Settings["diffgutter"].(bool) + h.Buf.SetOptionNative("diffgutter", diffgutter) + if diffgutter { h.Buf.UpdateDiff() InfoBar.Message("Enabled diff gutter") } else { - h.Buf.Settings["diffgutter"] = false InfoBar.Message("Disabled diff gutter") } return true @@ -1807,11 +1807,11 @@ func (h *BufPane) ToggleDiffGutter() bool { // ToggleRuler turns line numbers off and on func (h *BufPane) ToggleRuler() bool { - if !h.Buf.Settings["ruler"].(bool) { - h.Buf.Settings["ruler"] = true + ruler := !h.Buf.Settings["ruler"].(bool) + h.Buf.SetOptionNative("ruler", ruler) + if ruler { InfoBar.Message("Enabled ruler") } else { - h.Buf.Settings["ruler"] = false InfoBar.Message("Disabled ruler") } return true