0

I can't figure out what I am doing wrong. I have my bash_completion file setup as such:

_bcd()
{
    local cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $(compgen -W "$(back_directory.pl --complete $cur)" -- $cur) )
}
complete -o filenames -o nospace -F _bcd bcd

back_directory.pl is a program that will return directories paths up the tree: back_directory.pl --complete Th produces: This\ test/ But:

22:50:24-Josh@Joshuas-MacBook-Air:~/Desktop/bcd/This test/more    white/t$ bcd Th<TAB><TAB>
This   test/

As shown above, it doesn't auto complete for directories with whitespace in them (but it shows the completion option).

It should look like this: bcd This\ test/

I thought -o filenames should add the backslashes to escape the whitespace. Thanks for any help :)

  • 1
    Does `back_directory.pl` return more than one path? If so, how does it separate the values? – rici Nov 27 '14 at 04:24
  • @rici it will return more than one path if possible--separated by whitespace. But I think in this case it doesn't make a difference – rottweilers_anonymous Nov 27 '14 at 04:31
  • If they are separated by whitespace, they cannot contain whitespace. Whitespace is whitespace. How can you tell the difference? If you use some other separator, though, you could locally set `IFS` to get compgen to use it. (`IFS=: compgen ...`) – rici Nov 27 '14 at 04:33
  • Hehe... my bad. That works! It just screws up the formatting so that the completion options start on the command line. Eg. `bcd 'Desktop/ Josh/ ...` Also adds a single quote for some reason – rottweilers_anonymous Nov 27 '14 at 04:50
  • Are you testing with a path containing a newline? Or is the perl script outputing a newline (as well) to separate the paths? – rici Nov 27 '14 at 04:51
  • No. The path doesn't have a new line and the perl script has all the paths on the same line, separated by a colon, and no new lines at all. I edited my question to demonstrate the problem. – rottweilers_anonymous Nov 27 '14 at 04:58
  • And whitespace directories don't work again. Oh man :( Something is strange – rottweilers_anonymous Nov 27 '14 at 05:11
  • You are only passing a single word (with embedded newlines) to the `-W` option, not a list of (space-containig) words. I'm not sure `-W` is the way to go here, although you might be able to build up `COMPREPLY` more explitily by using loop like `while read -r line; do ...; done < <(back_directory --complete $cur)`. – chepner Nov 27 '14 at 14:17

2 Answers2

3

Your single call to compgen produces a single word (containing embdedded newlines), so you are only adding a single possible completion to COMPREPLY. Instead, you need to process the output of back_directory.pl one item at a time. Each item is tested as a possible match, and if compgen returns a non-empty string, add that to COMPREPLY.

_bcd() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    IFS=: read -a matches < <(back_directory.pl --complete "$cur")
    for match in "${matches[@]}"; do
        possible=$(IFS= compgen -W "$match" -- "$cur")
        [[ $possible ] && COMPREPLY+=( "$possible" )
    done
}

(Note: I'm assuming back_directory.pl will produce a single line of output similar to

directory1:directory two:directory three:directory4

)

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thanks for your feedback. Unfortunately this has the same problem I had before. If I use `complete -o filenames -o nospace -F _bcd bcd` I get completion for directories with spaces but completion with a beginning '/' is messed up and produces: `arqt/ joshua/ qko/ scripts/ tor/ users//` whereas `complete -o nospace -F _bcd bcd` does not work with directories including whitespace but produces (correctly): `/arqt/ /joshua/ /qko/ /scripts/ /tor/ /users/` – rottweilers_anonymous Nov 27 '14 at 14:58
  • Ughhh. Alright I got it working pretty decently using the line: `possible=$(IFS= compgen -W "$match" -- "${cur#/}")`. However, this will rewrite `bcd /us` to `bcd users/`. I guess I can live with this but it's a bit strange :/ Let me know if there's something I can do – rottweilers_anonymous Nov 27 '14 at 16:04
  • Okay... this seems to work: `if [[ ${#COMPREPLY[@]} -eq 1 && "$cur" =~ ^/ ]]; then COMPREPLY=( "/${COMPREPLY[0]}" ); fi` I'm going to fiddle around with it to see if it works properly :) – rottweilers_anonymous Nov 27 '14 at 16:12
  • seems to work wonderfully now! A bit hacky, but hey--it works! – rottweilers_anonymous Nov 27 '14 at 16:17
  • Of course, this breaks when it autocompletes `t` to `to` when the matches are `tor` and `tos`... – rottweilers_anonymous Nov 27 '14 at 16:27
1

For the sake of completion, this is the final file:

_bcd()
{
    local cur=${COMP_WORDS[COMP_CWORD]}
    IFS=: read -a matches < <(back_directory.pl --complete "$cur")
    for match in "${matches[@]}"; do
        possible=$(IFS= compgen -W "$match" -- "${cur#/}")
        [[ $possible ]] && COMPREPLY+=( "$possible" )
    done

    longest=""
    for e in "${COMPREPLY[@]}"; do
        if [[ "$longest" == "" ]]; then
            longest="$e"
        fi
        while [[ ! "$e" =~ ^$longest ]]; do
            longest=${longest%?}
        done
    done

    if [[ $longest != "$input" && "$cur" =~ ^/ ]]; then
        for ((i=0; i<${#COMPREPLY[@]}; i++))
        do
            COMPREPLY[$i]="/${COMPREPLY[$i]}"
        done
    fi
}
complete -o filenames -o nospace -F _bcd bcd

The script back_directory.pl --complete will return a single line of paths delimited by colons.

My solution seems to be pretty terrible but it works.

Basically it removes a beginning slash from the current word, creates all the matches (containing no beginning slash) and then checks to see whether $longest is different from $input, which would mean bash would change your current word to something different--in which case we add back a beginning slash.