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.
This commit is contained in:
Junegunn Choi 2015-06-19 00:31:48 +09:00
parent 5e8d8dab82
commit a8b2c257cd
3 changed files with 142 additions and 124 deletions

View File

@ -117,7 +117,7 @@ type Options struct {
Exit0 bool Exit0 bool
Filter *string Filter *string
ToggleSort bool ToggleSort bool
Expect []int Expect map[int]string
Keymap map[int]actionType Keymap map[int]actionType
Execmap map[int]string Execmap map[int]string
PrintQuery bool PrintQuery bool
@ -159,7 +159,7 @@ func defaultOptions() *Options {
Exit0: false, Exit0: false,
Filter: nil, Filter: nil,
ToggleSort: false, ToggleSort: false,
Expect: []int{}, Expect: make(map[int]string),
Keymap: defaultKeymap(), Keymap: defaultKeymap(),
Execmap: make(map[int]string), Execmap: make(map[int]string),
PrintQuery: false, PrintQuery: false,
@ -265,7 +265,7 @@ func isAlphabet(char uint8) bool {
return char >= 'a' && char <= 'z' 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 { if len(str) == 0 {
errorExit(message) errorExit(message)
} }
@ -275,54 +275,51 @@ func parseKeyChords(str string, message string, bind bool) []int {
tokens = append(tokens, ",") tokens = append(tokens, ",")
} }
var chords []int chords := make(map[int]string)
for _, key := range tokens { for _, key := range tokens {
if len(key) == 0 { if len(key) == 0 {
continue // ignore continue // ignore
} }
lkey := strings.ToLower(key) lkey := strings.ToLower(key)
chord := 0 chord := 0
if bind { switch lkey {
switch lkey { case "up":
case "up": chord = curses.Up
chord = curses.Up case "down":
case "down": chord = curses.Down
chord = curses.Down case "left":
case "left": chord = curses.Left
chord = curses.Left case "right":
case "right": chord = curses.Right
chord = curses.Right case "enter", "return":
case "enter", "return": chord = curses.CtrlM
chord = curses.CtrlM case "space":
case "space": chord = curses.AltZ + int(' ')
chord = curses.AltZ + int(' ') case "bspace", "bs":
case "bspace": chord = curses.BSpace
chord = curses.BSpace case "alt-bs", "alt-bspace":
case "alt-bs", "alt-bspace": chord = curses.AltBS
chord = curses.AltBS case "tab":
case "tab": chord = curses.Tab
chord = curses.Tab case "btab", "shift-tab":
case "btab": chord = curses.BTab
chord = curses.BTab case "esc":
case "esc": chord = curses.ESC
chord = curses.ESC case "del":
case "del": chord = curses.Del
chord = curses.Del case "home":
case "home": chord = curses.Home
chord = curses.Home case "end":
case "end": chord = curses.End
chord = curses.End case "pgup", "page-up":
case "pgup", "page-up": chord = curses.PgUp
chord = curses.PgUp case "pgdn", "page-down":
case "pgdn", "page-down": chord = curses.PgDn
chord = curses.PgDn case "shift-left":
case "shift-left": chord = curses.SLeft
chord = curses.SLeft case "shift-right":
case "shift-right": chord = curses.SRight
chord = curses.SRight default:
}
}
if chord == 0 {
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) { if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chord = curses.CtrlA + int(lkey[5]) - 'a' chord = curses.CtrlA + int(lkey[5]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) { } 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 { if chord > 0 {
chords = append(chords, chord) chords[chord] = key
} }
} }
return chords return chords
@ -428,6 +425,13 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme
var executeRegexp *regexp.Regexp 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) { func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort bool, str string) (map[int]actionType, map[int]string, bool) {
if executeRegexp == nil { if executeRegexp == nil {
// Backreferences are not supported. // Backreferences are not supported.
@ -451,11 +455,11 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
if len(pair) != 2 { if len(pair) != 2 {
fail() fail()
} }
keys := parseKeyChords(pair[0], "key name required", true) keys := parseKeyChords(pair[0], "key name required")
if len(keys) != 1 { if len(keys) != 1 {
fail() fail()
} }
key := keys[0] key := firstKey(keys)
act := strings.ToLower(pair[1]) act := strings.ToLower(pair[1])
switch act { switch act {
case "ignore": case "ignore":
@ -551,11 +555,11 @@ func isExecuteAction(str string) bool {
} }
func checkToggleSort(keymap map[int]actionType, str string) map[int]actionType { 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 { if len(keys) != 1 {
errorExit("multiple keys specified") errorExit("multiple keys specified")
} }
keymap[keys[0]] = actToggleSort keymap[firstKey(keys)] = actToggleSort
return keymap return keymap
} }
@ -600,7 +604,7 @@ func parseOptions(opts *Options, allArgs []string) {
filter := nextString(allArgs, &i, "query string required") filter := nextString(allArgs, &i, "query string required")
opts.Filter = &filter opts.Filter = &filter
case "--expect": 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": case "--tiebreak":
opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required")) opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
case "--bind": case "--bind":
@ -717,7 +721,7 @@ func parseOptions(opts *Options, allArgs []string) {
keymap = checkToggleSort(keymap, value) keymap = checkToggleSort(keymap, value)
opts.ToggleSort = true opts.ToggleSort = true
} else if match, value := optString(arg, "--expect="); match { } 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 { } else if match, value := optString(arg, "--tiebreak="); match {
opts.Tiebreak = parseTiebreak(value) opts.Tiebreak = parseTiebreak(value)
} else if match, value := optString(arg, "--color="); match { } else if match, value := optString(arg, "--color="); match {

View File

@ -72,77 +72,101 @@ func TestIrrelevantNth(t *testing.T) {
} }
func TestParseKeys(t *testing.T) { func TestParseKeys(t *testing.T) {
keys := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g", "", false) pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g", "")
check := func(key int, expected int) { check := func(i int, s string) {
if key != expected { if pairs[i] != s {
t.Errorf("%d != %d", key, expected) t.Errorf("%s != %s", pairs[i], s)
} }
} }
check(len(keys), 9) if len(pairs) != 9 {
check(keys[0], curses.CtrlZ) t.Error(9)
check(keys[1], curses.AltZ) }
check(keys[2], curses.F2) check(curses.CtrlZ, "ctrl-z")
check(keys[3], curses.AltZ+'@') check(curses.AltZ, "alt-z")
check(keys[4], curses.AltA) check(curses.F2, "f2")
check(keys[5], curses.AltZ+'!') check(curses.AltZ+'@', "@")
check(keys[6], curses.CtrlA+'g'-'a') check(curses.AltA, "Alt-a")
check(keys[7], curses.AltZ+'J') check(curses.AltZ+'!', "!")
check(keys[8], curses.AltZ+'g') check(curses.CtrlA+'g'-'a', "ctrl-G")
check(curses.AltZ+'J', "J")
check(curses.AltZ+'g', "g")
// Synonyms // Synonyms
keys = parseKeyChords("enter,return,space,tab,btab,esc,up,down,left,right", "", true) pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
check(len(keys), 10) if len(pairs) != 9 {
check(keys[0], curses.CtrlM) t.Error(9)
check(keys[1], curses.CtrlM) }
check(keys[2], curses.AltZ+' ') check(curses.CtrlM, "Return")
check(keys[3], curses.Tab) check(curses.AltZ+' ', "space")
check(keys[4], curses.BTab) check(curses.Tab, "tab")
check(keys[5], curses.ESC) check(curses.BTab, "btab")
check(keys[6], curses.Up) check(curses.ESC, "esc")
check(keys[7], curses.Down) check(curses.Up, "up")
check(keys[8], curses.Left) check(curses.Down, "down")
check(keys[9], curses.Right) 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) { func TestParseKeysWithComma(t *testing.T) {
check := func(key int, expected int) { checkN := func(a int, b int) {
if key != expected { if a != b {
t.Errorf("%d != %d", key, expected) 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) pairs := parseKeyChords(",", "")
check(len(keys), 1) checkN(len(pairs), 1)
check(keys[0], curses.AltZ+',') check(pairs, curses.AltZ+',', ",")
keys = parseKeyChords(",,a,b", "", false) pairs = parseKeyChords(",,a,b", "")
check(len(keys), 3) checkN(len(pairs), 3)
check(keys[0], curses.AltZ+'a') check(pairs, curses.AltZ+'a', "a")
check(keys[1], curses.AltZ+'b') check(pairs, curses.AltZ+'b', "b")
check(keys[2], curses.AltZ+',') check(pairs, curses.AltZ+',', ",")
keys = parseKeyChords("a,b,,", "", false) pairs = parseKeyChords("a,b,,", "")
check(len(keys), 3) checkN(len(pairs), 3)
check(keys[0], curses.AltZ+'a') check(pairs, curses.AltZ+'a', "a")
check(keys[1], curses.AltZ+'b') check(pairs, curses.AltZ+'b', "b")
check(keys[2], curses.AltZ+',') check(pairs, curses.AltZ+',', ",")
keys = parseKeyChords("a,,,b", "", false) pairs = parseKeyChords("a,,,b", "")
check(len(keys), 3) checkN(len(pairs), 3)
check(keys[0], curses.AltZ+'a') check(pairs, curses.AltZ+'a', "a")
check(keys[1], curses.AltZ+'b') check(pairs, curses.AltZ+'b', "b")
check(keys[2], curses.AltZ+',') check(pairs, curses.AltZ+',', ",")
keys = parseKeyChords("a,,,b,c", "", false) pairs = parseKeyChords("a,,,b,c", "")
check(len(keys), 4) checkN(len(pairs), 4)
check(keys[0], curses.AltZ+'a') check(pairs, curses.AltZ+'a', "a")
check(keys[1], curses.AltZ+'b') check(pairs, curses.AltZ+'b', "b")
check(keys[2], curses.AltZ+'c') check(pairs, curses.AltZ+'c', "c")
check(keys[3], curses.AltZ+',') check(pairs, curses.AltZ+',', ",")
keys = parseKeyChords(",,,", "", false) pairs = parseKeyChords(",,,", "")
check(len(keys), 1) checkN(len(pairs), 1)
check(keys[0], curses.AltZ+',') check(pairs, curses.AltZ+',', ",")
} }
func TestBind(t *testing.T) { func TestBind(t *testing.T) {

View File

@ -33,10 +33,10 @@ type Terminal struct {
multi bool multi bool
sort bool sort bool
toggleSort bool toggleSort bool
expect []int expect map[int]string
keymap map[int]actionType keymap map[int]actionType
execmap map[int]string execmap map[int]string
pressed int pressed string
printQuery bool printQuery bool
history *History history *History
cycle bool cycle bool
@ -193,7 +193,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
expect: opts.Expect, expect: opts.Expect,
keymap: opts.Keymap, keymap: opts.Keymap,
execmap: opts.Execmap, execmap: opts.Execmap,
pressed: 0, pressed: "",
printQuery: opts.PrintQuery, printQuery: opts.PrintQuery,
history: opts.History, history: opts.History,
cycle: opts.Cycle, cycle: opts.Cycle,
@ -257,17 +257,7 @@ func (t *Terminal) output() {
fmt.Println(string(t.input)) fmt.Println(string(t.input))
} }
if len(t.expect) > 0 { if len(t.expect) > 0 {
if t.pressed == 0 { fmt.Println(t.pressed)
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)
}
} }
if len(t.selected) == 0 { if len(t.selected) == 0 {
cnt := t.merger.Length() cnt := t.merger.Length()
@ -727,9 +717,9 @@ func (t *Terminal) Loop() {
req(reqInfo) req(reqInfo)
} }
} }
for _, key := range t.expect { for key, ret := range t.expect {
if keyMatch(key, event) { if keyMatch(key, event) {
t.pressed = key t.pressed = ret
req(reqClose) req(reqClose)
break break
} }