diff --git a/tools/tui/readline/actions.go b/tools/tui/readline/actions.go index db3756d91..3732c20a6 100644 --- a/tools/tui/readline/actions.go +++ b/tools/tui/readline/actions.go @@ -137,7 +137,7 @@ func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) (am func (self *Readline) move_cursor_to_target_line(source_line, target_line *ScreenLine) { if source_line != target_line { - visual_distance_into_text := source_line.CursorCell - source_line.PromptLen + visual_distance_into_text := source_line.CursorCell - source_line.Prompt.Length self.cursor.Y = target_line.ParentLineNumber tp := wcswidth.TruncateToVisualLength(target_line.Text, visual_distance_into_text) self.cursor.X = target_line.OffsetInParentLine + len(tp) @@ -647,6 +647,11 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error { self.loop.QueueWriteString("\r\n") self.ResetText() return nil + case ActionAddText: + text := strings.Repeat(self.text_to_be_added, int(repeat_count)) + self.text_to_be_added = "" + self.add_text(text) + return nil } return ErrCouldNotPerformAction } diff --git a/tools/tui/readline/actions_test.go b/tools/tui/readline/actions_test.go index 71936f6a8..d26b6d107 100644 --- a/tools/tui/readline/actions_test.go +++ b/tools/tui/readline/actions_test.go @@ -6,6 +6,8 @@ import ( "container/list" "fmt" "kitty/tools/tui/loop" + "strconv" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -64,6 +66,13 @@ func TestGetScreenLines(t *testing.T) { rl := New(lp, RlInit{Prompt: "$$ "}) rl.screen_width = 10 + p := func(primary bool) Prompt { + if primary { + return rl.prompt + } + return rl.continuation_prompt + } + tsl := func(expected ...ScreenLine) { q := rl.get_screen_lines() actual := make([]ScreenLine, len(q)) @@ -74,52 +83,52 @@ func TestGetScreenLines(t *testing.T) { t.Fatalf("Did not get expected screen lines for: %#v and cursor: %+v\n%s", rl.AllText(), rl.cursor, diff) } } - tsl(ScreenLine{PromptLen: 3, CursorCell: 3}) + tsl(ScreenLine{Prompt: p(true), CursorCell: 3}) rl.add_text("123") - tsl(ScreenLine{PromptLen: 3, CursorCell: 6, Text: "123", CursorTextPos: 3, TextLengthInCells: 3}) + tsl(ScreenLine{Prompt: p(true), CursorCell: 6, Text: "123", CursorTextPos: 3, TextLengthInCells: 3}) rl.add_text("456") - tsl(ScreenLine{PromptLen: 3, CursorCell: 9, Text: "123456", CursorTextPos: 6, TextLengthInCells: 6}) + tsl(ScreenLine{Prompt: p(true), CursorCell: 9, Text: "123456", CursorTextPos: 6, TextLengthInCells: 6}) rl.add_text("7") tsl( - ScreenLine{PromptLen: 3, CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7}, + ScreenLine{Prompt: p(true), CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7}, ScreenLine{OffsetInParentLine: 7}, ) rl.add_text("89") tsl( - ScreenLine{PromptLen: 3, CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7}, + ScreenLine{Prompt: p(true), CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7}, ScreenLine{OffsetInParentLine: 7, Text: "89", CursorCell: 2, TextLengthInCells: 2, CursorTextPos: 2}, ) rl.ResetText() rl.add_text("123\n456abcdeXYZ") tsl( - ScreenLine{PromptLen: 3, CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3}, - ScreenLine{ParentLineNumber: 1, PromptLen: 2, Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1}, + ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3}, + ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1}, ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: 3, CursorTextPos: 3, Text: "XYZ"}, ) rl.cursor = Position{X: 2} tsl( - ScreenLine{PromptLen: 3, CursorCell: 5, Text: "123", CursorTextPos: 2, TextLengthInCells: 3}, - ScreenLine{ParentLineNumber: 1, PromptLen: 2, Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1}, + ScreenLine{Prompt: p(true), CursorCell: 5, Text: "123", CursorTextPos: 2, TextLengthInCells: 3}, + ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1}, ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: -1, CursorTextPos: -1, Text: "XYZ"}, ) rl.cursor = Position{X: 2, Y: 1} tsl( - ScreenLine{PromptLen: 3, CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3}, - ScreenLine{ParentLineNumber: 1, PromptLen: 2, Text: "456abcde", TextLengthInCells: 8, CursorCell: 4, CursorTextPos: 2}, + ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3}, + ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: 4, CursorTextPos: 2}, ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: -1, CursorTextPos: -1, Text: "XYZ"}, ) rl.cursor = Position{X: 8, Y: 1} tsl( - ScreenLine{PromptLen: 3, CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3}, - ScreenLine{ParentLineNumber: 1, PromptLen: 2, Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1}, + ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3}, + ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1}, ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: 0, CursorTextPos: 0, Text: "XYZ"}, ) rl.ResetText() rl.add_text("1234567\nabc") rl.cursor = Position{X: 7} tsl( - ScreenLine{PromptLen: 3, CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7}, - ScreenLine{ParentLineNumber: 1, PromptLen: 2, Text: "abc", CursorCell: 2, TextLengthInCells: 3, CursorTextPos: 0}, + ScreenLine{Prompt: p(true), CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7}, + ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "abc", CursorCell: 2, TextLengthInCells: 3, CursorTextPos: 0}, ) } @@ -374,6 +383,51 @@ func TestEraseChars(t *testing.T) { }, "", "oree") } +func TestNumberArgument(t *testing.T) { + lp, _ := loop.New() + rl := New(lp, RlInit{Prompt: "$$ "}) + test := func(ac Action, before_cursor, after_cursor string) { + rl.dispatch_key_action(ac) + if diff := cmp.Diff(before_cursor, rl.text_upto_cursor_pos()); diff != "" { + t.Fatalf("The text before the cursor was not as expected for action: %#v\n%s", ac, diff) + } + if diff := cmp.Diff(after_cursor, rl.text_after_cursor_pos()); diff != "" { + t.Fatalf("The text after the cursor was not as expected for action: %#v\n%s", ac, diff) + } + } + sw := func(num int) { + q := rl.format_arg_prompt(strconv.Itoa(num)) + for _, sl := range rl.get_screen_lines() { + if num <= 0 && !strings.Contains(sl.Prompt.Text, "$$") { + t.Fatalf("arg prompt unexpectedly present for: %#v", rl.AllText()) + } + if num > 0 && !strings.Contains(sl.Prompt.Text, q) { + t.Fatalf("arg prompt unexpectedly not present for: %#v prompt: %#v", rl.AllText(), sl.Prompt.Text) + } + } + } + + sw(0) + rl.dispatch_key_action(ActionNumericArgumentDigit1) + sw(1) + rl.dispatch_key_action(ActionNumericArgumentDigit0) + sw(10) + rl.text_to_be_added = "x" + test(ActionAddText, "xxxxxxxxxx", "") + sw(0) + test(ActionNumericArgumentDigit0, "xxxxxxxxxx0", "") + sw(0) + rl.dispatch_key_action(ActionNumericArgumentDigit1) + test(ActionNumericArgumentDigitMinus, "xxxxxxxxxx0-", "") + sw(0) + rl.dispatch_key_action(ActionNumericArgumentDigit1) + sw(1) + rl.dispatch_key_action(ActionNumericArgumentDigit1) + sw(11) + test(ActionCursorLeft, "x", "xxxxxxxxx0-") + sw(0) +} + func TestHistory(t *testing.T) { lp, _ := loop.New() rl := New(lp, RlInit{Prompt: "$$ "}) diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index 70f9be73c..bb5c90fbe 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -73,6 +73,18 @@ const ( ActionYank ActionPopYank + + ActionNumericArgumentDigit0 + ActionNumericArgumentDigit1 + ActionNumericArgumentDigit2 + ActionNumericArgumentDigit3 + ActionNumericArgumentDigit4 + ActionNumericArgumentDigit5 + ActionNumericArgumentDigit6 + ActionNumericArgumentDigit7 + ActionNumericArgumentDigit8 + ActionNumericArgumentDigit9 + ActionNumericArgumentDigitMinus ) type kill_ring struct { @@ -115,8 +127,8 @@ func (self *kill_ring) clear() { } type Prompt struct { - text string - length int + Text string + Length int } type Readline struct { @@ -140,6 +152,9 @@ type Readline struct { bracketed_paste_buffer strings.Builder last_action Action history_matches *HistoryMatches + keyboard_state KeyboardState + fmt_ctx *markup.Context + text_to_be_added string } func New(loop *loop.Loop, r RlInit) *Readline { @@ -147,10 +162,9 @@ func New(loop *loop.Loop, r RlInit) *Readline { if hc == 0 { hc = 8192 } - c := markup.New(true) ans := &Readline{ - mark_prompts: !r.DontMarkPrompts, - loop: loop, lines: []string{""}, history: NewHistory(r.HistoryPath, hc), kill_ring: kill_ring{items: list.New().Init()}, + mark_prompts: !r.DontMarkPrompts, fmt_ctx: markup.New(true), + loop: loop, lines: []string{""}, history: NewHistory(r.HistoryPath, hc), kill_ring: kill_ring{items: list.New().Init()}, } make_prompt := func(text string, is_secondary bool) Prompt { if ans.mark_prompts { @@ -160,19 +174,19 @@ func New(loop *loop.Loop, r RlInit) *Readline { } text = m + ST + text } - return Prompt{text: text, length: wcswidth.Stringwidth(text)} + return Prompt{Text: text, Length: wcswidth.Stringwidth(text)} } ans.prompt = make_prompt(r.Prompt, false) t := "" if r.ContinuationPrompt != "" || !r.EmptyContinuationPrompt { t = r.ContinuationPrompt if t == "" { - t = c.Yellow(">") + " " + t = ans.fmt_ctx.Yellow(">") + " " } } ans.continuation_prompt = make_prompt(t, true) - ans.reverse_search_prompt = make_prompt(c.Blue("?")+": ", false) - ans.forward_search_prompt = make_prompt(c.Cyan("/")+": ", false) + ans.reverse_search_prompt = make_prompt(ans.fmt_ctx.Blue("?")+": ", false) + ans.forward_search_prompt = make_prompt(ans.fmt_ctx.Cyan("/")+": ", false) return ans } @@ -244,9 +258,8 @@ func (self *Readline) OnText(text string, from_key_event bool, in_bracketed_past text = self.bracketed_paste_buffer.String() self.bracketed_paste_buffer.Reset() } - self.add_text(text) - self.last_action = ActionAddText - return nil + self.text_to_be_added = text + return self.dispatch_key_action(ActionAddText) } func (self *Readline) TextBeforeCursor() string { diff --git a/tools/tui/readline/draw.go b/tools/tui/readline/draw.go index 99d7d6ccb..69651a627 100644 --- a/tools/tui/readline/draw.go +++ b/tools/tui/readline/draw.go @@ -25,12 +25,22 @@ func (self *Readline) update_current_screen_size() { } type ScreenLine struct { - ParentLineNumber, OffsetInParentLine, PromptLen int - TextLengthInCells, CursorCell, CursorTextPos int - Text string + ParentLineNumber, OffsetInParentLine int + Prompt Prompt + TextLengthInCells, CursorCell, CursorTextPos int + Text string +} + +func (self *Readline) format_arg_prompt(cna string) string { + return fmt.Sprintf("(arg: %s) ", self.fmt_ctx.Yellow(cna)) } func (self *Readline) prompt_for_line_number(i int) Prompt { + is_line_with_cursor := i == self.cursor.Y + if is_line_with_cursor && self.keyboard_state.current_numeric_argument != "" { + text := self.format_arg_prompt(self.keyboard_state.current_numeric_argument) + return Prompt{Text: text, Length: wcswidth.Stringwidth(text)} + } if i == 0 { return self.prompt } @@ -45,26 +55,26 @@ func (self *Readline) get_screen_lines() []*ScreenLine { found_cursor := false cursor_at_start_of_next_line := false for i, line := range self.lines { - plen := self.prompt_for_line_number(i).length + prompt := self.prompt_for_line_number(i) offset := 0 has_cursor := i == self.cursor.Y for is_first := true; is_first || offset < len(line); is_first = false { - l, width := wcswidth.TruncateToVisualLengthWithWidth(line[offset:], self.screen_width-plen) + l, width := wcswidth.TruncateToVisualLengthWithWidth(line[offset:], self.screen_width-prompt.Length) sl := ScreenLine{ ParentLineNumber: i, OffsetInParentLine: offset, - PromptLen: plen, TextLengthInCells: width, + Prompt: prompt, TextLengthInCells: width, CursorCell: -1, Text: l, CursorTextPos: -1, } if cursor_at_start_of_next_line { cursor_at_start_of_next_line = false - sl.CursorCell = plen + sl.CursorCell = prompt.Length sl.CursorTextPos = 0 } ans = append(ans, &sl) if has_cursor && !found_cursor && offset <= self.cursor.X && self.cursor.X <= offset+len(l) { found_cursor = true ctpos := self.cursor.X - offset - ccell := plen + wcswidth.Stringwidth(l[:ctpos]) + ccell := prompt.Length + wcswidth.Stringwidth(l[:ctpos]) if ccell >= self.screen_width { if offset+len(l) < len(line) || i < len(self.lines)-1 { cursor_at_start_of_next_line = true @@ -76,7 +86,7 @@ func (self *Readline) get_screen_lines() []*ScreenLine { sl.CursorCell = ccell } } - plen = 0 + prompt = Prompt{} offset += len(l) } } @@ -104,8 +114,8 @@ func (self *Readline) redraw() { if i > 0 { self.loop.QueueWriteString("\n") } - if sl.PromptLen > 0 { - self.loop.QueueWriteString(self.prompt_for_line_number(i).text) + if sl.Prompt.Length > 0 { + self.loop.QueueWriteString(self.prompt_for_line_number(i).Text) } self.loop.QueueWriteString(sl.Text) if sl.CursorCell > -1 { diff --git a/tools/tui/readline/keys.go b/tools/tui/readline/keys.go index 7227075a9..9ff7a6b50 100644 --- a/tools/tui/readline/keys.go +++ b/tools/tui/readline/keys.go @@ -5,61 +5,118 @@ package readline import ( "errors" "fmt" + "strconv" + "strings" "kitty/tools/tui/loop" ) var _ = fmt.Print -var default_shortcuts = map[string]Action{ - "backspace": ActionBackspace, - "ctrl+h": ActionBackspace, - "delete": ActionDelete, - - "home": ActionMoveToStartOfLine, - "ctrl+a": ActionMoveToStartOfLine, - - "end": ActionMoveToEndOfLine, - "ctrl+e": ActionMoveToEndOfLine, - - "ctrl+home": ActionMoveToStartOfDocument, - "ctrl+end": ActionMoveToEndOfDocument, - - "alt+f": ActionMoveToEndOfWord, - "ctrl+right": ActionMoveToEndOfWord, - "ctrl+left": ActionMoveToStartOfWord, - "alt+b": ActionMoveToStartOfWord, - - "left": ActionCursorLeft, - "ctrl+b": ActionCursorLeft, - "right": ActionCursorRight, - "ctrl+f": ActionCursorRight, - - "ctrl+l": ActionClearScreen, - - "ctrl+d": ActionEndInput, - "enter": ActionAcceptInput, - - "ctrl+k": ActionKillToEndOfLine, - "ctrl+x": ActionKillToStartOfLine, - "ctrl+u": ActionKillToStartOfLine, - "alt+d": ActionKillNextWord, - "alt+backspace": ActionKillPreviousWord, - "ctrl+w": ActionKillPreviousSpaceDelimitedWord, - "ctrl+y": ActionYank, - "alt+y": ActionPopYank, - - "up": ActionHistoryPreviousOrCursorUp, - "down": ActionHistoryNextOrCursorDown, - "ctrl+p": ActionHistoryPrevious, - "ctrl+n": ActionHistoryNext, - "alt+<": ActionHistoryFirst, - "alt+>": ActionHistoryLast, - - "ctrl+c": ActionAbortCurrentLine, +type ShortcutMap struct { + leaves map[string]Action + children map[string]*ShortcutMap } -func action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Action { +type KeyboardState struct { + 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(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(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 @@ -71,14 +128,67 @@ func action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Act 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 { + 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 len(self.keyboard_state.shortcut_maps) == 0 { + self.keyboard_state.shortcut_maps = []*ShortcutMap{default_shortcuts()} + } if event.Text != "" { return nil } - ac := action_for_key_event(event, default_shortcuts) - if ac != ActionNil { - event.Handled = true - return self.perform_action(ac, 1) + sm := self.keyboard_state.shortcut_maps[len(self.keyboard_state.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 }