From 5109f8369db9c55684b3be76809627172b4a11e1 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Sun, 25 Sep 2022 21:29:05 +0200 Subject: config: rename bindings -> binds For consistency with binds.conf Signed-off-by: Robin Jarry Acked-by: Moritz Poldrack --- config/bindings.go | 440 ------------------------------------------------ config/bindings_test.go | 76 --------- config/binds.go | 440 ++++++++++++++++++++++++++++++++++++++++++++++++ config/binds_test.go | 76 +++++++++ 4 files changed, 516 insertions(+), 516 deletions(-) delete mode 100644 config/bindings.go delete mode 100644 config/bindings_test.go create mode 100644 config/binds.go create mode 100644 config/binds_test.go (limited to 'config') diff --git a/config/bindings.go b/config/bindings.go deleted file mode 100644 index 7816296a..00000000 --- a/config/bindings.go +++ /dev/null @@ -1,440 +0,0 @@ -package config - -import ( - "bytes" - "errors" - "fmt" - "io" - "strings" - - "github.com/gdamore/tcell/v2" -) - -type KeyStroke struct { - Modifiers tcell.ModMask - Key tcell.Key - Rune rune -} - -type Binding struct { - Output []KeyStroke - Input []KeyStroke -} - -type KeyBindings struct { - Bindings []*Binding - - // If false, disable global keybindings in this context - Globals bool - // Which key opens the ex line (default is :) - ExKey KeyStroke -} - -const ( - BINDING_FOUND = iota - BINDING_INCOMPLETE - BINDING_NOT_FOUND -) - -type BindingSearchResult int - -func NewKeyBindings() *KeyBindings { - return &KeyBindings{ - ExKey: KeyStroke{tcell.ModNone, tcell.KeyRune, ':'}, - Globals: true, - } -} - -func MergeBindings(bindings ...*KeyBindings) *KeyBindings { - merged := NewKeyBindings() - for _, b := range bindings { - merged.Bindings = append(merged.Bindings, b.Bindings...) - } - merged.ExKey = bindings[0].ExKey - merged.Globals = bindings[0].Globals - return merged -} - -func (config AercConfig) MergeContextualBinds(baseBinds *KeyBindings, - contextType ContextType, reg string, bindCtx string, -) *KeyBindings { - bindings := baseBinds - for _, contextualBind := range config.ContextualBinds { - if contextualBind.ContextType != contextType { - continue - } - - if !contextualBind.Regex.Match([]byte(reg)) { - continue - } - - if contextualBind.BindContext != bindCtx { - continue - } - - bindings = MergeBindings(contextualBind.Bindings, bindings) - } - return bindings -} - -func (bindings *KeyBindings) Add(binding *Binding) { - // TODO: Search for conflicts? - bindings.Bindings = append(bindings.Bindings, binding) -} - -func (bindings *KeyBindings) GetBinding( - input []KeyStroke, -) (BindingSearchResult, []KeyStroke) { - incomplete := false - // TODO: This could probably be a sorted list to speed things up - // TODO: Deal with bindings that share a prefix - for _, binding := range bindings.Bindings { - if len(binding.Input) < len(input) { - continue - } - for i, stroke := range input { - if stroke.Modifiers != binding.Input[i].Modifiers { - goto next - } - if stroke.Key != binding.Input[i].Key { - goto next - } - if stroke.Key == tcell.KeyRune && - stroke.Rune != binding.Input[i].Rune { - - goto next - } - } - if len(binding.Input) != len(input) { - incomplete = true - } else { - return BINDING_FOUND, binding.Output - } - next: - } - if incomplete { - return BINDING_INCOMPLETE, nil - } - return BINDING_NOT_FOUND, nil -} - -func (bindings *KeyBindings) GetReverseBindings(output []KeyStroke) [][]KeyStroke { - var inputs [][]KeyStroke - - for _, binding := range bindings.Bindings { - if len(binding.Output) != len(output) { - continue - } - for i, stroke := range output { - if stroke.Modifiers != binding.Output[i].Modifiers { - goto next - } - if stroke.Key != binding.Output[i].Key { - goto next - } - if stroke.Key == tcell.KeyRune && stroke.Rune != binding.Output[i].Rune { - goto next - } - } - inputs = append(inputs, binding.Input) - next: - } - return inputs -} - -func FormatKeyStrokes(keystrokes []KeyStroke) string { - var sb strings.Builder - - for _, stroke := range keystrokes { - s := "" - for name, ks := range keyNames { - if ks.Modifiers == stroke.Modifiers && ks.Key == stroke.Key && ks.Rune == stroke.Rune { - switch name { - case "cr", "c-m": - name = "enter" - case "c-i": - name = "tab" - } - s = fmt.Sprintf("<%s>", name) - break - } - } - if s == "" && stroke.Key == tcell.KeyRune { - s = string(stroke.Rune) - } - sb.WriteString(s) - } - - return sb.String() -} - -var keyNames map[string]KeyStroke - -func ParseKeyStrokes(keystrokes string) ([]KeyStroke, error) { - var strokes []KeyStroke - buf := bytes.NewBufferString(keystrokes) - for { - tok, _, err := buf.ReadRune() - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - // TODO: make it possible to bind to < or > themselves (and default to - // switching accounts) - switch tok { - case '<': - name, err := buf.ReadString(byte('>')) - switch { - case err == io.EOF: - return nil, errors.New("Expecting '>'") - case err != nil: - return nil, err - case name == ">": - return nil, errors.New("Expected a key name") - } - name = name[:len(name)-1] - if key, ok := keyNames[strings.ToLower(name)]; ok { - strokes = append(strokes, key) - } else { - return nil, fmt.Errorf("Unknown key '%s'", name) - } - case '>': - return nil, errors.New("Found '>' without '<'") - case '\\': - tok, _, err = buf.ReadRune() - if err == io.EOF { - tok = '\\' - } else if err != nil { - return nil, err - } - fallthrough - default: - strokes = append(strokes, KeyStroke{ - Modifiers: tcell.ModNone, - Key: tcell.KeyRune, - Rune: tok, - }) - } - } - return strokes, nil -} - -func ParseBinding(input, output string) (*Binding, error) { - in, err := ParseKeyStrokes(input) - if err != nil { - return nil, err - } - out, err := ParseKeyStrokes(output) - if err != nil { - return nil, err - } - return &Binding{ - Input: in, - Output: out, - }, nil -} - -func init() { - keyNames = make(map[string]KeyStroke) - keyNames["space"] = KeyStroke{tcell.ModNone, tcell.KeyRune, ' '} - keyNames["semicolon"] = KeyStroke{tcell.ModNone, tcell.KeyRune, ';'} - keyNames["enter"] = KeyStroke{tcell.ModNone, tcell.KeyEnter, 0} - keyNames["c-enter"] = KeyStroke{tcell.ModCtrl, tcell.KeyEnter, 0} - keyNames["a-enter"] = KeyStroke{tcell.ModAlt, tcell.KeyEnter, 0} - keyNames["up"] = KeyStroke{tcell.ModNone, tcell.KeyUp, 0} - keyNames["c-up"] = KeyStroke{tcell.ModCtrl, tcell.KeyUp, 0} - keyNames["a-up"] = KeyStroke{tcell.ModAlt, tcell.KeyUp, 0} - keyNames["down"] = KeyStroke{tcell.ModNone, tcell.KeyDown, 0} - keyNames["c-down"] = KeyStroke{tcell.ModCtrl, tcell.KeyDown, 0} - keyNames["a-down"] = KeyStroke{tcell.ModAlt, tcell.KeyDown, 0} - keyNames["right"] = KeyStroke{tcell.ModNone, tcell.KeyRight, 0} - keyNames["c-right"] = KeyStroke{tcell.ModCtrl, tcell.KeyRight, 0} - keyNames["a-right"] = KeyStroke{tcell.ModAlt, tcell.KeyRight, 0} - keyNames["left"] = KeyStroke{tcell.ModNone, tcell.KeyLeft, 0} - keyNames["c-left"] = KeyStroke{tcell.ModCtrl, tcell.KeyLeft, 0} - keyNames["a-left"] = KeyStroke{tcell.ModAlt, tcell.KeyLeft, 0} - keyNames["upleft"] = KeyStroke{tcell.ModNone, tcell.KeyUpLeft, 0} - keyNames["upright"] = KeyStroke{tcell.ModNone, tcell.KeyUpRight, 0} - keyNames["downleft"] = KeyStroke{tcell.ModNone, tcell.KeyDownLeft, 0} - keyNames["downright"] = KeyStroke{tcell.ModNone, tcell.KeyDownRight, 0} - keyNames["center"] = KeyStroke{tcell.ModNone, tcell.KeyCenter, 0} - keyNames["pgup"] = KeyStroke{tcell.ModNone, tcell.KeyPgUp, 0} - keyNames["c-pgup"] = KeyStroke{tcell.ModCtrl, tcell.KeyPgUp, 0} - keyNames["a-pgup"] = KeyStroke{tcell.ModAlt, tcell.KeyPgUp, 0} - keyNames["pgdn"] = KeyStroke{tcell.ModNone, tcell.KeyPgDn, 0} - keyNames["c-pgdn"] = KeyStroke{tcell.ModCtrl, tcell.KeyPgDn, 0} - keyNames["a-pgdn"] = KeyStroke{tcell.ModAlt, tcell.KeyPgDn, 0} - keyNames["home"] = KeyStroke{tcell.ModNone, tcell.KeyHome, 0} - keyNames["end"] = KeyStroke{tcell.ModNone, tcell.KeyEnd, 0} - keyNames["insert"] = KeyStroke{tcell.ModNone, tcell.KeyInsert, 0} - keyNames["delete"] = KeyStroke{tcell.ModNone, tcell.KeyDelete, 0} - keyNames["help"] = KeyStroke{tcell.ModNone, tcell.KeyHelp, 0} - keyNames["exit"] = KeyStroke{tcell.ModNone, tcell.KeyExit, 0} - keyNames["clear"] = KeyStroke{tcell.ModNone, tcell.KeyClear, 0} - keyNames["cancel"] = KeyStroke{tcell.ModNone, tcell.KeyCancel, 0} - keyNames["print"] = KeyStroke{tcell.ModNone, tcell.KeyPrint, 0} - keyNames["pause"] = KeyStroke{tcell.ModNone, tcell.KeyPause, 0} - keyNames["backtab"] = KeyStroke{tcell.ModNone, tcell.KeyBacktab, 0} - keyNames["f1"] = KeyStroke{tcell.ModNone, tcell.KeyF1, 0} - keyNames["f2"] = KeyStroke{tcell.ModNone, tcell.KeyF2, 0} - keyNames["f3"] = KeyStroke{tcell.ModNone, tcell.KeyF3, 0} - keyNames["f4"] = KeyStroke{tcell.ModNone, tcell.KeyF4, 0} - keyNames["f5"] = KeyStroke{tcell.ModNone, tcell.KeyF5, 0} - keyNames["f6"] = KeyStroke{tcell.ModNone, tcell.KeyF6, 0} - keyNames["f7"] = KeyStroke{tcell.ModNone, tcell.KeyF7, 0} - keyNames["f8"] = KeyStroke{tcell.ModNone, tcell.KeyF8, 0} - keyNames["f9"] = KeyStroke{tcell.ModNone, tcell.KeyF9, 0} - keyNames["f10"] = KeyStroke{tcell.ModNone, tcell.KeyF10, 0} - keyNames["f11"] = KeyStroke{tcell.ModNone, tcell.KeyF11, 0} - keyNames["f12"] = KeyStroke{tcell.ModNone, tcell.KeyF12, 0} - keyNames["f13"] = KeyStroke{tcell.ModNone, tcell.KeyF13, 0} - keyNames["f14"] = KeyStroke{tcell.ModNone, tcell.KeyF14, 0} - keyNames["f15"] = KeyStroke{tcell.ModNone, tcell.KeyF15, 0} - keyNames["f16"] = KeyStroke{tcell.ModNone, tcell.KeyF16, 0} - keyNames["f17"] = KeyStroke{tcell.ModNone, tcell.KeyF17, 0} - keyNames["f18"] = KeyStroke{tcell.ModNone, tcell.KeyF18, 0} - keyNames["f19"] = KeyStroke{tcell.ModNone, tcell.KeyF19, 0} - keyNames["f20"] = KeyStroke{tcell.ModNone, tcell.KeyF20, 0} - keyNames["f21"] = KeyStroke{tcell.ModNone, tcell.KeyF21, 0} - keyNames["f22"] = KeyStroke{tcell.ModNone, tcell.KeyF22, 0} - keyNames["f23"] = KeyStroke{tcell.ModNone, tcell.KeyF23, 0} - keyNames["f24"] = KeyStroke{tcell.ModNone, tcell.KeyF24, 0} - keyNames["f25"] = KeyStroke{tcell.ModNone, tcell.KeyF25, 0} - keyNames["f26"] = KeyStroke{tcell.ModNone, tcell.KeyF26, 0} - keyNames["f27"] = KeyStroke{tcell.ModNone, tcell.KeyF27, 0} - keyNames["f28"] = KeyStroke{tcell.ModNone, tcell.KeyF28, 0} - keyNames["f29"] = KeyStroke{tcell.ModNone, tcell.KeyF29, 0} - keyNames["f30"] = KeyStroke{tcell.ModNone, tcell.KeyF30, 0} - keyNames["f31"] = KeyStroke{tcell.ModNone, tcell.KeyF31, 0} - keyNames["f32"] = KeyStroke{tcell.ModNone, tcell.KeyF32, 0} - keyNames["f33"] = KeyStroke{tcell.ModNone, tcell.KeyF33, 0} - keyNames["f34"] = KeyStroke{tcell.ModNone, tcell.KeyF34, 0} - keyNames["f35"] = KeyStroke{tcell.ModNone, tcell.KeyF35, 0} - keyNames["f36"] = KeyStroke{tcell.ModNone, tcell.KeyF36, 0} - keyNames["f37"] = KeyStroke{tcell.ModNone, tcell.KeyF37, 0} - keyNames["f38"] = KeyStroke{tcell.ModNone, tcell.KeyF38, 0} - keyNames["f39"] = KeyStroke{tcell.ModNone, tcell.KeyF39, 0} - keyNames["f40"] = KeyStroke{tcell.ModNone, tcell.KeyF40, 0} - keyNames["f41"] = KeyStroke{tcell.ModNone, tcell.KeyF41, 0} - keyNames["f42"] = KeyStroke{tcell.ModNone, tcell.KeyF42, 0} - keyNames["f43"] = KeyStroke{tcell.ModNone, tcell.KeyF43, 0} - keyNames["f44"] = KeyStroke{tcell.ModNone, tcell.KeyF44, 0} - keyNames["f45"] = KeyStroke{tcell.ModNone, tcell.KeyF45, 0} - keyNames["f46"] = KeyStroke{tcell.ModNone, tcell.KeyF46, 0} - keyNames["f47"] = KeyStroke{tcell.ModNone, tcell.KeyF47, 0} - keyNames["f48"] = KeyStroke{tcell.ModNone, tcell.KeyF48, 0} - keyNames["f49"] = KeyStroke{tcell.ModNone, tcell.KeyF49, 0} - keyNames["f50"] = KeyStroke{tcell.ModNone, tcell.KeyF50, 0} - keyNames["f51"] = KeyStroke{tcell.ModNone, tcell.KeyF51, 0} - keyNames["f52"] = KeyStroke{tcell.ModNone, tcell.KeyF52, 0} - keyNames["f53"] = KeyStroke{tcell.ModNone, tcell.KeyF53, 0} - keyNames["f54"] = KeyStroke{tcell.ModNone, tcell.KeyF54, 0} - keyNames["f55"] = KeyStroke{tcell.ModNone, tcell.KeyF55, 0} - keyNames["f56"] = KeyStroke{tcell.ModNone, tcell.KeyF56, 0} - keyNames["f57"] = KeyStroke{tcell.ModNone, tcell.KeyF57, 0} - keyNames["f58"] = KeyStroke{tcell.ModNone, tcell.KeyF58, 0} - keyNames["f59"] = KeyStroke{tcell.ModNone, tcell.KeyF59, 0} - keyNames["f60"] = KeyStroke{tcell.ModNone, tcell.KeyF60, 0} - keyNames["f61"] = KeyStroke{tcell.ModNone, tcell.KeyF61, 0} - keyNames["f62"] = KeyStroke{tcell.ModNone, tcell.KeyF62, 0} - keyNames["f63"] = KeyStroke{tcell.ModNone, tcell.KeyF63, 0} - keyNames["f64"] = KeyStroke{tcell.ModNone, tcell.KeyF64, 0} - keyNames["c-space"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlSpace, 0} - keyNames["c-a"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlA, 0} - keyNames["c-b"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlB, 0} - keyNames["c-c"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlC, 0} - keyNames["c-d"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlD, 0} - keyNames["c-e"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlE, 0} - keyNames["c-f"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlF, 0} - keyNames["c-g"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlG, 0} - keyNames["c-h"] = KeyStroke{tcell.ModNone, tcell.KeyCtrlH, 0} - keyNames["c-i"] = KeyStroke{tcell.ModNone, tcell.KeyCtrlI, 0} - keyNames["c-j"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlJ, 0} - keyNames["c-k"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlK, 0} - keyNames["c-l"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlL, 0} - keyNames["c-m"] = KeyStroke{tcell.ModNone, tcell.KeyCtrlM, 0} - keyNames["c-n"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlN, 0} - keyNames["c-o"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlO, 0} - keyNames["c-p"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlP, 0} - keyNames["c-q"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlQ, 0} - keyNames["c-r"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlR, 0} - keyNames["c-s"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlS, 0} - keyNames["c-t"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlT, 0} - keyNames["c-u"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlU, 0} - keyNames["c-v"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlV, 0} - keyNames["c-w"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlW, 0} - keyNames["c-x"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlX, rune(tcell.KeyCAN)} - keyNames["c-y"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlY, 0} // TODO: runes for the rest - keyNames["c-z"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlZ, 0} - keyNames["c-]"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlRightSq, 0} - keyNames["c-\\"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlBackslash, 0} - keyNames["c-["] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlLeftSq, 0} - keyNames["c-^"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlCarat, 0} - keyNames["c-_"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlUnderscore, 0} - keyNames["a-space"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, ' '} - keyNames["a-a"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'a'} - keyNames["a-b"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'b'} - keyNames["a-c"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'c'} - keyNames["a-d"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'd'} - keyNames["a-e"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'e'} - keyNames["a-f"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'f'} - keyNames["a-g"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'g'} - keyNames["a-h"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'h'} - keyNames["a-i"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'i'} - keyNames["a-j"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'j'} - keyNames["a-k"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'k'} - keyNames["a-l"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'l'} - keyNames["a-m"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'm'} - keyNames["a-n"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'n'} - keyNames["a-o"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'o'} - keyNames["a-p"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'p'} - keyNames["a-q"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'q'} - keyNames["a-r"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'r'} - keyNames["a-s"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 's'} - keyNames["a-t"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 't'} - keyNames["a-u"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'u'} - keyNames["a-v"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'v'} - keyNames["a-w"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'w'} - keyNames["a-x"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'x'} - keyNames["a-y"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'y'} - keyNames["a-z"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'z'} - keyNames["a-]"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, ']'} - keyNames["a-\\"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '\\'} - keyNames["a-["] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '['} - keyNames["a-^"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '^'} - keyNames["a-_"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '_'} - keyNames["nul"] = KeyStroke{tcell.ModNone, tcell.KeyNUL, 0} - keyNames["soh"] = KeyStroke{tcell.ModNone, tcell.KeySOH, 0} - keyNames["stx"] = KeyStroke{tcell.ModNone, tcell.KeySTX, 0} - keyNames["etx"] = KeyStroke{tcell.ModNone, tcell.KeyETX, 0} - keyNames["eot"] = KeyStroke{tcell.ModNone, tcell.KeyEOT, 0} - keyNames["enq"] = KeyStroke{tcell.ModNone, tcell.KeyENQ, 0} - keyNames["ack"] = KeyStroke{tcell.ModNone, tcell.KeyACK, 0} - keyNames["bel"] = KeyStroke{tcell.ModNone, tcell.KeyBEL, 0} - keyNames["bs"] = KeyStroke{tcell.ModNone, tcell.KeyBS, 0} - keyNames["tab"] = KeyStroke{tcell.ModNone, tcell.KeyTAB, 0} - keyNames["lf"] = KeyStroke{tcell.ModNone, tcell.KeyLF, 0} - keyNames["vt"] = KeyStroke{tcell.ModNone, tcell.KeyVT, 0} - keyNames["ff"] = KeyStroke{tcell.ModNone, tcell.KeyFF, 0} - keyNames["cr"] = KeyStroke{tcell.ModNone, tcell.KeyCR, 0} - keyNames["so"] = KeyStroke{tcell.ModNone, tcell.KeySO, 0} - keyNames["si"] = KeyStroke{tcell.ModNone, tcell.KeySI, 0} - keyNames["dle"] = KeyStroke{tcell.ModNone, tcell.KeyDLE, 0} - keyNames["dc1"] = KeyStroke{tcell.ModNone, tcell.KeyDC1, 0} - keyNames["dc2"] = KeyStroke{tcell.ModNone, tcell.KeyDC2, 0} - keyNames["dc3"] = KeyStroke{tcell.ModNone, tcell.KeyDC3, 0} - keyNames["dc4"] = KeyStroke{tcell.ModNone, tcell.KeyDC4, 0} - keyNames["nak"] = KeyStroke{tcell.ModNone, tcell.KeyNAK, 0} - keyNames["syn"] = KeyStroke{tcell.ModNone, tcell.KeySYN, 0} - keyNames["etb"] = KeyStroke{tcell.ModNone, tcell.KeyETB, 0} - keyNames["can"] = KeyStroke{tcell.ModNone, tcell.KeyCAN, 0} - keyNames["em"] = KeyStroke{tcell.ModNone, tcell.KeyEM, 0} - keyNames["sub"] = KeyStroke{tcell.ModNone, tcell.KeySUB, 0} - keyNames["esc"] = KeyStroke{tcell.ModNone, tcell.KeyESC, 0} - keyNames["fs"] = KeyStroke{tcell.ModNone, tcell.KeyFS, 0} - keyNames["gs"] = KeyStroke{tcell.ModNone, tcell.KeyGS, 0} - keyNames["rs"] = KeyStroke{tcell.ModNone, tcell.KeyRS, 0} - keyNames["us"] = KeyStroke{tcell.ModNone, tcell.KeyUS, 0} - keyNames["del"] = KeyStroke{tcell.ModNone, tcell.KeyDEL, 0} -} diff --git a/config/bindings_test.go b/config/bindings_test.go deleted file mode 100644 index dab3b9f1..00000000 --- a/config/bindings_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package config - -import ( - "fmt" - "testing" - - "github.com/gdamore/tcell/v2" - "github.com/stretchr/testify/assert" -) - -func TestGetBinding(t *testing.T) { - assert := assert.New(t) - - bindings := NewKeyBindings() - add := func(binding, cmd string) { - b, _ := ParseBinding(binding, cmd) - bindings.Add(b) - } - - add("abc", ":abc") - add("cba", ":cba") - add("foo", ":foo") - add("bar", ":bar") - - test := func(input []KeyStroke, result int, output string) { - _output, _ := ParseKeyStrokes(output) - r, out := bindings.GetBinding(input) - assert.Equal(result, int(r), fmt.Sprintf( - "%s: Expected result %d, got %d", output, result, r)) - assert.Equal(_output, out, fmt.Sprintf( - "%s: Expected output %v, got %v", output, _output, out)) - } - - test([]KeyStroke{ - {tcell.ModNone, tcell.KeyRune, 'a'}, - }, BINDING_INCOMPLETE, "") - test([]KeyStroke{ - {tcell.ModNone, tcell.KeyRune, 'a'}, - {tcell.ModNone, tcell.KeyRune, 'b'}, - {tcell.ModNone, tcell.KeyRune, 'c'}, - }, BINDING_FOUND, ":abc") - test([]KeyStroke{ - {tcell.ModNone, tcell.KeyRune, 'c'}, - {tcell.ModNone, tcell.KeyRune, 'b'}, - {tcell.ModNone, tcell.KeyRune, 'a'}, - }, BINDING_FOUND, ":cba") - test([]KeyStroke{ - {tcell.ModNone, tcell.KeyRune, 'f'}, - {tcell.ModNone, tcell.KeyRune, 'o'}, - }, BINDING_INCOMPLETE, "") - test([]KeyStroke{ - {tcell.ModNone, tcell.KeyRune, '4'}, - {tcell.ModNone, tcell.KeyRune, '0'}, - {tcell.ModNone, tcell.KeyRune, '4'}, - }, BINDING_NOT_FOUND, "") - - add("", "c-a") - add("", ":next") - add("", ":prev") - add("", ":open") - test([]KeyStroke{ - {tcell.ModCtrl, tcell.KeyCtrlA, 0}, - }, BINDING_FOUND, "c-a") - test([]KeyStroke{ - {tcell.ModCtrl, tcell.KeyDown, 0}, - }, BINDING_FOUND, ":next") - test([]KeyStroke{ - {tcell.ModCtrl, tcell.KeyPgUp, 0}, - }, BINDING_FOUND, ":prev") - test([]KeyStroke{ - {tcell.ModCtrl, tcell.KeyPgDn, 0}, - }, BINDING_NOT_FOUND, "") - test([]KeyStroke{ - {tcell.ModCtrl, tcell.KeyEnter, 0}, - }, BINDING_FOUND, ":open") -} diff --git a/config/binds.go b/config/binds.go new file mode 100644 index 00000000..7816296a --- /dev/null +++ b/config/binds.go @@ -0,0 +1,440 @@ +package config + +import ( + "bytes" + "errors" + "fmt" + "io" + "strings" + + "github.com/gdamore/tcell/v2" +) + +type KeyStroke struct { + Modifiers tcell.ModMask + Key tcell.Key + Rune rune +} + +type Binding struct { + Output []KeyStroke + Input []KeyStroke +} + +type KeyBindings struct { + Bindings []*Binding + + // If false, disable global keybindings in this context + Globals bool + // Which key opens the ex line (default is :) + ExKey KeyStroke +} + +const ( + BINDING_FOUND = iota + BINDING_INCOMPLETE + BINDING_NOT_FOUND +) + +type BindingSearchResult int + +func NewKeyBindings() *KeyBindings { + return &KeyBindings{ + ExKey: KeyStroke{tcell.ModNone, tcell.KeyRune, ':'}, + Globals: true, + } +} + +func MergeBindings(bindings ...*KeyBindings) *KeyBindings { + merged := NewKeyBindings() + for _, b := range bindings { + merged.Bindings = append(merged.Bindings, b.Bindings...) + } + merged.ExKey = bindings[0].ExKey + merged.Globals = bindings[0].Globals + return merged +} + +func (config AercConfig) MergeContextualBinds(baseBinds *KeyBindings, + contextType ContextType, reg string, bindCtx string, +) *KeyBindings { + bindings := baseBinds + for _, contextualBind := range config.ContextualBinds { + if contextualBind.ContextType != contextType { + continue + } + + if !contextualBind.Regex.Match([]byte(reg)) { + continue + } + + if contextualBind.BindContext != bindCtx { + continue + } + + bindings = MergeBindings(contextualBind.Bindings, bindings) + } + return bindings +} + +func (bindings *KeyBindings) Add(binding *Binding) { + // TODO: Search for conflicts? + bindings.Bindings = append(bindings.Bindings, binding) +} + +func (bindings *KeyBindings) GetBinding( + input []KeyStroke, +) (BindingSearchResult, []KeyStroke) { + incomplete := false + // TODO: This could probably be a sorted list to speed things up + // TODO: Deal with bindings that share a prefix + for _, binding := range bindings.Bindings { + if len(binding.Input) < len(input) { + continue + } + for i, stroke := range input { + if stroke.Modifiers != binding.Input[i].Modifiers { + goto next + } + if stroke.Key != binding.Input[i].Key { + goto next + } + if stroke.Key == tcell.KeyRune && + stroke.Rune != binding.Input[i].Rune { + + goto next + } + } + if len(binding.Input) != len(input) { + incomplete = true + } else { + return BINDING_FOUND, binding.Output + } + next: + } + if incomplete { + return BINDING_INCOMPLETE, nil + } + return BINDING_NOT_FOUND, nil +} + +func (bindings *KeyBindings) GetReverseBindings(output []KeyStroke) [][]KeyStroke { + var inputs [][]KeyStroke + + for _, binding := range bindings.Bindings { + if len(binding.Output) != len(output) { + continue + } + for i, stroke := range output { + if stroke.Modifiers != binding.Output[i].Modifiers { + goto next + } + if stroke.Key != binding.Output[i].Key { + goto next + } + if stroke.Key == tcell.KeyRune && stroke.Rune != binding.Output[i].Rune { + goto next + } + } + inputs = append(inputs, binding.Input) + next: + } + return inputs +} + +func FormatKeyStrokes(keystrokes []KeyStroke) string { + var sb strings.Builder + + for _, stroke := range keystrokes { + s := "" + for name, ks := range keyNames { + if ks.Modifiers == stroke.Modifiers && ks.Key == stroke.Key && ks.Rune == stroke.Rune { + switch name { + case "cr", "c-m": + name = "enter" + case "c-i": + name = "tab" + } + s = fmt.Sprintf("<%s>", name) + break + } + } + if s == "" && stroke.Key == tcell.KeyRune { + s = string(stroke.Rune) + } + sb.WriteString(s) + } + + return sb.String() +} + +var keyNames map[string]KeyStroke + +func ParseKeyStrokes(keystrokes string) ([]KeyStroke, error) { + var strokes []KeyStroke + buf := bytes.NewBufferString(keystrokes) + for { + tok, _, err := buf.ReadRune() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + // TODO: make it possible to bind to < or > themselves (and default to + // switching accounts) + switch tok { + case '<': + name, err := buf.ReadString(byte('>')) + switch { + case err == io.EOF: + return nil, errors.New("Expecting '>'") + case err != nil: + return nil, err + case name == ">": + return nil, errors.New("Expected a key name") + } + name = name[:len(name)-1] + if key, ok := keyNames[strings.ToLower(name)]; ok { + strokes = append(strokes, key) + } else { + return nil, fmt.Errorf("Unknown key '%s'", name) + } + case '>': + return nil, errors.New("Found '>' without '<'") + case '\\': + tok, _, err = buf.ReadRune() + if err == io.EOF { + tok = '\\' + } else if err != nil { + return nil, err + } + fallthrough + default: + strokes = append(strokes, KeyStroke{ + Modifiers: tcell.ModNone, + Key: tcell.KeyRune, + Rune: tok, + }) + } + } + return strokes, nil +} + +func ParseBinding(input, output string) (*Binding, error) { + in, err := ParseKeyStrokes(input) + if err != nil { + return nil, err + } + out, err := ParseKeyStrokes(output) + if err != nil { + return nil, err + } + return &Binding{ + Input: in, + Output: out, + }, nil +} + +func init() { + keyNames = make(map[string]KeyStroke) + keyNames["space"] = KeyStroke{tcell.ModNone, tcell.KeyRune, ' '} + keyNames["semicolon"] = KeyStroke{tcell.ModNone, tcell.KeyRune, ';'} + keyNames["enter"] = KeyStroke{tcell.ModNone, tcell.KeyEnter, 0} + keyNames["c-enter"] = KeyStroke{tcell.ModCtrl, tcell.KeyEnter, 0} + keyNames["a-enter"] = KeyStroke{tcell.ModAlt, tcell.KeyEnter, 0} + keyNames["up"] = KeyStroke{tcell.ModNone, tcell.KeyUp, 0} + keyNames["c-up"] = KeyStroke{tcell.ModCtrl, tcell.KeyUp, 0} + keyNames["a-up"] = KeyStroke{tcell.ModAlt, tcell.KeyUp, 0} + keyNames["down"] = KeyStroke{tcell.ModNone, tcell.KeyDown, 0} + keyNames["c-down"] = KeyStroke{tcell.ModCtrl, tcell.KeyDown, 0} + keyNames["a-down"] = KeyStroke{tcell.ModAlt, tcell.KeyDown, 0} + keyNames["right"] = KeyStroke{tcell.ModNone, tcell.KeyRight, 0} + keyNames["c-right"] = KeyStroke{tcell.ModCtrl, tcell.KeyRight, 0} + keyNames["a-right"] = KeyStroke{tcell.ModAlt, tcell.KeyRight, 0} + keyNames["left"] = KeyStroke{tcell.ModNone, tcell.KeyLeft, 0} + keyNames["c-left"] = KeyStroke{tcell.ModCtrl, tcell.KeyLeft, 0} + keyNames["a-left"] = KeyStroke{tcell.ModAlt, tcell.KeyLeft, 0} + keyNames["upleft"] = KeyStroke{tcell.ModNone, tcell.KeyUpLeft, 0} + keyNames["upright"] = KeyStroke{tcell.ModNone, tcell.KeyUpRight, 0} + keyNames["downleft"] = KeyStroke{tcell.ModNone, tcell.KeyDownLeft, 0} + keyNames["downright"] = KeyStroke{tcell.ModNone, tcell.KeyDownRight, 0} + keyNames["center"] = KeyStroke{tcell.ModNone, tcell.KeyCenter, 0} + keyNames["pgup"] = KeyStroke{tcell.ModNone, tcell.KeyPgUp, 0} + keyNames["c-pgup"] = KeyStroke{tcell.ModCtrl, tcell.KeyPgUp, 0} + keyNames["a-pgup"] = KeyStroke{tcell.ModAlt, tcell.KeyPgUp, 0} + keyNames["pgdn"] = KeyStroke{tcell.ModNone, tcell.KeyPgDn, 0} + keyNames["c-pgdn"] = KeyStroke{tcell.ModCtrl, tcell.KeyPgDn, 0} + keyNames["a-pgdn"] = KeyStroke{tcell.ModAlt, tcell.KeyPgDn, 0} + keyNames["home"] = KeyStroke{tcell.ModNone, tcell.KeyHome, 0} + keyNames["end"] = KeyStroke{tcell.ModNone, tcell.KeyEnd, 0} + keyNames["insert"] = KeyStroke{tcell.ModNone, tcell.KeyInsert, 0} + keyNames["delete"] = KeyStroke{tcell.ModNone, tcell.KeyDelete, 0} + keyNames["help"] = KeyStroke{tcell.ModNone, tcell.KeyHelp, 0} + keyNames["exit"] = KeyStroke{tcell.ModNone, tcell.KeyExit, 0} + keyNames["clear"] = KeyStroke{tcell.ModNone, tcell.KeyClear, 0} + keyNames["cancel"] = KeyStroke{tcell.ModNone, tcell.KeyCancel, 0} + keyNames["print"] = KeyStroke{tcell.ModNone, tcell.KeyPrint, 0} + keyNames["pause"] = KeyStroke{tcell.ModNone, tcell.KeyPause, 0} + keyNames["backtab"] = KeyStroke{tcell.ModNone, tcell.KeyBacktab, 0} + keyNames["f1"] = KeyStroke{tcell.ModNone, tcell.KeyF1, 0} + keyNames["f2"] = KeyStroke{tcell.ModNone, tcell.KeyF2, 0} + keyNames["f3"] = KeyStroke{tcell.ModNone, tcell.KeyF3, 0} + keyNames["f4"] = KeyStroke{tcell.ModNone, tcell.KeyF4, 0} + keyNames["f5"] = KeyStroke{tcell.ModNone, tcell.KeyF5, 0} + keyNames["f6"] = KeyStroke{tcell.ModNone, tcell.KeyF6, 0} + keyNames["f7"] = KeyStroke{tcell.ModNone, tcell.KeyF7, 0} + keyNames["f8"] = KeyStroke{tcell.ModNone, tcell.KeyF8, 0} + keyNames["f9"] = KeyStroke{tcell.ModNone, tcell.KeyF9, 0} + keyNames["f10"] = KeyStroke{tcell.ModNone, tcell.KeyF10, 0} + keyNames["f11"] = KeyStroke{tcell.ModNone, tcell.KeyF11, 0} + keyNames["f12"] = KeyStroke{tcell.ModNone, tcell.KeyF12, 0} + keyNames["f13"] = KeyStroke{tcell.ModNone, tcell.KeyF13, 0} + keyNames["f14"] = KeyStroke{tcell.ModNone, tcell.KeyF14, 0} + keyNames["f15"] = KeyStroke{tcell.ModNone, tcell.KeyF15, 0} + keyNames["f16"] = KeyStroke{tcell.ModNone, tcell.KeyF16, 0} + keyNames["f17"] = KeyStroke{tcell.ModNone, tcell.KeyF17, 0} + keyNames["f18"] = KeyStroke{tcell.ModNone, tcell.KeyF18, 0} + keyNames["f19"] = KeyStroke{tcell.ModNone, tcell.KeyF19, 0} + keyNames["f20"] = KeyStroke{tcell.ModNone, tcell.KeyF20, 0} + keyNames["f21"] = KeyStroke{tcell.ModNone, tcell.KeyF21, 0} + keyNames["f22"] = KeyStroke{tcell.ModNone, tcell.KeyF22, 0} + keyNames["f23"] = KeyStroke{tcell.ModNone, tcell.KeyF23, 0} + keyNames["f24"] = KeyStroke{tcell.ModNone, tcell.KeyF24, 0} + keyNames["f25"] = KeyStroke{tcell.ModNone, tcell.KeyF25, 0} + keyNames["f26"] = KeyStroke{tcell.ModNone, tcell.KeyF26, 0} + keyNames["f27"] = KeyStroke{tcell.ModNone, tcell.KeyF27, 0} + keyNames["f28"] = KeyStroke{tcell.ModNone, tcell.KeyF28, 0} + keyNames["f29"] = KeyStroke{tcell.ModNone, tcell.KeyF29, 0} + keyNames["f30"] = KeyStroke{tcell.ModNone, tcell.KeyF30, 0} + keyNames["f31"] = KeyStroke{tcell.ModNone, tcell.KeyF31, 0} + keyNames["f32"] = KeyStroke{tcell.ModNone, tcell.KeyF32, 0} + keyNames["f33"] = KeyStroke{tcell.ModNone, tcell.KeyF33, 0} + keyNames["f34"] = KeyStroke{tcell.ModNone, tcell.KeyF34, 0} + keyNames["f35"] = KeyStroke{tcell.ModNone, tcell.KeyF35, 0} + keyNames["f36"] = KeyStroke{tcell.ModNone, tcell.KeyF36, 0} + keyNames["f37"] = KeyStroke{tcell.ModNone, tcell.KeyF37, 0} + keyNames["f38"] = KeyStroke{tcell.ModNone, tcell.KeyF38, 0} + keyNames["f39"] = KeyStroke{tcell.ModNone, tcell.KeyF39, 0} + keyNames["f40"] = KeyStroke{tcell.ModNone, tcell.KeyF40, 0} + keyNames["f41"] = KeyStroke{tcell.ModNone, tcell.KeyF41, 0} + keyNames["f42"] = KeyStroke{tcell.ModNone, tcell.KeyF42, 0} + keyNames["f43"] = KeyStroke{tcell.ModNone, tcell.KeyF43, 0} + keyNames["f44"] = KeyStroke{tcell.ModNone, tcell.KeyF44, 0} + keyNames["f45"] = KeyStroke{tcell.ModNone, tcell.KeyF45, 0} + keyNames["f46"] = KeyStroke{tcell.ModNone, tcell.KeyF46, 0} + keyNames["f47"] = KeyStroke{tcell.ModNone, tcell.KeyF47, 0} + keyNames["f48"] = KeyStroke{tcell.ModNone, tcell.KeyF48, 0} + keyNames["f49"] = KeyStroke{tcell.ModNone, tcell.KeyF49, 0} + keyNames["f50"] = KeyStroke{tcell.ModNone, tcell.KeyF50, 0} + keyNames["f51"] = KeyStroke{tcell.ModNone, tcell.KeyF51, 0} + keyNames["f52"] = KeyStroke{tcell.ModNone, tcell.KeyF52, 0} + keyNames["f53"] = KeyStroke{tcell.ModNone, tcell.KeyF53, 0} + keyNames["f54"] = KeyStroke{tcell.ModNone, tcell.KeyF54, 0} + keyNames["f55"] = KeyStroke{tcell.ModNone, tcell.KeyF55, 0} + keyNames["f56"] = KeyStroke{tcell.ModNone, tcell.KeyF56, 0} + keyNames["f57"] = KeyStroke{tcell.ModNone, tcell.KeyF57, 0} + keyNames["f58"] = KeyStroke{tcell.ModNone, tcell.KeyF58, 0} + keyNames["f59"] = KeyStroke{tcell.ModNone, tcell.KeyF59, 0} + keyNames["f60"] = KeyStroke{tcell.ModNone, tcell.KeyF60, 0} + keyNames["f61"] = KeyStroke{tcell.ModNone, tcell.KeyF61, 0} + keyNames["f62"] = KeyStroke{tcell.ModNone, tcell.KeyF62, 0} + keyNames["f63"] = KeyStroke{tcell.ModNone, tcell.KeyF63, 0} + keyNames["f64"] = KeyStroke{tcell.ModNone, tcell.KeyF64, 0} + keyNames["c-space"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlSpace, 0} + keyNames["c-a"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlA, 0} + keyNames["c-b"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlB, 0} + keyNames["c-c"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlC, 0} + keyNames["c-d"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlD, 0} + keyNames["c-e"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlE, 0} + keyNames["c-f"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlF, 0} + keyNames["c-g"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlG, 0} + keyNames["c-h"] = KeyStroke{tcell.ModNone, tcell.KeyCtrlH, 0} + keyNames["c-i"] = KeyStroke{tcell.ModNone, tcell.KeyCtrlI, 0} + keyNames["c-j"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlJ, 0} + keyNames["c-k"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlK, 0} + keyNames["c-l"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlL, 0} + keyNames["c-m"] = KeyStroke{tcell.ModNone, tcell.KeyCtrlM, 0} + keyNames["c-n"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlN, 0} + keyNames["c-o"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlO, 0} + keyNames["c-p"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlP, 0} + keyNames["c-q"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlQ, 0} + keyNames["c-r"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlR, 0} + keyNames["c-s"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlS, 0} + keyNames["c-t"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlT, 0} + keyNames["c-u"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlU, 0} + keyNames["c-v"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlV, 0} + keyNames["c-w"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlW, 0} + keyNames["c-x"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlX, rune(tcell.KeyCAN)} + keyNames["c-y"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlY, 0} // TODO: runes for the rest + keyNames["c-z"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlZ, 0} + keyNames["c-]"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlRightSq, 0} + keyNames["c-\\"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlBackslash, 0} + keyNames["c-["] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlLeftSq, 0} + keyNames["c-^"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlCarat, 0} + keyNames["c-_"] = KeyStroke{tcell.ModCtrl, tcell.KeyCtrlUnderscore, 0} + keyNames["a-space"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, ' '} + keyNames["a-a"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'a'} + keyNames["a-b"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'b'} + keyNames["a-c"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'c'} + keyNames["a-d"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'd'} + keyNames["a-e"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'e'} + keyNames["a-f"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'f'} + keyNames["a-g"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'g'} + keyNames["a-h"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'h'} + keyNames["a-i"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'i'} + keyNames["a-j"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'j'} + keyNames["a-k"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'k'} + keyNames["a-l"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'l'} + keyNames["a-m"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'm'} + keyNames["a-n"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'n'} + keyNames["a-o"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'o'} + keyNames["a-p"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'p'} + keyNames["a-q"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'q'} + keyNames["a-r"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'r'} + keyNames["a-s"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 's'} + keyNames["a-t"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 't'} + keyNames["a-u"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'u'} + keyNames["a-v"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'v'} + keyNames["a-w"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'w'} + keyNames["a-x"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'x'} + keyNames["a-y"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'y'} + keyNames["a-z"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, 'z'} + keyNames["a-]"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, ']'} + keyNames["a-\\"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '\\'} + keyNames["a-["] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '['} + keyNames["a-^"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '^'} + keyNames["a-_"] = KeyStroke{tcell.ModAlt, tcell.KeyRune, '_'} + keyNames["nul"] = KeyStroke{tcell.ModNone, tcell.KeyNUL, 0} + keyNames["soh"] = KeyStroke{tcell.ModNone, tcell.KeySOH, 0} + keyNames["stx"] = KeyStroke{tcell.ModNone, tcell.KeySTX, 0} + keyNames["etx"] = KeyStroke{tcell.ModNone, tcell.KeyETX, 0} + keyNames["eot"] = KeyStroke{tcell.ModNone, tcell.KeyEOT, 0} + keyNames["enq"] = KeyStroke{tcell.ModNone, tcell.KeyENQ, 0} + keyNames["ack"] = KeyStroke{tcell.ModNone, tcell.KeyACK, 0} + keyNames["bel"] = KeyStroke{tcell.ModNone, tcell.KeyBEL, 0} + keyNames["bs"] = KeyStroke{tcell.ModNone, tcell.KeyBS, 0} + keyNames["tab"] = KeyStroke{tcell.ModNone, tcell.KeyTAB, 0} + keyNames["lf"] = KeyStroke{tcell.ModNone, tcell.KeyLF, 0} + keyNames["vt"] = KeyStroke{tcell.ModNone, tcell.KeyVT, 0} + keyNames["ff"] = KeyStroke{tcell.ModNone, tcell.KeyFF, 0} + keyNames["cr"] = KeyStroke{tcell.ModNone, tcell.KeyCR, 0} + keyNames["so"] = KeyStroke{tcell.ModNone, tcell.KeySO, 0} + keyNames["si"] = KeyStroke{tcell.ModNone, tcell.KeySI, 0} + keyNames["dle"] = KeyStroke{tcell.ModNone, tcell.KeyDLE, 0} + keyNames["dc1"] = KeyStroke{tcell.ModNone, tcell.KeyDC1, 0} + keyNames["dc2"] = KeyStroke{tcell.ModNone, tcell.KeyDC2, 0} + keyNames["dc3"] = KeyStroke{tcell.ModNone, tcell.KeyDC3, 0} + keyNames["dc4"] = KeyStroke{tcell.ModNone, tcell.KeyDC4, 0} + keyNames["nak"] = KeyStroke{tcell.ModNone, tcell.KeyNAK, 0} + keyNames["syn"] = KeyStroke{tcell.ModNone, tcell.KeySYN, 0} + keyNames["etb"] = KeyStroke{tcell.ModNone, tcell.KeyETB, 0} + keyNames["can"] = KeyStroke{tcell.ModNone, tcell.KeyCAN, 0} + keyNames["em"] = KeyStroke{tcell.ModNone, tcell.KeyEM, 0} + keyNames["sub"] = KeyStroke{tcell.ModNone, tcell.KeySUB, 0} + keyNames["esc"] = KeyStroke{tcell.ModNone, tcell.KeyESC, 0} + keyNames["fs"] = KeyStroke{tcell.ModNone, tcell.KeyFS, 0} + keyNames["gs"] = KeyStroke{tcell.ModNone, tcell.KeyGS, 0} + keyNames["rs"] = KeyStroke{tcell.ModNone, tcell.KeyRS, 0} + keyNames["us"] = KeyStroke{tcell.ModNone, tcell.KeyUS, 0} + keyNames["del"] = KeyStroke{tcell.ModNone, tcell.KeyDEL, 0} +} diff --git a/config/binds_test.go b/config/binds_test.go new file mode 100644 index 00000000..dab3b9f1 --- /dev/null +++ b/config/binds_test.go @@ -0,0 +1,76 @@ +package config + +import ( + "fmt" + "testing" + + "github.com/gdamore/tcell/v2" + "github.com/stretchr/testify/assert" +) + +func TestGetBinding(t *testing.T) { + assert := assert.New(t) + + bindings := NewKeyBindings() + add := func(binding, cmd string) { + b, _ := ParseBinding(binding, cmd) + bindings.Add(b) + } + + add("abc", ":abc") + add("cba", ":cba") + add("foo", ":foo") + add("bar", ":bar") + + test := func(input []KeyStroke, result int, output string) { + _output, _ := ParseKeyStrokes(output) + r, out := bindings.GetBinding(input) + assert.Equal(result, int(r), fmt.Sprintf( + "%s: Expected result %d, got %d", output, result, r)) + assert.Equal(_output, out, fmt.Sprintf( + "%s: Expected output %v, got %v", output, _output, out)) + } + + test([]KeyStroke{ + {tcell.ModNone, tcell.KeyRune, 'a'}, + }, BINDING_INCOMPLETE, "") + test([]KeyStroke{ + {tcell.ModNone, tcell.KeyRune, 'a'}, + {tcell.ModNone, tcell.KeyRune, 'b'}, + {tcell.ModNone, tcell.KeyRune, 'c'}, + }, BINDING_FOUND, ":abc") + test([]KeyStroke{ + {tcell.ModNone, tcell.KeyRune, 'c'}, + {tcell.ModNone, tcell.KeyRune, 'b'}, + {tcell.ModNone, tcell.KeyRune, 'a'}, + }, BINDING_FOUND, ":cba") + test([]KeyStroke{ + {tcell.ModNone, tcell.KeyRune, 'f'}, + {tcell.ModNone, tcell.KeyRune, 'o'}, + }, BINDING_INCOMPLETE, "") + test([]KeyStroke{ + {tcell.ModNone, tcell.KeyRune, '4'}, + {tcell.ModNone, tcell.KeyRune, '0'}, + {tcell.ModNone, tcell.KeyRune, '4'}, + }, BINDING_NOT_FOUND, "") + + add("", "c-a") + add("", ":next") + add("", ":prev") + add("", ":open") + test([]KeyStroke{ + {tcell.ModCtrl, tcell.KeyCtrlA, 0}, + }, BINDING_FOUND, "c-a") + test([]KeyStroke{ + {tcell.ModCtrl, tcell.KeyDown, 0}, + }, BINDING_FOUND, ":next") + test([]KeyStroke{ + {tcell.ModCtrl, tcell.KeyPgUp, 0}, + }, BINDING_FOUND, ":prev") + test([]KeyStroke{ + {tcell.ModCtrl, tcell.KeyPgDn, 0}, + }, BINDING_NOT_FOUND, "") + test([]KeyStroke{ + {tcell.ModCtrl, tcell.KeyEnter, 0}, + }, BINDING_FOUND, ":open") +} -- cgit