6

I have the following directory structure:

/home/tichy/xxx/yyy/aaa
/home/tichy/xxx/yyy/aab
/home/tichy/xxx/yyy/aac

I would like to enter cdw y<TAB> and get cdw yyy/<CURSOR> as a result, so I could add cdw yyy/a<TAB> and get cdw yyy/aa<CURSOR>

The solution I came up with gives me the following:
cdw y<TAB> => cdw yyy<SPACE><CURSOR>

Following code I have so far:

_cdw () {
    local cur prev dirs
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    COMPREPLY=($(compgen -d -- /home/tichy/xxx/${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
    # no difference, a bit more logical:
    dirs=$(compgen -o nospace -d /home/tichy/xxx/${cur}|perl -pe 's/{^/home/tichy/xxx/}{}')
    COMPREPLY=($(compgen -d -W ${dir} ${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
    return 0
}
complete -F _cdw cdw
cdw () {
    cd /home/tichy/xxx/$@
}

Any ideas what's wrong? It seems to me that the completion process seems to be finished and isn't expecting any more input.

Vampire
  • 35,631
  • 4
  • 76
  • 102
tichy
  • 501
  • 4
  • 10

4 Answers4

9

The simplest solution I've found so far is to generate completions that look like this:

COMPREPLY=( $( compgen -W "file1 file2 file3 dir1/ dir2/ dir3/" ) )

and add this line just before returning

[[ $COMPREPLY == */ ]] && compopt -o nospace

This sets the nospace option whenever the completion may fill in until the slash so that the user ends up with:

cmd dir1/<cursorhere>

instead of:

cmd dir1/ <cursorhere>

and it doesn't set the nospace option whenever the completion may fill in until a full filename so that the user ends up with:

cmd file1 <cursorhere>

instead of:

cmd file1<cursorhere>
codeshot
  • 1,183
  • 1
  • 9
  • 20
5

If I understand correctly, you want to bash-autocomplete a directory name, and not have the extra space? (That's what I was looking for when I got to this page).

If so, when you register the completion function, use "-o nospace".

complete -o nospace -F _cdw cdw

I don't know if nospace works on compgen.

Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276
none
  • 51
  • 1
  • 1
2

How about something like this:

COMPREPLY=( $(cdw; compgen -W "$(for d in ${cur}* ${cur}*/*; do [[ -d "$d" ]] && echo $d/; done)" -- ${cur}) )

(I'm not sure if you can call your shell function from here or not, otherwise you may have to duplicate it a bit.)

This also gets rid of your perl hack :-)

Tanktalus
  • 21,664
  • 5
  • 41
  • 68
  • That doesn't work: it basically does what _compgen -d -o dirnames ${cur}_ does. It also gives me the complete path I don't want (e.g. _/home/tichy/..._ (which I could filter out with the usual perl command). It also somehow doesn't complete anything: it only shows the matches but doesn't fill in the matching characters. As far as I understand it, you want to postfix a directory with a /: That's what dirs=$(compgen -o nospace -o dirnames /home/.../${cur}|perl -pe 's{^/home/...}{};s/$/\//') does too. – tichy Dec 15 '11 at 07:38
  • @tichy : It does work: I'm using it right now. The part I'm using differently is that I'm not using cdw, I'm using a different shell script. It does not do what compgen -d -o dirnames does as mine also looks for subdirectories, and adds the slash at the end. By adding the subdirs, this blocks the space from being added (unless there are no further subdirs), thus we don't need -o nospace. – Tanktalus Dec 15 '11 at 16:40
  • I apologize: I must have made an error pasting it into my script. It does indeed work like I intended. – tichy Dec 16 '11 at 13:45
  • @tichy - sok. Thank you for the question - what I used to have before you asked didn't work, but your question made me think of my approach all over again. It made me re-write my completion function and suddenly it worked. My completion function is now better than it was before you asked. So, again, thanks. :-) – Tanktalus Dec 17 '11 at 05:49
  • Very well: thanks for accepting my apologizies. After getting your solution to work I was wondering whether it is not possible to have it run through compgen alone, e.g. something like compgen -o dirnames ${cur} ${cur}/*. It doesn't work that simple, but that's what your command basically does -- it only then filters the unwanted files out. But that's one of the purposes of compgen -o dirnames, isn't it? – tichy Dec 18 '11 at 16:49
1

completion provides a solution for this without any workaround: funciton _filedir defined in /etc/bash_completion:

 626 # This function performs file and directory completion. It's better than
 627 # simply using 'compgen -f', because it honours spaces in filenames.
 628 # @param $1  If `-d', complete only on directories.  Otherwise filter/pick only
 629 #            completions with `.$1' and the uppercase version of it as file
 630 #            extension.
 631 #
 632 _filedir()

Then, specifying the following is enough:

_cdw () {
    local cur prev dirs
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    _filedir # add -d at the end to complete only dirs, no files
    return 0
}
Carles Sala
  • 1,989
  • 1
  • 16
  • 34
  • This is the correct generic solution, it is simpler and it works much better than compgen -f -d. – koral Aug 21 '19 at 00:29