Simulate Bash's COMPREPLY response without actually completing it

795 Views Asked by At

Steps to reproduce

  • Create directory on tmp, add 1 file inside.

    mkdir /tmp/testdir && touch /tmp/testdir/examplefile
    
  • Paste below script on /tmp/completion.sh

    # BEGIN AUTOCOMPLETE
    function _foo_complete() {
    local comnum cur opts
    [[ "${COMP_WORDS[@]}" == *"-"* ]] && comnum=2 || comnum=1;
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    opts="--help --restart -h -r"
    if (( COMP_CWORD > comnum )); then
     COMPREPLY=( $(for filename in "/tmp/testdir/"*; do echo ${filename##*/}; done) )
     return
    fi
    if [[ ${cur} == -* ]]; then
     COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
     return 0
    else
     COMPREPLY=( $(for filelist in /tmp/testdir/"$2"*; do echo ${filelist##*/}; done) )
    fi
    }
    complete -F _foo_complete foo.sh
    # END AUTOCOMPLETE
    
  • Source it then.

    . /tmp/completion.sh
    

Expected result

$ foo.sh --restart examplefile <tab><tab>
examplefile           
$ foo.sh --restart examplefile <tab><tab>
examplefile        
$ foo.sh --restart examplefile <tab><tab>
examplefile     
$ foo.sh --restart examplefile <tab><tab>

What happen instead

$ foo.sh --restart examplefile <tab><tab> examplefile <tab><tab> examplefile <tab><tab> examplefile <tab><tab> examplefile <tab><tab> examplefile

I want the suggestion to appear as possible completion, but without actually completing it (for display purposes). This question has been asked before, but to this date no answer is given.

Regarding -o nosort

I look into that option and it's only available at Bash 4.4+, I tried on my 16.04 machine and it fail. Looking for more globalish solution

3

There are 3 best solutions below

3
pynexj On BEST ANSWER

Try the following compspec.sh (based on OP's code):

function _foo_complete()
{
    local comnum cur opts

    [[ "${COMP_WORDS[@]}" == *"-"* ]] && comnum=2 || comnum=1;

    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    opts="--help --restart -h -r"

    if (( COMP_CWORD > comnum )); then
        COMPREPLY=( $(for filename in "/tmp/testdir/"*; do echo ${filename##*/}; done) )
        if [[ ${#COMPREPLY[@]} -gt 0 ]]; then
            #
            # you can try COMPREPLY+=( zzz ) and see what's happening
            #
            COMPREPLY+=( ' ' )
        fi
        return
    fi

    if [[ ${cur} == -* ]]; then
        COMPREPLY=( $( compgen -W "${opts}" -- ${cur} ) )
        return 0
    else
        COMPREPLY=( $( for filelist in /tmp/testdir/"$2"*; do echo ${filelist##*/}; done ) )
    fi
}

complete -F _foo_complete foo.sh

enter image description here


UPDATE:

is it possible to move the empty string to end of completion, so it doesn't look like there are empty space?

You can use complete -o nosort (requires Bash 4.4+) if you can sort the completion candidates all by yourself.

7
Nadim Khemir On

Edited code corresponding to your complete requirements:

_test(){
   COMPREPLY=(                                                                                                                      
      $( [[ "$2" == -* ]] && compgen -W'-a -ab -c' -- "$2" \                                                                
      || [[ -d test ]] && compgen -W'$(basename -a test/*)' -- "$2" | grep -v -w -f <(printf "%s\n" "${COMP_WORDS[@    ]}:1:((COMP_CWORD-1))"))
   )
}

if the test directory doesn't exist completion would work with current directory contents so we have to check it

options can be used multiple times as you didn't specify it. 

1 2 complete -F _test test

9
Fravadona On

You can try this trick; depending on your OS, this might work for you:

note: I stripped the completion function down to the relevant TabTab functionality

#!/bin/bash
mkdir -p /tmp/testdir && touch /tmp/testdir/examplefile

_foo_complete() {
    local dirpath=/tmp/testdir
    local nullglob=$( shopt -q nullglob && echo true || echo false )

    $nullglob || shopt -s nullglob
    COMPREPLY=( "$dirpath"/* )
    $nullglob || shopt -u nullglob

    COMPREPLY=( "${COMPREPLY[@]#"$dirpath"/}" )

    [ ${#COMPREPLY[@]} -eq 1 ] && COMPREPLY+=( $'\xC2\xA0' )
}
complete -F _foo_complete foo.sh

Postscript:

It's basically the same idea than @pynexj answer: adding a second, invisible, element to COMPREPLY, thus disabling the Tab auto-completion while keeping the TabTab listing.

Now the problem is that bash sorts COMREPLY automatically, so, with the ASCII space character being numerically the first printable character, it will be displayed as the first column of the listing.

To work around it I tried almost all other white-space characters in the UTF-8 charset and they all get sorted to the same rank than the ASCII space character; all but the NON-BREAKING-SPACE, that's what I'm using here.

The fix is not full proof because there's a lot of UTF-8 characters that will get sorted after it, and above all, it gets different treatments depending on the OS: some sort implementations will give it the same rank than the ASCII space character, while others won't. Think about it, what is NON-BREAKING-SPACE conceptually? It's a white-space, but also like a letter at the same time!