[Bash-completion-devel] Directory name completion fails if name contains spaces and is quoted

Morita Sho morita-pub-en-debian at inz.sakura.ne.jp
Mon May 12 02:59:15 UTC 2008


Hi,

Let me explain about the function _get_cword I suggested and my opinion for that.

Previously, _get_cword was same as ${COMP_WORDS[$COMP_CWORD]}.
But it does not work in the middle of a word.
To work the completion in the middle of a word, we need to know the current 
cursor position.
Bash provides $COMP_POINT, this variable hold the current cursor position for 
the current command line($COMP_LINE).
Unfortunately, Bash does not provide the current cursor position for the current 
word.

I think it is a reason why the current _get_cword was switched to $COMP_LINE 
from ${COMP_WORDS[$COMP_CWORD]}.
The current _get_cword works as follow:
  1) Get the substring from the beginning of the current command line to the 
current cursor position.
  2) For that substring, find the last COMP_WORDBREAKS character that is not 
quoted by '\',
     and returns substring after the COMP_WORDBREAKS character found to the end 
of the string.

The problem of this way is handling for " and ' quoting.
To handle such quoting correctly, we need to parse the command line correctly.
It means we need to implement a command line parsing code as same as Bash does.
It is very hard to do in shell script.
I think it is better to use $COMP_WORDS instead.
Since $COMP_WORDS is an array consisting of words that splitted by Bash's parser,
so we don't need to consider for quoting.

However, $COMP_POINT is the position for $COMP_LINE,
and Bash does not provide the current cursor position for $COMP_WORDS.
If we know the current cursor position for the current word,
it is easy to do like this:
   ${COMP_WORDS[$COMP_CWORD]:0:$cursor_position_for_current_word}
But how to converting "the current cursor position for the command line" to "the 
current cursor position for the current word" ?


My idea (and what my _get_cword does) is as follow:

1) Find the offset for ${COMP_WORDS[$COMP_CWORD]} in $COMP_LINE.
    To do this, construct a regexp pattern like this:
 
/$IFS*${COMP_WORD[0]}$IFS+${COMP_WORD[1]}$IFS+${COMP_WORD[2]}$IFS+....${COMP_WORD[$COMP_CWORD-1]}$IFS+/
2) and then perform pattern matching against $COMP_LINE using expr(1)
      expr "$COMP_LINE" : "$pattern"
    this returns the number of characters matched.
    It value is the offset for ${COMP_WORDS[$COMP_CWORD]} in $COMP_LINE.
3) Subtract that value from $COMP_POINT and I get the cursor position for the 
current word.
4) Finally, get the substring from the beginning of the current word to the 
cursor position for the current word.
      ${COMP_LINE:$offset_for_current_word:$cursor_position_for_current_word}
     or
      ${COMP_WORDS[$COMP_CWORD]:0:$cursor_position_for_current_word}

That's all for explanation.


By the way, I cleaned up the code I posted.
* Removed unnesessary printf.
* Removed _quote_meta function.
* Slightly changed the loop condition from "i < $COMP_CWORD" to "i <= 
$COMP_CWORD - 1".
   I think it is more clear since it constructs the pattern for ${COMP_WORD[0 .. 
$COMP_CWORD-1]}.
* Append + for each arguments in expr(1) to pass an arbitrary string safely.

_get_cword()
{
   local pattern
   local i

   pattern=$'[ \t\n]*'
   for (( i=0; i <= $COMP_CWORD - 1; i++ )); do
     pattern="${pattern}$( printf "%s" "${COMP_WORDS[$i]}" | sed 's/\\/\\\\/g; 
s/[.*[$^]/\\&/g; s/[?+{()|]/[&]/g' )"$'[ \t\n]\+'
   done

   local LC_CTYPE=C
   printf "%s" "${COMP_WORDS[$COMP_CWORD]:0:$(( $COMP_POINT - $( expr + 
"$COMP_LINE" : + "$pattern" ) ))}"
}



FYI,
"local LC_CTYPE=C" may not work as expected.
See #478096 for details.
#478096 - bash: local does not work for some Bash variables - Debian Bug report logs
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=478096


Sorry for my poor English.
I tried to write a correct sentence, but my skill was not enough.
Ask me if you have any question.

Thanks,

-- 
Morita Sho <morita-pub-en-debian at inz.sakura.ne.jp>




More information about the Bash-completion-devel mailing list