You are not logged in.

#1 2016-06-24 09:55:23

Starfish
Member
From: Germany
Registered: 2015-10-21
Posts: 134

[SOLVED] Sophisticated completion in bash

Hello there,

I had a hard time trying to understand how programmable command line completion works in bash. After some fumbling around, I wrote the following into my bashrc:

_comp(){
   local names=($(compgen -f $2))
   local type0="-e :[^:]*"
   if [ ${#names[@]} -gt 0 ]; then
      case $1 in
         cd) type0=$type0"directory" ;;
         okular) type0=$type0"PDF" ;;
         pdflatex) type0=$type0"LaTeX 2e document" ;;
         vim) type0=$type0"ASCII "$type0"UTF-8" ;;
         cat) type0=$type0"ASCII "$type0"UTF-8" ;;
      esac
      COMPREPLY=($( file ${names[@]} | grep $type0 | sed 's/:[^:]*$//' ))
      return 0
   else
      return -1
   fi  
}
complete -o bashdefault -F _comp cd
complete -o bashdefault -F _comp okular
complete -o bashdefault -F _comp pdflatex
complete -o bashdefault -F _comp vim 
complete -o bashdefault -F _comp cat

Inclusion of other commands should be obvious.
Since there are not too many example on the web, I wanted to ask the bash-gurus among us Archers if this looks like a reasonable approach to program completion, or if there are any major flaws.

PS.: I have read the according chapter in the bash manual (https://www.gnu.org/software/bash/manual/bash.pdf) plus its example for "cd" (p. 130-132), but it seemed like a complete overkill to me.

Last edited by Starfish (2016-06-24 15:52:59)


"Yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present." - Master Oogway

Offline

#2 2016-06-24 13:09:51

jsoy9pQbYVNu5nfU
Member
Registered: 2013-04-19
Posts: 108

Re: [SOLVED] Sophisticated completion in bash

I don't think this is a good approach.

You're shelling out to external programs. This makes the function noticeably slow. You should strive to only use shell builtins.

This is also going to become unmaintainable for large completion functions. Personally, I use the pattern

local cur=${COMP_WORDS[COMP_CWORD]};
local prev=${COMP_WORDS[COMP_CWORD-1]};
# COMP_WORDBREAKS=${COMP_WORDBREAKS/=/}; special case

at the beginning of the function and then base the logic on $prev and $cur resp. doing matching on them. You can use COMP_WORDS for doing arbitrary lookahead and lookbehind.

Your function is also incorrect/incomplete. Create file named [ABC]

echo foo > "[ABC]"

and try to complete that file name using your function. You need to do proper escaping of strings you return to the shell as suggestions (printf %q). This is also only an obvious case; I can imagine that things can break in pretty hilarious ways, even with the seemingly complex default completion functions.

Perhaps you could base your stuff on the default _filedir function; it's not very long:

_filedir () 
{ 
    local IFS='
';
    _tilde "$cur" || return 0;
    local -a toks;
    local x tmp;
    x=$( compgen -d -- "$cur" ) && while read -r tmp; do
        toks+=("$tmp");
    done <<< "$x";
    if [[ "$1" != -d ]]; then
        local quoted;
        _quote_readline_by_ref "$cur" quoted;
        local xspec=${1:+"!*.@($1|${1^^})"};
        x=$( compgen -f -X "$xspec" -- $quoted ) && while read -r tmp; do
            toks+=("$tmp");
        done <<< "$x";
        [[ -n ${COMP_FILEDIR_FALLBACK:-} && -n "$1" && ${#toks[@]} -lt 1 ]] && x=$( compgen -f -- $quoted ) && while read -r tmp; do
            toks+=("$tmp");
        done <<< "$x";
    fi;
    if [[ ${#toks[@]} -ne 0 ]]; then
        compopt -o filenames 2> /dev/null;
        COMPREPLY+=("${toks[@]}");
    fi
}

Last edited by jsoy9pQbYVNu5nfU (2016-06-24 13:13:37)

Offline

#3 2016-06-24 13:42:27

Starfish
Member
From: Germany
Registered: 2015-10-21
Posts: 134

Re: [SOLVED] Sophisticated completion in bash

2ion wrote:

You're shelling out to external programs. This makes the function noticeably slow. You should strive to only use shell builtins.

You're right, but the latency is like a jiffy for this small function.

2ion wrote:

Your function is also incorrect/incomplete. Create file named [ABC]

echo foo > "[ABC]"

and try to complete that file name using your function. You need to do proper escaping of strings you return to the shell as suggestions (printf %q).

That's strange, it worked for me. I created a test file just this way, typed "vim " (with the space), then hit TAB and got "vim [ABC]". It also worked with "vim [". And the file showed "foo".

2ion wrote:

This is also going to become unmaintainable for large completion functions.

With that I agree. For more complicated completion I will have to separate the functions for each command. I'll save your _filedir function and work myself through it.

Thank you for your feedback!

Last edited by Starfish (2016-06-24 13:43:28)


"Yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present." - Master Oogway

Offline

Board footer

Powered by FluxBB