summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-01-27 01:46:21 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-01-27 01:46:21 +0900
commit80da0776f85612d527e380226734f9372e7a0719 (patch)
tree8e7b4c2dc223e4312148ba39ba903f942a91012e
parente91f10ab167f10328816fab65fc38370059986f3 (diff)
downloadfzf-80da0776f85612d527e380226734f9372e7a0719.tar.gz
Allow actions to multiple keys and events at once
Close #4206
-rw-r--r--ADVANCED.md10
-rw-r--r--man/man1/fzf.14
-rw-r--r--src/options.go45
-rw-r--r--test/test_core.rb4
4 files changed, 36 insertions, 27 deletions
diff --git a/ADVANCED.md b/ADVANCED.md
index 69c738a9..a045140c 100644
--- a/ADVANCED.md
+++ b/ADVANCED.md
@@ -515,8 +515,6 @@ remainder of the query is passed to fzf for secondary filtering.
```sh
#!/usr/bin/env bash
-# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)
-RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
TRANSFORMER='
words=($FZF_QUERY)
@@ -530,15 +528,14 @@ TRANSFORMER='
# restart ripgrep and reload the list
elif ! [[ $FZF_QUERY =~ \ $ ]]; then
pat=${words[0]}
- echo "reload:sleep 0.1; $RG_PREFIX \"$pat\" || true"
+ echo "reload:sleep 0.1; rg --column --line-number --no-heading --color=always --smart-case \"$pat\" || true"
else
echo search:
fi
'
fzf --ansi --disabled --query "$INITIAL_QUERY" \
--with-shell 'bash -c' \
- --bind "start:transform:$TRANSFORMER" \
- --bind "change:transform:$TRANSFORMER" \
+ --bind "start,change:transform:$TRANSFORMER" \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
@@ -575,8 +572,7 @@ pods() {
--info=inline --layout=reverse --header-lines=1 \
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
- --bind 'start:reload:$command' \
- --bind 'ctrl-r:reload:$command' \
+ --bind 'start,ctrl-r:reload:$command' \
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash' \
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2})' \
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index b5f0ce68..a36384ae 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -1291,7 +1291,9 @@ more \fBactions\fR. You can use it to customize key bindings or implement
dynamic behaviors.
\fB\-\-bind\fR takes a comma-separated list of binding expressions. Each binding
-expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR.
+expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR. You can bind actions to
+multiple keys and events by writing comma-separated list of keys and events
+before the colon. e.g. \fBKEY1,KEY2,EVENT1,EVENT2:ACTION\fR.
e.g.
\fBfzf \-\-bind=ctrl\-j:accept,ctrl\-k:kill\-line\fR
diff --git a/src/options.go b/src/options.go
index 5865f0ea..4d5d0ca2 100644
--- a/src/options.go
+++ b/src/options.go
@@ -1638,33 +1638,44 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
var err error
masked := maskActionContents(str)
idx := 0
+ keys := []string{}
for _, pairStr := range strings.Split(masked, ",") {
origPairStr := str[idx : idx+len(pairStr)]
idx += len(pairStr) + 1
pair := strings.SplitN(pairStr, ":", 2)
+ if len(pair[0]) == 0 {
+ return errors.New("key name required")
+ }
+ keys = append(keys, pair[0])
if len(pair) < 2 {
- return errors.New("bind action not specified: " + origPairStr)
- }
- var key tui.Event
- if len(pair[0]) == 1 && pair[0][0] == escapedColon {
- key = tui.Key(':')
- } else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
- key = tui.Key(',')
- } else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
- key = tui.Key('+')
- } else {
- keys, err := parseKeyChordsImpl(pair[0], "key name required")
+ continue
+ }
+ for _, keyName := range keys {
+ var key tui.Event
+ if len(keyName) == 1 && keyName[0] == escapedColon {
+ key = tui.Key(':')
+ } else if len(keyName) == 1 && keyName[0] == escapedComma {
+ key = tui.Key(',')
+ } else if len(keyName) == 1 && keyName[0] == escapedPlus {
+ key = tui.Key('+')
+ } else {
+ keys, err := parseKeyChordsImpl(keyName, "key name required")
+ if err != nil {
+ return err
+ }
+ key = firstKey(keys)
+ }
+ putAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char)
+ keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed)
if err != nil {
return err
}
- key = firstKey(keys)
- }
- putAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char)
- keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed)
- if err != nil {
- return err
}
+ keys = keys[:0]
+ }
+ if len(keys) > 0 {
+ return errors.New("bind action not specified: " + strings.Join(keys, ", "))
}
return nil
}
diff --git a/test/test_core.rb b/test/test_core.rb
index 0d80644b..75cf3d77 100644
--- a/test/test_core.rb
+++ b/test/test_core.rb
@@ -418,9 +418,9 @@ class TestCore < TestInteractive
end
def test_bind
- tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:accept,u:up,T:toggle-up,t:toggle')}", :Enter
+ tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:accept,u,U:up,X,Y,Z:toggle-up,t:toggle')}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
- tmux.send_keys 'uuu', 'TTT', 'tt', 'uu', 'ttt', 'C-j'
+ tmux.send_keys 'uUu', 'XYZ', 'tt', 'uu', 'ttt', 'C-j'
assert_equal %w[4 5 6 9], fzf_output_lines
end