bash-completion
bash-completion copied to clipboard
Make target completion doesn't work with subdirectories
Describe the bug
Make target completion doesn't work when target files are in subdirectories.
To reproduce
There are two behaviours that happen here. Sample makefile:
.PHONY: abc/xyz
abc/xyz:
mkdir -p abc
date > $@
For clarity, i've marked spaces with ·
- When typing
make·TAB
the result ismake·abc/·
- When typing
make·abc/TAB
the result ismake·xyz·
Expected behavior
Both described cases should auto complete to make·abc/xyz·
because it is unambiguous.
Versions (please complete the following information)
- [ ] Operating system name/distribution and version: Artix Linux on WSL
- [ ] bash version:
5.1.8(1)-release
- [ ] bash-completion version:
2.11
(md5sum completions/make =84866af8ff59c9e1698f90a8f3a67f22
)
Additional context
Debug trace
+++ _on_command_start
[?2004h[38;5;214mᛋ [97;1mmake abc/+ local cur prev words cword split
+ _init_completion -s
+ local exclude= flag outx errx inx OPTIND=1
+ getopts n:e:o:i:s flag -s
+ case $flag in
+ split=false
+ exclude+==
+ getopts n:e:o:i:s flag -s
+ COMPREPLY=()
+ local 'redir=@(?([0-9])<|?([0-9&])>?(>)|>&)'
+ _get_comp_words_by_ref -n '=<>&' cur prev words cword
+ local exclude flag i OPTIND=1
+ words=()
+ local cur cword words
+ upargs=()
+ upvars=()
+ local upargs upvars vcur vcword vprev vwords
+ getopts c:i:n:p:w: flag -n '=<>&' cur prev words cword
+ case $flag in
+ exclude='=<>&'
+ getopts c:i:n:p:w: flag -n '=<>&' cur prev words cword
+ [[ 6 -ge 3 ]]
+ case ${!OPTIND} in
+ vcur=cur
+ (( OPTIND += 1 ))
+ [[ 6 -ge 4 ]]
+ case ${!OPTIND} in
+ vprev=prev
+ (( OPTIND += 1 ))
+ [[ 6 -ge 5 ]]
+ case ${!OPTIND} in
+ vwords=words
+ (( OPTIND += 1 ))
+ [[ 6 -ge 6 ]]
+ case ${!OPTIND} in
+ vcword=cword
+ (( OPTIND += 1 ))
+ [[ 6 -ge 7 ]]
+ __get_cword_at_cursor_by_ref '=<>&' words cword cur
+ words=()
+ local cword words
+ __reassemble_comp_words_by_ref '=<>&' words cword
+ local exclude i j line ref
+ [[ -n =<>& ]]
+ exclude='[=<>&]'
+ printf -v cword %s 1
+ [[ -v exclude ]]
+ line='make abc/'
+ (( i = 0, j = 0 ))
+ (( i < 2 ))
+ [[ 0 -gt 0 ]]
+ ref='words[0]'
+ printf -v 'words[0]' %s make
+ line=' abc/'
+ (( i == COMP_CWORD ))
+ (( i++, j++ ))
+ (( i < 2 ))
+ [[ 1 -gt 0 ]]
+ [[ abc/ == +([=<>&]) ]]
+ ref='words[1]'
+ printf -v 'words[1]' %s abc/
+ line=
+ (( i == COMP_CWORD ))
+ printf -v cword %s 1
+ (( i++, j++ ))
+ (( i < 2 ))
+ (( i == COMP_CWORD ))
+ local i cur= index=9 'lead=make abc/'
+ [[ 9 -gt 0 ]]
+ [[ -n make abc/ ]]
+ [[ -n makeabc/ ]]
+ cur='make abc/'
+ (( i = 0 ))
+ (( i <= cword ))
+ [[ 9 -ge 4 ]]
+ [[ make != \m\a\k\e ]]
+ (( i < cword ))
+ local old_size=9
+ cur=' abc/'
+ local new_size=5
+ (( index -= old_size - new_size ))
+ (( ++i ))
+ (( i <= cword ))
+ [[ 5 -ge 4 ]]
+ [[ abc != \a\b\c\/ ]]
+ cur=abc/
+ (( index > 0 ))
+ (( index-- ))
+ [[ 4 -ge 4 ]]
+ [[ abc/ != \a\b\c\/ ]]
+ (( i < cword ))
+ (( ++i ))
+ (( i <= cword ))
+ [[ -n abc/ ]]
+ [[ ! -n abc/ ]]
+ (( index < 0 ))
+ local words cword cur
+ _upvars -a2 words make abc/ -v cword 1 -v cur abc/
+ (( 10 ))
+ (( 10 ))
+ case $1 in
+ [[ -n 2 ]]
+ printf %d 2
+ [[ -n words ]]
+ unset -v words
+ eval 'words=("${@:3:2}")'
++ words=("${@:3:2}")
+ shift 4
+ (( 6 ))
+ case $1 in
+ [[ -n cword ]]
+ unset -v cword
+ eval 'cword="$3"'
++ cword=1
+ shift 3
+ (( 3 ))
+ case $1 in
+ [[ -n cur ]]
+ unset -v cur
+ eval 'cur="$3"'
++ cur=abc/
+ shift 3
+ (( 0 ))
+ [[ -v vcur ]]
+ upvars+=("$vcur")
+ upargs+=(-v $vcur "$cur")
+ [[ -v vcword ]]
+ upvars+=("$vcword")
+ upargs+=(-v $vcword "$cword")
+ [[ -v vprev ]]
+ [[ 1 -ge 1 ]]
+ upvars+=("$vprev")
+ upargs+=(-v $vprev "${words[cword - 1]}")
+ [[ -v vwords ]]
+ upvars+=("$vwords")
+ upargs+=(-a${#words[@]} $vwords ${words+"${words[@]}"})
+ (( 4 ))
+ local cur cword prev words
+ _upvars -v cur abc/ -v cword 1 -v prev make -a2 words make abc/
+ (( 13 ))
+ (( 13 ))
+ case $1 in
+ [[ -n cur ]]
+ unset -v cur
+ eval 'cur="$3"'
++ cur=abc/
+ shift 3
+ (( 10 ))
+ case $1 in
+ [[ -n cword ]]
+ unset -v cword
+ eval 'cword="$3"'
++ cword=1
+ shift 3
+ (( 7 ))
+ case $1 in
+ [[ -n prev ]]
+ unset -v prev
+ eval 'prev="$3"'
++ prev=make
+ shift 3
+ (( 4 ))
+ case $1 in
+ [[ -n 2 ]]
+ printf %d 2
+ [[ -n words ]]
+ unset -v words
+ eval 'words=("${@:3:2}")'
++ words=("${@:3:2}")
+ shift 4
+ (( 0 ))
+ _variables
+ [[ abc/ =~ ^(\$(\{[!#]?)?)([A-Za-z0-9_]*)$ ]]
+ [[ abc/ =~ ^(\$\{[#!]?)([A-Za-z0-9_]*)\[([^]]*)$ ]]
+ [[ abc/ =~ ^\$\{[#!]?[A-Za-z0-9_]*\[.*]$ ]]
+ case ${prev-} in
+ return 1
+ [[ abc/ == @(?([0-9])<|?([0-9&])>?(>)|>&)* ]]
+ [[ make == @(?([0-9])<|?([0-9&])>?(>)|>&) ]]
+ local i skip
+ (( i = 1 ))
+ (( i < 2 ))
+ [[ abc/ == @(?([0-9])<|?([0-9&])>?(>)|>&)* ]]
+ (( i++ ))
+ (( 1 ))
+ (( i < 2 ))
+ (( cword <= 0 ))
+ prev=make
+ [[ -n false ]]
+ _split_longopt
+ [[ abc/ == --?*=* ]]
+ return 1
+ return 0
+ makef_dir=('-C' '.')
+ local makef makef_dir i
+ case $prev in
+ false
+ [[ abc/ == -* ]]
+ [[ abc/ == *=* ]]
+ (( i = 1 ))
+ (( i < 2 ))
+ [[ abc/ == -@(C|-directory) ]]
+ (( i++ ))
+ (( i < 2 ))
+ (( i = 1 ))
+ (( i < 2 ))
+ [[ abc/ == -@(f|-?(make)file) ]]
+ (( i++ ))
+ (( i < 2 ))
+ local mode=--
+ (( COMP_TYPE != 9 ))
+ mode=-d
++ _make_target_extract_script -d abc/
++ local mode=-d
++ shift
++ local prefix=abc/
+++ command sed 's/[][\,.*^$(){}?+|/]/\\&/g'
++ local 'prefix_pat=abc\/'
++ local basename=
++ local dirname_len=4
++ [[ -d == -d ]]
++ local 'output=\2'
++ cat
++ [[ -z abc\/ ]]
++ [[ abc\/ == */ ]]
++ cat
++ cat
+ local 'IFS=
' 'script= 1,/^# * Make data base/ d; # skip any makefile output
/^# * Finished Make data base/,/^# * Make data base/{
d; # skip any makefile output
}
/^# * Variables/,/^# * Files/ d; # skip until files section
/^# * Not a target/,/^$/ d; # skip not target blocks
/^abc\//,/^$/! d; # skip anything user dont want
# The stuff above here describes lines that are not
# explicit targets or not targets other than special ones
# The stuff below here decides whether an explicit target
# should be output.
/^# * File is an intermediate prerequisite/ {
s/^.*$//;x; # unhold target
d; # delete line
}
/^$/ { # end of target block
x; # unhold target
/^$/d; # dont print blanks
s|^\(.\{4\}\)\(.\{0\}[^:/]*/\{0,1\}\)[^:]*:.*$|\2|p;
d; # hide any bugs
}
# This pattern includes a literal tab character as \t is not a portable
# representation and fails with BSD sed
/^[^# :%]\{1,\}:/ { # found target block
/^\.PHONY:/ d; # special target
/^\.SUFFIXES:/ d; # special target
/^\.DEFAULT:/ d; # special target
/^\.PRECIOUS:/ d; # special target
/^\.INTERMEDIATE:/ d; # special target
/^\.SECONDARY:/ d; # special target
/^\.SECONDEXPANSION:/ d; # special target
/^\.DELETE_ON_ERROR:/ d; # special target
/^\.IGNORE:/ d; # special target
/^\.LOW_RESOLUTION_TIME:/ d; # special target
/^\.SILENT:/ d; # special target
/^\.EXPORT_ALL_VARIABLES:/ d; # special target
/^\.NOTPARALLEL:/ d; # special target
/^\.ONESHELL:/ d; # special target
/^\.POSIX:/ d; # special target
/^\.NOEXPORT:/ d; # special target
/^\.MAKE:/ d; # special target
/^abc\/[^a-zA-Z0-9]/d; # convention for hidden tgt
h; # hold target
d; # delete line
}'
+ COMPREPLY=($(LC_ALL=C $1 -npq __BASH_MAKE_COMPLETION__=1 ${makef+"${makef[@]}"} "${makef_dir[@]}" .DEFAULT 2>/dev/null |
command sed -ne "$script"))
++ LC_ALL=C
++ make -npq __BASH_MAKE_COMPLETION__=1 -C . .DEFAULT
++ command sed -ne ' 1,/^# * Make data base/ d; # skip any makefile output
/^# * Finished Make data base/,/^# * Make data base/{
d; # skip any makefile output
}
/^# * Variables/,/^# * Files/ d; # skip until files section
/^# * Not a target/,/^$/ d; # skip not target blocks
/^abc\//,/^$/! d; # skip anything user dont want
# The stuff above here describes lines that are not
# explicit targets or not targets other than special ones
# The stuff below here decides whether an explicit target
# should be output.
/^# * File is an intermediate prerequisite/ {
s/^.*$//;x; # unhold target
d; # delete line
}
/^$/ { # end of target block
x; # unhold target
/^$/d; # dont print blanks
s|^\(.\{4\}\)\(.\{0\}[^:/]*/\{0,1\}\)[^:]*:.*$|\2|p;
d; # hide any bugs
}
# This pattern includes a literal tab character as \t is not a portable
# representation and fails with BSD sed
/^[^# :%]\{1,\}:/ { # found target block
/^\.PHONY:/ d; # special target
/^\.SUFFIXES:/ d; # special target
/^\.DEFAULT:/ d; # special target
/^\.PRECIOUS:/ d; # special target
/^\.INTERMEDIATE:/ d; # special target
/^\.SECONDARY:/ d; # special target
/^\.SECONDEXPANSION:/ d; # special target
/^\.DELETE_ON_ERROR:/ d; # special target
/^\.IGNORE:/ d; # special target
/^\.LOW_RESOLUTION_TIME:/ d; # special target
/^\.SILENT:/ d; # special target
/^\.EXPORT_ALL_VARIABLES:/ d; # special target
/^\.NOTPARALLEL:/ d; # special target
/^\.ONESHELL:/ d; # special target
/^\.POSIX:/ d; # special target
/^\.NOEXPORT:/ d; # special target
/^\.MAKE:/ d; # special target
/^abc\/[^a-zA-Z0-9]/d; # convention for hidden tgt
h; # hold target
d; # delete line
}'
+ [[ -d != -d ]]
xyz
[C[C[Kset +x && exec 2>/dev/tty
[?2004l
++ _on_command_start
+ set +x
For clarity, i've marked spaces with
·
- When typing
make·TAB
the result ismake·abc/·
- When typing
make·abc/TAB
the result ismake·xyz·
I could reproduce 1 but not 2.
- (md5sum completions/make =
84866af8ff59c9e1698f90a8f3a67f22
)
This seems to be completions/make
of 1a633f3a551154ac3c70bfdc5263d73d8b3d9f3f
$ git cat-file -p 1a633f3a551154ac3c70bfdc5263d73d8b3d9f3f:completions/make | md5sum
84866af8ff59c9e1698f90a8f3a67f22 -
I tried with this version but still cannot reproduce behavior 2 (with bash 5.1.8 on Fedora 31). This is the diff of the trace (trace1.txt
is from @PhilipRoman, trace2.txt
is mine):
--- trace1.txt^I2021-06-13 21:58:06.993893157 +0900
+++ trace2.txt^I2021-06-13 21:57:38.461345637 +0900
@@ -1,5 +1,4 @@
-+++ _on_command_start
-�[?2004h�[38;5;214mᛋ �[97;1mmake abc/+ local cur prev words cword split
+$ make abc/+ local cur prev words cword split
+ _init_completion -s
+ local exclude= flag outx errx inx OPTIND=1
+ getopts n:e:o:i:s flag -s
@@ -174,7 +173,6 @@
+ [[ abc/ =~ ^(\$(\{[!#]?)?)([A-Za-z0-9_]*)$ ]]
+ [[ abc/ =~ ^(\$\{[#!]?)([A-Za-z0-9_]*)\[([^]]*)$ ]]
+ [[ abc/ =~ ^\$\{[#!]?[A-Za-z0-9_]*\[.*]$ ]]
-+ case ${prev-} in
+ return 1
+ [[ abc/ == @(?([0-9])<|?([0-9&])>?(>)|>&)* ]]
+ [[ make == @(?([0-9])<|?([0-9&])>?(>)|>&) ]]
@@ -210,17 +208,16 @@
+ (( i < 2 ))
+ local mode=--
+ (( COMP_TYPE != 9 ))
-+ mode=-d
-++ _make_target_extract_script -d abc/
-++ local mode=-d
+++ _make_target_extract_script -- abc/
+++ local mode=--
++ shift
++ local prefix=abc/
+++ command sed 's/[][\,.*^$(){}?+|/]/\\&/g'
++ local 'prefix_pat=abc\/'
++ local basename=
++ local dirname_len=4
-++ [[ -d == -d ]]
-++ local 'output=\2'
+++ [[ -- == -d ]]
+++ local 'output=\1\2'
++ cat
++ [[ -z abc\/ ]]
++ [[ abc\/ == */ ]]
@@ -248,7 +245,7 @@
/^$/ { # end of target block
x; # unhold target
/^$/d; # dont print blanks
- s|^\(.\{4\}\)\(.\{0\}[^:/]*/\{0,1\}\)[^:]*:.*$|\2|p;
+ s|^\(.\{4\}\)\(.\{0\}[^:/]*/\{0,1\}\)[^:]*:.*$|\1\2|p;
d; # hide any bugs
}
@@ -301,7 +298,7 @@
/^$/ { # end of target block
x; # unhold target
/^$/d; # dont print blanks
- s|^\(.\{4\}\)\(.\{0\}[^:/]*/\{0,1\}\)[^:]*:.*$|\2|p;
+ s|^\(.\{4\}\)\(.\{0\}[^:/]*/\{0,1\}\)[^:]*:.*$|\1\2|p;
d; # hide any bugs
}
@@ -329,9 +326,5 @@
h; # hold target
d; # delete line
}'
-+ [[ -d != -d ]]
-����xyz
-�[C�[C�[Kset +x && exec 2>/dev/tty
-�[?2004l
-++ _on_command_start
-+ set +x
++ [[ -- != -d ]]
++ [[ abc/xyz == */ ]]
This implies that COMP_TYPE
for tab completions has some other values (different from 9) in @PhilipRoman's environment. What will be shown with the following command?
$ _make_debug() { printf 'COMP_TYPE=%q\n' "$COMP_TYPE" >/dev/tty; } $ complete -F _make_debug make $ make abc/TAB
This implies that COMP_TYPE for tab completions has some other values (different from 9) in @PhilipRoman's environment. What will be shown with the following command?
$ _make_debug() { printf 'COMP_TYPE=%q\n' "$COMP_TYPE" >/dev/tty; } $ complete -F _make_debug make $ make abc/TAB
COMP_TYPE=37
I have show-all-if-ambiguous
on and tab is bound to menu-complete
. When I reset these settings back to default, everything happens as you described.
OK. Thank you for the information! I could reproduce the behavior with bind 'set show-all-if-ambiguous on'
.
I guess we should also check for COMP_TYPE == 37
(%
, menu-complete). I tried to search what the other completions do with COMP_TYPE
but found that the only completion that references COMP_TYPE
is completions/make
. This special behavior for COMP_TYPE != 9
is introduced in commit 39f00f92e52b783e7e9e43aac4b4274cc9dee152 by @code5hot. Maybe @code5hot has comments though he/she seems to be inactive in recent years.
Besides, it is a good suggestion to complete the full path abc/xyz
when unambiguous.
It seems there's no reply from @code5hot, so I've made a PR #546.