[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