Skip to content

Commit 199851f

Browse files
authored
Merge pull request #696 from akinomyoga/extend-search-paths
2 parents c314aec + b22af05 commit 199851f

File tree

14 files changed

+204
-17
lines changed

14 files changed

+204
-17
lines changed

README.md

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,12 @@ A. No. Use `M-/` to (in the words of the bash man page) attempt file
112112

113113
A. Install a local completion of your own appropriately for the desired
114114
command, and it will take precedence over the one shipped by us. See the
115-
next answer for details where to install it, if you are doing it on per
116-
user basis. If you want to do it system wide, you can install eagerly
117-
loaded files in `compatdir` (see a couple of questions further down for
118-
more info) and install a completion for the commands to override our
119-
completion for in them.
115+
next answer for details where to install it, if you are doing it on per user
116+
basis. If you want to do it system wide, you can install eagerly loaded
117+
files in `compatdir` (see a couple of questions further down for more
118+
info. To get the path of `compatdir` for the current system, the output of
119+
`pkg-config bash-completion --variable compatdir` can be used) and install a
120+
completion for the commands to override our completion for in them.
120121

121122
If you want to use bash's default completion instead of one of ours,
122123
something like this should work (where `$cmd` is the command to override
@@ -138,9 +139,14 @@ A. Put them in the `completions` subdir of `$BASH_COMPLETION_USER_DIR`
138139
completion code for this package. Where should I put it to be sure
139140
that interactive bash shells will find it and source it?**
140141

141-
A. Install it in one of the directories pointed to by
142-
bash-completion's `pkgconfig` file variables. There are two
143-
alternatives:
142+
A. [ Disclaimer: Here, how to make the completion code visible to
143+
bash-completion is explained. We do not require always making the
144+
completion code visible to bash-completion. In what condition the
145+
completion code is installed should be determined at the author/maintainers'
146+
own discretion. ]
147+
148+
Install it in one of the directories pointed to by bash-completion's
149+
`pkgconfig` file variables. There are two alternatives:
144150

145151
- The recommended directory is `completionsdir`, which you can get with
146152
`pkg-config --variable=completionsdir bash-completion`. From this
@@ -169,7 +175,7 @@ A. Install it in one of the directories pointed to by
169175

170176
```makefile
171177
bashcompdir = @bashcompdir@
172-
dist_bashcomp_DATA = # completion files go here
178+
dist_bashcomp_DATA = your-completion-file # completion files go here
173179
```
174180
175181
For cmake we ship the `bash-completion-config.cmake` and
@@ -190,6 +196,28 @@ A. Install it in one of the directories pointed to by
190196
${BASH_COMPLETION_COMPLETIONSDIR})
191197
```
192198

199+
In bash-completion >= 2.12, we search the data directory of
200+
`bash-completion` under the installation prefix where the target command is
201+
installed. When one can assume that the version of the target
202+
bash-completion is 2.12 or higher, the completion script can actually be
203+
installed to `$PREFIX/share/bash-completion/completions/` under the same
204+
installation prefix as the target program installed under `$PREFIX/bin/` or
205+
`$PREFIX/sbin/`. For the detailed search order, see also "Q. What is the
206+
search order for the completion file of each target command?" below.
207+
208+
Example for `Makefile.am`:
209+
210+
```makefile
211+
bashcompdir = $(datarootdir)/bash-completion/completions
212+
dist_bashcomp_DATA = your-completion-file
213+
```
214+
215+
Example for `CMakeLists.txt`:
216+
217+
```cmake
218+
install(FILES your-completion-file DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions")
219+
```
220+
193221
**Q. When completing on a symlink to a directory, bash does not append
194222
the trailing `/` and I have to hit <kbd>&lt;Tab></kbd> again.
195223
I don't like this.**
@@ -299,3 +327,28 @@ A. Absolutely not. zsh has an extremely sophisticated completion system
299327
that offers many features absent from the bash implementation. Its
300328
users often cannot resist pointing this out. More information can
301329
be found at <https://www.zsh.org/>.
330+
331+
**Q. What is the search order for the completion file of each target command?**
332+
333+
A. The completion files of commands are looked up by the shell function
334+
`__load_completion`. Here, the search order in bash-completion >= 2.12 is
335+
explained.
336+
337+
1. `BASH_COMPLETION_USER_DIR`. The subdirectory `completions` of each paths
338+
in `BASH_COMPLETION_USER_DIR` separated by colons is considered for a
339+
completion directory.
340+
2. The location of the main `bash_completion` file. The subdirectory
341+
`completions` in the same directory as `bash_completion` is considered.
342+
3. The location of the target command. When the real location of the command
343+
is in the directory `<prefix>/bin` or `<prefix>/sbin`, the directory
344+
`<prefix>/share/bash-completion/completions` is considered.
345+
4. `XDG_DATA_DIRS` (or the system directories `/usr/local/share:/usr/share`
346+
if empty). The subdirectory `bash-completion/completions` of each paths
347+
in `XDG_DATA_DIRS` separated by colons is considered.
348+
349+
The completion files of the name `<cmd>` or `<cmd>.bash`, where `<cmd>` is
350+
the name of the target command, are searched in the above completion
351+
directories in order. The file that is found first is used. When no
352+
completion file is found in any completion directories in this process, the
353+
completion files of the name `_<cmd>` is next searched in the completion
354+
directories in order.

bash_completion

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2587,20 +2587,48 @@ complete -F _minimal ''
25872587
25882588
__load_completion()
25892589
{
2590-
local -a dirs=(${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions)
2591-
local IFS=: dir cmd="${1##*/}" compfile
2590+
local cmd="${1##*/}" dir compfile
2591+
local -a paths
25922592
[[ $cmd ]] || return 1
2593-
for dir in ${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do
2594-
dirs+=($dir/bash-completion/completions)
2595-
done
2596-
_comp_unlocal IFS
25972593
2594+
local -a dirs=()
2595+
2596+
# Lookup order:
2597+
# 1) From BASH_COMPLETION_USER_DIR (e.g. ~/.local/share/bash-completion):
2598+
# User installed completions.
2599+
if [[ ${BASH_COMPLETION_USER_DIR-} ]]; then
2600+
_comp_split -F : paths "$BASH_COMPLETION_USER_DIR"
2601+
dirs+=("${paths[@]/%//completions}")
2602+
else
2603+
dirs=("${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions")
2604+
fi
2605+
2606+
# 2) From the location of bash_completion: Completions relative to the main
2607+
# script. This is primarily for run-in-place-from-git-clone setups, where
2608+
# we want to prefer in-tree completions over ones possibly coming with a
2609+
# system installed bash-completion. (Due to usual install layouts, this
2610+
# often hits the correct completions in system installations, too.)
25982611
if [[ $BASH_SOURCE == */* ]]; then
25992612
dirs+=("${BASH_SOURCE%/*}/completions")
26002613
else
26012614
dirs+=(./completions)
26022615
fi
26032616
2617+
# 3) From bin directories extracted from $(realpath "$cmd") and PATH
2618+
dir=$(_realcommand "$1")
2619+
paths=("${dir%/*}")
2620+
_comp_split -aF : paths "$PATH"
2621+
for dir in "${paths[@]%/}"; do
2622+
if [[ -d $dir && $dir == ?*/@(bin|sbin) ]]; then
2623+
dirs+=("${dir%/*}/share/bash-completion/completions")
2624+
fi
2625+
done
2626+
2627+
# 4) From XDG_DATA_DIRS or system dirs (e.g. /usr/share, /usr/local/share):
2628+
# Completions in the system data dirs.
2629+
_comp_split -F : paths "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
2630+
dirs+=("${paths[@]/%//bash-completion/completions}")
2631+
26042632
local backslash=
26052633
if [[ $cmd == \\* ]]; then
26062634
cmd=${cmd:1}
@@ -2611,7 +2639,7 @@ __load_completion()
26112639
26122640
for dir in "${dirs[@]}"; do
26132641
[[ -d $dir ]] || continue
2614-
for compfile in "$cmd" "$cmd.bash" "_$cmd"; do
2642+
for compfile in "$cmd" "$cmd.bash"; do
26152643
compfile="$dir/$compfile"
26162644
# Avoid trying to source dirs as long as we support bash < 4.3
26172645
# to avoid an fd leak; https://bugzilla.redhat.com/903540
@@ -2626,6 +2654,22 @@ __load_completion()
26262654
done
26272655
done
26282656
2657+
# search deprecated completions "_$cmd"
2658+
for dir in "${dirs[@]}"; do
2659+
[[ -d $dir ]] || continue
2660+
compfile="$dir/_$cmd"
2661+
# Avoid trying to source dirs as long as we support bash < 4.3
2662+
# to avoid an fd leak; https://bugzilla.redhat.com/903540
2663+
if [[ -d $compfile ]]; then
2664+
# Do not warn with . or .. (especially the former is common)
2665+
[[ $compfile == */.?(.) ]] ||
2666+
echo "bash_completion: $compfile: is a directory" >&2
2667+
elif [[ -e $compfile ]] && . "$compfile"; then
2668+
[[ $backslash ]] && $(complete -p "$cmd") "\\$cmd"
2669+
return 0
2670+
fi
2671+
done
2672+
26292673
# Look up simple "xspec" completions
26302674
[[ -v _xspecs[$cmd] ]] &&
26312675
complete -F _filedir_xspec "$cmd" "$backslash$cmd" && return 0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../prefix1/bin/cmd1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../prefix1/sbin/cmd2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo cmd1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo sh
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo cmd2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo 'cmd1: sourced from prefix1'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo 'cmd2: sourced from prefix1'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo 'sh: sourced from prefix1'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo 'cmd1: sourced from userdir1'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo 'cmd2: sourced from userdir2'

test/t/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def bash(request) -> pexpect.spawn:
268268
# FIXME: Tests shouldn't depend on dimensions, but it's difficult to
269269
# expect robustly enough for Bash to wrap lines anywhere (e.g. inside
270270
# MAGIC_MARK). Increase window width to reduce wrapping.
271-
dimensions=(24, 200),
271+
dimensions=(24, 240),
272272
# TODO? codec_errors="replace",
273273
)
274274
bash.expect_exact(PS1)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import pytest
2+
3+
from conftest import assert_bash_exec, bash_env_saved
4+
5+
6+
@pytest.mark.bashcomp(cmd=None, cwd="__load_completion")
7+
class TestLoadCompletion:
8+
def test_userdir_1(self, bash):
9+
with bash_env_saved(bash) as bash_env:
10+
bash_env.write_variable(
11+
"BASH_COMPLETION_USER_DIR",
12+
"$PWD/userdir1:$PWD/userdir2:$BASH_COMPLETION_USER_DIR",
13+
quote=False,
14+
)
15+
bash_env.write_variable(
16+
"PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False
17+
)
18+
output = assert_bash_exec(
19+
bash, "__load_completion cmd1", want_output=True
20+
)
21+
assert output.strip() == "cmd1: sourced from userdir1"
22+
output = assert_bash_exec(
23+
bash, "__load_completion cmd2", want_output=True
24+
)
25+
assert output.strip() == "cmd2: sourced from userdir2"
26+
27+
def test_PATH_1(self, bash):
28+
with bash_env_saved(bash) as bash_env:
29+
bash_env.write_variable(
30+
"PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False
31+
)
32+
output = assert_bash_exec(
33+
bash, "__load_completion cmd1", want_output=True
34+
)
35+
assert output.strip() == "cmd1: sourced from prefix1"
36+
output = assert_bash_exec(
37+
bash, "__load_completion cmd2", want_output=True
38+
)
39+
assert output.strip() == "cmd2: sourced from prefix1"
40+
41+
def test_cmd_path_1(self, bash):
42+
output = assert_bash_exec(
43+
bash, "__load_completion prefix1/bin/cmd1", want_output=True
44+
)
45+
assert output.strip() == "cmd1: sourced from prefix1"
46+
output = assert_bash_exec(
47+
bash, "__load_completion prefix1/sbin/cmd2", want_output=True
48+
)
49+
assert output.strip() == "cmd2: sourced from prefix1"
50+
output = assert_bash_exec(
51+
bash, "__load_completion bin/cmd1", want_output=True
52+
)
53+
assert output.strip() == "cmd1: sourced from prefix1"
54+
output = assert_bash_exec(
55+
bash, "__load_completion bin/cmd2", want_output=True
56+
)
57+
assert output.strip() == "cmd2: sourced from prefix1"
58+
59+
def test_cmd_path_2(self, bash):
60+
with bash_env_saved(bash) as bash_env:
61+
bash_env.write_variable("PATH", "$PWD/bin:$PATH", quote=False)
62+
output = assert_bash_exec(
63+
bash, "__load_completion cmd1", want_output=True
64+
)
65+
assert output.strip() == "cmd1: sourced from prefix1"
66+
output = assert_bash_exec(
67+
bash, "__load_completion cmd2", want_output=True
68+
)
69+
assert output.strip() == "cmd2: sourced from prefix1"
70+
71+
def test_cmd_intree_precedence(self, bash):
72+
"""
73+
Test in-tree, i.e. completions/$cmd relative to the main script
74+
has precedence over location derived from PATH.
75+
"""
76+
with bash_env_saved(bash) as bash_env:
77+
bash_env.write_variable("PATH", "$PWD/prefix1/bin", quote=False)
78+
# The in-tree `sh` completion should be loaded here,
79+
# and cause no output, unlike our `$PWD/prefix1/bin/sh` canary.
80+
assert_bash_exec(bash, "__load_completion sh", want_output=False)

0 commit comments

Comments
 (0)