diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index 17c29bc3b..5b6df226e 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -17,7 +17,7 @@ var _ = fmt.Print const ST = "\x1b\\" const PROMPT_MARK = "\x1b]133;" -type SyntaxHighlightFunction func(text string, x, y int) string +type SyntaxHighlightFunction = func(text string, x, y int) string type RlInit struct { Prompt string diff --git a/tools/tui/readline/keys.go b/tools/tui/readline/keys.go index e11d9a2ab..e9db60d7a 100644 --- a/tools/tui/readline/keys.go +++ b/tools/tui/readline/keys.go @@ -6,17 +6,14 @@ import ( "errors" "fmt" "strconv" - "strings" "kitty/tools/tui/loop" + "kitty/tools/tui/shortcuts" ) var _ = fmt.Print -type ShortcutMap struct { - leaves map[string]Action - children map[string]*ShortcutMap -} +type ShortcutMap = shortcuts.ShortcutMap[Action] type KeyboardState struct { active_shortcut_maps []*ShortcutMap @@ -24,141 +21,116 @@ type KeyboardState struct { current_numeric_argument string } -func (self *ShortcutMap) add(ac Action, base string, keys ...string) error { - items := []string{base} - items = append(items, keys...) - sm := self - for i, key := range items { - if i == len(items)-1 { - if sm.children[key] != nil { - return fmt.Errorf("The shortcut %s conflicts with another multi-key shortcut", strings.Join(items, " > ")) - } - sm.leaves[key] = ac - } else { - if _, found := sm.leaves[key]; found { - return fmt.Errorf("The shortcut %s conflicts with another multi-key shortcut", strings.Join(items, " > ")) - } - q := sm.children[key] - if q == nil { - q = &ShortcutMap{leaves: map[string]Action{}, children: map[string]*ShortcutMap{}} - sm.children[key] = q - } - sm = q - } - } - return nil -} - var _default_shortcuts *ShortcutMap func default_shortcuts() *ShortcutMap { if _default_shortcuts == nil { - sm := ShortcutMap{leaves: make(map[string]Action, 32), children: map[string]*ShortcutMap{}} - sm.add(ActionBackspace, "backspace") - sm.add(ActionBackspace, "ctrl+h") - sm.add(ActionDelete, "delete") + sm := shortcuts.New[Action]() + sm.AddOrPanic(ActionBackspace, "backspace") + sm.AddOrPanic(ActionBackspace, "ctrl+h") + sm.AddOrPanic(ActionDelete, "delete") - sm.add(ActionMoveToStartOfLine, "home") - sm.add(ActionMoveToStartOfLine, "ctrl+a") + sm.AddOrPanic(ActionMoveToStartOfLine, "home") + sm.AddOrPanic(ActionMoveToStartOfLine, "ctrl+a") - sm.add(ActionMoveToEndOfLine, "end") - sm.add(ActionMoveToEndOfLine, "ctrl+e") + sm.AddOrPanic(ActionMoveToEndOfLine, "end") + sm.AddOrPanic(ActionMoveToEndOfLine, "ctrl+e") - sm.add(ActionMoveToStartOfDocument, "ctrl+home") - sm.add(ActionMoveToEndOfDocument, "ctrl+end") + sm.AddOrPanic(ActionMoveToStartOfDocument, "ctrl+home") + sm.AddOrPanic(ActionMoveToEndOfDocument, "ctrl+end") - sm.add(ActionMoveToEndOfWord, "alt+f") - sm.add(ActionMoveToEndOfWord, "ctrl+right") - sm.add(ActionMoveToStartOfWord, "ctrl+left") - sm.add(ActionMoveToStartOfWord, "alt+b") + sm.AddOrPanic(ActionMoveToEndOfWord, "alt+f") + sm.AddOrPanic(ActionMoveToEndOfWord, "ctrl+right") + sm.AddOrPanic(ActionMoveToStartOfWord, "ctrl+left") + sm.AddOrPanic(ActionMoveToStartOfWord, "alt+b") - sm.add(ActionCursorLeft, "left") - sm.add(ActionCursorLeft, "ctrl+b") - sm.add(ActionCursorRight, "right") - sm.add(ActionCursorRight, "ctrl+f") + sm.AddOrPanic(ActionCursorLeft, "left") + sm.AddOrPanic(ActionCursorLeft, "ctrl+b") + sm.AddOrPanic(ActionCursorRight, "right") + sm.AddOrPanic(ActionCursorRight, "ctrl+f") - sm.add(ActionClearScreen, "ctrl+l") - sm.add(ActionAbortCurrentLine, "ctrl+c") - sm.add(ActionAbortCurrentLine, "ctrl+g") + sm.AddOrPanic(ActionClearScreen, "ctrl+l") + sm.AddOrPanic(ActionAbortCurrentLine, "ctrl+c") + sm.AddOrPanic(ActionAbortCurrentLine, "ctrl+g") - sm.add(ActionEndInput, "ctrl+d") - sm.add(ActionAcceptInput, "enter") + sm.AddOrPanic(ActionEndInput, "ctrl+d") + sm.AddOrPanic(ActionAcceptInput, "enter") - sm.add(ActionKillToEndOfLine, "ctrl+k") - sm.add(ActionKillToStartOfLine, "ctrl+x") - sm.add(ActionKillToStartOfLine, "ctrl+u") - sm.add(ActionKillNextWord, "alt+d") - sm.add(ActionKillPreviousWord, "alt+backspace") - sm.add(ActionKillPreviousSpaceDelimitedWord, "ctrl+w") - sm.add(ActionYank, "ctrl+y") - sm.add(ActionPopYank, "alt+y") + sm.AddOrPanic(ActionKillToEndOfLine, "ctrl+k") + sm.AddOrPanic(ActionKillToStartOfLine, "ctrl+x") + sm.AddOrPanic(ActionKillToStartOfLine, "ctrl+u") + sm.AddOrPanic(ActionKillNextWord, "alt+d") + sm.AddOrPanic(ActionKillPreviousWord, "alt+backspace") + sm.AddOrPanic(ActionKillPreviousSpaceDelimitedWord, "ctrl+w") + sm.AddOrPanic(ActionYank, "ctrl+y") + sm.AddOrPanic(ActionPopYank, "alt+y") - sm.add(ActionHistoryPreviousOrCursorUp, "up") - sm.add(ActionHistoryNextOrCursorDown, "down") - sm.add(ActionHistoryPrevious, "ctrl+p") - sm.add(ActionHistoryNext, "ctrl+n") - sm.add(ActionHistoryFirst, "alt+<") - sm.add(ActionHistoryLast, "alt+>") - sm.add(ActionHistoryIncrementalSearchBackwards, "ctrl+r") - sm.add(ActionHistoryIncrementalSearchBackwards, "ctrl+?") - sm.add(ActionHistoryIncrementalSearchForwards, "ctrl+s") - sm.add(ActionHistoryIncrementalSearchForwards, "ctrl+/") + sm.AddOrPanic(ActionHistoryPreviousOrCursorUp, "up") + sm.AddOrPanic(ActionHistoryNextOrCursorDown, "down") + sm.AddOrPanic(ActionHistoryPrevious, "ctrl+p") + sm.AddOrPanic(ActionHistoryNext, "ctrl+n") + sm.AddOrPanic(ActionHistoryFirst, "alt+<") + sm.AddOrPanic(ActionHistoryLast, "alt+>") + sm.AddOrPanic(ActionHistoryIncrementalSearchBackwards, "ctrl+r") + sm.AddOrPanic(ActionHistoryIncrementalSearchBackwards, "ctrl+?") + sm.AddOrPanic(ActionHistoryIncrementalSearchForwards, "ctrl+s") + sm.AddOrPanic(ActionHistoryIncrementalSearchForwards, "ctrl+/") - sm.add(ActionNumericArgumentDigit0, "alt+0") - sm.add(ActionNumericArgumentDigit1, "alt+1") - sm.add(ActionNumericArgumentDigit2, "alt+2") - sm.add(ActionNumericArgumentDigit3, "alt+3") - sm.add(ActionNumericArgumentDigit4, "alt+4") - sm.add(ActionNumericArgumentDigit5, "alt+5") - sm.add(ActionNumericArgumentDigit6, "alt+6") - sm.add(ActionNumericArgumentDigit7, "alt+7") - sm.add(ActionNumericArgumentDigit8, "alt+8") - sm.add(ActionNumericArgumentDigit9, "alt+9") - sm.add(ActionNumericArgumentDigitMinus, "alt+-") + sm.AddOrPanic(ActionNumericArgumentDigit0, "alt+0") + sm.AddOrPanic(ActionNumericArgumentDigit1, "alt+1") + sm.AddOrPanic(ActionNumericArgumentDigit2, "alt+2") + sm.AddOrPanic(ActionNumericArgumentDigit3, "alt+3") + sm.AddOrPanic(ActionNumericArgumentDigit4, "alt+4") + sm.AddOrPanic(ActionNumericArgumentDigit5, "alt+5") + sm.AddOrPanic(ActionNumericArgumentDigit6, "alt+6") + sm.AddOrPanic(ActionNumericArgumentDigit7, "alt+7") + sm.AddOrPanic(ActionNumericArgumentDigit8, "alt+8") + sm.AddOrPanic(ActionNumericArgumentDigit9, "alt+9") + sm.AddOrPanic(ActionNumericArgumentDigitMinus, "alt+-") - _default_shortcuts = &sm + _default_shortcuts = sm } return _default_shortcuts } -var _history_search_shortcuts *ShortcutMap +var _history_search_shortcuts *shortcuts.ShortcutMap[Action] -func history_search_shortcuts() *ShortcutMap { +func history_search_shortcuts() *shortcuts.ShortcutMap[Action] { if _history_search_shortcuts == nil { - sm := ShortcutMap{leaves: make(map[string]Action, 32), children: map[string]*ShortcutMap{}} - sm.add(ActionBackspace, "backspace") - sm.add(ActionBackspace, "ctrl+h") + sm := shortcuts.New[Action]() + sm.AddOrPanic(ActionBackspace, "backspace") + sm.AddOrPanic(ActionBackspace, "ctrl+h") - sm.add(ActionTerminateHistorySearchAndRestore, "home") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+a") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "home") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+a") - sm.add(ActionTerminateHistorySearchAndRestore, "end") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+e") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "end") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+e") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+home") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+end") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+home") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+end") - sm.add(ActionTerminateHistorySearchAndRestore, "alt+f") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+right") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+left") - sm.add(ActionTerminateHistorySearchAndRestore, "alt+b") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "alt+f") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+right") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+left") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "alt+b") - sm.add(ActionTerminateHistorySearchAndRestore, "left") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+b") - sm.add(ActionTerminateHistorySearchAndRestore, "right") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+f") - sm.add(ActionTerminateHistorySearchAndRestore, "up") - sm.add(ActionTerminateHistorySearchAndRestore, "down") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "left") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+b") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "right") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+f") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "up") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "down") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+c") - sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+g") - sm.add(ActionTerminateHistorySearchAndRestore, "escape") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+c") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "ctrl+g") + sm.AddOrPanic(ActionTerminateHistorySearchAndRestore, "escape") - sm.add(ActionTerminateHistorySearchAndApply, "ctrl+d") - sm.add(ActionTerminateHistorySearchAndApply, "enter") - sm.add(ActionTerminateHistorySearchAndApply, "ctrl+j") + sm.AddOrPanic(ActionTerminateHistorySearchAndApply, "ctrl+d") + sm.AddOrPanic(ActionTerminateHistorySearchAndApply, "enter") + sm.AddOrPanic(ActionTerminateHistorySearchAndApply, "ctrl+j") - _history_search_shortcuts = &sm + _history_search_shortcuts = sm } return _history_search_shortcuts } @@ -230,21 +202,16 @@ func (self *Readline) handle_key_event(event *loop.KeyEvent) error { if len(self.keyboard_state.active_shortcut_maps) > 0 { sm = self.keyboard_state.active_shortcut_maps[len(self.keyboard_state.active_shortcut_maps)-1] } - for _, pk := range self.keyboard_state.current_pending_keys { - sm = sm.children[pk] - } - for k := range sm.children { - if event.MatchesPressOrRepeat(k) { - event.Handled = true - if self.keyboard_state.current_pending_keys == nil { - self.keyboard_state.current_pending_keys = []string{} - } - self.keyboard_state.current_pending_keys = append(self.keyboard_state.current_pending_keys, k) - return nil + ac, pending := sm.ResolveKeyEvent(event, self.keyboard_state.current_pending_keys...) + if pending != "" { + event.Handled = true + if self.keyboard_state.current_pending_keys == nil { + self.keyboard_state.current_pending_keys = []string{} } - } - for k, ac := range sm.leaves { - if event.MatchesPressOrRepeat(k) { + self.keyboard_state.current_pending_keys = append(self.keyboard_state.current_pending_keys, pending) + } else { + self.keyboard_state.current_pending_keys = nil + if ac != ActionNil { event.Handled = true return self.dispatch_key_action(ac) } diff --git a/tools/tui/shortcuts/api.go b/tools/tui/shortcuts/api.go new file mode 100644 index 000000000..384ac65c3 --- /dev/null +++ b/tools/tui/shortcuts/api.go @@ -0,0 +1,56 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package shortcuts + +import ( + "fmt" + "kitty/tools/tui/loop" + "strings" +) + +var _ = fmt.Print + +type ShortcutMap[T comparable] struct { + leaves map[string]T + children map[string]*ShortcutMap[T] +} + +func (self *ShortcutMap[T]) ResolveKeyEvent(k *loop.KeyEvent, pending_keys ...string) (ac T, pending string) { + q := self + for _, pk := range pending_keys { + q = self.children[pk] + if q == nil { + return + } + } + for c, ans := range q.leaves { + if k.MatchesPressOrRepeat(c) { + ac = ans + return + } + } + for c := range q.children { + if k.MatchesPressOrRepeat(c) { + pending = c + return + } + } + return +} + +func (self *ShortcutMap[T]) Add(ac T, keys ...string) (conflict T) { + return self.add(ac, keys) +} + +func (self *ShortcutMap[T]) AddOrPanic(ac T, keys ...string) { + var zero T + c := self.add(ac, keys) + if c != zero { + panic(fmt.Sprintf("The shortcut for %#v (%s) conflicted with the shortcut for %#v (%s)", + ac, strings.Join(keys, " "), c, strings.Join(self.shortcut_for(c), " "))) + } +} + +func New[T comparable]() *ShortcutMap[T] { + return &ShortcutMap[T]{leaves: make(map[string]T), children: make(map[string]*ShortcutMap[T])} +} diff --git a/tools/tui/shortcuts/implementation.go b/tools/tui/shortcuts/implementation.go new file mode 100644 index 000000000..d7c32e869 --- /dev/null +++ b/tools/tui/shortcuts/implementation.go @@ -0,0 +1,64 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package shortcuts + +import ( + "fmt" +) + +var _ = fmt.Print + +func (self *ShortcutMap[T]) first_action() (ans T) { + for _, ac := range self.leaves { + return ac + } + for _, child := range self.children { + return child.first_action() + } + return +} + +func (self *ShortcutMap[T]) shortcut_for(ac T) (keys []string) { + keys = []string{} + for key, q := range self.leaves { + if ac == q { + return append(keys, key) + } + } + for key, child := range self.children { + ckeys := child.shortcut_for(ac) + if len(ckeys) > 0 { + return append(append(keys, key), ckeys...) + } + } + return +} + +func (self *ShortcutMap[T]) add(ac T, keys []string) (conflict T) { + sm := self + last := len(keys) - 1 + for i, key := range keys { + if i == last { + if c, found := sm.leaves[key]; found { + conflict = c + } + sm.leaves[key] = ac + if c, found := sm.children[key]; found { + conflict = c.first_action() + delete(sm.children, key) + } + } else { + if c, found := sm.leaves[key]; found { + conflict = c + delete(sm.leaves, key) + } + q := sm.children[key] + if q == nil { + q = &ShortcutMap[T]{leaves: map[string]T{}, children: map[string]*ShortcutMap[T]{}} + sm.children[key] = q + } + sm = q + } + } + return +}