Implement number args for repeats

This commit is contained in:
Kovid Goyal 2022-11-06 11:33:36 +05:30
parent 2f2dbfb45f
commit 5a425ccaad
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 282 additions and 90 deletions

View File

@ -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
}

View File

@ -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: "$$ "})

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}