mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-01 04:32:05 -07:00
Extend support for alt key chords
"alt-" with any case-sensitive character is allowed
This commit is contained in:
@@ -26,6 +26,10 @@ CHANGELOG
|
|||||||
- Added `last` action to move the cursor to the last match
|
- Added `last` action to move the cursor to the last match
|
||||||
- The opposite action `top` is renamed to `first`, but `top` is still
|
- The opposite action `top` is renamed to `first`, but `top` is still
|
||||||
recognized as a synonym for backward compatibility
|
recognized as a synonym for backward compatibility
|
||||||
|
- Extended support for alt key chords: alt with any case-sensitive single character
|
||||||
|
```sh
|
||||||
|
fzf --bind alt-,:first,alt-.:last
|
||||||
|
```
|
||||||
|
|
||||||
0.24.4
|
0.24.4
|
||||||
------
|
------
|
||||||
|
@@ -666,9 +666,7 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIctrl-alt-[a-z]\fR
|
\fIctrl-alt-[a-z]\fR
|
||||||
.br
|
.br
|
||||||
\fIalt-[a-z]\fR
|
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
|
||||||
.br
|
|
||||||
\fIalt-[0-9]\fR
|
|
||||||
.br
|
.br
|
||||||
\fIf[1-12]\fR
|
\fIf[1-12]\fR
|
||||||
.br
|
.br
|
||||||
@@ -692,8 +690,6 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||||
.br
|
.br
|
||||||
\fIalt-/\fR
|
|
||||||
.br
|
|
||||||
\fItab\fR
|
\fItab\fR
|
||||||
.br
|
.br
|
||||||
\fIbtab\fR (\fIshift-tab\fR)
|
\fIbtab\fR (\fIshift-tab\fR)
|
||||||
|
162
src/options.go
162
src/options.go
@@ -7,7 +7,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
@@ -211,8 +210,8 @@ type Options struct {
|
|||||||
Exit0 bool
|
Exit0 bool
|
||||||
Filter *string
|
Filter *string
|
||||||
ToggleSort bool
|
ToggleSort bool
|
||||||
Expect map[int]string
|
Expect map[tui.Event]string
|
||||||
Keymap map[int][]action
|
Keymap map[tui.Event][]action
|
||||||
Preview previewOpts
|
Preview previewOpts
|
||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
@@ -272,8 +271,8 @@ func defaultOptions() *Options {
|
|||||||
Exit0: false,
|
Exit0: false,
|
||||||
Filter: nil,
|
Filter: nil,
|
||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[int]string),
|
Expect: make(map[tui.Event]string),
|
||||||
Keymap: make(map[int][]action),
|
Keymap: make(map[tui.Event][]action),
|
||||||
Preview: defaultPreviewOpts(""),
|
Preview: defaultPreviewOpts(""),
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
@@ -445,7 +444,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
return tui.BorderNone
|
return tui.BorderNone
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeyChords(str string, message string) map[int]string {
|
func parseKeyChords(str string, message string) map[tui.Event]string {
|
||||||
if len(str) == 0 {
|
if len(str) == 0 {
|
||||||
errorExit(message)
|
errorExit(message)
|
||||||
}
|
}
|
||||||
@@ -455,124 +454,129 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
tokens = append(tokens, ",")
|
tokens = append(tokens, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
chords := make(map[int]string)
|
chords := make(map[tui.Event]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
|
add := func(e tui.EventType) {
|
||||||
|
chords[e.AsEvent()] = key
|
||||||
|
}
|
||||||
switch lkey {
|
switch lkey {
|
||||||
case "up":
|
case "up":
|
||||||
chord = tui.Up
|
add(tui.Up)
|
||||||
case "down":
|
case "down":
|
||||||
chord = tui.Down
|
add(tui.Down)
|
||||||
case "left":
|
case "left":
|
||||||
chord = tui.Left
|
add(tui.Left)
|
||||||
case "right":
|
case "right":
|
||||||
chord = tui.Right
|
add(tui.Right)
|
||||||
case "enter", "return":
|
case "enter", "return":
|
||||||
chord = tui.CtrlM
|
add(tui.CtrlM)
|
||||||
case "space":
|
case "space":
|
||||||
chord = tui.AltZ + int(' ')
|
chords[tui.Key(' ')] = key
|
||||||
case "bspace", "bs":
|
case "bspace", "bs":
|
||||||
chord = tui.BSpace
|
add(tui.BSpace)
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
chord = tui.CtrlSpace
|
add(tui.CtrlSpace)
|
||||||
case "ctrl-^", "ctrl-6":
|
case "ctrl-^", "ctrl-6":
|
||||||
chord = tui.CtrlCaret
|
add(tui.CtrlCaret)
|
||||||
case "ctrl-/", "ctrl-_":
|
case "ctrl-/", "ctrl-_":
|
||||||
chord = tui.CtrlSlash
|
add(tui.CtrlSlash)
|
||||||
case "ctrl-\\":
|
case "ctrl-\\":
|
||||||
chord = tui.CtrlBackSlash
|
add(tui.CtrlBackSlash)
|
||||||
case "ctrl-]":
|
case "ctrl-]":
|
||||||
chord = tui.CtrlRightBracket
|
add(tui.CtrlRightBracket)
|
||||||
case "change":
|
case "change":
|
||||||
chord = tui.Change
|
add(tui.Change)
|
||||||
case "backward-eof":
|
case "backward-eof":
|
||||||
chord = tui.BackwardEOF
|
add(tui.BackwardEOF)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chord = tui.CtrlAltM
|
chords[tui.CtrlAltKey('m')] = key
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
chord = tui.AltSpace
|
chords[tui.AltKey(' ')] = key
|
||||||
case "alt-/":
|
|
||||||
chord = tui.AltSlash
|
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace":
|
||||||
chord = tui.AltBS
|
add(tui.AltBS)
|
||||||
case "alt-up":
|
case "alt-up":
|
||||||
chord = tui.AltUp
|
add(tui.AltUp)
|
||||||
case "alt-down":
|
case "alt-down":
|
||||||
chord = tui.AltDown
|
add(tui.AltDown)
|
||||||
case "alt-left":
|
case "alt-left":
|
||||||
chord = tui.AltLeft
|
add(tui.AltLeft)
|
||||||
case "alt-right":
|
case "alt-right":
|
||||||
chord = tui.AltRight
|
add(tui.AltRight)
|
||||||
case "tab":
|
case "tab":
|
||||||
chord = tui.Tab
|
add(tui.Tab)
|
||||||
case "btab", "shift-tab":
|
case "btab", "shift-tab":
|
||||||
chord = tui.BTab
|
add(tui.BTab)
|
||||||
case "esc":
|
case "esc":
|
||||||
chord = tui.ESC
|
add(tui.ESC)
|
||||||
case "del":
|
case "del":
|
||||||
chord = tui.Del
|
add(tui.Del)
|
||||||
case "home":
|
case "home":
|
||||||
chord = tui.Home
|
add(tui.Home)
|
||||||
case "end":
|
case "end":
|
||||||
chord = tui.End
|
add(tui.End)
|
||||||
case "insert":
|
case "insert":
|
||||||
chord = tui.Insert
|
add(tui.Insert)
|
||||||
case "pgup", "page-up":
|
case "pgup", "page-up":
|
||||||
chord = tui.PgUp
|
add(tui.PgUp)
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
chord = tui.PgDn
|
add(tui.PgDn)
|
||||||
case "alt-shift-up", "shift-alt-up":
|
case "alt-shift-up", "shift-alt-up":
|
||||||
chord = tui.AltSUp
|
add(tui.AltSUp)
|
||||||
case "alt-shift-down", "shift-alt-down":
|
case "alt-shift-down", "shift-alt-down":
|
||||||
chord = tui.AltSDown
|
add(tui.AltSDown)
|
||||||
case "alt-shift-left", "shift-alt-left":
|
case "alt-shift-left", "shift-alt-left":
|
||||||
chord = tui.AltSLeft
|
add(tui.AltSLeft)
|
||||||
case "alt-shift-right", "shift-alt-right":
|
case "alt-shift-right", "shift-alt-right":
|
||||||
chord = tui.AltSRight
|
add(tui.AltSRight)
|
||||||
case "shift-up":
|
case "shift-up":
|
||||||
chord = tui.SUp
|
add(tui.SUp)
|
||||||
case "shift-down":
|
case "shift-down":
|
||||||
chord = tui.SDown
|
add(tui.SDown)
|
||||||
case "shift-left":
|
case "shift-left":
|
||||||
chord = tui.SLeft
|
add(tui.SLeft)
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
chord = tui.SRight
|
add(tui.SRight)
|
||||||
case "left-click":
|
case "left-click":
|
||||||
chord = tui.LeftClick
|
add(tui.LeftClick)
|
||||||
case "right-click":
|
case "right-click":
|
||||||
chord = tui.RightClick
|
add(tui.RightClick)
|
||||||
case "double-click":
|
case "double-click":
|
||||||
chord = tui.DoubleClick
|
add(tui.DoubleClick)
|
||||||
case "f10":
|
case "f10":
|
||||||
chord = tui.F10
|
add(tui.F10)
|
||||||
case "f11":
|
case "f11":
|
||||||
chord = tui.F11
|
add(tui.F11)
|
||||||
case "f12":
|
case "f12":
|
||||||
chord = tui.F12
|
add(tui.F12)
|
||||||
default:
|
default:
|
||||||
|
runes := []rune(key)
|
||||||
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
||||||
chord = tui.CtrlAltA + int(lkey[9]) - 'a'
|
chords[tui.CtrlAltKey(rune(key[9]))] = key
|
||||||
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||||
chord = tui.CtrlA + int(lkey[5]) - 'a'
|
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
|
||||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
|
||||||
chord = tui.AltA + int(lkey[4]) - 'a'
|
r := runes[4]
|
||||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) {
|
switch r {
|
||||||
chord = tui.Alt0 + int(lkey[4]) - '0'
|
case escapedColon:
|
||||||
|
r = ':'
|
||||||
|
case escapedComma:
|
||||||
|
r = ','
|
||||||
|
case escapedPlus:
|
||||||
|
r = '+'
|
||||||
|
}
|
||||||
|
chords[tui.AltKey(r)] = key
|
||||||
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
||||||
chord = tui.F1 + int(key[1]) - '1'
|
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
|
||||||
} else if utf8.RuneCountInString(key) == 1 {
|
} else if len(runes) == 1 {
|
||||||
chord = tui.AltZ + int([]rune(key)[0])
|
chords[tui.Key(runes[0])] = key
|
||||||
} else {
|
} else {
|
||||||
errorExit("unsupported key: " + key)
|
errorExit("unsupported key: " + key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if chord > 0 {
|
|
||||||
chords[chord] = key
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return chords
|
return chords
|
||||||
}
|
}
|
||||||
@@ -720,11 +724,11 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
|
|
||||||
var executeRegexp *regexp.Regexp
|
var executeRegexp *regexp.Regexp
|
||||||
|
|
||||||
func firstKey(keymap map[int]string) int {
|
func firstKey(keymap map[tui.Event]string) tui.Event {
|
||||||
for k := range keymap {
|
for k := range keymap {
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
return 0
|
return tui.EventType(0).AsEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -740,7 +744,7 @@ func init() {
|
|||||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
symbol := ":"
|
symbol := ":"
|
||||||
if strings.HasPrefix(src, "+") {
|
if strings.HasPrefix(src, "+") {
|
||||||
@@ -776,13 +780,13 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
if len(pair) < 2 {
|
if len(pair) < 2 {
|
||||||
errorExit("bind action not specified: " + origPairStr)
|
errorExit("bind action not specified: " + origPairStr)
|
||||||
}
|
}
|
||||||
var key int
|
var key tui.Event
|
||||||
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
||||||
key = ':' + tui.AltZ
|
key = tui.Key(':')
|
||||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
||||||
key = ',' + tui.AltZ
|
key = tui.Key(',')
|
||||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
||||||
key = '+' + tui.AltZ
|
key = tui.Key('+')
|
||||||
} else {
|
} else {
|
||||||
keys := parseKeyChords(pair[0], "key name required")
|
keys := parseKeyChords(pair[0], "key name required")
|
||||||
key = firstKey(keys)
|
key = firstKey(keys)
|
||||||
@@ -981,7 +985,7 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToggleSort(keymap map[int][]action, str string) {
|
func parseToggleSort(keymap map[tui.Event][]action, str string) {
|
||||||
keys := parseKeyChords(str, "key name required")
|
keys := parseKeyChords(str, "key name required")
|
||||||
if len(keys) != 1 {
|
if len(keys) != 1 {
|
||||||
errorExit("multiple keys specified")
|
errorExit("multiple keys specified")
|
||||||
@@ -1188,7 +1192,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Expect[k] = v
|
opts.Expect[k] = v
|
||||||
}
|
}
|
||||||
case "--no-expect":
|
case "--no-expect":
|
||||||
opts.Expect = make(map[int]string)
|
opts.Expect = make(map[tui.Event]string)
|
||||||
case "--no-phony":
|
case "--no-phony":
|
||||||
opts.Phony = false
|
opts.Phony = false
|
||||||
case "--phony":
|
case "--phony":
|
||||||
@@ -1512,11 +1516,11 @@ func postProcessOptions(opts *Options) {
|
|||||||
}
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
|
||||||
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
|
opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
|
||||||
}
|
}
|
||||||
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
|
||||||
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
|
opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -125,26 +125,29 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||||
check := func(i int, s string) {
|
checkEvent := func(e tui.Event, s string) {
|
||||||
if pairs[i] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[i], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
check := func(et tui.EventType, s string) {
|
||||||
|
checkEvent(et.AsEvent(), s)
|
||||||
|
}
|
||||||
if len(pairs) != 12 {
|
if len(pairs) != 12 {
|
||||||
t.Error(12)
|
t.Error(12)
|
||||||
}
|
}
|
||||||
check(tui.CtrlZ, "ctrl-z")
|
check(tui.CtrlZ, "ctrl-z")
|
||||||
check(tui.AltZ, "alt-z")
|
|
||||||
check(tui.F2, "f2")
|
check(tui.F2, "f2")
|
||||||
check(tui.AltZ+'@', "@")
|
check(tui.CtrlG, "ctrl-G")
|
||||||
check(tui.AltA, "Alt-a")
|
checkEvent(tui.AltKey('z'), "alt-z")
|
||||||
check(tui.AltZ+'!', "!")
|
checkEvent(tui.Key('@'), "@")
|
||||||
check(tui.CtrlA+'g'-'a', "ctrl-G")
|
checkEvent(tui.AltKey('a'), "Alt-a")
|
||||||
check(tui.AltZ+'J', "J")
|
checkEvent(tui.Key('!'), "!")
|
||||||
check(tui.AltZ+'g', "g")
|
checkEvent(tui.Key('J'), "J")
|
||||||
check(tui.CtrlAltA, "ctrl-alt-a")
|
checkEvent(tui.Key('g'), "g")
|
||||||
check(tui.CtrlAltM, "ALT-enter")
|
checkEvent(tui.CtrlAltKey('a'), "ctrl-alt-a")
|
||||||
check(tui.AltSpace, "alt-SPACE")
|
checkEvent(tui.CtrlAltKey('m'), "ALT-enter")
|
||||||
|
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
@@ -152,7 +155,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
check(tui.CtrlM, "Return")
|
check(tui.CtrlM, "Return")
|
||||||
check(tui.AltZ+' ', "space")
|
checkEvent(tui.Key(' '), "space")
|
||||||
check(tui.Tab, "tab")
|
check(tui.Tab, "tab")
|
||||||
check(tui.BTab, "btab")
|
check(tui.BTab, "btab")
|
||||||
check(tui.ESC, "esc")
|
check(tui.ESC, "esc")
|
||||||
@@ -184,63 +187,64 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
t.Errorf("%d != %d", a, b)
|
t.Errorf("%d != %d", a, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check := func(pairs map[int]string, i int, s string) {
|
check := func(pairs map[tui.Event]string, e tui.Event, s string) {
|
||||||
if pairs[i] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[i], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs := parseKeyChords(",", "")
|
pairs := parseKeyChords(",", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,a,b", "")
|
pairs = parseKeyChords(",,a,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,b,,", "")
|
pairs = parseKeyChords("a,b,,", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b", "")
|
pairs = parseKeyChords("a,,,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b,c", "")
|
pairs = parseKeyChords("a,,,b,c", "")
|
||||||
checkN(len(pairs), 4)
|
checkN(len(pairs), 4)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+'c', "c")
|
check(pairs, tui.Key('c'), "c")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,,", "")
|
pairs = parseKeyChords(",,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBind(t *testing.T) {
|
func TestBind(t *testing.T) {
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
check := func(keyName int, arg1 string, types ...actionType) {
|
check := func(event tui.Event, arg1 string, types ...actionType) {
|
||||||
if len(keymap[keyName]) != len(types) {
|
if len(keymap[event]) != len(types) {
|
||||||
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
|
t.Errorf("invalid number of actions for %v (%d != %d)",
|
||||||
|
event, len(types), len(keymap[event]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for idx, action := range keymap[keyName] {
|
for idx, action := range keymap[event] {
|
||||||
if types[idx] != action.t {
|
if types[idx] != action.t {
|
||||||
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
|
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
|
if len(arg1) > 0 && keymap[event][0].a != arg1 {
|
||||||
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
|
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[event][0].a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check(tui.CtrlA, "", actBeginningOfLine)
|
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
@@ -248,29 +252,29 @@ func TestBind(t *testing.T) {
|
|||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
",f1:+first,f1:+top"+
|
",f1:+first,f1:+top"+
|
||||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
check(tui.CtrlA, "", actKillLine)
|
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
||||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
||||||
check(tui.AltZ+'c', "", actPageUp)
|
check(tui.Key('c'), "", actPageUp)
|
||||||
check(tui.AltZ+',', "", actAbort)
|
check(tui.Key(','), "", actAbort)
|
||||||
check(tui.AltZ+':', "", actAccept)
|
check(tui.Key(':'), "", actAccept)
|
||||||
check(tui.AltZ, "", actPageDown)
|
check(tui.AltKey('z'), "", actPageDown)
|
||||||
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)
|
check(tui.F1.AsEvent(), "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)
|
||||||
check(tui.F2, "echo {}, {}, {}", actExecute)
|
check(tui.F2.AsEvent(), "echo {}, {}, {}", actExecute)
|
||||||
check(tui.F3, "echo '({})'", actExecute)
|
check(tui.F3.AsEvent(), "echo '({})'", actExecute)
|
||||||
check(tui.F4, "less {}", actExecute)
|
check(tui.F4.AsEvent(), "less {}", actExecute)
|
||||||
check(tui.AltZ+'x', "foo+bar", actExecute)
|
check(tui.Key('x'), "foo+bar", actExecute)
|
||||||
check(tui.AltZ+'X', "bar+baz", actExecute)
|
check(tui.Key('X'), "bar+baz", actExecute)
|
||||||
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
check(tui.AltKey('a'), "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
||||||
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
|
check(tui.AltKey('b'), "echo (,),[,],/,:,@,%,{}", actExecute)
|
||||||
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
|
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||||
|
|
||||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||||
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||||
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeymap(keymap, "f1:abort")
|
parseKeymap(keymap, "f1:abort")
|
||||||
check(tui.F1, "", actAbort)
|
check(tui.F1.AsEvent(), "", actAbort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorSpec(t *testing.T) {
|
func TestColorSpec(t *testing.T) {
|
||||||
@@ -314,11 +318,12 @@ func TestColorSpec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultCtrlNP(t *testing.T) {
|
func TestDefaultCtrlNP(t *testing.T) {
|
||||||
check := func(words []string, key int, expected actionType) {
|
check := func(words []string, et tui.EventType, expected actionType) {
|
||||||
|
e := et.AsEvent()
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if opts.Keymap[key][0].t != expected {
|
if opts.Keymap[e][0].t != expected {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
167
src/terminal.go
167
src/terminal.go
@@ -109,8 +109,8 @@ type Terminal struct {
|
|||||||
sort bool
|
sort bool
|
||||||
toggleSort bool
|
toggleSort bool
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
expect map[int]string
|
expect map[tui.Event]string
|
||||||
keymap map[int][]action
|
keymap map[tui.Event][]action
|
||||||
pressed string
|
pressed string
|
||||||
printQuery bool
|
printQuery bool
|
||||||
history *History
|
history *History
|
||||||
@@ -304,62 +304,68 @@ func toActions(types ...actionType) []action {
|
|||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultKeymap() map[int][]action {
|
func defaultKeymap() map[tui.Event][]action {
|
||||||
keymap := make(map[int][]action)
|
keymap := make(map[tui.Event][]action)
|
||||||
keymap[tui.Invalid] = toActions(actInvalid)
|
add := func(e tui.EventType, a actionType) {
|
||||||
keymap[tui.Resize] = toActions(actClearScreen)
|
keymap[e.AsEvent()] = toActions(a)
|
||||||
keymap[tui.CtrlA] = toActions(actBeginningOfLine)
|
}
|
||||||
keymap[tui.CtrlB] = toActions(actBackwardChar)
|
addEvent := func(e tui.Event, a actionType) {
|
||||||
keymap[tui.CtrlC] = toActions(actAbort)
|
keymap[e] = toActions(a)
|
||||||
keymap[tui.CtrlG] = toActions(actAbort)
|
|
||||||
keymap[tui.CtrlQ] = toActions(actAbort)
|
|
||||||
keymap[tui.ESC] = toActions(actAbort)
|
|
||||||
keymap[tui.CtrlD] = toActions(actDeleteCharEOF)
|
|
||||||
keymap[tui.CtrlE] = toActions(actEndOfLine)
|
|
||||||
keymap[tui.CtrlF] = toActions(actForwardChar)
|
|
||||||
keymap[tui.CtrlH] = toActions(actBackwardDeleteChar)
|
|
||||||
keymap[tui.BSpace] = toActions(actBackwardDeleteChar)
|
|
||||||
keymap[tui.Tab] = toActions(actToggleDown)
|
|
||||||
keymap[tui.BTab] = toActions(actToggleUp)
|
|
||||||
keymap[tui.CtrlJ] = toActions(actDown)
|
|
||||||
keymap[tui.CtrlK] = toActions(actUp)
|
|
||||||
keymap[tui.CtrlL] = toActions(actClearScreen)
|
|
||||||
keymap[tui.CtrlM] = toActions(actAccept)
|
|
||||||
keymap[tui.CtrlN] = toActions(actDown)
|
|
||||||
keymap[tui.CtrlP] = toActions(actUp)
|
|
||||||
keymap[tui.CtrlU] = toActions(actUnixLineDiscard)
|
|
||||||
keymap[tui.CtrlW] = toActions(actUnixWordRubout)
|
|
||||||
keymap[tui.CtrlY] = toActions(actYank)
|
|
||||||
if !util.IsWindows() {
|
|
||||||
keymap[tui.CtrlZ] = toActions(actSigStop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keymap[tui.AltB] = toActions(actBackwardWord)
|
add(tui.Invalid, actInvalid)
|
||||||
keymap[tui.SLeft] = toActions(actBackwardWord)
|
add(tui.Resize, actClearScreen)
|
||||||
keymap[tui.AltF] = toActions(actForwardWord)
|
add(tui.CtrlA, actBeginningOfLine)
|
||||||
keymap[tui.SRight] = toActions(actForwardWord)
|
add(tui.CtrlB, actBackwardChar)
|
||||||
keymap[tui.AltD] = toActions(actKillWord)
|
add(tui.CtrlC, actAbort)
|
||||||
keymap[tui.AltBS] = toActions(actBackwardKillWord)
|
add(tui.CtrlG, actAbort)
|
||||||
|
add(tui.CtrlQ, actAbort)
|
||||||
|
add(tui.ESC, actAbort)
|
||||||
|
add(tui.CtrlD, actDeleteCharEOF)
|
||||||
|
add(tui.CtrlE, actEndOfLine)
|
||||||
|
add(tui.CtrlF, actForwardChar)
|
||||||
|
add(tui.CtrlH, actBackwardDeleteChar)
|
||||||
|
add(tui.BSpace, actBackwardDeleteChar)
|
||||||
|
add(tui.Tab, actToggleDown)
|
||||||
|
add(tui.BTab, actToggleUp)
|
||||||
|
add(tui.CtrlJ, actDown)
|
||||||
|
add(tui.CtrlK, actUp)
|
||||||
|
add(tui.CtrlL, actClearScreen)
|
||||||
|
add(tui.CtrlM, actAccept)
|
||||||
|
add(tui.CtrlN, actDown)
|
||||||
|
add(tui.CtrlP, actUp)
|
||||||
|
add(tui.CtrlU, actUnixLineDiscard)
|
||||||
|
add(tui.CtrlW, actUnixWordRubout)
|
||||||
|
add(tui.CtrlY, actYank)
|
||||||
|
if !util.IsWindows() {
|
||||||
|
add(tui.CtrlZ, actSigStop)
|
||||||
|
}
|
||||||
|
|
||||||
keymap[tui.Up] = toActions(actUp)
|
addEvent(tui.AltKey('b'), actBackwardWord)
|
||||||
keymap[tui.Down] = toActions(actDown)
|
add(tui.SLeft, actBackwardWord)
|
||||||
keymap[tui.Left] = toActions(actBackwardChar)
|
addEvent(tui.AltKey('f'), actForwardWord)
|
||||||
keymap[tui.Right] = toActions(actForwardChar)
|
add(tui.SRight, actForwardWord)
|
||||||
|
addEvent(tui.AltKey('d'), actKillWord)
|
||||||
|
add(tui.AltBS, actBackwardKillWord)
|
||||||
|
|
||||||
keymap[tui.Home] = toActions(actBeginningOfLine)
|
add(tui.Up, actUp)
|
||||||
keymap[tui.End] = toActions(actEndOfLine)
|
add(tui.Down, actDown)
|
||||||
keymap[tui.Del] = toActions(actDeleteChar)
|
add(tui.Left, actBackwardChar)
|
||||||
keymap[tui.PgUp] = toActions(actPageUp)
|
add(tui.Right, actForwardChar)
|
||||||
keymap[tui.PgDn] = toActions(actPageDown)
|
|
||||||
|
|
||||||
keymap[tui.SUp] = toActions(actPreviewUp)
|
add(tui.Home, actBeginningOfLine)
|
||||||
keymap[tui.SDown] = toActions(actPreviewDown)
|
add(tui.End, actEndOfLine)
|
||||||
|
add(tui.Del, actDeleteChar)
|
||||||
|
add(tui.PgUp, actPageUp)
|
||||||
|
add(tui.PgDn, actPageDown)
|
||||||
|
|
||||||
keymap[tui.Rune] = toActions(actRune)
|
add(tui.SUp, actPreviewUp)
|
||||||
keymap[tui.Mouse] = toActions(actMouse)
|
add(tui.SDown, actPreviewDown)
|
||||||
keymap[tui.DoubleClick] = toActions(actAccept)
|
|
||||||
keymap[tui.LeftClick] = toActions(actIgnore)
|
add(tui.Mouse, actMouse)
|
||||||
keymap[tui.RightClick] = toActions(actToggle)
|
add(tui.DoubleClick, actAccept)
|
||||||
|
add(tui.LeftClick, actIgnore)
|
||||||
|
add(tui.RightClick, actToggle)
|
||||||
return keymap
|
return keymap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1452,10 +1458,9 @@ func (t *Terminal) rubout(pattern string) {
|
|||||||
t.input = append(t.input[:t.cx], after...)
|
t.input = append(t.input[:t.cx], after...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyMatch(key int, event tui.Event) bool {
|
func keyMatch(key tui.Event, event tui.Event) bool {
|
||||||
return event.Type == key ||
|
return event.Type == key.Type && event.Char == key.Char ||
|
||||||
event.Type == tui.Rune && int(event.Char) == key-tui.AltZ ||
|
key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
|
||||||
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntryCmd(entry string) string {
|
func quoteEntryCmd(entry string) string {
|
||||||
@@ -2163,16 +2168,20 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var doAction func(action, int) bool
|
actionsFor := func(eventType tui.EventType) []action {
|
||||||
doActions := func(actions []action, mapkey int) bool {
|
return t.keymap[eventType.AsEvent()]
|
||||||
|
}
|
||||||
|
|
||||||
|
var doAction func(action) bool
|
||||||
|
doActions := func(actions []action) bool {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
if !doAction(action, mapkey) {
|
if !doAction(action) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
doAction = func(a action, mapkey int) bool {
|
doAction = func(a action) bool {
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
@@ -2326,14 +2335,14 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actToggleIn:
|
case actToggleIn:
|
||||||
if t.layout != layoutDefault {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp})
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown})
|
||||||
case actToggleOut:
|
case actToggleOut:
|
||||||
if t.layout != layoutDefault {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown})
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp})
|
||||||
case actToggleDown:
|
case actToggleDown:
|
||||||
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
t.vmove(-1, true)
|
t.vmove(-1, true)
|
||||||
@@ -2490,7 +2499,7 @@ func (t *Terminal) Loop() {
|
|||||||
// Double-click
|
// Double-click
|
||||||
if my >= min {
|
if my >= min {
|
||||||
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
||||||
return doActions(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
return doActions(actionsFor(tui.DoubleClick))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if me.Down {
|
} else if me.Down {
|
||||||
@@ -2504,9 +2513,9 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
if me.Left {
|
if me.Left {
|
||||||
return doActions(t.keymap[tui.LeftClick], tui.LeftClick)
|
return doActions(actionsFor(tui.LeftClick))
|
||||||
}
|
}
|
||||||
return doActions(t.keymap[tui.RightClick], tui.RightClick)
|
return doActions(actionsFor(tui.RightClick))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2528,33 +2537,29 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
mapkey := event.Type
|
|
||||||
if t.jumping == jumpDisabled {
|
if t.jumping == jumpDisabled {
|
||||||
actions := t.keymap[mapkey]
|
actions := t.keymap[event.Comparable()]
|
||||||
if mapkey == tui.Rune {
|
if len(actions) == 0 && event.Type == tui.Rune {
|
||||||
mapkey = int(event.Char) + int(tui.AltZ)
|
doAction(action{t: actRune})
|
||||||
if act, prs := t.keymap[mapkey]; prs {
|
} else if !doActions(actions) {
|
||||||
actions = act
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !doActions(actions, mapkey) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
queryChanged = string(previousInput) != string(t.input)
|
queryChanged = string(previousInput) != string(t.input)
|
||||||
changed = changed || queryChanged
|
changed = changed || queryChanged
|
||||||
if onChanges, prs := t.keymap[tui.Change]; queryChanged && prs {
|
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
|
||||||
if !doActions(onChanges, tui.Change) {
|
if !doActions(onChanges) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if onEOFs, prs := t.keymap[tui.BackwardEOF]; beof && prs {
|
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
|
||||||
if !doActions(onEOFs, tui.BackwardEOF) {
|
if !doActions(onEOFs) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if mapkey == tui.Rune {
|
if event.Type == tui.Rune {
|
||||||
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
||||||
t.cy = idx + t.offset
|
t.cy = idx + t.offset
|
||||||
if t.jumping == jumpAcceptEnabled {
|
if t.jumping == jumpAcceptEnabled {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -230,7 +231,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
if c == ESC || nonblock {
|
if c == ESC.Int() || nonblock {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
@@ -245,7 +246,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else if c == ESC && pc != c {
|
} else if c == ESC.Int() && pc != c {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
} else {
|
} else {
|
||||||
retries = 0
|
retries = 0
|
||||||
@@ -278,11 +279,11 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
switch r.buffer[0] {
|
switch r.buffer[0] {
|
||||||
case CtrlC:
|
case CtrlC.Byte():
|
||||||
return Event{CtrlC, 0, nil}
|
return Event{CtrlC, 0, nil}
|
||||||
case CtrlG:
|
case CtrlG.Byte():
|
||||||
return Event{CtrlG, 0, nil}
|
return Event{CtrlG, 0, nil}
|
||||||
case CtrlQ:
|
case CtrlQ.Byte():
|
||||||
return Event{CtrlQ, 0, nil}
|
return Event{CtrlQ, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
@@ -296,7 +297,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{CtrlCaret, 0, nil}
|
return Event{CtrlCaret, 0, nil}
|
||||||
case 31:
|
case 31:
|
||||||
return Event{CtrlSlash, 0, nil}
|
return Event{CtrlSlash, 0, nil}
|
||||||
case ESC:
|
case ESC.Byte():
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
if ev.Type == Invalid {
|
if ev.Type == Invalid {
|
||||||
@@ -307,8 +308,8 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CTRL-A ~ CTRL-Z
|
// CTRL-A ~ CTRL-Z
|
||||||
if r.buffer[0] <= CtrlZ {
|
if r.buffer[0] <= CtrlZ.Byte() {
|
||||||
return Event{int(r.buffer[0]), 0, nil}
|
return Event{EventType(r.buffer[0]), 0, nil}
|
||||||
}
|
}
|
||||||
char, rsz := utf8.DecodeRune(r.buffer)
|
char, rsz := utf8.DecodeRune(r.buffer)
|
||||||
if char == utf8.RuneError {
|
if char == utf8.RuneError {
|
||||||
@@ -331,26 +332,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
|
|
||||||
*sz = 2
|
*sz = 2
|
||||||
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
||||||
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
||||||
}
|
}
|
||||||
alt := false
|
alt := false
|
||||||
if len(r.buffer) > 2 && r.buffer[1] == ESC {
|
if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
|
||||||
r.buffer = r.buffer[1:]
|
r.buffer = r.buffer[1:]
|
||||||
alt = true
|
alt = true
|
||||||
}
|
}
|
||||||
switch r.buffer[1] {
|
switch r.buffer[1] {
|
||||||
case ESC:
|
case ESC.Byte():
|
||||||
return Event{ESC, 0, nil}
|
return Event{ESC, 0, nil}
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
case 'b':
|
|
||||||
return Event{AltB, 0, nil}
|
|
||||||
case 'd':
|
|
||||||
return Event{AltD, 0, nil}
|
|
||||||
case 'f':
|
|
||||||
return Event{AltF, 0, nil}
|
|
||||||
case 127:
|
case 127:
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
case '[', 'O':
|
case '[', 'O':
|
||||||
@@ -518,11 +509,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
} // r.buffer[2]
|
} // r.buffer[2]
|
||||||
} // r.buffer[2]
|
} // r.buffer[2]
|
||||||
} // r.buffer[1]
|
} // r.buffer[1]
|
||||||
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
rest := bytes.NewBuffer(r.buffer[1:])
|
||||||
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
c, size, err := rest.ReadRune()
|
||||||
}
|
if err == nil {
|
||||||
if r.buffer[1] >= '0' && r.buffer[1] <= '9' {
|
*sz = 1 + size
|
||||||
return Event{Alt0 + int(r.buffer[1]) - '0', 0, nil}
|
return AltKey(c)
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
@@ -226,65 +226,65 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
alt := (mods & tcell.ModAlt) > 0
|
alt := (mods & tcell.ModAlt) > 0
|
||||||
shift := (mods & tcell.ModShift) > 0
|
shift := (mods & tcell.ModShift) > 0
|
||||||
altShift := alt && shift
|
altShift := alt && shift
|
||||||
keyfn := func(r rune) int {
|
keyfn := func(r rune) Event {
|
||||||
if alt {
|
if alt {
|
||||||
return CtrlAltA - 'a' + int(r)
|
return CtrlAltKey(r)
|
||||||
}
|
}
|
||||||
return CtrlA - 'a' + int(r)
|
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
|
||||||
}
|
}
|
||||||
switch ev.Key() {
|
switch ev.Key() {
|
||||||
case tcell.KeyCtrlA:
|
case tcell.KeyCtrlA:
|
||||||
return Event{keyfn('a'), 0, nil}
|
return keyfn('a')
|
||||||
case tcell.KeyCtrlB:
|
case tcell.KeyCtrlB:
|
||||||
return Event{keyfn('b'), 0, nil}
|
return keyfn('b')
|
||||||
case tcell.KeyCtrlC:
|
case tcell.KeyCtrlC:
|
||||||
return Event{keyfn('c'), 0, nil}
|
return keyfn('c')
|
||||||
case tcell.KeyCtrlD:
|
case tcell.KeyCtrlD:
|
||||||
return Event{keyfn('d'), 0, nil}
|
return keyfn('d')
|
||||||
case tcell.KeyCtrlE:
|
case tcell.KeyCtrlE:
|
||||||
return Event{keyfn('e'), 0, nil}
|
return keyfn('e')
|
||||||
case tcell.KeyCtrlF:
|
case tcell.KeyCtrlF:
|
||||||
return Event{keyfn('f'), 0, nil}
|
return keyfn('f')
|
||||||
case tcell.KeyCtrlG:
|
case tcell.KeyCtrlG:
|
||||||
return Event{keyfn('g'), 0, nil}
|
return keyfn('g')
|
||||||
case tcell.KeyCtrlH:
|
case tcell.KeyCtrlH:
|
||||||
return Event{keyfn('h'), 0, nil}
|
return keyfn('h')
|
||||||
case tcell.KeyCtrlI:
|
case tcell.KeyCtrlI:
|
||||||
return Event{keyfn('i'), 0, nil}
|
return keyfn('i')
|
||||||
case tcell.KeyCtrlJ:
|
case tcell.KeyCtrlJ:
|
||||||
return Event{keyfn('j'), 0, nil}
|
return keyfn('j')
|
||||||
case tcell.KeyCtrlK:
|
case tcell.KeyCtrlK:
|
||||||
return Event{keyfn('k'), 0, nil}
|
return keyfn('k')
|
||||||
case tcell.KeyCtrlL:
|
case tcell.KeyCtrlL:
|
||||||
return Event{keyfn('l'), 0, nil}
|
return keyfn('l')
|
||||||
case tcell.KeyCtrlM:
|
case tcell.KeyCtrlM:
|
||||||
return Event{keyfn('m'), 0, nil}
|
return keyfn('m')
|
||||||
case tcell.KeyCtrlN:
|
case tcell.KeyCtrlN:
|
||||||
return Event{keyfn('n'), 0, nil}
|
return keyfn('n')
|
||||||
case tcell.KeyCtrlO:
|
case tcell.KeyCtrlO:
|
||||||
return Event{keyfn('o'), 0, nil}
|
return keyfn('o')
|
||||||
case tcell.KeyCtrlP:
|
case tcell.KeyCtrlP:
|
||||||
return Event{keyfn('p'), 0, nil}
|
return keyfn('p')
|
||||||
case tcell.KeyCtrlQ:
|
case tcell.KeyCtrlQ:
|
||||||
return Event{keyfn('q'), 0, nil}
|
return keyfn('q')
|
||||||
case tcell.KeyCtrlR:
|
case tcell.KeyCtrlR:
|
||||||
return Event{keyfn('r'), 0, nil}
|
return keyfn('r')
|
||||||
case tcell.KeyCtrlS:
|
case tcell.KeyCtrlS:
|
||||||
return Event{keyfn('s'), 0, nil}
|
return keyfn('s')
|
||||||
case tcell.KeyCtrlT:
|
case tcell.KeyCtrlT:
|
||||||
return Event{keyfn('t'), 0, nil}
|
return keyfn('t')
|
||||||
case tcell.KeyCtrlU:
|
case tcell.KeyCtrlU:
|
||||||
return Event{keyfn('u'), 0, nil}
|
return keyfn('u')
|
||||||
case tcell.KeyCtrlV:
|
case tcell.KeyCtrlV:
|
||||||
return Event{keyfn('v'), 0, nil}
|
return keyfn('v')
|
||||||
case tcell.KeyCtrlW:
|
case tcell.KeyCtrlW:
|
||||||
return Event{keyfn('w'), 0, nil}
|
return keyfn('w')
|
||||||
case tcell.KeyCtrlX:
|
case tcell.KeyCtrlX:
|
||||||
return Event{keyfn('x'), 0, nil}
|
return keyfn('x')
|
||||||
case tcell.KeyCtrlY:
|
case tcell.KeyCtrlY:
|
||||||
return Event{keyfn('y'), 0, nil}
|
return keyfn('y')
|
||||||
case tcell.KeyCtrlZ:
|
case tcell.KeyCtrlZ:
|
||||||
return Event{keyfn('z'), 0, nil}
|
return keyfn('z')
|
||||||
case tcell.KeyCtrlSpace:
|
case tcell.KeyCtrlSpace:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
case tcell.KeyCtrlBackslash:
|
case tcell.KeyCtrlBackslash:
|
||||||
@@ -389,18 +389,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
case tcell.KeyRune:
|
case tcell.KeyRune:
|
||||||
r := ev.Rune()
|
r := ev.Rune()
|
||||||
if alt {
|
if alt {
|
||||||
switch r {
|
return AltKey(r)
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
}
|
|
||||||
if r >= 'a' && r <= 'z' {
|
|
||||||
return Event{AltA + int(r) - 'a', 0, nil}
|
|
||||||
}
|
|
||||||
if r >= '0' && r <= '9' {
|
|
||||||
return Event{Alt0 + int(r) - '0', 0, nil}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Event{Rune, r, nil}
|
return Event{Rune, r, nil}
|
||||||
|
|
||||||
|
@@ -8,8 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Types of user action
|
// Types of user action
|
||||||
|
type EventType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Rune = iota
|
Rune EventType = iota
|
||||||
|
|
||||||
CtrlA
|
CtrlA
|
||||||
CtrlB
|
CtrlB
|
||||||
@@ -89,8 +91,6 @@ const (
|
|||||||
Change
|
Change
|
||||||
BackwardEOF
|
BackwardEOF
|
||||||
|
|
||||||
AltSpace
|
|
||||||
AltSlash
|
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
AltUp
|
AltUp
|
||||||
@@ -103,20 +103,38 @@ const (
|
|||||||
AltSLeft
|
AltSLeft
|
||||||
AltSRight
|
AltSRight
|
||||||
|
|
||||||
Alt0
|
Alt
|
||||||
|
CtrlAlt
|
||||||
)
|
)
|
||||||
|
|
||||||
const ( // Reset iota
|
func (t EventType) AsEvent() Event {
|
||||||
AltA = Alt0 + 'a' - '0' + iota
|
return Event{t, 0, nil}
|
||||||
AltB
|
}
|
||||||
AltC
|
|
||||||
AltD
|
func (t EventType) Int() int {
|
||||||
AltE
|
return int(t)
|
||||||
AltF
|
}
|
||||||
AltZ = AltA + 'z' - 'a'
|
|
||||||
CtrlAltA = AltZ + 1
|
func (t EventType) Byte() byte {
|
||||||
CtrlAltM = CtrlAltA + 'm' - 'a'
|
return byte(t)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
func (e Event) Comparable() Event {
|
||||||
|
// Ignore MouseEvent pointer
|
||||||
|
return Event{e.Type, e.Char, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Key(r rune) Event {
|
||||||
|
return Event{Rune, r, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AltKey(r rune) Event {
|
||||||
|
return Event{Alt, r, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlAltKey(r rune) Event {
|
||||||
|
return Event{CtrlAlt, r, nil}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
doubleClickDuration = 500 * time.Millisecond
|
doubleClickDuration = 500 * time.Millisecond
|
||||||
@@ -251,7 +269,7 @@ type ColorTheme struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type int
|
Type EventType
|
||||||
Char rune
|
Char rune
|
||||||
MouseEvent *MouseEvent
|
MouseEvent *MouseEvent
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user