diff --git a/cmd/micro/initlua.go b/cmd/micro/initlua.go index 7eac5637..ffe175a0 100644 --- a/cmd/micro/initlua.go +++ b/cmd/micro/initlua.go @@ -73,7 +73,7 @@ func luaImportMicroConfig() *lua.LTable { ulua.L.SetField(pkg, "OptionComplete", luar.New(ulua.L, action.OptionComplete)) ulua.L.SetField(pkg, "OptionValueComplete", luar.New(ulua.L, action.OptionValueComplete)) ulua.L.SetField(pkg, "NoComplete", luar.New(ulua.L, nil)) - ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey)) + ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKeyPlug)) ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig)) ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory)) ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory)) @@ -88,8 +88,8 @@ func luaImportMicroConfig() *lua.LTable { ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug)) ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug)) ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption)) - ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption)) - ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative)) + ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOptionPlug)) + ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNativePlug)) ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir)) return pkg diff --git a/internal/action/bindings.go b/internal/action/bindings.go index 37517d9b..9a5cb86f 100644 --- a/internal/action/bindings.go +++ b/internal/action/bindings.go @@ -261,9 +261,18 @@ func eventsEqual(e1 Event, e2 Event) bool { return e1 == e2 } +// TryBindKeyPlug tries to bind a key for the plugin without writing to bindings.json. +// This operation can be rejected by lockbindings to prevent unexpected actions by the user. +func TryBindKeyPlug(k, v string, overwrite bool) (bool, error) { + if l, ok := config.GlobalSettings["lockbindings"]; ok && l.(bool) { + return false, errors.New("bindings is locked by the user") + } + return TryBindKey(k, v, overwrite, false) +} + // TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json -// Returns true if the keybinding already existed and a possible error -func TryBindKey(k, v string, overwrite bool) (bool, error) { +// Returns true if the keybinding already existed or is binded successfully and a possible error +func TryBindKey(k, v string, overwrite bool, writeToFile bool) (bool, error) { var e error var parsed map[string]any @@ -310,7 +319,12 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) { txt, _ := json.MarshalIndent(parsed, "", " ") txt = append(txt, '\n') - return true, writeFile(filename, txt) + + if writeToFile { + return true, writeFile(filename, txt) + } else { + return true, nil + } } return false, e } diff --git a/internal/action/command.go b/internal/action/command.go index 7cb5e523..bc26f0e9 100644 --- a/internal/action/command.go +++ b/internal/action/command.go @@ -630,7 +630,7 @@ func doSetGlobalOptionNative(option string, nativeValue any) error { return nil } -func SetGlobalOptionNative(option string, nativeValue any) error { +func SetGlobalOptionNative(option string, nativeValue any, writeToFile bool) error { if err := config.OptionIsValid(option, nativeValue); err != nil { return err } @@ -653,6 +653,10 @@ func SetGlobalOptionNative(option string, nativeValue any) error { delete(b.LocalSettings, option) } + if !writeToFile { + return nil + } + err := config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json")) if err != nil { if errors.Is(err, util.ErrOverwrite) { @@ -665,7 +669,7 @@ func SetGlobalOptionNative(option string, nativeValue any) error { return nil } -func SetGlobalOption(option, value string) error { +func SetGlobalOption(option, value string, writeToFile bool) error { if _, ok := config.GlobalSettings[option]; !ok { return config.ErrInvalidOption } @@ -675,7 +679,15 @@ func SetGlobalOption(option, value string) error { return err } - return SetGlobalOptionNative(option, nativeValue) + return SetGlobalOptionNative(option, nativeValue, writeToFile) +} + +func SetGlobalOptionNativePlug(option string, nativeValue any) error { + return SetGlobalOptionNative(option, nativeValue, false) +} + +func SetGlobalOptionPlug(option, value string) error { + return SetGlobalOption(option, value, false) } // ResetCmd resets a setting to its default value @@ -689,7 +701,7 @@ func (h *BufPane) ResetCmd(args []string) { defaults := config.DefaultAllSettings() if _, ok := defaults[option]; ok { - SetGlobalOptionNative(option, defaults[option]) + SetGlobalOptionNative(option, defaults[option], true) return } InfoBar.Error(config.ErrInvalidOption) @@ -705,7 +717,7 @@ func (h *BufPane) SetCmd(args []string) { option := args[0] value := args[1] - err := SetGlobalOption(option, value) + err := SetGlobalOption(option, value, true) if err == config.ErrInvalidOption { err := h.Buf.SetOption(option, value) if err != nil { @@ -761,7 +773,7 @@ func (h *BufPane) toggleOption(option string, local bool) error { return err } } else { - if err := SetGlobalOptionNative(option, newVal); err != nil { + if err := SetGlobalOptionNative(option, newVal, true); err != nil { return err } } @@ -844,7 +856,7 @@ func (h *BufPane) BindCmd(args []string) { return } - _, err := TryBindKey(parseKeyArg(args[0]), args[1], true) + _, err := TryBindKey(parseKeyArg(args[0]), args[1], true, true) if err != nil { if errors.Is(err, util.ErrOverwrite) { screen.TermMessage(err) diff --git a/internal/config/settings.go b/internal/config/settings.go index 685b55ef..2f815833 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -119,6 +119,7 @@ var DefaultGlobalOnlySettings = map[string]any{ "helpsplit": "hsplit", "infobar": true, "keymenu": false, + "lockbindings": false, "mouse": true, "multiopen": "tab", "parsecursor": false, diff --git a/runtime/help/options.md b/runtime/help/options.md index 0347f5e1..b2a5244d 100644 --- a/runtime/help/options.md +++ b/runtime/help/options.md @@ -227,6 +227,12 @@ Here are the available options: default value: `false` +* `lockbindings`: prevent plugins and lua scripts from binding any keys. + Any custom actions must be binded manually either via commands like `bind` + or by modifying the `bindings.json` file. + + default value: `false` + * `matchbrace`: show matching braces for '()', '{}', '[]' when the cursor is on a brace character or (if `matchbraceleft` is enabled) next to it. diff --git a/runtime/help/plugins.md b/runtime/help/plugins.md index c683804f..11170507 100644 --- a/runtime/help/plugins.md +++ b/runtime/help/plugins.md @@ -174,11 +174,12 @@ The packages and their contents are listed below (in Go type signatures): values afterwards - `NoComplete`: no autocompletion suggestions - - `TryBindKey(k, v string, overwrite bool) (bool, error)`: bind the key - `k` to the string `v` in the `bindings.json` file. If `overwrite` is - true, this will overwrite any existing binding to key `k`. Returns true - if the binding was made, and a possible error (for example writing to - `bindings.json` can cause an error). + - `TryBindKey(k, v string, overwrite bool) (bool, error)`: + bind the key `k` to the string `v`. If `overwrite` is true, this will + overwrite any existing binding to key `k`. + Returns true if the binding was made, and a possible error. + This operation can be rejected by `lockbindings` to prevent undesired + actions by the user. - `Reload()`: reload configuration files. @@ -224,9 +225,9 @@ The packages and their contents are listed below (in Go type signatures): given plugin in the `GlobalSettings` map. - `SetGlobalOption(option, value string) error`: sets an option to a - given value. Same as using the `> set` command. This will try to convert - the value into the proper type for the option. Can return an error if the - option name is not valid, or the value can not be converted. + given value. This will try to convert the value into the proper + type for the option. Can return an error if the option name is not + valid, or the value can not be converted. - `SetGlobalOptionNative(option string, value any) error`: sets an option to a given value, where the type of value is the actual