hishtory/client/tui/keybindings/keybindings.go
David Dworken c933cbc792
Add support for custom key bindings for #190 (#209)
* Add support for custom key bindings for #190

* Add tests for configuring custom key bindings

* Simplify key bindings test

* Add docs on custom key bindings + error message for unhandled actions
2024-04-28 13:50:09 -07:00

327 lines
9.0 KiB
Go

package keybindings
import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/key"
)
type SerializableKeyMap struct {
Up []string
Down []string
PageUp []string
PageDown []string
SelectEntry []string
SelectEntryAndChangeDir []string
Left []string
Right []string
TableLeft []string
TableRight []string
DeleteEntry []string
Help []string
Quit []string
JumpStartOfInput []string
JumpEndOfInput []string
WordLeft []string
WordRight []string
}
func prettifyKeyBinding(kb string) string {
if kb == "up" {
return "↑ "
}
if kb == "down" {
return "↓ "
}
if kb == "left" {
return "←"
}
if kb == "right" {
return "→"
}
subs := [][]string{
{"+left", "+← "},
{"+right", "+→ "},
{"+down", "+↓ "},
{"+up", "+↑ "},
{"pgdown", "pgdn"},
}
for _, sub := range subs {
kb = strings.ReplaceAll(kb, sub[0], sub[1])
}
return kb
}
func (s SerializableKeyMap) ToKeyMap() KeyMap {
if len(s.Up) == 0 {
panic(fmt.Sprintf("%#v", s))
}
return KeyMap{
Up: key.NewBinding(
key.WithKeys(s.Up...),
key.WithHelp(prettifyKeyBinding(s.Up[0]), "scroll up "),
),
Down: key.NewBinding(
key.WithKeys(s.Down...),
key.WithHelp(prettifyKeyBinding(s.Down[0]), "scroll down "),
),
PageUp: key.NewBinding(
key.WithKeys(s.PageUp...),
key.WithHelp(prettifyKeyBinding(s.PageUp[0]), "page up "),
),
PageDown: key.NewBinding(
key.WithKeys(s.PageDown...),
key.WithHelp(prettifyKeyBinding(s.PageDown[0]), "page down "),
),
SelectEntry: key.NewBinding(
key.WithKeys(s.SelectEntry...),
key.WithHelp(prettifyKeyBinding(s.SelectEntry[0]), "select an entry "),
),
SelectEntryAndChangeDir: key.NewBinding(
key.WithKeys(s.SelectEntryAndChangeDir...),
key.WithHelp(prettifyKeyBinding(s.SelectEntryAndChangeDir[0]), "select an entry and cd into that directory"),
),
Left: key.NewBinding(
key.WithKeys(s.Left...),
key.WithHelp(prettifyKeyBinding(s.Left[0]), "move left "),
),
Right: key.NewBinding(
key.WithKeys(s.Right...),
key.WithHelp(prettifyKeyBinding(s.Right[0]), "move right "),
),
TableLeft: key.NewBinding(
key.WithKeys(s.TableLeft...),
key.WithHelp(prettifyKeyBinding(s.TableLeft[0]), "scroll the table left "),
),
TableRight: key.NewBinding(
key.WithKeys(s.TableRight...),
key.WithHelp(prettifyKeyBinding(s.TableRight[0]), "scroll the table right "),
),
DeleteEntry: key.NewBinding(
key.WithKeys(s.DeleteEntry...),
key.WithHelp(prettifyKeyBinding(s.DeleteEntry[0]), "delete the highlighted entry "),
),
Help: key.NewBinding(
key.WithKeys(s.Help...),
key.WithHelp(prettifyKeyBinding(s.Help[0]), "help "),
),
Quit: key.NewBinding(
key.WithKeys(s.Quit...),
key.WithHelp(prettifyKeyBinding(s.Quit[0]), "exit hiSHtory "),
),
JumpStartOfInput: key.NewBinding(
key.WithKeys(s.JumpStartOfInput...),
key.WithHelp(prettifyKeyBinding(s.JumpStartOfInput[0]), "jump to the start of the input "),
),
JumpEndOfInput: key.NewBinding(
key.WithKeys(s.JumpEndOfInput...),
key.WithHelp(prettifyKeyBinding(s.JumpEndOfInput[0]), "jump to the end of the input "),
),
WordLeft: key.NewBinding(
key.WithKeys(s.WordLeft...),
key.WithHelp(prettifyKeyBinding(s.WordLeft[0]), "jump left one word "),
),
WordRight: key.NewBinding(
key.WithKeys(s.WordRight...),
key.WithHelp(prettifyKeyBinding(s.WordRight[0]), "jump right one word "),
),
}
}
func (s SerializableKeyMap) WithDefaults() SerializableKeyMap {
if len(s.Up) == 0 {
s.Up = DefaultKeyMap.Up.Keys()
}
if len(s.Down) == 0 {
s.Down = DefaultKeyMap.Down.Keys()
}
if len(s.PageUp) == 0 {
s.PageUp = DefaultKeyMap.PageUp.Keys()
}
if len(s.PageDown) == 0 {
s.PageDown = DefaultKeyMap.PageDown.Keys()
}
if len(s.SelectEntry) == 0 {
s.SelectEntry = DefaultKeyMap.SelectEntry.Keys()
}
if len(s.SelectEntryAndChangeDir) == 0 {
s.SelectEntryAndChangeDir = DefaultKeyMap.SelectEntryAndChangeDir.Keys()
}
if len(s.Left) == 0 {
s.Left = DefaultKeyMap.Left.Keys()
}
if len(s.Right) == 0 {
s.Right = DefaultKeyMap.Right.Keys()
}
if len(s.TableLeft) == 0 {
s.TableLeft = DefaultKeyMap.TableLeft.Keys()
}
if len(s.TableRight) == 0 {
s.TableRight = DefaultKeyMap.TableRight.Keys()
}
if len(s.DeleteEntry) == 0 {
s.DeleteEntry = DefaultKeyMap.DeleteEntry.Keys()
}
if len(s.Help) == 0 {
s.Help = DefaultKeyMap.Help.Keys()
}
if len(s.Quit) == 0 {
s.Quit = DefaultKeyMap.Quit.Keys()
}
if len(s.JumpStartOfInput) == 0 {
s.JumpStartOfInput = DefaultKeyMap.JumpStartOfInput.Keys()
}
if len(s.JumpEndOfInput) == 0 {
s.JumpEndOfInput = DefaultKeyMap.JumpEndOfInput.Keys()
}
if len(s.WordLeft) == 0 {
s.WordLeft = DefaultKeyMap.WordLeft.Keys()
}
if len(s.WordRight) == 0 {
s.WordRight = DefaultKeyMap.WordRight.Keys()
}
return s
}
type KeyMap struct {
Up key.Binding
Down key.Binding
PageUp key.Binding
PageDown key.Binding
SelectEntry key.Binding
SelectEntryAndChangeDir key.Binding
Left key.Binding
Right key.Binding
TableLeft key.Binding
TableRight key.Binding
DeleteEntry key.Binding
Help key.Binding
Quit key.Binding
JumpStartOfInput key.Binding
JumpEndOfInput key.Binding
WordLeft key.Binding
WordRight key.Binding
}
func (k KeyMap) ToSerializable() SerializableKeyMap {
return SerializableKeyMap{
Up: k.Up.Keys(),
Down: k.Down.Keys(),
PageUp: k.PageUp.Keys(),
PageDown: k.PageDown.Keys(),
SelectEntry: k.SelectEntry.Keys(),
SelectEntryAndChangeDir: k.SelectEntryAndChangeDir.Keys(),
Left: k.Left.Keys(),
Right: k.Right.Keys(),
TableLeft: k.TableLeft.Keys(),
TableRight: k.TableRight.Keys(),
DeleteEntry: k.DeleteEntry.Keys(),
Help: k.Help.Keys(),
Quit: k.Quit.Keys(),
JumpStartOfInput: k.JumpStartOfInput.Keys(),
JumpEndOfInput: k.JumpEndOfInput.Keys(),
WordLeft: k.WordLeft.Keys(),
WordRight: k.WordRight.Keys(),
}
}
var fakeTitleKeyBinding key.Binding = key.NewBinding(
key.WithKeys(""),
key.WithHelp("hiSHtory: Search your shell history", ""),
)
var fakeEmptyKeyBinding key.Binding = key.NewBinding(
key.WithKeys(""),
key.WithHelp("", ""),
)
func (k KeyMap) ShortHelp() []key.Binding {
return []key.Binding{fakeTitleKeyBinding, k.Help}
}
func (k KeyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{fakeTitleKeyBinding, k.Up, k.Left, k.SelectEntry, k.SelectEntryAndChangeDir},
{fakeEmptyKeyBinding, k.Down, k.Right, k.DeleteEntry},
{fakeEmptyKeyBinding, k.PageUp, k.TableLeft, k.Quit},
{fakeEmptyKeyBinding, k.PageDown, k.TableRight, k.Help},
}
}
type Binding struct {
Keys []string `json:"keys"`
Help key.Help `json:"help"`
}
var DefaultKeyMap = KeyMap{
Up: key.NewBinding(
key.WithKeys("up", "alt+OA", "ctrl+p"),
key.WithHelp("↑ ", "scroll up "),
),
Down: key.NewBinding(
key.WithKeys("down", "alt+OB", "ctrl+n"),
key.WithHelp("↓ ", "scroll down "),
),
PageUp: key.NewBinding(
key.WithKeys("pgup"),
key.WithHelp("pgup", "page up "),
),
PageDown: key.NewBinding(
key.WithKeys("pgdown"),
key.WithHelp("pgdn", "page down "),
),
SelectEntry: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "select an entry "),
),
SelectEntryAndChangeDir: key.NewBinding(
key.WithKeys("ctrl+x"),
key.WithHelp("ctrl+x", "select an entry and cd into that directory"),
),
Left: key.NewBinding(
key.WithKeys("left"),
key.WithHelp("← ", "move left "),
),
Right: key.NewBinding(
key.WithKeys("right"),
key.WithHelp("→ ", "move right "),
),
TableLeft: key.NewBinding(
key.WithKeys("shift+left"),
key.WithHelp("shift+← ", "scroll the table left "),
),
TableRight: key.NewBinding(
key.WithKeys("shift+right"),
key.WithHelp("shift+→ ", "scroll the table right "),
),
DeleteEntry: key.NewBinding(
key.WithKeys("ctrl+k"),
key.WithHelp("ctrl+k", "delete the highlighted entry "),
),
Help: key.NewBinding(
key.WithKeys("ctrl+h"),
key.WithHelp("ctrl+h", "help "),
),
Quit: key.NewBinding(
key.WithKeys("esc", "ctrl+c", "ctrl+d"),
key.WithHelp("esc", "exit hiSHtory "),
),
JumpStartOfInput: key.NewBinding(
key.WithKeys("ctrl+a"),
key.WithHelp("ctrl+a", "jump to the start of the input "),
),
JumpEndOfInput: key.NewBinding(
key.WithKeys("ctrl+e"),
key.WithHelp("ctrl+e", "jump to the end of the input "),
),
WordLeft: key.NewBinding(
key.WithKeys("ctrl+left"),
key.WithHelp("ctrl+left", "jump left one word "),
),
WordRight: key.NewBinding(
key.WithKeys("ctrl+right"),
key.WithHelp("ctrl+right", "jump right one word "),
),
}