Skip to content

Commit 9617647

Browse files
committed
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)
1 parent 68c8426 commit 9617647

File tree

4 files changed

+84
-35
lines changed

4 files changed

+84
-35
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,14 @@ export FZF_COMPLETION_TRIGGER='~~'
259259

260260
# Options to fzf command
261261
export FZF_COMPLETION_OPTS='+c -x'
262+
263+
# Use ag instead of the default find command for listing candidates.
264+
# - The first argument to the function is the base path to start traversal
265+
# - Note that ag only lists files not directories
266+
# - See the source code (completion.{bash,zsh}) for the details.
267+
_fzf_compgen_paths() {
268+
ag -g "" "$1"
269+
}
262270
```
263271

264272
#### Supported commands

shell/completion.bash

+35-26
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@
1010
# - $FZF_COMPLETION_TRIGGER (default: '**')
1111
# - $FZF_COMPLETION_OPTS (default: empty)
1212

13+
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
14+
if ! declare -f _fzf_compgen_path > /dev/null; then
15+
_fzf_compgen_path() {
16+
echo "$1"
17+
\find -L "$1" \
18+
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
19+
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
20+
}
21+
fi
22+
23+
if ! declare -f _fzf_compgen_dir > /dev/null; then
24+
_fzf_compgen_dir() {
25+
\find -L "$1" \
26+
-name .git -prune -o -name .svn -prune -o -type d \
27+
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
28+
}
29+
fi
30+
31+
###########################################################
32+
1333
_fzf_orig_completion_filter() {
1434
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
1535
awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}'
@@ -113,7 +133,7 @@ __fzf_generic_path_completion() {
113133
[ -z "$dir" ] && dir='.'
114134
[ "$dir" != "/" ] && dir="${dir/%\//}"
115135
tput sc
116-
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
136+
matches=$(eval "$1 $(printf %q "$dir")" | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
117137
printf "%q$3 " "$item"
118138
done)
119139
matches=${matches% }
@@ -171,21 +191,16 @@ _fzf_complete() {
171191
}
172192
173193
_fzf_path_completion() {
174-
__fzf_generic_path_completion \
175-
"-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
176-
"-m" "" "$@"
194+
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
177195
}
178196
197+
# Deprecated. No file only completion.
179198
_fzf_file_completion() {
180-
__fzf_generic_path_completion \
181-
"-name .git -prune -o -name .svn -prune -o ( -type f -o -type l )" \
182-
"-m" "" "$@"
199+
_fzf_path_completion "$@"
183200
}
184201
185202
_fzf_dir_completion() {
186-
__fzf_generic_path_completion \
187-
"-name .git -prune -o -name .svn -prune -o -type d" \
188-
"" "/" "$@"
203+
__fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
189204
}
190205
191206
_fzf_complete_kill() {
@@ -239,13 +254,12 @@ _fzf_complete_unalias() {
239254
# fzf options
240255
complete -o default -F _fzf_opts_completion fzf
241256
242-
d_cmds="cd pushd rmdir"
243-
f_cmds="
257+
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
258+
a_cmds="
244259
awk cat diff diff3
245260
emacs emacsclient ex file ftp g++ gcc gvim head hg java
246261
javac ld less more mvim nvim patch perl python ruby
247-
sed sftp sort source tail tee uniq vi view vim wc xdg-open"
248-
a_cmds="
262+
sed sftp sort source tail tee uniq vi view vim wc xdg-open
249263
basename bunzip2 bzip2 chmod chown curl cp dirname du
250264
find git grep gunzip gzip hg jar
251265
ln ls mv open rm rsync scp
@@ -256,7 +270,7 @@ x_cmds="kill ssh telnet unset unalias export"
256270
if [ "$_fzf_completion_loaded" != '0.10.8' ]; then
257271
# Really wish I could use associative array but OSX comes with bash 3.2 :(
258272
eval $(complete | \grep '\-F' | \grep -v _fzf_ |
259-
\grep -E " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
273+
\grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
260274
export _fzf_completion_loaded=0.10.8
261275
fi
262276
@@ -278,21 +292,16 @@ _fzf_defc() {
278292
fi
279293
}
280294
281-
# Directory
282-
for cmd in $d_cmds; do
283-
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
284-
done
285-
286-
# File
287-
for cmd in $f_cmds; do
288-
_fzf_defc "$cmd" _fzf_file_completion "-o default -o bashdefault"
289-
done
290-
291295
# Anything
292296
for cmd in $a_cmds; do
293297
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
294298
done
295299
300+
# Directory
301+
for cmd in $d_cmds; do
302+
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
303+
done
304+
296305
unset _fzf_defc
297306
298307
# Kill completion
@@ -307,4 +316,4 @@ complete -F _fzf_complete_unset -o default -o bashdefault unset
307316
complete -F _fzf_complete_export -o default -o bashdefault export
308317
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
309318
310-
unset cmd d_cmds f_cmds a_cmds x_cmds
319+
unset cmd d_cmds a_cmds x_cmds

shell/completion.zsh

+26-8
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,32 @@
1010
# - $FZF_COMPLETION_TRIGGER (default: '**')
1111
# - $FZF_COMPLETION_OPTS (default: empty)
1212

13+
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
14+
if ! declare -f _fzf_compgen_path > /dev/null; then
15+
_fzf_compgen_path() {
16+
echo "$1"
17+
\find -L "$1" \
18+
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
19+
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
20+
}
21+
fi
22+
23+
if ! declare -f _fzf_compgen_dir > /dev/null; then
24+
_fzf_compgen_dir() {
25+
\find -L "$1" \
26+
-name .git -prune -o -name .svn -prune -o -type d \
27+
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
28+
}
29+
fi
30+
31+
###########################################################
32+
1333
__fzf_generic_path_completion() {
14-
local base lbuf find_opts fzf_opts suffix tail fzf dir leftover matches nnm
34+
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches nnm
1535
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
1636
base=${(Q)1}
1737
lbuf=$2
18-
find_opts=$3
38+
compgen=$3
1939
fzf_opts=$4
2040
suffix=$5
2141
tail=$6
@@ -33,7 +53,7 @@ __fzf_generic_path_completion() {
3353
[ -z "$dir" ] && dir='.'
3454
[ "$dir" != "/" ] && dir="${dir/%\//}"
3555
dir=${~dir}
36-
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
56+
matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do
3757
printf "%q$suffix " "$item"
3858
done)
3959
matches=${matches% }
@@ -50,14 +70,12 @@ __fzf_generic_path_completion() {
5070
}
5171
5272
_fzf_path_completion() {
53-
__fzf_generic_path_completion "$1" "$2" \
54-
"-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
73+
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
5574
"-m" "" " "
5675
}
5776
5877
_fzf_dir_completion() {
59-
__fzf_generic_path_completion "$1" "$2" \
60-
"-name .git -prune -o -name .svn -prune -o -type d" \
78+
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
6179
"" "/" ""
6280
}
6381
@@ -145,7 +163,7 @@ fzf-completion() {
145163
zle redisplay
146164
# Trigger sequence given
147165
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
148-
d_cmds=(cd pushd rmdir)
166+
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
149167
150168
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
151169
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}

test/test_go.rb

+15-1
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ def test_file_completion
12691269
tmux.send_keys 'C-u'
12701270
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0
12711271
tmux.until(1) { |lines| lines.item_count > 0 }
1272-
tmux.send_keys :Enter
1272+
tmux.send_keys 'C-K', :Enter
12731273
tmux.until do |lines|
12741274
tmux.send_keys 'C-L'
12751275
lines[-1].end_with?('/tmp/fzf\ test/foobar')
@@ -1339,6 +1339,20 @@ def test_process_completion
13391339
tmux.send_keys 'C-L'
13401340
lines[-1] == "kill #{pid}"
13411341
end
1342+
1343+
def test_custom_completion
1344+
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
1345+
tmux.prepare
1346+
tmux.send_keys 'ls /tmp/**', :Tab, pane: 0
1347+
tmux.until(1) { |lines| lines.item_count == 11 }
1348+
tmux.send_keys :BTab, :BTab, :BTab
1349+
tmux.until(1) { |lines| lines[-2].include? '(3)' }
1350+
tmux.send_keys :Enter
1351+
tmux.until do |lines|
1352+
tmux.send_keys 'C-L'
1353+
lines[-1] == "ls /tmp 1 2"
1354+
end
1355+
end
13421356
ensure
13431357
Process.kill 'KILL', pid.to_i rescue nil if pid
13441358
end

0 commit comments

Comments
 (0)