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

Make target completion doesn't work with subdirectories #544

Closed
3 tasks
PhilipRoman opened this issue Jun 9, 2021 · 4 comments · Fixed by #546
Closed
3 tasks

Make target completion doesn't work with subdirectories #544

PhilipRoman opened this issue Jun 9, 2021 · 4 comments · Fixed by #546
Assignees

Comments

@PhilipRoman
Copy link

PhilipRoman commented Jun 9, 2021

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 ·

  1. When typing make·TAB the result is make·abc/·
  2. When typing make·abc/TAB the result is make·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
@akinomyoga
Copy link
Collaborator

For clarity, i've marked spaces with ·

  1. When typing make·TAB the result is make·abc/·
  2. When typing make·abc/TAB the result is make·xyz·

I could reproduce 1 but not 2.

  • (md5sum completions/make = 84866af8ff59c9e1698f90a8f3a67f22)

This seems to be completions/make of 1a633f3

$ 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

@PhilipRoman
Copy link
Author

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.

@akinomyoga
Copy link
Collaborator

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 39f00f9 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.

@akinomyoga
Copy link
Collaborator

akinomyoga commented Jun 20, 2021

It seems there's no reply from @code5hot, so I've made a PR #546.

akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Aug 21, 2021
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Aug 24, 2021
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Aug 24, 2021
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Sep 3, 2021
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 26, 2021
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Jan 6, 2022
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Feb 24, 2022
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Aug 28, 2022
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 17, 2022
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 19, 2022
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 19, 2022
In the display-only mode where COMP_TYPE=9, 37, or 42, only a part of
the completion is stored in COMPREPLY for displaying purposes.  For
example, "xyz" is stored in COMPREPLY when "abc/xyz" is the candidate
and "abc/" is already inserted.

However, the test framework extracts generated completions using
COMP_TYPE=37 by setting "set show-all-if-ambiguous off", which would
be broken by the display-only mode.  There is no simple way to make it
work with the test framework, and the display-only mode does not seem
to be essential.  For the time being, we deactivate the display-only
mode.  See also discussions in the following links:

scop#544
scop#546
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 19, 2022
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 19, 2022
In the display-only mode where COMP_TYPE=9, 37, or 42, only a part of
the completion is stored in COMPREPLY for displaying purposes.  For
example, "xyz" is stored in COMPREPLY when "abc/xyz" is the candidate
and "abc/" is already inserted.

However, the test framework extracts generated completions using
COMP_TYPE=37 by setting "set show-all-if-ambiguous off", which would
be broken by the display-only mode.  There is no simple way to make it
work with the test framework, and the display-only mode does not seem
to be essential.  For the time being, we deactivate the display-only
mode.  See also discussions in the following links:

scop#544
scop#546
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 22, 2022
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Dec 22, 2022
In the display-only mode where COMP_TYPE=9, 37, or 42, only a part of
the completion is stored in COMPREPLY for displaying purposes.  For
example, "xyz" is stored in COMPREPLY when "abc/xyz" is the candidate
and "abc/" is already inserted.

However, the test framework extracts generated completions using
COMP_TYPE=37 by setting "set show-all-if-ambiguous off", which would
be broken by the display-only mode.  There is no simple way to make it
work with the test framework, and the display-only mode does not seem
to be essential.  For the time being, we deactivate the display-only
mode.  See also discussions in the following links:

scop#544
scop#546
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Jan 10, 2023
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Jan 10, 2023
In the display-only mode where COMP_TYPE=9, 37, or 42, only a part of
the completion is stored in COMPREPLY for displaying purposes.  For
example, "xyz" is stored in COMPREPLY when "abc/xyz" is the candidate
and "abc/" is already inserted.

However, the test framework extracts generated completions using
COMP_TYPE=37 by setting "set show-all-if-ambiguous off", which would
be broken by the display-only mode.  There is no simple way to make it
work with the test framework, and the display-only mode does not seem
to be essential.  For the time being, we deactivate the display-only
mode.  See also discussions in the following links:

scop#544
scop#546
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Jan 10, 2023
When many targets in subdirectories are defined in Makefile, we
typically want to list the files or subdirectories in the current
directory.  However, when the file in the subdirectory is unique, we
may generate the full path.  See also the discussion on GitHub:

scop#544
scop#546 (comment)
akinomyoga added a commit to akinomyoga/bash-completion that referenced this issue Jan 10, 2023
In the display-only mode where COMP_TYPE=9, 37, or 42, only a part of
the completion is stored in COMPREPLY for displaying purposes.  For
example, "xyz" is stored in COMPREPLY when "abc/xyz" is the candidate
and "abc/" is already inserted.

However, the test framework extracts generated completions using
COMP_TYPE=37 by setting "set show-all-if-ambiguous off", which would
be broken by the display-only mode.  There is no simple way to make it
work with the test framework, and the display-only mode does not seem
to be essential.  For the time being, we deactivate the display-only
mode.  See also discussions in the following links:

scop#544
scop#546
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants