Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 94 additions & 33 deletions internal/action/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,41 @@ var commands map[string]Command

func InitCommands() {
commands = map[string]Command{
"set": {(*BufPane).SetCmd, OptionValueComplete},
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
"show": {(*BufPane).ShowCmd, OptionComplete},
"showkey": {(*BufPane).ShowKeyCmd, nil},
"run": {(*BufPane).RunCmd, nil},
"bind": {(*BufPane).BindCmd, nil},
"unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil},
"jump": {(*BufPane).JumpCmd, nil},
"save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
"help": {(*BufPane).HelpCmd, HelpComplete},
"eval": {(*BufPane).EvalCmd, nil},
"log": {(*BufPane).ToggleLogCmd, nil},
"plugin": {(*BufPane).PluginCmd, PluginComplete},
"reload": {(*BufPane).ReloadCmd, nil},
"reopen": {(*BufPane).ReopenCmd, nil},
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabmove": {(*BufPane).TabMoveCmd, nil},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
"retab": {(*BufPane).RetabCmd, nil},
"raw": {(*BufPane).RawCmd, nil},
"textfilter": {(*BufPane).TextFilterCmd, nil},
"set": {(*BufPane).SetCmd, OptionValueComplete},
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
"toggle": {(*BufPane).ToggleCmd, OptionValueComplete},
"togglelocal": {(*BufPane).ToggleLocalCmd, OptionValueComplete},
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
"show": {(*BufPane).ShowCmd, OptionComplete},
"showkey": {(*BufPane).ShowKeyCmd, nil},
"run": {(*BufPane).RunCmd, nil},
"bind": {(*BufPane).BindCmd, nil},
"unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil},
"jump": {(*BufPane).JumpCmd, nil},
"save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
"help": {(*BufPane).HelpCmd, HelpComplete},
"eval": {(*BufPane).EvalCmd, nil},
"log": {(*BufPane).ToggleLogCmd, nil},
"plugin": {(*BufPane).PluginCmd, PluginComplete},
"reload": {(*BufPane).ReloadCmd, nil},
"reopen": {(*BufPane).ReopenCmd, nil},
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabmove": {(*BufPane).TabMoveCmd, nil},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
"retab": {(*BufPane).RetabCmd, nil},
"raw": {(*BufPane).RawCmd, nil},
"textfilter": {(*BufPane).TextFilterCmd, nil},
}
}

Expand Down Expand Up @@ -730,6 +732,65 @@ func (h *BufPane) SetLocalCmd(args []string) {
}
}

func (h *BufPane) toggleOption(option string, local bool) error {
var curVal, newVal any

if local {
curVal = h.Buf.Settings[option]
} else {
curVal = config.GetGlobalOption(option)
}
if curVal == nil {
return config.ErrInvalidOption
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we bother saying it's an invalid option or should we just say it's not a toggleable option anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this position it is invalid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is what is invalid? An invalid option or a invalid option for this command?

For example: setlocal autosave 0 -> invalid option
Autosave is indeed a valid option but it's not a local option.

I was willing to improve error messages that's why I created ErrOptNotToggleable.

At this position it is invalid.

Note that at this point it might be a valid option yet a global-only.

To improve accuracy in this command and other parts of the code, we could encapsulate the logic into dedicated functions, something like this:

func GetGlobalOption*(name string) (interface{}, error) {
	if v, ok := GlobalSettings[name]; ok {
		return v, nil
	}
	return nil, ErrInvalidOption
}

func (b *Buffer) GetOption(name string) (interface{}, error) {
	if v, ok := b.Settings[name]; ok {
		return v, nil
	}
	if _, err := GetGlobalOption(name); err != nil {
		return nil, err
	}
	return nil, ErrNotLocalOpt
}

What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is what is invalid? An invalid option or a invalid option for this command?

When toggleOption() is called with local set to false and calls config.GetGlobalOption(option) with a option not tracked within, then it is indeed an ErrInvalidOption. If it is toggleable will be checked later on.

GetGlobalOption() is already exported to plugins:

ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))

