203 lines
5.8 KiB
Go
203 lines
5.8 KiB
Go
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
|
|
|
package readline
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"kitty/tools/tui/loop"
|
|
)
|
|
|
|
var _ = fmt.Print
|
|
|
|
type ShortcutMap struct {
|
|
leaves map[string]Action
|
|
children map[string]*ShortcutMap
|
|
}
|
|
|
|
type KeyboardState struct {
|
|
active_shortcut_maps []*ShortcutMap
|
|
current_pending_keys []string
|
|
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.add(ActionMoveToStartOfLine, "home")
|
|
sm.add(ActionMoveToStartOfLine, "ctrl+a")
|
|
|
|
sm.add(ActionMoveToEndOfLine, "end")
|
|
sm.add(ActionMoveToEndOfLine, "ctrl+e")
|
|
|
|
sm.add(ActionMoveToStartOfDocument, "ctrl+home")
|
|
sm.add(ActionMoveToEndOfDocument, "ctrl+end")
|
|
|
|
sm.add(ActionMoveToEndOfWord, "alt+f")
|
|
sm.add(ActionMoveToEndOfWord, "ctrl+right")
|
|
sm.add(ActionMoveToStartOfWord, "ctrl+left")
|
|
sm.add(ActionMoveToStartOfWord, "alt+b")
|
|
|
|
sm.add(ActionCursorLeft, "left")
|
|
sm.add(ActionCursorLeft, "ctrl+b")
|
|
sm.add(ActionCursorRight, "right")
|
|
sm.add(ActionCursorRight, "ctrl+f")
|
|
|
|
sm.add(ActionClearScreen, "ctrl+l")
|
|
sm.add(ActionAbortCurrentLine, "ctrl+c")
|
|
sm.add(ActionAbortCurrentLine, "ctrl+g")
|
|
|
|
sm.add(ActionEndInput, "ctrl+d")
|
|
sm.add(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.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.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+-")
|
|
|
|
_default_shortcuts = &sm
|
|
}
|
|
return _default_shortcuts
|
|
}
|
|
|
|
func (self *Readline) action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Action {
|
|
for sc, ac := range shortcuts {
|
|
if event.MatchesPressOrRepeat(sc) {
|
|
return ac
|
|
}
|
|
}
|
|
return ActionNil
|
|
}
|
|
|
|
var ErrCouldNotPerformAction = errors.New("Could not perform the specified action")
|
|
var ErrAcceptInput = errors.New("Accept input")
|
|
|
|
func (self *Readline) handle_numeric_arg(ac Action) {
|
|
t := "-"
|
|
num := int(ac - ActionNumericArgumentDigit0)
|
|
if num < 10 {
|
|
t = strconv.Itoa(num)
|
|
}
|
|
cna := self.keyboard_state.current_numeric_argument
|
|
if (cna == "" && t == "0") || (cna != "" && t == "-") {
|
|
self.add_text(t)
|
|
self.keyboard_state.current_numeric_argument = ""
|
|
self.last_action = ActionAddText
|
|
} else {
|
|
self.keyboard_state.current_numeric_argument += t
|
|
self.last_action = ac
|
|
}
|
|
}
|
|
|
|
func (self *Readline) dispatch_key_action(ac Action) error {
|
|
self.keyboard_state.current_pending_keys = nil
|
|
if ActionNumericArgumentDigit0 <= ac && ac <= ActionNumericArgumentDigitMinus {
|
|
if self.history_search != nil {
|
|
return ErrCouldNotPerformAction
|
|
}
|
|
self.handle_numeric_arg(ac)
|
|
return nil
|
|
}
|
|
cna := self.keyboard_state.current_numeric_argument
|
|
self.keyboard_state.current_numeric_argument = ""
|
|
if cna == "" {
|
|
cna = "1"
|
|
}
|
|
repeat_count, err := strconv.Atoi(cna)
|
|
if err != nil || repeat_count <= 0 {
|
|
repeat_count = 1
|
|
}
|
|
return self.perform_action(ac, uint(repeat_count))
|
|
}
|
|
|
|
func (self *Readline) handle_key_event(event *loop.KeyEvent) error {
|
|
if event.Text != "" {
|
|
return nil
|
|
}
|
|
sm := default_shortcuts()
|
|
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
|
|
}
|
|
}
|
|
for k, ac := range sm.leaves {
|
|
if event.MatchesPressOrRepeat(k) {
|
|
event.Handled = true
|
|
return self.dispatch_key_action(ac)
|
|
}
|
|
}
|
|
return nil
|
|
}
|