Skip to content

Commit b1374fc

Browse files
authored
Merge pull request #1361 from akinomyoga/cdable_vars-filter
fix(cd): generate only cdable_vars containing valid directory paths
2 parents 461ae45 + 355eb4d commit b1374fc

File tree

2 files changed

+72
-39
lines changed

2 files changed

+72
-39
lines changed

completions/cd

+58-39
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,78 @@
11
# cd(1) completion -*- shell-script -*-
22

3-
# This meta-cd function observes the CDPATH variable, so that `cd`
4-
# additionally completes on directories under those specified in CDPATH.
5-
_comp_cmd_cd()
3+
_comp_cmd_cd__compgen_cdable_vars()
64
{
7-
local cur prev words cword comp_args
8-
_comp_initialize -- "$@" || return
5+
shopt -q cdable_vars || return 1
96

10-
if [[ $cur == -* ]]; then
11-
_comp_compgen_help -c help "$1"
12-
compopt +o nospace
13-
return
14-
fi
7+
local vars
8+
_comp_compgen -v vars -- -v || return "$?"
159

16-
local i j k
10+
# Remove variables that do not contain a valid directory path.
11+
local _i
12+
for _i in "${!vars[@]}"; do
13+
# Note: ${!vars[_i]} produces the "nounset" error when vars[_i] is an
14+
# empty array name.
15+
[[ -d ${!vars[_i]-} ]] || unset -v 'vars[_i]'
16+
done
1717

18-
compopt -o filenames
18+
_comp_compgen -U vars set "${vars[@]}"
19+
}
20+
21+
# This generator function observes the CDPATH variable, to additionally
22+
# complete directories under those specified in CDPATH.
23+
_comp_cmd_cd__compgen_cdpath()
24+
{
25+
local _p
1926

20-
# Use standard dir completion if no CDPATH or parameter starts with /,
27+
# Generate CDPATH completions when the parameter does not start with /,
2128
# ./ or ../
22-
if [[ ! ${CDPATH-} || $cur == ?(.)?(.)/* ]]; then
23-
_comp_compgen_filedir -d
24-
return
25-
fi
29+
[[ ! ${CDPATH-} || $cur == ?(.)?(.)/* ]] && return 1
2630

27-
local mark_dirs="" mark_symdirs=""
28-
_comp_readline_variable_on mark-directories && mark_dirs=set
29-
_comp_readline_variable_on mark-symlinked-directories && mark_symdirs=set
31+
local _mark_dirs="" _mark_symdirs=""
32+
_comp_readline_variable_on mark-directories && _mark_dirs=set
33+
_comp_readline_variable_on mark-symlinked-directories && _mark_symdirs=set
34+
35+
local -a _cdpaths=()
3036

3137
# we have a CDPATH, so loop on its contents
32-
local paths dirs
38+
local paths dirs _d
3339
_comp_split -F : paths "$CDPATH"
34-
for i in "${paths[@]}"; do
40+
for _p in "${paths[@]}"; do
3541
# create an array of matched subdirs
36-
k=${#COMPREPLY[@]}
37-
_comp_compgen -v dirs -c "$i/$cur" -- -d
38-
for j in "${dirs[@]}"; do
39-
if [[ ($mark_symdirs && -L $j || $mark_dirs && ! -L $j) && ! -d ${j#"$i/"} ]]; then
40-
j+="/"
42+
_comp_compgen -v dirs -c "$_p/$cur" -- -d
43+
for _d in "${dirs[@]}"; do
44+
if [[ ($_mark_symdirs && -L $_d || $_mark_dirs && ! -L $_d) && ! -d ${_d#"$_p/"} ]]; then
45+
_d+="/"
4146
fi
42-
COMPREPLY[k++]=${j#"$i/"}
47+
_cdpaths+=("${_d#"$_p/"}")
4348
done
4449
done
50+
_comp_unlocal paths dirs
4551

46-
_comp_compgen -a filedir -d
47-
48-
if ((${#COMPREPLY[@]} == 1)); then
49-
i=${COMPREPLY[0]}
50-
if [[ $i == "$cur" && $i != "*/" ]]; then
51-
COMPREPLY[0]="${i}/"
52+
if ((${#_cdpaths[@]} == 1)); then
53+
_p=${_cdpaths[0]}
54+
if [[ $_p == "$cur" && $_p != */ ]]; then
55+
_cdpaths[0]=$_p/
5256
fi
5357
fi
58+
59+
_comp_compgen_set "${_cdpaths[@]}"
60+
}
61+
62+
_comp_cmd_cd()
63+
{
64+
local cur prev words cword comp_args
65+
_comp_initialize -- "$@" || return
66+
67+
if [[ $cur == -* ]]; then
68+
_comp_compgen_help -c help "$1"
69+
compopt +o nospace
70+
return
71+
fi
72+
73+
compopt -o filenames
74+
_comp_cmd_cd__compgen_cdable_vars
75+
_comp_cmd_cd__compgen_cdpath
76+
_comp_compgen -a filedir -d
5477
}
55-
if shopt -q cdable_vars; then
56-
complete -v -F _comp_cmd_cd -o nospace cd pushd
57-
else
58-
complete -F _comp_cmd_cd -o nospace cd pushd
59-
fi
78+
complete -F _comp_cmd_cd -o nospace cd pushd

test/t/test_cd.py

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import pytest
22

3+
from conftest import assert_complete, bash_env_saved
4+
35

46
@pytest.mark.bashcomp(ignore_env=r"^\+CDPATH=$")
57
class TestCd:
@@ -28,3 +30,15 @@ def test_dir_at_point(self, completion):
2830
@pytest.mark.complete("cd -")
2931
def test_options(self, completion):
3032
assert completion
33+
34+
def test_cdable_vars(self, bash):
35+
with bash_env_saved(bash) as bash_env:
36+
bash_env.shopt("cdable_vars", True)
37+
bash_env.write_variable("foo1", "shared")
38+
bash_env.write_variable("foo2", "shared/default")
39+
bash_env.write_variable("foo3", "nonexistent")
40+
bash_env.write_variable("foo4", "nonexistent")
41+
bash_env.write_variable("foo5", "shared/default/foo")
42+
bash_env.write_variable("foo6", "shared/default/bar")
43+
completion = assert_complete(bash, "cd f")
44+
assert completion == ["foo1", "foo2"]

0 commit comments

Comments
 (0)