// Package keys defines the TUI's keybindings and how config.File's `keys:` // section can rebind them. package keys import "charm.land/bubbles/v2/key" // KeyMap holds every action the TUI's root Update dispatches on. Screens // consult the same KeyMap so a rebind in config.yaml takes effect // everywhere at once. type KeyMap struct { Up, Down key.Binding Top, Bottom key.Binding Enter, Back key.Binding Refresh key.Binding Filter key.Binding Palette key.Binding ToggleMask key.Binding ToggleMaskAll key.Binding CopyValue key.Binding Versions key.Binding Rollback key.Binding Undelete key.Binding New, Edit key.Binding Save, Cancel key.Binding AddField key.Binding DeleteField key.Binding Delete, Destroy key.Binding Profiles key.Binding Logout key.Binding Help, Quit key.Binding ForceQuit key.Binding } // Default returns the built-in vim-flavoured keymap. func Default() *KeyMap { return &KeyMap{ Up: key.NewBinding(key.WithKeys("k", "up"), key.WithHelp("k/↑", "up")), Down: key.NewBinding(key.WithKeys("j", "down"), key.WithHelp("j/↓", "down")), Top: key.NewBinding(key.WithKeys("g", "home"), key.WithHelp("g", "top")), Bottom: key.NewBinding(key.WithKeys("G", "end"), key.WithHelp("G", "bottom")), Enter: key.NewBinding(key.WithKeys("enter", "l"), key.WithHelp("↵", "open")), Back: key.NewBinding(key.WithKeys("esc", "h"), key.WithHelp("esc", "back")), Refresh: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "refresh")), Filter: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "filter")), Palette: key.NewBinding(key.WithKeys(":"), key.WithHelp(":", "goto path")), ToggleMask: key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "show/hide value")), ToggleMaskAll: key.NewBinding(key.WithKeys("S"), key.WithHelp("S", "show/hide all")), CopyValue: key.NewBinding(key.WithKeys("y"), key.WithHelp("y", "copy value")), Versions: key.NewBinding(key.WithKeys("V"), key.WithHelp("V", "versions")), Rollback: key.NewBinding(key.WithKeys("R"), key.WithHelp("R", "rollback to this version")), Undelete: key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "undelete this version")), New: key.NewBinding(key.WithKeys("n"), key.WithHelp("n", "new secret")), Edit: key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "edit")), Save: key.NewBinding(key.WithKeys("ctrl+s"), key.WithHelp("^s", "save")), Cancel: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "cancel")), AddField: key.NewBinding(key.WithKeys("ctrl+a"), key.WithHelp("^a", "add field")), DeleteField: key.NewBinding(key.WithKeys("ctrl+x"), key.WithHelp("^x", "remove field")), Delete: key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "delete")), Destroy: key.NewBinding(key.WithKeys("D"), key.WithHelp("D", "destroy")), Profiles: key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "switch profile")), Logout: key.NewBinding(key.WithKeys("ctrl+l"), key.WithHelp("^l", "logout")), Help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "help")), Quit: key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "quit")), ForceQuit: key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("^c", "quit")), } } func (k *KeyMap) registry() map[string]*key.Binding { return map[string]*key.Binding{ "up": &k.Up, "down": &k.Down, "top": &k.Top, "bottom": &k.Bottom, "enter": &k.Enter, "back": &k.Back, "refresh": &k.Refresh, "filter": &k.Filter, "palette": &k.Palette, "toggle_mask": &k.ToggleMask, "toggle_mask_all": &k.ToggleMaskAll, "copy_value": &k.CopyValue, "versions": &k.Versions, "rollback": &k.Rollback, "undelete": &k.Undelete, "new": &k.New, "edit": &k.Edit, "save": &k.Save, "cancel": &k.Cancel, "add_field": &k.AddField, "delete_field": &k.DeleteField, "delete": &k.Delete, "destroy": &k.Destroy, "profiles": &k.Profiles, "logout": &k.Logout, "help": &k.Help, "quit": &k.Quit, "force_quit": &k.ForceQuit, } } // Apply overrides the defaults from config.File's `keys:` map. An unknown // action name is a startup error, not a silently ignored typo. func (k *KeyMap) Apply(cfg map[string][]string) error { reg := k.registry() for action, keyStrs := range cfg { b, ok := reg[action] if !ok { return unknownActionError(action) } *b = key.NewBinding(key.WithKeys(keyStrs...), key.WithHelp(b.Help().Key, b.Help().Desc)) } return nil } type unknownActionError string func (e unknownActionError) Error() string { return "unknown keybinding action " + string(e) + " in config `keys:` section" } // DisableWrites turns off every write-capable binding — the UI-side half of // read-only enforcement (the real guard is vault.KV.ReadOnly). func (k *KeyMap) DisableWrites() { for _, b := range []*key.Binding{ &k.New, &k.Edit, &k.Save, &k.AddField, &k.DeleteField, &k.Delete, &k.Destroy, &k.Rollback, &k.Undelete, } { b.SetEnabled(false) } } // ShortHelp/FullHelp implement help.KeyMap for the global bindings and // back the "?" help overlay (screen_help.go). Individual screens' footers // use the narrower, screen-specific views in help.go instead. func (k *KeyMap) ShortHelp() []key.Binding { return []key.Binding{k.Up, k.Down, k.Enter, k.Back, k.Help, k.Quit} } func (k *KeyMap) FullHelp() [][]key.Binding { return [][]key.Binding{ {k.Up, k.Down, k.Top, k.Bottom, k.Enter, k.Back}, {k.Refresh, k.Filter, k.Palette, k.ToggleMask, k.ToggleMaskAll, k.CopyValue}, {k.Versions, k.Rollback, k.Undelete, k.New, k.Edit, k.Save}, {k.Cancel, k.AddField, k.DeleteField, k.Delete, k.Destroy}, {k.Profiles, k.Logout, k.Help, k.Quit, k.ForceQuit}, } }