From a8b2c257cdd51181bec67eca80c7879787c49776 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Fri, 19 Jun 2015 00:31:48 +0900 Subject: [PATCH] Improve handling of key names Remember the exact string given as the key name so that it's possible to correctly handle synonyms and print the original string. --- src/options.go | 108 ++++++++++++++++++----------------- src/options_test.go | 136 ++++++++++++++++++++++++++------------------ src/terminal.go | 22 ++----- 3 files changed, 142 insertions(+), 124 deletions(-) diff --git a/src/options.go b/src/options.go index 8281ada..a3b7cc8 100644 --- a/src/options.go +++ b/src/options.go @@ -117,7 +117,7 @@ type Options struct { Exit0 bool Filter *string ToggleSort bool - Expect []int + Expect map[int]string Keymap map[int]actionType Execmap map[int]string PrintQuery bool @@ -159,7 +159,7 @@ func defaultOptions() *Options { Exit0: false, Filter: nil, ToggleSort: false, - Expect: []int{}, + Expect: make(map[int]string), Keymap: defaultKeymap(), Execmap: make(map[int]string), PrintQuery: false, @@ -265,7 +265,7 @@ func isAlphabet(char uint8) bool { return char >= 'a' && char <= 'z' } -func parseKeyChords(str string, message string, bind bool) []int { +func parseKeyChords(str string, message string) map[int]string { if len(str) == 0 { errorExit(message) } @@ -275,54 +275,51 @@ func parseKeyChords(str string, message string, bind bool) []int { tokens = append(tokens, ",") } - var chords []int + chords := make(map[int]string) for _, key := range tokens { if len(key) == 0 { continue // ignore } lkey := strings.ToLower(key) chord := 0 - if bind { - switch lkey { - case "up": - chord = curses.Up - case "down": - chord = curses.Down - case "left": - chord = curses.Left - case "right": - chord = curses.Right - case "enter", "return": - chord = curses.CtrlM - case "space": - chord = curses.AltZ + int(' ') - case "bspace": - chord = curses.BSpace - case "alt-bs", "alt-bspace": - chord = curses.AltBS - case "tab": - chord = curses.Tab - case "btab": - chord = curses.BTab - case "esc": - chord = curses.ESC - case "del": - chord = curses.Del - case "home": - chord = curses.Home - case "end": - chord = curses.End - case "pgup", "page-up": - chord = curses.PgUp - case "pgdn", "page-down": - chord = curses.PgDn - case "shift-left": - chord = curses.SLeft - case "shift-right": - chord = curses.SRight - } - } - if chord == 0 { + switch lkey { + case "up": + chord = curses.Up + case "down": + chord = curses.Down + case "left": + chord = curses.Left + case "right": + chord = curses.Right + case "enter", "return": + chord = curses.CtrlM + case "space": + chord = curses.AltZ + int(' ') + case "bspace", "bs": + chord = curses.BSpace + case "alt-bs", "alt-bspace": + chord = curses.AltBS + case "tab": + chord = curses.Tab + case "btab", "shift-tab": + chord = curses.BTab + case "esc": + chord = curses.ESC + case "del": + chord = curses.Del + case "home": + chord = curses.Home + case "end": + chord = curses.End + case "pgup", "page-up": + chord = curses.PgUp + case "pgdn", "page-down": + chord = curses.PgDn + case "shift-left": + chord = curses.SLeft + case "shift-right": + chord = curses.SRight + default: if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) { chord = curses.CtrlA + int(lkey[5]) - 'a' } else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) { @@ -336,7 +333,7 @@ func parseKeyChords(str string, message string, bind bool) []int { } } if chord > 0 { - chords = append(chords, chord) + chords[chord] = key } } return chords @@ -428,6 +425,13 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme var executeRegexp *regexp.Regexp +func firstKey(keymap map[int]string) int { + for k := range keymap { + return k + } + return 0 +} + func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort bool, str string) (map[int]actionType, map[int]string, bool) { if executeRegexp == nil { // Backreferences are not supported. @@ -451,11 +455,11 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b if len(pair) != 2 { fail() } - keys := parseKeyChords(pair[0], "key name required", true) + keys := parseKeyChords(pair[0], "key name required") if len(keys) != 1 { fail() } - key := keys[0] + key := firstKey(keys) act := strings.ToLower(pair[1]) switch act { case "ignore": @@ -551,11 +555,11 @@ func isExecuteAction(str string) bool { } func checkToggleSort(keymap map[int]actionType, str string) map[int]actionType { - keys := parseKeyChords(str, "key name required", true) + keys := parseKeyChords(str, "key name required") if len(keys) != 1 { errorExit("multiple keys specified") } - keymap[keys[0]] = actToggleSort + keymap[firstKey(keys)] = actToggleSort return keymap } @@ -600,7 +604,7 @@ func parseOptions(opts *Options, allArgs []string) { filter := nextString(allArgs, &i, "query string required") opts.Filter = &filter case "--expect": - opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required", false) + opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") case "--tiebreak": opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required")) case "--bind": @@ -717,7 +721,7 @@ func parseOptions(opts *Options, allArgs []string) { keymap = checkToggleSort(keymap, value) opts.ToggleSort = true } else if match, value := optString(arg, "--expect="); match { - opts.Expect = parseKeyChords(value, "key names required", false) + opts.Expect = parseKeyChords(value, "key names required") } else if match, value := optString(arg, "--tiebreak="); match { opts.Tiebreak = parseTiebreak(value) } else if match, value := optString(arg, "--color="); match { diff --git a/src/options_test.go b/src/options_test.go index dee1c0d..8e44585 100644 --- a/src/options_test.go +++ b/src/options_test.go @@ -72,77 +72,101 @@ func TestIrrelevantNth(t *testing.T) { } func TestParseKeys(t *testing.T) { - keys := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g", "", false) - check := func(key int, expected int) { - if key != expected { - t.Errorf("%d != %d", key, expected) + pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g", "") + check := func(i int, s string) { + if pairs[i] != s { + t.Errorf("%s != %s", pairs[i], s) } } - check(len(keys), 9) - check(keys[0], curses.CtrlZ) - check(keys[1], curses.AltZ) - check(keys[2], curses.F2) - check(keys[3], curses.AltZ+'@') - check(keys[4], curses.AltA) - check(keys[5], curses.AltZ+'!') - check(keys[6], curses.CtrlA+'g'-'a') - check(keys[7], curses.AltZ+'J') - check(keys[8], curses.AltZ+'g') + if len(pairs) != 9 { + t.Error(9) + } + check(curses.CtrlZ, "ctrl-z") + check(curses.AltZ, "alt-z") + check(curses.F2, "f2") + check(curses.AltZ+'@', "@") + check(curses.AltA, "Alt-a") + check(curses.AltZ+'!', "!") + check(curses.CtrlA+'g'-'a', "ctrl-G") + check(curses.AltZ+'J', "J") + check(curses.AltZ+'g', "g") // Synonyms - keys = parseKeyChords("enter,return,space,tab,btab,esc,up,down,left,right", "", true) - check(len(keys), 10) - check(keys[0], curses.CtrlM) - check(keys[1], curses.CtrlM) - check(keys[2], curses.AltZ+' ') - check(keys[3], curses.Tab) - check(keys[4], curses.BTab) - check(keys[5], curses.ESC) - check(keys[6], curses.Up) - check(keys[7], curses.Down) - check(keys[8], curses.Left) - check(keys[9], curses.Right) + pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "") + if len(pairs) != 9 { + t.Error(9) + } + check(curses.CtrlM, "Return") + check(curses.AltZ+' ', "space") + check(curses.Tab, "tab") + check(curses.BTab, "btab") + check(curses.ESC, "esc") + check(curses.Up, "up") + check(curses.Down, "down") + check(curses.Left, "left") + check(curses.Right, "right") + + pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "") + if len(pairs) != 11 { + t.Error(11) + } + check(curses.Tab, "Ctrl-I") + check(curses.PgUp, "page-up") + check(curses.PgDn, "Page-Down") + check(curses.Home, "Home") + check(curses.End, "End") + check(curses.AltBS, "Alt-BSpace") + check(curses.SLeft, "shift-left") + check(curses.SRight, "shift-right") + check(curses.BTab, "shift-tab") + check(curses.CtrlM, "Enter") + check(curses.BSpace, "bspace") } func TestParseKeysWithComma(t *testing.T) { - check := func(key int, expected int) { - if key != expected { - t.Errorf("%d != %d", key, expected) + checkN := func(a int, b int) { + if a != b { + t.Errorf("%d != %d", a, b) + } + } + check := func(pairs map[int]string, i int, s string) { + if pairs[i] != s { + t.Errorf("%s != %s", pairs[i], s) } } - keys := parseKeyChords(",", "", false) - check(len(keys), 1) - check(keys[0], curses.AltZ+',') + pairs := parseKeyChords(",", "") + checkN(len(pairs), 1) + check(pairs, curses.AltZ+',', ",") - keys = parseKeyChords(",,a,b", "", false) - check(len(keys), 3) - check(keys[0], curses.AltZ+'a') - check(keys[1], curses.AltZ+'b') - check(keys[2], curses.AltZ+',') + pairs = parseKeyChords(",,a,b", "") + checkN(len(pairs), 3) + check(pairs, curses.AltZ+'a', "a") + check(pairs, curses.AltZ+'b', "b") + check(pairs, curses.AltZ+',', ",") - keys = parseKeyChords("a,b,,", "", false) - check(len(keys), 3) - check(keys[0], curses.AltZ+'a') - check(keys[1], curses.AltZ+'b') - check(keys[2], curses.AltZ+',') + pairs = parseKeyChords("a,b,,", "") + checkN(len(pairs), 3) + check(pairs, curses.AltZ+'a', "a") + check(pairs, curses.AltZ+'b', "b") + check(pairs, curses.AltZ+',', ",") - keys = parseKeyChords("a,,,b", "", false) - check(len(keys), 3) - check(keys[0], curses.AltZ+'a') - check(keys[1], curses.AltZ+'b') - check(keys[2], curses.AltZ+',') + pairs = parseKeyChords("a,,,b", "") + checkN(len(pairs), 3) + check(pairs, curses.AltZ+'a', "a") + check(pairs, curses.AltZ+'b', "b") + check(pairs, curses.AltZ+',', ",") - keys = parseKeyChords("a,,,b,c", "", false) - check(len(keys), 4) - check(keys[0], curses.AltZ+'a') - check(keys[1], curses.AltZ+'b') - check(keys[2], curses.AltZ+'c') - check(keys[3], curses.AltZ+',') + pairs = parseKeyChords("a,,,b,c", "") + checkN(len(pairs), 4) + check(pairs, curses.AltZ+'a', "a") + check(pairs, curses.AltZ+'b', "b") + check(pairs, curses.AltZ+'c', "c") + check(pairs, curses.AltZ+',', ",") - keys = parseKeyChords(",,,", "", false) - check(len(keys), 1) - check(keys[0], curses.AltZ+',') + pairs = parseKeyChords(",,,", "") + checkN(len(pairs), 1) + check(pairs, curses.AltZ+',', ",") } func TestBind(t *testing.T) { diff --git a/src/terminal.go b/src/terminal.go index aca8aad..9c73197 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -33,10 +33,10 @@ type Terminal struct { multi bool sort bool toggleSort bool - expect []int + expect map[int]string keymap map[int]actionType execmap map[int]string - pressed int + pressed string printQuery bool history *History cycle bool @@ -193,7 +193,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { expect: opts.Expect, keymap: opts.Keymap, execmap: opts.Execmap, - pressed: 0, + pressed: "", printQuery: opts.PrintQuery, history: opts.History, cycle: opts.Cycle, @@ -257,17 +257,7 @@ func (t *Terminal) output() { fmt.Println(string(t.input)) } if len(t.expect) > 0 { - if t.pressed == 0 { - fmt.Println() - } else if util.Between(t.pressed, C.AltA, C.AltZ) { - fmt.Printf("alt-%c\n", t.pressed+'a'-C.AltA) - } else if util.Between(t.pressed, C.F1, C.F4) { - fmt.Printf("f%c\n", t.pressed+'1'-C.F1) - } else if util.Between(t.pressed, C.CtrlA, C.CtrlZ) { - fmt.Printf("ctrl-%c\n", t.pressed+'a'-C.CtrlA) - } else { - fmt.Printf("%c\n", t.pressed-C.AltZ) - } + fmt.Println(t.pressed) } if len(t.selected) == 0 { cnt := t.merger.Length() @@ -727,9 +717,9 @@ func (t *Terminal) Loop() { req(reqInfo) } } - for _, key := range t.expect { + for key, ret := range t.expect { if keyMatch(key, event) { - t.pressed = key + t.pressed = ret req(reqClose) break }