summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2020-03-11 18:29:39 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2020-03-11 18:32:35 +0900
commit50b7608f9d27c09093846e172f230d92b401f956 (patch)
treec3257ce2128ee0329c161f2e2b1da2191abb0ccd
parent7085e5b629dcc6eaef4205802f07979f535c8d5f (diff)
Change custom fuzzy completion API
To make it easier to write more complex fzf options. Although this does not break backward compatibility, users are encouraged to update their code accordingly. # Before _fzf_complete "FZF_ARG1 FZF_ARG2..." "$@" < <( # Print candidates ) # After _fzf_complete FZF_ARG1 FZF_ARG2... -- "$@" < <( # Print candidates )
-rw-r--r--CHANGELOG.md14
-rw-r--r--README.md53
-rw-r--r--shell/completion.bash32
-rw-r--r--shell/completion.zsh42
-rwxr-xr-xtest/test_go.rb53
5 files changed, 177 insertions, 17 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a16fd53..41d949e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,20 @@ CHANGELOG
- Bug fixes and improvements
- Vim plugin: Floating windows support
- bash: Various improvements in key bindings (CTRL-T, CTRL-R, ALT-C)
+- Fuzzy completion API changed
+ ```sh
+ # Previous: fzf arguments given as a single string argument
+ # - This style is still supported, but it is deprecated
+ _fzf_complete "--multi --reverse --prompt=\"doge> \"" "$@" < <(
+ echo foo
+ )
+
+ # New API: multiple fzf arguments before "--"
+ # - More rebust and easier to write options
+ _fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
+ echo foo
+ )
+ ```
0.20.0
------
diff --git a/README.md b/README.md
index 669881b2..bfd85800 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@ Table of Contents
* [Environment variables / Aliases](#environment-variables--aliases)
* [Settings](#settings)
* [Supported commands](#supported-commands)
+ * [Custom fuzzy completion](#custom-fuzzy-completion)
* [Vim plugin](#vim-plugin)
* [Advanced topics](#advanced-topics)
* [Performance](#performance)
@@ -414,7 +415,7 @@ _fzf_comprun() {
case "$command" in
cd) fzf "$@" --preview 'tree -C {} | head -200' ;;
- export|unset) fzf "$@" --preview "eval 'echo \$'{}" "$@" ;;
+ export|unset) fzf "$@" --preview "eval 'echo \$'{}" ;;
ssh) fzf "$@" --preview 'dig {}' ;;
*) fzf "$@" ;;
esac
@@ -433,6 +434,56 @@ _fzf_setup_completion path ag git kubectl
_fzf_setup_completion dir tree
```
+#### Custom fuzzy completion
+
+_**(Custom completion API is experimental and subject to change)**_
+
+For a command named _"COMMAND"_, define `_fzf_complete_COMMAND` function using
+`_fzf_complete` helper.
+
+```sh
+# Custom fuzzy completion for "doge" command
+# e.g. doge **<TAB>
+_fzf_complete_doge() {
+ _fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
+ echo very
+ echo wow
+ echo such
+ echo doge
+ )
+}
+```
+
+- The arguments before `--` are the options to fzf.
+- After `--`, simply pass the original completion arguments unchanged (`"$@"`).
+- Then write a set of commands that generates the completion candidates and
+ feed its output to the function using process substitution (`< <(...)`).
+
+zsh will automatically pick up the function using the naming convention but in
+bash you have to manually associate the function with the command using
+`complete` command.
+
+```sh
+[ -n "$BASH" ] && complete -F _fzf_complete_doge -o default -o bashdefault doge
+```
+
+If you need to post-process the output from fzf, define
+`_fzf_complete_COMMAND_post` as follows.
+
+```sh
+_fzf_complete_foo() {
+ _fzf_complete --multi --reverse --header-lines=3 -- "$@" < <(
+ ls -al
+ )
+}
+
+_fzf_complete_foo_post() {
+ awk '{print $NF}'
+}
+
+[ -n "$BASH" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo
+```
+
Vim plugin
----------
diff --git a/shell/completion.bash b/shell/completion.bash
index bd94c51a..b1f7ac97 100644
--- a/shell/completion.bash
+++ b/shell/completion.bash
@@ -190,6 +190,27 @@ __fzf_generic_path_completion() {
}
_fzf_complete() {
+ # Split arguments around --
+ local args rest str_arg i sep
+ args=("$@")
+ sep=
+ for i in "${!args[@]}"; do
+ if [[ "${args[$i]}" = -- ]]; then
+ sep=$i
+ break
+ fi
+ done
+ if [[ -n "$sep" ]]; then
+ str_arg=
+ rest=("${args[@]:$((sep + 1)):${#args[@]}}")
+ args=("${args[@]:0:$sep}")
+ else
+ str_arg=$1
+ args=()
+ shift
+ rest=("$@")
+ fi
+
local cur selected trigger cmd post
post="$(caller 0 | awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post=cat
@@ -200,7 +221,7 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}}
- selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $1" __fzf_comprun "$2" -q "$cur" | $post | tr '\n' ' ')
+ selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
@@ -210,8 +231,7 @@ _fzf_complete() {
printf '\e[5n'
return 0
else
- shift
- _fzf_handle_dynamic_completion "$cmd" "$@"
+ _fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
fi
}
@@ -243,7 +263,7 @@ _fzf_complete_kill() {
}
_fzf_host_completion() {
- _fzf_complete '+m' "$@" < <(
+ _fzf_complete +m -- "$@" < <(
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
@@ -252,13 +272,13 @@ _fzf_host_completion() {
}
_fzf_var_completion() {
- _fzf_complete '-m' "$@" < <(
+ _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_alias_completion() {
- _fzf_complete '-m' "$@" < <(
+ _fzf_complete -m -- "$@" < <(
alias | sed 's/=.*//' | sed 's/.* //'
)
}
diff --git a/shell/completion.zsh b/shell/completion.zsh
index 95f6d685..e82de4c9 100644
--- a/shell/completion.zsh
+++ b/shell/completion.zsh
@@ -107,16 +107,38 @@ _fzf_feed_fifo() (
)
_fzf_complete() {
- local fifo fzf_opts lbuf cmd matches post
+ setopt localoptions ksh_arrays
+ # Split arguments around --
+ local args rest str_arg i sep
+ args=("$@")
+ sep=
+ for i in {0..$#args}; do
+ if [[ "${args[$i]}" = -- ]]; then
+ sep=$i
+ break
+ fi
+ done
+ if [[ -n "$sep" ]]; then
+ str_arg=
+ rest=("${args[@]:$((sep + 1)):${#args[@]}}")
+ args=("${args[@]:0:$sep}")
+ else
+ str_arg=$1
+ args=()
+ shift
+ rest=("$@")
+ fi
+
+ local fifo lbuf cmd matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
- fzf_opts=$1
- lbuf=$2
+ lbuf=${rest[0]}
cmd=$(__fzf_extract_command "$lbuf")
- post="${funcstack[2]}_post"
+ post="${funcstack[1]}_post"
+ echo "$post"
type $post > /dev/null 2>&1 || post=cat
_fzf_feed_fifo "$fifo"
- matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
+ matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
@@ -125,14 +147,14 @@ _fzf_complete() {
}
_fzf_complete_telnet() {
- _fzf_complete '+m' "$@" < <(
+ _fzf_complete +m -- "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_ssh() {
- _fzf_complete '+m' "$@" < <(
+ _fzf_complete +m -- "$@" < <(
setopt localoptions nonomatch
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
@@ -142,19 +164,19 @@ _fzf_complete_ssh() {
}
_fzf_complete_export() {
- _fzf_complete '-m' "$@" < <(
+ _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unset() {
- _fzf_complete '-m' "$@" < <(
+ _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unalias() {
- _fzf_complete '+m' "$@" < <(
+ _fzf_complete +m -- "$@" < <(
alias | sed 's/=.*//'
)
}
diff --git a/test/test_go.rb b/test/test_go.rb
index fa4a6ef9..9bacb8ba 100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -2064,6 +2064,21 @@ module CompletionTest
tmux.send_keys :Enter
tmux.until { |lines| lines[-1].include? 'test3test4' }
end
+
+ def test_custom_completion_api
+ %w[f g].each do |command|
+ tmux.prepare
+ tmux.send_keys "#{command} b**", :Tab
+ tmux.until do |lines|
+ lines.item_count == 2 && lines.match_count == 1 &&
+ lines.any_include?("prompt-#{command}") &&
+ lines.any_include?("preview-#{command}-bar")
+ end
+ tmux.send_keys :Enter
+ tmux.until { |lines| lines[-1].include?("#{command} #{command}barbar") }
+ tmux.send_keys 'C-u'
+ end
+ end
end
class TestBash < TestBase
@@ -2149,3 +2164,41 @@ source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
unset <%= UNSETS.join(' ') %>
+
+# Old API
+_fzf_complete_f() {
+ _fzf_complete "--multi --prompt \"prompt-f> \"" "$@" < <(
+ echo foo
+ echo bar
+ )
+}
+
+# New API
+_fzf_complete_g() {
+ _fzf_complete --multi --prompt "prompt-g> " -- "$@" < <(
+ echo foo
+ echo bar
+ )
+}
+
+_fzf_complete_f_post() {
+ awk '{print "f" $0 $0}'
+}
+
+_fzf_complete_g_post() {
+ awk '{print "g" $0 $0}'
+}
+
+[ -n "$BASH" ] && complete -F _fzf_complete_f -o default -o bashdefault f
+[ -n "$BASH" ] && complete -F _fzf_complete_g -o default -o bashdefault g
+
+_fzf_comprun() {
+ local command=$1
+ shift
+
+ case "$command" in
+ f) fzf "$@" --preview 'echo preview-f-{}' ;;
+ g) fzf "$@" --preview 'echo preview-g-{}' ;;
+ *) fzf "$@" ;;
+ esac
+}