Skip to content

Commit

Permalink
Merge pull request #546 from akinomyoga/fix-make-for-show-all-if-ambi…
Browse files Browse the repository at this point in the history
…guous

fix(make): fix stripped directory names with `bind 'set show-all-if-ambiguous on'`
  • Loading branch information
akinomyoga authored Jan 23, 2023
2 parents fd7eadc + 5eb1042 commit 5927d57
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 8 deletions.
71 changes: 65 additions & 6 deletions completions/make
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ _make_target_extract_script()
/^$/ { # end of target block
x; # unhold target
/^$/d; # dont print blanks
s|^${dirname_re-}\(.\{${#basename}\}[^:/]*/\{0,1\}\)[^:]*:.*$|${output}|p;
s|^${dirname_re-}\(.\{${#basename}\}[^:]*\):.*$|${output}|p;
d; # hide any bugs
}
Expand Down Expand Up @@ -87,6 +87,56 @@ EOF
EOF
}

# Truncate the non-unique filepaths in COMPRELY to only generate unique
# directories or files. This function discards the files under subdirectories
# unless the path is unique under each subdirectory and instead generate the
# subdirectory path. For example, when there are two candidates, "abc/def" and
# "abc/xyz", we generate "abc/" instead of generating both candidates directly.
# When there is only one candidate "abc/def", we generate the full path
# "abc/def".
#
# @var[in] cur
# @var[in] mode
# @var[in,out] COMPREPLY
_comp_make__truncate_non_unique_paths()
{
local prefix=$cur
[[ $mode == -d ]] && prefix=
if ((${#COMPREPLY[@]} > 0)); then
# collect the possible completions including the directory names in
# `paths' and count the number of children of each subdirectory in
# `nchild'.
local -A paths nchild
local target
for target in "${COMPREPLY[@]}"; do
local path=${target%/}
while [[ ! ${paths[$path]+set} ]] &&
paths[$path]=1 &&
[[ $path == "$prefix"*/* ]]; do
path=${path%/*}
nchild[$path]=$((${nchild[$path]-0} + 1))
done
done

COMPREPLY=()
local nreply=0
for target in "${!paths[@]}"; do
# generate only the paths that do not have a unique child and whose
# all parent and ancestor directories have a unique child.
((${nchild[$target]-0} == 1)) && continue
local path=$target
while [[ $path == "$prefix"*/* ]]; do
path=${path%/*}
((${nchild[$path]-0} == 1)) || continue 2
done

# suffix `/' when the target path is a subdiretory, which has
# at least one child.
COMPREPLY[nreply++]=$target${nchild[$target]+/}
done
fi
}

_make()
{
local cur prev words cword split comp_args
Expand Down Expand Up @@ -159,19 +209,28 @@ _make()
fi
done

# recognise that possible completions are only going to be displayed
# so only the base name is shown
# recognise that possible completions are only going to be displayed so
# only the base name is shown.
#
# Note: This is currently turned off because the test suite of
# bash-completion conflicts with it; it uses "set show-all-if-ambiguous
# on" (causing COMP_TYPE == 37) to retrieve the action completion
# results, and also the compact form with only the basenames is not
# essentially needed. To re-enable it, please uncomment the following
# if-statement.
local mode=--
if ((COMP_TYPE != 9)); then
mode=-d # display-only mode
fi
# if ((COMP_TYPE != 9 && COMP_TYPE != 37 && COMP_TYPE != 42)); then
# mode=-d # display-only mode
# fi

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

_comp_make__truncate_non_unique_paths

if [[ $mode != -d ]]; then
# Completion will occur if there is only one suggestion
# so set options for completion based on the first one
Expand Down
23 changes: 23 additions & 0 deletions test/fixtures/make/test2/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# makefile

all: abc/xyz
.PHONY: abc/xyz
abc/xyz 123/xaa 123/xbb:
mkdir -p $(@:/%=)
date > $@

sub1test/bar/alpha sub1test/bar/beta:
mkdir -p $(@:/%=)
date > $@

sub2test/bar/alpha:
mkdir -p $(@:/%=)
date > $@

sub3test/bar/alpha sub3test/foo/alpha:
mkdir -p $(@:/%=)
date > $@

sub4test/bar/alpha sub4test/bar/beta sub4test2/foo/gamma:
mkdir -p $(@:/%=)
date > $@
41 changes: 39 additions & 2 deletions test/t/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import pytest

from conftest import assert_complete


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

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

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

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

@pytest.mark.complete("make -C make ", require_cmd=True)
Expand All @@ -45,3 +47,38 @@ def test_8(self, bash, completion):
@pytest.mark.complete("make -", require_cmd=True)
def test_9(self, completion):
assert completion


@pytest.mark.bashcomp(require_cmd=True, cwd="make/test2")
class TestMake2:
def test_github_issue_544_1(self, bash):
completion = assert_complete(bash, "make ab")
assert completion == "c/xyz"

def test_github_issue_544_2(self, bash):
completion = assert_complete(bash, "make 1")
assert completion == "23/"

def test_github_issue_544_3(self, bash):
completion = assert_complete(bash, "make 123/")
assert completion == ["123/xaa", "123/xbb"]

def test_github_issue_544_4(self, bash):
completion = assert_complete(bash, "make 123/xa")
assert completion == "a"

def test_subdir_1(self, bash):
completion = assert_complete(bash, "make sub1")
assert completion == "test/bar/"

def test_subdir_2(self, bash):
completion = assert_complete(bash, "make sub2")
assert completion == "test/bar/alpha"

def test_subdir_3(self, bash):
completion = assert_complete(bash, "make sub3")
assert completion == "test/"

def test_subdir_4(self, bash):
completion = assert_complete(bash, "make sub4")
assert completion == "sub4test/bar/ sub4test2/foo/gamma".split()

0 comments on commit 5927d57

Please sign in to comment.