Skip to content

Commit 5927d57

Browse files
authored
Merge pull request #546 from akinomyoga/fix-make-for-show-all-if-ambiguous
fix(make): fix stripped directory names with `bind 'set show-all-if-ambiguous on'`
2 parents fd7eadc + 5eb1042 commit 5927d57

File tree

3 files changed

+127
-8
lines changed

3 files changed

+127
-8
lines changed

completions/make

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ _make_target_extract_script()
4646
/^$/ { # end of target block
4747
x; # unhold target
4848
/^$/d; # dont print blanks
49-
s|^${dirname_re-}\(.\{${#basename}\}[^:/]*/\{0,1\}\)[^:]*:.*$|${output}|p;
49+
s|^${dirname_re-}\(.\{${#basename}\}[^:]*\):.*$|${output}|p;
5050
d; # hide any bugs
5151
}
5252
@@ -87,6 +87,56 @@ EOF
8787
EOF
8888
}
8989

90+
# Truncate the non-unique filepaths in COMPRELY to only generate unique
91+
# directories or files. This function discards the files under subdirectories
92+
# unless the path is unique under each subdirectory and instead generate the
93+
# subdirectory path. For example, when there are two candidates, "abc/def" and
94+
# "abc/xyz", we generate "abc/" instead of generating both candidates directly.
95+
# When there is only one candidate "abc/def", we generate the full path
96+
# "abc/def".
97+
#
98+
# @var[in] cur
99+
# @var[in] mode
100+
# @var[in,out] COMPREPLY
101+
_comp_make__truncate_non_unique_paths()
102+
{
103+
local prefix=$cur
104+
[[ $mode == -d ]] && prefix=
105+
if ((${#COMPREPLY[@]} > 0)); then
106+
# collect the possible completions including the directory names in
107+
# `paths' and count the number of children of each subdirectory in
108+
# `nchild'.
109+
local -A paths nchild
110+
local target
111+
for target in "${COMPREPLY[@]}"; do
112+
local path=${target%/}
113+
while [[ ! ${paths[$path]+set} ]] &&
114+
paths[$path]=1 &&
115+
[[ $path == "$prefix"*/* ]]; do
116+
path=${path%/*}
117+
nchild[$path]=$((${nchild[$path]-0} + 1))
118+
done
119+
done
120+
121+
COMPREPLY=()
122+
local nreply=0
123+
for target in "${!paths[@]}"; do
124+
# generate only the paths that do not have a unique child and whose
125+
# all parent and ancestor directories have a unique child.
126+
((${nchild[$target]-0} == 1)) && continue
127+
local path=$target
128+
while [[ $path == "$prefix"*/* ]]; do
129+
path=${path%/*}
130+
((${nchild[$path]-0} == 1)) || continue 2
131+
done
132+
133+
# suffix `/' when the target path is a subdiretory, which has
134+
# at least one child.
135+
COMPREPLY[nreply++]=$target${nchild[$target]+/}
136+
done
137+
fi
138+
}
139+
90140
_make()
91141
{
92142
local cur prev words cword split comp_args
@@ -159,19 +209,28 @@ _make()
159209
fi
160210
done
161211

162-
# recognise that possible completions are only going to be displayed
163-
# so only the base name is shown
212+
# recognise that possible completions are only going to be displayed so
213+
# only the base name is shown.
214+
#
215+
# Note: This is currently turned off because the test suite of
216+
# bash-completion conflicts with it; it uses "set show-all-if-ambiguous
217+
# on" (causing COMP_TYPE == 37) to retrieve the action completion
218+
# results, and also the compact form with only the basenames is not
219+
# essentially needed. To re-enable it, please uncomment the following
220+
# if-statement.
164221
local mode=--
165-
if ((COMP_TYPE != 9)); then
166-
mode=-d # display-only mode
167-
fi
222+
# if ((COMP_TYPE != 9 && COMP_TYPE != 37 && COMP_TYPE != 42)); then
223+
# mode=-d # display-only mode
224+
# fi
168225

169226
local IFS=$' \t\n' script=$(_make_target_extract_script $mode "$cur")
170227
COMPREPLY=($(LC_ALL=C \
171228
$1 -npq __BASH_MAKE_COMPLETION__=1 \
172229
${makef+"${makef[@]}"} "${makef_dir[@]}" .DEFAULT 2>/dev/null |
173230
command sed -ne "$script"))
174231

232+
_comp_make__truncate_non_unique_paths
233+
175234
if [[ $mode != -d ]]; then
176235
# Completion will occur if there is only one suggestion
177236
# so set options for completion based on the first one

test/fixtures/make/test2/Makefile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# makefile
2+
3+
all: abc/xyz
4+
.PHONY: abc/xyz
5+
abc/xyz 123/xaa 123/xbb:
6+
mkdir -p $(@:/%=)
7+
date > $@
8+
9+
sub1test/bar/alpha sub1test/bar/beta:
10+
mkdir -p $(@:/%=)
11+
date > $@
12+
13+
sub2test/bar/alpha:
14+
mkdir -p $(@:/%=)
15+
date > $@
16+
17+
sub3test/bar/alpha sub3test/foo/alpha:
18+
mkdir -p $(@:/%=)
19+
date > $@
20+
21+
sub4test/bar/alpha sub4test/bar/beta sub4test2/foo/gamma:
22+
mkdir -p $(@:/%=)
23+
date > $@

test/t/test_make.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import pytest
44

5+
from conftest import assert_complete
6+
57

68
class TestMake:
79
@pytest.mark.complete("make -f Ma", cwd="make")
@@ -16,7 +18,7 @@ def test_2(self, bash, completion):
1618

1719
@pytest.mark.complete("make .cache/", cwd="make", require_cmd=True)
1820
def test_3(self, bash, completion):
19-
assert completion == "1 2".split()
21+
assert completion == ".cache/1 .cache/2".split()
2022
os.remove(f"{bash.cwd}/make/extra_makefile")
2123

2224
@pytest.mark.complete("make ", cwd="shared/empty_dir")
@@ -34,7 +36,7 @@ def test_6(self, bash, completion):
3436

3537
@pytest.mark.complete("make .cache/.", cwd="make", require_cmd=True)
3638
def test_7(self, bash, completion):
37-
assert completion == ".1 .2".split()
39+
assert completion == ".cache/.1 .cache/.2".split()
3840
os.remove(f"{bash.cwd}/make/extra_makefile")
3941

4042
@pytest.mark.complete("make -C make ", require_cmd=True)
@@ -45,3 +47,38 @@ def test_8(self, bash, completion):
4547
@pytest.mark.complete("make -", require_cmd=True)
4648
def test_9(self, completion):
4749
assert completion
50+
51+
52+
@pytest.mark.bashcomp(require_cmd=True, cwd="make/test2")
53+
class TestMake2:
54+
def test_github_issue_544_1(self, bash):
55+
completion = assert_complete(bash, "make ab")
56+
assert completion == "c/xyz"
57+
58+
def test_github_issue_544_2(self, bash):
59+
completion = assert_complete(bash, "make 1")
60+
assert completion == "23/"
61+
62+
def test_github_issue_544_3(self, bash):
63+
completion = assert_complete(bash, "make 123/")
64+
assert completion == ["123/xaa", "123/xbb"]
65+
66+
def test_github_issue_544_4(self, bash):
67+
completion = assert_complete(bash, "make 123/xa")
68+
assert completion == "a"
69+
70+
def test_subdir_1(self, bash):
71+
completion = assert_complete(bash, "make sub1")
72+
assert completion == "test/bar/"
73+
74+
def test_subdir_2(self, bash):
75+
completion = assert_complete(bash, "make sub2")
76+
assert completion == "test/bar/alpha"
77+
78+
def test_subdir_3(self, bash):
79+
completion = assert_complete(bash, "make sub3")
80+
assert completion == "test/"
81+
82+
def test_subdir_4(self, bash):
83+
completion = assert_complete(bash, "make sub4")
84+
assert completion == "sub4test/bar/ sub4test2/foo/gamma".split()

0 commit comments

Comments
 (0)