parent
a06ccc928f
commit
131aa5dd15
@ -1,6 +1,12 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.16.2
|
||||||
|
------
|
||||||
|
- Added support for composite actions in `--bind`. Multiple actions can be
|
||||||
|
chained using `+` separator.
|
||||||
|
- e.g. `fzf --bind 'ctrl-y:execute(echo -n {} | pbcopy)+abort'`
|
||||||
|
|
||||||
0.16.1
|
0.16.1
|
||||||
------
|
------
|
||||||
- Fixed `--height` option to properly fill the window with the background
|
- Fixed `--height` option to properly fill the window with the background
|
||||||
|
@ -481,17 +481,21 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBselect-all\fR
|
\fBselect-all\fR
|
||||||
\fBtoggle\fR
|
\fBtoggle\fR
|
||||||
\fBtoggle-all\fR
|
\fBtoggle-all\fR
|
||||||
\fBtoggle-down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle-up\fR : \fBtoggle-down\fR)
|
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle-down\fR : \fBtoggle-up\fR)
|
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
|
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
|
||||||
\fBtoggle-up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
|
Multiple actions can be chained using \fB+\fR separator.
|
||||||
|
|
||||||
|
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||||
|
|
||||||
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
||||||
leaving fzf. For example, you can turn fzf into a simple file browser by
|
leaving fzf. For example, you can turn fzf into a simple file browser by
|
||||||
binding \fBenter\fR key to \fBless\fR command like follows.
|
binding \fBenter\fR key to \fBless\fR command like follows.
|
||||||
|
294
src/options.go
294
src/options.go
@ -171,8 +171,7 @@ type Options struct {
|
|||||||
Filter *string
|
Filter *string
|
||||||
ToggleSort bool
|
ToggleSort bool
|
||||||
Expect map[int]string
|
Expect map[int]string
|
||||||
Keymap map[int]actionType
|
Keymap map[int][]action
|
||||||
Execmap map[int]string
|
|
||||||
Preview previewOpts
|
Preview previewOpts
|
||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
@ -220,8 +219,7 @@ func defaultOptions() *Options {
|
|||||||
Filter: nil,
|
Filter: nil,
|
||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[int]string),
|
Expect: make(map[int]string),
|
||||||
Keymap: make(map[int]actionType),
|
Keymap: make(map[int][]action),
|
||||||
Execmap: make(map[int]string),
|
|
||||||
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
|
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
@ -578,23 +576,25 @@ func firstKey(keymap map[int]string) int {
|
|||||||
const (
|
const (
|
||||||
escapedColon = 0
|
escapedColon = 0
|
||||||
escapedComma = 1
|
escapedComma = 1
|
||||||
|
escapedPlus = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string) {
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
if executeRegexp == nil {
|
if executeRegexp == nil {
|
||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
"(?s):execute(-multi)?:.*|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
"(?si):execute(-multi)?:.+|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||||
}
|
}
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
if strings.HasPrefix(src, ":execute-multi") {
|
if src[len(":execute")] == '-' {
|
||||||
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
|
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
|
||||||
}
|
}
|
||||||
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
|
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
|
||||||
})
|
})
|
||||||
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
||||||
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
||||||
|
masked = strings.Replace(masked, "+:", string([]rune{escapedPlus, ':'}), -1)
|
||||||
|
|
||||||
idx := 0
|
idx := 0
|
||||||
for _, pairStr := range strings.Split(masked, ",") {
|
for _, pairStr := range strings.Split(masked, ",") {
|
||||||
@ -610,151 +610,173 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
|
|||||||
key = ':' + tui.AltZ
|
key = ':' + tui.AltZ
|
||||||
} 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.AltZ
|
||||||
|
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
||||||
|
key = '+' + tui.AltZ
|
||||||
} else {
|
} else {
|
||||||
keys := parseKeyChords(pair[0], "key name required")
|
keys := parseKeyChords(pair[0], "key name required")
|
||||||
key = firstKey(keys)
|
key = firstKey(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
act := origPairStr[len(pair[0])+1 : len(origPairStr)]
|
idx2 := len(pair[0]) + 1
|
||||||
actLower := strings.ToLower(act)
|
specs := strings.Split(pair[1], "+")
|
||||||
switch actLower {
|
actions := make([]action, 0, len(specs))
|
||||||
case "ignore":
|
appendAction := func(types ...actionType) {
|
||||||
keymap[key] = actIgnore
|
actions = append(actions, toActions(types...)...)
|
||||||
case "beginning-of-line":
|
|
||||||
keymap[key] = actBeginningOfLine
|
|
||||||
case "abort":
|
|
||||||
keymap[key] = actAbort
|
|
||||||
case "accept":
|
|
||||||
keymap[key] = actAccept
|
|
||||||
case "print-query":
|
|
||||||
keymap[key] = actPrintQuery
|
|
||||||
case "backward-char":
|
|
||||||
keymap[key] = actBackwardChar
|
|
||||||
case "backward-delete-char":
|
|
||||||
keymap[key] = actBackwardDeleteChar
|
|
||||||
case "backward-word":
|
|
||||||
keymap[key] = actBackwardWord
|
|
||||||
case "clear-screen":
|
|
||||||
keymap[key] = actClearScreen
|
|
||||||
case "delete-char":
|
|
||||||
keymap[key] = actDeleteChar
|
|
||||||
case "delete-char/eof":
|
|
||||||
keymap[key] = actDeleteCharEOF
|
|
||||||
case "end-of-line":
|
|
||||||
keymap[key] = actEndOfLine
|
|
||||||
case "cancel":
|
|
||||||
keymap[key] = actCancel
|
|
||||||
case "forward-char":
|
|
||||||
keymap[key] = actForwardChar
|
|
||||||
case "forward-word":
|
|
||||||
keymap[key] = actForwardWord
|
|
||||||
case "jump":
|
|
||||||
keymap[key] = actJump
|
|
||||||
case "jump-accept":
|
|
||||||
keymap[key] = actJumpAccept
|
|
||||||
case "kill-line":
|
|
||||||
keymap[key] = actKillLine
|
|
||||||
case "kill-word":
|
|
||||||
keymap[key] = actKillWord
|
|
||||||
case "unix-line-discard", "line-discard":
|
|
||||||
keymap[key] = actUnixLineDiscard
|
|
||||||
case "unix-word-rubout", "word-rubout":
|
|
||||||
keymap[key] = actUnixWordRubout
|
|
||||||
case "yank":
|
|
||||||
keymap[key] = actYank
|
|
||||||
case "backward-kill-word":
|
|
||||||
keymap[key] = actBackwardKillWord
|
|
||||||
case "toggle-down":
|
|
||||||
keymap[key] = actToggleDown
|
|
||||||
case "toggle-up":
|
|
||||||
keymap[key] = actToggleUp
|
|
||||||
case "toggle-in":
|
|
||||||
keymap[key] = actToggleIn
|
|
||||||
case "toggle-out":
|
|
||||||
keymap[key] = actToggleOut
|
|
||||||
case "toggle-all":
|
|
||||||
keymap[key] = actToggleAll
|
|
||||||
case "select-all":
|
|
||||||
keymap[key] = actSelectAll
|
|
||||||
case "deselect-all":
|
|
||||||
keymap[key] = actDeselectAll
|
|
||||||
case "toggle":
|
|
||||||
keymap[key] = actToggle
|
|
||||||
case "down":
|
|
||||||
keymap[key] = actDown
|
|
||||||
case "up":
|
|
||||||
keymap[key] = actUp
|
|
||||||
case "page-up":
|
|
||||||
keymap[key] = actPageUp
|
|
||||||
case "page-down":
|
|
||||||
keymap[key] = actPageDown
|
|
||||||
case "half-page-up":
|
|
||||||
keymap[key] = actHalfPageUp
|
|
||||||
case "half-page-down":
|
|
||||||
keymap[key] = actHalfPageDown
|
|
||||||
case "previous-history":
|
|
||||||
keymap[key] = actPreviousHistory
|
|
||||||
case "next-history":
|
|
||||||
keymap[key] = actNextHistory
|
|
||||||
case "toggle-preview":
|
|
||||||
keymap[key] = actTogglePreview
|
|
||||||
case "toggle-sort":
|
|
||||||
keymap[key] = actToggleSort
|
|
||||||
case "preview-up":
|
|
||||||
keymap[key] = actPreviewUp
|
|
||||||
case "preview-down":
|
|
||||||
keymap[key] = actPreviewDown
|
|
||||||
case "preview-page-up":
|
|
||||||
keymap[key] = actPreviewPageUp
|
|
||||||
case "preview-page-down":
|
|
||||||
keymap[key] = actPreviewPageDown
|
|
||||||
default:
|
|
||||||
if isExecuteAction(actLower) {
|
|
||||||
var offset int
|
|
||||||
if strings.HasPrefix(actLower, "execute-multi") {
|
|
||||||
keymap[key] = actExecuteMulti
|
|
||||||
offset = len("execute-multi")
|
|
||||||
} else {
|
|
||||||
keymap[key] = actExecute
|
|
||||||
offset = len("execute")
|
|
||||||
}
|
|
||||||
if act[offset] == ':' {
|
|
||||||
execmap[key] = act[offset+1:]
|
|
||||||
} else {
|
|
||||||
execmap[key] = act[offset+1 : len(act)-1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorExit("unknown action: " + act)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
prevSpec := ""
|
||||||
|
for specIndex, maskedSpec := range specs {
|
||||||
|
spec := origPairStr[idx2 : idx2+len(maskedSpec)]
|
||||||
|
idx2 += len(maskedSpec) + 1
|
||||||
|
spec = prevSpec + spec
|
||||||
|
specLower := strings.ToLower(spec)
|
||||||
|
switch specLower {
|
||||||
|
case "ignore":
|
||||||
|
appendAction(actIgnore)
|
||||||
|
case "beginning-of-line":
|
||||||
|
appendAction(actBeginningOfLine)
|
||||||
|
case "abort":
|
||||||
|
appendAction(actAbort)
|
||||||
|
case "accept":
|
||||||
|
appendAction(actAccept)
|
||||||
|
case "print-query":
|
||||||
|
appendAction(actPrintQuery)
|
||||||
|
case "backward-char":
|
||||||
|
appendAction(actBackwardChar)
|
||||||
|
case "backward-delete-char":
|
||||||
|
appendAction(actBackwardDeleteChar)
|
||||||
|
case "backward-word":
|
||||||
|
appendAction(actBackwardWord)
|
||||||
|
case "clear-screen":
|
||||||
|
appendAction(actClearScreen)
|
||||||
|
case "delete-char":
|
||||||
|
appendAction(actDeleteChar)
|
||||||
|
case "delete-char/eof":
|
||||||
|
appendAction(actDeleteCharEOF)
|
||||||
|
case "end-of-line":
|
||||||
|
appendAction(actEndOfLine)
|
||||||
|
case "cancel":
|
||||||
|
appendAction(actCancel)
|
||||||
|
case "forward-char":
|
||||||
|
appendAction(actForwardChar)
|
||||||
|
case "forward-word":
|
||||||
|
appendAction(actForwardWord)
|
||||||
|
case "jump":
|
||||||
|
appendAction(actJump)
|
||||||
|
case "jump-accept":
|
||||||
|
appendAction(actJumpAccept)
|
||||||
|
case "kill-line":
|
||||||
|
appendAction(actKillLine)
|
||||||
|
case "kill-word":
|
||||||
|
appendAction(actKillWord)
|
||||||
|
case "unix-line-discard", "line-discard":
|
||||||
|
appendAction(actUnixLineDiscard)
|
||||||
|
case "unix-word-rubout", "word-rubout":
|
||||||
|
appendAction(actUnixWordRubout)
|
||||||
|
case "yank":
|
||||||
|
appendAction(actYank)
|
||||||
|
case "backward-kill-word":
|
||||||
|
appendAction(actBackwardKillWord)
|
||||||
|
case "toggle-down":
|
||||||
|
appendAction(actToggle, actDown)
|
||||||
|
case "toggle-up":
|
||||||
|
appendAction(actToggle, actUp)
|
||||||
|
case "toggle-in":
|
||||||
|
appendAction(actToggleIn)
|
||||||
|
case "toggle-out":
|
||||||
|
appendAction(actToggleOut)
|
||||||
|
case "toggle-all":
|
||||||
|
appendAction(actToggleAll)
|
||||||
|
case "select-all":
|
||||||
|
appendAction(actSelectAll)
|
||||||
|
case "deselect-all":
|
||||||
|
appendAction(actDeselectAll)
|
||||||
|
case "toggle":
|
||||||
|
appendAction(actToggle)
|
||||||
|
case "down":
|
||||||
|
appendAction(actDown)
|
||||||
|
case "up":
|
||||||
|
appendAction(actUp)
|
||||||
|
case "page-up":
|
||||||
|
appendAction(actPageUp)
|
||||||
|
case "page-down":
|
||||||
|
appendAction(actPageDown)
|
||||||
|
case "half-page-up":
|
||||||
|
appendAction(actHalfPageUp)
|
||||||
|
case "half-page-down":
|
||||||
|
appendAction(actHalfPageDown)
|
||||||
|
case "previous-history":
|
||||||
|
appendAction(actPreviousHistory)
|
||||||
|
case "next-history":
|
||||||
|
appendAction(actNextHistory)
|
||||||
|
case "toggle-preview":
|
||||||
|
appendAction(actTogglePreview)
|
||||||
|
case "toggle-sort":
|
||||||
|
appendAction(actToggleSort)
|
||||||
|
case "preview-up":
|
||||||
|
appendAction(actPreviewUp)
|
||||||
|
case "preview-down":
|
||||||
|
appendAction(actPreviewDown)
|
||||||
|
case "preview-page-up":
|
||||||
|
appendAction(actPreviewPageUp)
|
||||||
|
case "preview-page-down":
|
||||||
|
appendAction(actPreviewPageDown)
|
||||||
|
default:
|
||||||
|
t := isExecuteAction(specLower)
|
||||||
|
if t == actIgnore {
|
||||||
|
errorExit("unknown action: " + spec)
|
||||||
|
} else {
|
||||||
|
var offset int
|
||||||
|
if t == actExecuteMulti {
|
||||||
|
offset = len("execute-multi")
|
||||||
|
} else {
|
||||||
|
offset = len("execute")
|
||||||
|
}
|
||||||
|
if spec[offset] == ':' {
|
||||||
|
if specIndex == len(specs)-1 {
|
||||||
|
actions = append(actions, action{t: t, a: spec[offset+1:]})
|
||||||
|
} else {
|
||||||
|
prevSpec = spec + "+"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actions = append(actions, action{t: t, a: spec[offset+1 : len(spec)-1]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevSpec = ""
|
||||||
|
}
|
||||||
|
keymap[key] = actions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isExecuteAction(str string) bool {
|
func isExecuteAction(str string) actionType {
|
||||||
if !strings.HasPrefix(str, "execute") || len(str) < len("execute()") {
|
t := actExecute
|
||||||
return false
|
if !strings.HasPrefix(str, "execute") || len(str) < len("execute(") {
|
||||||
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
b := str[len("execute")]
|
b := str[len("execute")]
|
||||||
if strings.HasPrefix(str, "execute-multi") {
|
if strings.HasPrefix(str, "execute-multi") {
|
||||||
if len(str) < len("execute-multi()") {
|
if len(str) < len("execute-multi(") {
|
||||||
return false
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
t = actExecuteMulti
|
||||||
b = str[len("execute-multi")]
|
b = str[len("execute-multi")]
|
||||||
}
|
}
|
||||||
e := str[len(str)-1]
|
e := str[len(str)-1]
|
||||||
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
||||||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
||||||
return true
|
return t
|
||||||
}
|
}
|
||||||
return false
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToggleSort(keymap map[int]actionType, str string) {
|
func parseToggleSort(keymap map[int][]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")
|
||||||
}
|
}
|
||||||
keymap[firstKey(keys)] = actToggleSort
|
keymap[firstKey(keys)] = toActions(actToggleSort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func strLines(str string) []string {
|
func strLines(str string) []string {
|
||||||
@ -919,7 +941,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||||
case "--bind":
|
case "--bind":
|
||||||
parseKeymap(opts.Keymap, opts.Execmap, nextString(allArgs, &i, "bind expression required"))
|
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
|
||||||
case "--color":
|
case "--color":
|
||||||
spec := optionalNextString(allArgs, &i)
|
spec := optionalNextString(allArgs, &i)
|
||||||
if len(spec) == 0 {
|
if len(spec) == 0 {
|
||||||
@ -1089,7 +1111,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--color="); match {
|
} else if match, value := optString(arg, "--color="); match {
|
||||||
opts.Theme = parseTheme(opts.Theme, value)
|
opts.Theme = parseTheme(opts.Theme, value)
|
||||||
} else if match, value := optString(arg, "--bind="); match {
|
} else if match, value := optString(arg, "--bind="); match {
|
||||||
parseKeymap(opts.Keymap, opts.Execmap, value)
|
parseKeymap(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--history="); match {
|
} else if match, value := optString(arg, "--history="); match {
|
||||||
setHistory(value)
|
setHistory(value)
|
||||||
} else if match, value := optString(arg, "--history-size="); match {
|
} else if match, value := optString(arg, "--history-size="); match {
|
||||||
@ -1145,20 +1167,22 @@ 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]; !prs {
|
||||||
opts.Keymap[tui.CtrlP] = actPreviousHistory
|
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
|
||||||
}
|
}
|
||||||
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
|
||||||
opts.Keymap[tui.CtrlN] = actNextHistory
|
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend the default key map
|
// Extend the default key map
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
for key, act := range opts.Keymap {
|
for key, actions := range opts.Keymap {
|
||||||
if act == actToggleSort {
|
for _, act := range actions {
|
||||||
opts.ToggleSort = true
|
if act.t == actToggleSort {
|
||||||
|
opts.ToggleSort = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
keymap[key] = act
|
keymap[key] = actions
|
||||||
}
|
}
|
||||||
opts.Keymap = keymap
|
opts.Keymap = keymap
|
||||||
|
|
||||||
|
@ -225,49 +225,51 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBind(t *testing.T) {
|
func TestBind(t *testing.T) {
|
||||||
check := func(action actionType, expected actionType) {
|
|
||||||
if action != expected {
|
|
||||||
t.Errorf("%d != %d", action, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkString := func(action string, expected string) {
|
|
||||||
if action != expected {
|
|
||||||
t.Errorf("%d != %d", action, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
execmap := make(map[int]string)
|
check := func(keyName int, arg1 string, types ...actionType) {
|
||||||
check(actBeginningOfLine, keymap[tui.CtrlA])
|
if len(keymap[keyName]) != len(types) {
|
||||||
parseKeymap(keymap, execmap,
|
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
|
return
|
||||||
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
}
|
||||||
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
|
for idx, action := range keymap[keyName] {
|
||||||
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
|
if types[idx] != action.t {
|
||||||
check(actKillLine, keymap[tui.CtrlA])
|
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
|
||||||
check(actToggleSort, keymap[tui.CtrlB])
|
}
|
||||||
check(actPageUp, keymap[tui.AltZ+'c'])
|
}
|
||||||
check(actAbort, keymap[tui.AltZ+','])
|
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
|
||||||
check(actAccept, keymap[tui.AltZ+':'])
|
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
|
||||||
check(actPageDown, keymap[tui.AltZ])
|
}
|
||||||
check(actExecute, keymap[tui.F1])
|
}
|
||||||
check(actExecute, keymap[tui.F2])
|
check(tui.CtrlA, "", actBeginningOfLine)
|
||||||
check(actExecute, keymap[tui.F3])
|
parseKeymap(keymap,
|
||||||
check(actExecute, keymap[tui.F4])
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
checkString("ls {}", execmap[tui.F1])
|
"f1:execute(ls {})+abort,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
checkString("echo {}, {}, {}", execmap[tui.F2])
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
checkString("echo '({})'", execmap[tui.F3])
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
checkString("less {}", execmap[tui.F4])
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
checkString("echo (,),[,],/,:,;,%,{}", execmap[tui.AltA])
|
check(tui.CtrlA, "", actKillLine)
|
||||||
checkString("echo (,),[,],/,:,@,%,{}", execmap[tui.AltB])
|
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
||||||
checkString("\nfoobar,Y:execute(baz)", execmap[tui.AltZ+'X'])
|
check(tui.AltZ+'c', "", actPageUp)
|
||||||
|
check(tui.AltZ+',', "", actAbort)
|
||||||
|
check(tui.AltZ+':', "", actAccept)
|
||||||
|
check(tui.AltZ, "", actPageDown)
|
||||||
|
check(tui.F1, "ls {}", actExecute, actAbort)
|
||||||
|
check(tui.F2, "echo {}, {}, {}", actExecute)
|
||||||
|
check(tui.F3, "echo '({})'", actExecute)
|
||||||
|
check(tui.F4, "less {}", actExecute)
|
||||||
|
check(tui.AltZ+'x', "foo+bar", actExecute)
|
||||||
|
check(tui.AltZ+'X', "bar+baz", actExecute)
|
||||||
|
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
||||||
|
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
|
||||||
|
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||||
|
|
||||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||||
parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||||
checkString("foobar", execmap[tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])])
|
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeymap(keymap, execmap, "f1:abort")
|
parseKeymap(keymap, "f1:abort")
|
||||||
check(actAbort, keymap[tui.F1])
|
check(tui.F1, "", actAbort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorSpec(t *testing.T) {
|
func TestColorSpec(t *testing.T) {
|
||||||
@ -327,7 +329,7 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if opts.Keymap[key] != expected {
|
if opts.Keymap[key][0].t != expected {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
141
src/terminal.go
141
src/terminal.go
@ -72,8 +72,7 @@ type Terminal struct {
|
|||||||
toggleSort bool
|
toggleSort bool
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
expect map[int]string
|
expect map[int]string
|
||||||
keymap map[int]actionType
|
keymap map[int][]action
|
||||||
execmap map[int]string
|
|
||||||
pressed string
|
pressed string
|
||||||
printQuery bool
|
printQuery bool
|
||||||
history *History
|
history *History
|
||||||
@ -148,6 +147,11 @@ const (
|
|||||||
reqQuit
|
reqQuit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type action struct {
|
||||||
|
t actionType
|
||||||
|
a string
|
||||||
|
}
|
||||||
|
|
||||||
type actionType int
|
type actionType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -203,54 +207,62 @@ const (
|
|||||||
actExecuteMulti
|
actExecuteMulti
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultKeymap() map[int]actionType {
|
func toActions(types ...actionType) []action {
|
||||||
keymap := make(map[int]actionType)
|
actions := make([]action, len(types))
|
||||||
keymap[tui.Invalid] = actInvalid
|
for idx, t := range types {
|
||||||
keymap[tui.Resize] = actClearScreen
|
actions[idx] = action{t: t, a: ""}
|
||||||
keymap[tui.CtrlA] = actBeginningOfLine
|
}
|
||||||
keymap[tui.CtrlB] = actBackwardChar
|
return actions
|
||||||
keymap[tui.CtrlC] = actAbort
|
}
|
||||||
keymap[tui.CtrlG] = actAbort
|
|
||||||
keymap[tui.CtrlQ] = actAbort
|
|
||||||
keymap[tui.ESC] = actAbort
|
|
||||||
keymap[tui.CtrlD] = actDeleteCharEOF
|
|
||||||
keymap[tui.CtrlE] = actEndOfLine
|
|
||||||
keymap[tui.CtrlF] = actForwardChar
|
|
||||||
keymap[tui.CtrlH] = actBackwardDeleteChar
|
|
||||||
keymap[tui.BSpace] = actBackwardDeleteChar
|
|
||||||
keymap[tui.Tab] = actToggleDown
|
|
||||||
keymap[tui.BTab] = actToggleUp
|
|
||||||
keymap[tui.CtrlJ] = actDown
|
|
||||||
keymap[tui.CtrlK] = actUp
|
|
||||||
keymap[tui.CtrlL] = actClearScreen
|
|
||||||
keymap[tui.CtrlM] = actAccept
|
|
||||||
keymap[tui.CtrlN] = actDown
|
|
||||||
keymap[tui.CtrlP] = actUp
|
|
||||||
keymap[tui.CtrlU] = actUnixLineDiscard
|
|
||||||
keymap[tui.CtrlW] = actUnixWordRubout
|
|
||||||
keymap[tui.CtrlY] = actYank
|
|
||||||
|
|
||||||
keymap[tui.AltB] = actBackwardWord
|
func defaultKeymap() map[int][]action {
|
||||||
keymap[tui.SLeft] = actBackwardWord
|
keymap := make(map[int][]action)
|
||||||
keymap[tui.AltF] = actForwardWord
|
keymap[tui.Invalid] = toActions(actInvalid)
|
||||||
keymap[tui.SRight] = actForwardWord
|
keymap[tui.Resize] = toActions(actClearScreen)
|
||||||
keymap[tui.AltD] = actKillWord
|
keymap[tui.CtrlA] = toActions(actBeginningOfLine)
|
||||||
keymap[tui.AltBS] = actBackwardKillWord
|
keymap[tui.CtrlB] = toActions(actBackwardChar)
|
||||||
|
keymap[tui.CtrlC] = toActions(actAbort)
|
||||||
|
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)
|
||||||
|
|
||||||
keymap[tui.Up] = actUp
|
keymap[tui.AltB] = toActions(actBackwardWord)
|
||||||
keymap[tui.Down] = actDown
|
keymap[tui.SLeft] = toActions(actBackwardWord)
|
||||||
keymap[tui.Left] = actBackwardChar
|
keymap[tui.AltF] = toActions(actForwardWord)
|
||||||
keymap[tui.Right] = actForwardChar
|
keymap[tui.SRight] = toActions(actForwardWord)
|
||||||
|
keymap[tui.AltD] = toActions(actKillWord)
|
||||||
|
keymap[tui.AltBS] = toActions(actBackwardKillWord)
|
||||||
|
|
||||||
keymap[tui.Home] = actBeginningOfLine
|
keymap[tui.Up] = toActions(actUp)
|
||||||
keymap[tui.End] = actEndOfLine
|
keymap[tui.Down] = toActions(actDown)
|
||||||
keymap[tui.Del] = actDeleteChar
|
keymap[tui.Left] = toActions(actBackwardChar)
|
||||||
keymap[tui.PgUp] = actPageUp
|
keymap[tui.Right] = toActions(actForwardChar)
|
||||||
keymap[tui.PgDn] = actPageDown
|
|
||||||
|
|
||||||
keymap[tui.Rune] = actRune
|
keymap[tui.Home] = toActions(actBeginningOfLine)
|
||||||
keymap[tui.Mouse] = actMouse
|
keymap[tui.End] = toActions(actEndOfLine)
|
||||||
keymap[tui.DoubleClick] = actAccept
|
keymap[tui.Del] = toActions(actDeleteChar)
|
||||||
|
keymap[tui.PgUp] = toActions(actPageUp)
|
||||||
|
keymap[tui.PgDn] = toActions(actPageDown)
|
||||||
|
|
||||||
|
keymap[tui.Rune] = toActions(actRune)
|
||||||
|
keymap[tui.Mouse] = toActions(actMouse)
|
||||||
|
keymap[tui.DoubleClick] = toActions(actAccept)
|
||||||
return keymap
|
return keymap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +335,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
delimiter: opts.Delimiter,
|
delimiter: opts.Delimiter,
|
||||||
expect: opts.Expect,
|
expect: opts.Expect,
|
||||||
keymap: opts.Keymap,
|
keymap: opts.Keymap,
|
||||||
execmap: opts.Execmap,
|
|
||||||
pressed: "",
|
pressed: "",
|
||||||
printQuery: opts.PrintQuery,
|
printQuery: opts.PrintQuery,
|
||||||
history: opts.History,
|
history: opts.History,
|
||||||
@ -1314,13 +1325,21 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var doAction func(actionType, int) bool
|
var doAction func(action, int) bool
|
||||||
doAction = func(action actionType, mapkey int) bool {
|
doActions := func(actions []action, mapkey int) bool {
|
||||||
switch action {
|
for _, action := range actions {
|
||||||
|
if !doAction(action, mapkey) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
doAction = func(a action, mapkey int) bool {
|
||||||
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute:
|
case actExecute:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||||
t.executeCommand(t.execmap[mapkey], []*Item{t.currentItem()})
|
t.executeCommand(a.a, []*Item{t.currentItem()})
|
||||||
}
|
}
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
if len(t.selected) > 0 {
|
if len(t.selected) > 0 {
|
||||||
@ -1328,9 +1347,9 @@ func (t *Terminal) Loop() {
|
|||||||
for i, sel := range t.sortSelected() {
|
for i, sel := range t.sortSelected() {
|
||||||
sels[i] = sel.item
|
sels[i] = sel.item
|
||||||
}
|
}
|
||||||
t.executeCommand(t.execmap[mapkey], sels)
|
t.executeCommand(a.a, sels)
|
||||||
} else {
|
} else {
|
||||||
return doAction(actExecute, mapkey)
|
return doAction(action{t: actExecute, a: a.a}, mapkey)
|
||||||
}
|
}
|
||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
@ -1431,14 +1450,14 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actToggleIn:
|
case actToggleIn:
|
||||||
if t.reverse {
|
if t.reverse {
|
||||||
return doAction(actToggleUp, mapkey)
|
return doAction(action{t: actToggleUp}, mapkey)
|
||||||
}
|
}
|
||||||
return doAction(actToggleDown, mapkey)
|
return doAction(action{t: actToggleDown}, mapkey)
|
||||||
case actToggleOut:
|
case actToggleOut:
|
||||||
if t.reverse {
|
if t.reverse {
|
||||||
return doAction(actToggleDown, mapkey)
|
return doAction(action{t: actToggleDown}, mapkey)
|
||||||
}
|
}
|
||||||
return doAction(actToggleUp, mapkey)
|
return doAction(action{t: actToggleUp}, mapkey)
|
||||||
case actToggleDown:
|
case actToggleDown:
|
||||||
if t.multi && t.merger.Length() > 0 {
|
if t.multi && t.merger.Length() > 0 {
|
||||||
toggle()
|
toggle()
|
||||||
@ -1558,7 +1577,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 doAction(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
return doActions(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if me.Down {
|
} else if me.Down {
|
||||||
@ -1580,14 +1599,14 @@ func (t *Terminal) Loop() {
|
|||||||
changed := false
|
changed := false
|
||||||
mapkey := event.Type
|
mapkey := event.Type
|
||||||
if t.jumping == jumpDisabled {
|
if t.jumping == jumpDisabled {
|
||||||
action := t.keymap[mapkey]
|
actions := t.keymap[mapkey]
|
||||||
if mapkey == tui.Rune {
|
if mapkey == tui.Rune {
|
||||||
mapkey = int(event.Char) + int(tui.AltZ)
|
mapkey = int(event.Char) + int(tui.AltZ)
|
||||||
if act, prs := t.keymap[mapkey]; prs {
|
if act, prs := t.keymap[mapkey]; prs {
|
||||||
action = act
|
actions = act
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !doAction(action, mapkey) {
|
if !doActions(actions, mapkey) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Truncate the query if it's too long
|
// Truncate the query if it's too long
|
||||||
|
Loading…
x
Reference in New Issue
Block a user