Copy link
Contributor Author

@cutelisp cutelisp Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetGlobalOption() is already exported to plugins:

Yes, and used on go side as well. Thats why GetGlobalOption*, it would be a different function.
Note that we already use this logic.

if _, ok := config.GlobalSettings[option]; !ok {
  return config.ErrInvalidOption
}

When toggleOption() is called with local set to false and calls config.GetGlobalOption(option) with a option not tracked within, then it is indeed an ErrInvalidOption. If it is toggleable will be checked later on.

I am aware, the edge case comes when we call it with local set to true and we give a valid global-only option.
(b *Buffer) GetOption() would handle it with a specific error

}

if choices, ok := config.OptionChoices[option]; ok && len(choices) == 2 {
if curVal == choices[0] {
newVal = choices[1]
} else {
newVal = choices[0]
}
} else if curValBool, ok := curVal.(bool); ok {
newVal = !curValBool
} else {
return config.ErrOptNotToggleable
}

if local {
if err := h.Buf.SetOptionNative(option, newVal); err != nil {
return err
}
} else {
if err := SetGlobalOptionNative(option, newVal); err != nil {
return err
}
}

return nil
}

// ToggleCmd toggles a toggleable option
func (h *BufPane) ToggleCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments: provide a toggleable option")
return
}
if err := h.toggleOption(args[0], false); err != nil {
InfoBar.Error(err)
}
}

// ToggleCmd toggles a toggleable option local to the buffer
func (h *BufPane) ToggleLocalCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguments: provide a toggleable option")
return
}
if err := h.toggleOption(args[0], true); err != nil {
InfoBar.Error(err)
}
}

// ShowCmd shows the value of the given option
func (h *BufPane) ShowCmd(args []string) {
if len(args) < 1 {
Expand Down
5 changes: 3 additions & 2 deletions internal/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ var LocalSettings = []string{
}

var (
ErrInvalidOption = errors.New("Invalid option")
ErrInvalidValue = errors.New("Invalid value")
ErrInvalidOption = errors.New("Invalid option")
ErrInvalidValue = errors.New("Invalid value")
ErrOptNotToggleable = errors.New("Option not toggleable")

// The options that the user can set
GlobalSettings map[string]any
Expand Down
17 changes: 12 additions & 5 deletions runtime/help/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,20 @@ quotes here but these are not necessary when entering the command in micro.
* `setlocal 'option' 'value'`: sets the option to value locally (only in the
current buffer). This will *not* modify `settings.json`.

* `toggle 'option'`: toggles the option. Only works with options that accept
exactly two values. This will modify your `settings.json` with the new value.

* `togglelocal 'option'`: toggles the option locally (only in the
current buffer). Only works with options that accept exactly two values.
This will *not* modify `settings.json`.

* `reset 'option'`: resets the given option to its default value.

* `show 'option'`: shows the current value of the given option.

* `showkey 'key'`: Show the action(s) bound to a given key. For example
running `> showkey Ctrl-c` will display `Copy`.

* `run 'sh-command'`: runs the given shell command in the background. The
command's output will be displayed in one line when it finishes running.

Expand Down Expand Up @@ -129,8 +141,6 @@ quotes here but these are not necessary when entering the command in micro.

* `reopen`: Reopens the current file from disk.

* `reset 'option'`: resets the given option to its default value

* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs
depending on the value of `tabstospaces`.

Expand All @@ -139,9 +149,6 @@ quotes here but these are not necessary when entering the command in micro.
the terminal and helps you see which bindings aren't possible and why. This
is most useful for debugging keybindings.

* `showkey 'key'`: Show the action(s) bound to a given key. For example
running `> showkey Ctrl-c` will display `Copy`.

* `term ['exec']`: Open a terminal emulator running the given executable. If no
executable is given, this will open the default shell in the terminal
emulator.
Expand Down
Loading