summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2016-01-20 01:09:58 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2016-01-20 01:38:24 +0900
commit96176476f3990507b38f2ec45d24c12fcb7aa075 (patch)
tree81d15944ac1e5778e3f75888c595e23f60bff146
parent68c84264af17e735102957a5263026e3f96b1cb7 (diff)
Make fuzzy completion customizable with _fzf_compgen_{path,dir}
Notes: - You can now override _fzf_compgen_path and _fzf_compgen_dir functions to use custom commands such as ag instead of find for listing completion candidates. - The first argument is the base path to start traversal - Removed file-only completion in bash, i.e. _fzf_file_completion. Maintaining a list of commands that only expect files, not directories, is cumbersome (there are too many) and error-prone. TBD: - Added $FZF_COMPLETION_DIR_COMMANDS to customize the list of commands which use directory-only completion. The default is "cd pushd rmdir". Not sure if it's the best approach to address the requirement, I'll leave it as an undocumented feature. Related: #406 (@thomcom), #456 (@frizinak)
-rw-r--r--README.md8
-rw-r--r--shell/completion.bash61
-rw-r--r--shell/completion.zsh34
-rw-r--r--test/test_go.rb16
4 files changed, 84 insertions, 35 deletions
diff --git a/README.md b/README.md
index 567271c5..5e3a1f04 100644
--- a/README.md
+++ b/README.md
@@ -259,6 +259,14 @@ export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command
export FZF_COMPLETION_OPTS='+c -x'
+
+# Use ag instead of the default find command for listing candidates.
+# - The first argument to the function is the base path to start traversal
+# - Note that ag only lists files not directories
+# - See the source code (completion.{bash,zsh}) for the details.
+_fzf_compgen_paths() {
+ ag -g "" "$1"
+}
```
#### Supported commands
diff --git a/shell/completion.bash b/shell/completion.bash
index ef3a965c..9d84ce52 100644
--- a/shell/completion.bash
+++ b/shell/completion.bash
@@ -10,6 +10,26 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
+# To use custom commands instead of find, override _fzf_compgen_{path,dir}
+if ! declare -f _fzf_compgen_path > /dev/null; then
+ _fzf_compgen_path() {
+ echo "$1"
+ \find -L "$1" \
+ -name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
+ -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+ }
+fi
+
+if ! declare -f _fzf_compgen_dir > /dev/null; then
+ _fzf_compgen_dir() {
+ \find -L "$1" \
+ -name .git -prune -o -name .svn -prune -o -type d \
+ -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+ }
+fi
+
+###########################################################
+
_fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}'
@@ -113,7 +133,7 @@ __fzf_generic_path_completion() {
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
tput sc
- matches=$(\find -L "$dir" $1 -a -not -path "$dir" -print 2> /dev/null | sed 's@^\./@@' | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
+ matches=$(eval "$1 $(printf %q "$dir")" | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
printf "%q$3 " "$item"
done)
matches=${matches% }
@@ -171,21 +191,16 @@ _fzf_complete() {
}
_fzf_path_completion() {
- __fzf_generic_path_completion \
- "-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
- "-m" "" "$@"
+ __fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
}
+# Deprecated. No file only completion.
_fzf_file_completion() {
- __fzf_generic_path_completion \
- "-name .git -prune -o -name .svn -prune -o ( -type f -o -type l )" \
- "-m" "" "$@"
+ _fzf_path_completion "$@"
}
_fzf_dir_completion() {
- __fzf_generic_path_completion \
- "-name .git -prune -o -name .svn -prune -o -type d" \
- "" "/" "$@"
+ __fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
}
_fzf_complete_kill() {
@@ -239,13 +254,12 @@ _fzf_complete_unalias() {
# fzf options
complete -o default -F _fzf_opts_completion fzf
-d_cmds="cd pushd rmdir"
-f_cmds="
+d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
+a_cmds="
awk cat diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg java
javac ld less more mvim nvim patch perl python ruby
- sed sftp sort source tail tee uniq vi view vim wc xdg-open"
-a_cmds="
+ sed sftp sort source tail tee uniq vi view vim wc xdg-open
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
@@ -256,7 +270,7 @@ x_cmds="kill ssh telnet unset unalias export"
if [ "$_fzf_completion_loaded" != '0.10.8' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | \grep '\-F' | \grep -v _fzf_ |
- \grep -E " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
+ \grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
export _fzf_completion_loaded=0.10.8
fi
@@ -278,21 +292,16 @@ _fzf_defc() {
fi
}
-# Directory
-for cmd in $d_cmds; do
- _fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
-done
-
-# File
-for cmd in $f_cmds; do
- _fzf_defc "$cmd" _fzf_file_completion "-o default -o bashdefault"
-done
-
# Anything
for cmd in $a_cmds; do
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
done
+# Directory
+for cmd in $d_cmds; do
+ _fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
+done
+
unset _fzf_defc
# Kill completion
@@ -307,4 +316,4 @@ complete -F _fzf_complete_unset -o default -o bashdefault unset
complete -F _fzf_complete_export -o default -o bashdefault export
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
-unset cmd d_cmds f_cmds a_cmds x_cmds
+unset cmd d_cmds a_cmds x_cmds
diff --git a/shell/completion.zsh b/shell/completion.zsh
index 6e2d9321..c7dee7ab 100644
--- a/shell/completion.zsh
+++ b/shell/completion.zsh
@@ -10,12 +10,32 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
+# To use custom commands instead of find, override _fzf_compgen_{path,dir}
+if ! declare -f _fzf_compgen_path > /dev/null; then
+ _fzf_compgen_path() {
+ echo "$1"
+ \find -L "$1" \
+ -name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
+ -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+ }
+fi
+
+if ! declare -f _fzf_compgen_dir > /dev/null; then
+ _fzf_compgen_dir() {
+ \find -L "$1" \
+ -name .git -prune -o -name .svn -prune -o -type d \
+ -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+ }
+fi
+
+###########################################################
+
__fzf_generic_path_completion() {
- local base lbuf find_opts fzf_opts suffix tail fzf dir leftover matches nnm
+ local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches nnm
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
base=${(Q)1}
lbuf=$2
- find_opts=$3
+ compgen=$3
fzf_opts=$4
suffix=$5
tail=$6
@@ -33,7 +53,7 @@ __fzf_generic_path_completion() {
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
dir=${~dir}
- matches=$(\find -L "$dir" ${=find_opts} -a -not -path "$dir" -print 2> /dev/null | sed 's@^\./@@' | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do
+ matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do
printf "%q$suffix " "$item"
done)
matches=${matches% }
@@ -50,14 +70,12 @@ __fzf_generic_path_completion() {
}
_fzf_path_completion() {
- __fzf_generic_path_completion "$1" "$2" \
- "-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
+ __fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
"-m" "" " "
}
_fzf_dir_completion() {
- __fzf_generic_path_completion "$1" "$2" \
- "-name .git -prune -o -name .svn -prune -o -type d" \
+ __fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
"" "/" ""
}
@@ -145,7 +163,7 @@ fzf-completion() {
zle redisplay
# Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
- d_cmds=(cd pushd rmdir)
+ d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
diff --git a/test/test_go.rb b/test/test_go.rb
index efbbf250..a8f75c64 100644
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -1269,7 +1269,7 @@ module CompletionTest
tmux.send_keys 'C-u'
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0
tmux.until(1) { |lines| lines.item_count > 0 }
- tmux.send_keys :Enter
+ tmux.send_keys 'C-K', :Enter
tmux.until do |lines|
tmux.send_keys 'C-L'
lines[-1].end_with?('/tmp/fzf\ test/foobar')
@@ -1339,6 +1339,20 @@ module CompletionTest
tmux.send_keys 'C-L'
lines[-1] == "kill #{pid}"
end
+
+ def test_custom_completion
+ tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
+ tmux.prepare
+ tmux.send_keys 'ls /tmp/**', :Tab, pane: 0
+ tmux.until(1) { |lines| lines.item_count == 11 }
+ tmux.send_keys :BTab, :BTab, :BTab
+ tmux.until(1) { |lines| lines[-2].include? '(3)' }
+ tmux.send_keys :Enter
+ tmux.until do |lines|
+ tmux.send_keys 'C-L'
+ lines[-1] == "ls /tmp 1 2"
+ end
+ end
ensure
Process.kill 'KILL', pid.to_i rescue nil if pid
end