Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(make): fix stripped directory names with bind 'set show-all-if-ambiguous on' #546

Merged
merged 4 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()