How to make bash autocomplete work in the middle of line?

1.9k Views Asked by At

How to write autocomplete in bash, so that if I have:

mycommand first_argument|garbage

where | denotes the cursor, it should pass "first_argument" and not "first_argumentgarbage" to compgen?

In the examples I have it behaves in the wrong way

COMPREPLY=( $(compgen -W "add remove list use current" -- "$cur") ) # buggy
1

There are 1 best solutions below

3
Aserre On BEST ANSWER

Bash completion uses a lot of different variables. Some of them are used to process input and determine which parameter to complete.

For the following explanation, I will use this test input (with | as the cursor) :

./test.sh ad re|garbage
  • ${COMP_WORDS} : contains all the words of the input in the form of an array. In this case, it contains : ${COMP_WORDS[@]} == {"./test.sh", "ad", "regarbage"}
    • The word separator characters are found in the $COMP_WORDBREAKS variable
  • $COMP_CWORD : contains the position of the word the cursor is curently selecting. In this case, it contains : $COMP_CWORD == 2
  • $COMP_LINE : contains the whole input in form of string. In this case, it contains : $COMP_LINE == "./test.sh ad regarbage"
  • $COMP_POINT : contains the position of the cursor in the whole line. In this case, it contains : $COMP_POINT == 15

Still using the same data, doing cur=${COMP_WORDS[COMP_CWORD]} will return the element at index 2 in the ${COMP_WORD} array, which is regarbage.

To circumvent this behaviour, you'll have to play around with the $COMP_LINE and $COMP_POINT variables as well. Here is what I came up with :

# we truncate our line up to the position of our cursor
# we transform the result into an array
cur=(${COMP_LINE:0:$COMP_POINT})

# we use ${cur} the same way we would use ${COMP_WORDS}
COMPREPLY=( $( compgen -W "add remove list use current" -- "${cur[$COMP_CWORD]}" ) )

Output :

> ./test2.sh ad re|garbage
# press TAB
> ./test2.sh ad remove|garbage

Note that by default, there will be no space between remove and garbage. You'll have to play around the completion mechanics if this is a behaviour you want.