Я знаю, что вы используете bash, и я не уверен, что то, что вы спрашиваете, возможно в bash. Я покажу вам, как реализовать запрошенную функцию в ZSH. (ZSH немного похож на улучшенный bash - если вы переключаетесь, вы все равно должны оставаться опытным).
В ZSH есть ZSH Line Editor (zle для краткости). Это предоставляет все клавиши перемещения как привязываемые команды, так же, как bash. Куда это идет дальше, это способность определять пользовательские команды. Пользовательская команда - это любая функция оболочки, которая была превращена в виджет.
Эти функции могут выполнять другие команды, и они также получают доступ к нескольким переменным, которые представляют интерес для вашей проблемы. Те, о которых я расскажу:
- $ BUFFER - это вся строка, которую вы сейчас редактируете
- $ CURSOR - это позиция вставки в текущей строке
Есть и другие доступные, такие как:
- $ LBUFFER - это все перед курсором
- $ RBUFFER - это все после курсора
Теперь случается, что ZSH не только способен предоставлять пользовательские привязки клавиш, но и имеет гораздо более полный набор операций, которые вы можете выполнять над переменными. Один из тех, которые интересны для этой проблемы:
- z - разделить результат раскрытия на слова, используя синтаксический анализ оболочки, чтобы найти слова, т.е. с учетом любых кавычек в значении.
Вы можете назначить расширенный $ BUFFER непосредственно переменной, например так:
line=${(z)BUFFER}
(строка теперь является массивом, но, к сожалению, этот массив начинается с индекса 1, в отличие от bash!)
Это не приведет к расширению глобальных символов, поэтому вернет массив фактических аргументов в вашей текущей строке. Если у вас есть это, вас интересует положение начальной точки каждого слова в буфере. К сожалению, вы можете иметь несколько пробелов между любыми двумя словами, а также повторяющиеся слова. Лучшее, что я могу придумать на данный момент, - это удалить каждое рассматриваемое слово из текущего буфера, как вы его считаете. Что-то вроде:
buffer=$BUFFER
words=${(z)buffer}
for word in $words[@]
do
# doing regular expression matching here,
# so need to quote every special char in $word.
escaped_word=${(q)word}
# Fancy ZSH to the rescue! (q) will quote the special characters in a string.
# Pattern matching like this creates $MBEGIN $MEND and $MATCH, when successful
if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
then
echo "Something strange happened... no match for current word"
return 1
fi
buffer=${buffer[$MEND,-1]}
done
Мы почти у цели! Необходим способ узнать, какое слово является последним словом перед курсором, а какое слово является началом следующего слова после курсора.
buffer=$BUFFER
words=${(z)buffer}
index=1
for word in $words[@]
do
if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
then
echo "Something strange happened... no match for current word"
return 1
fi
old_length=${#buffer}
buffer=${buffer[$MEND,-1]}
new_length=${#buffer}
old_index=$index
index=$(($index + $old_length - $new_length))
if [[ $old_index -lt $CURSOR && $index -ge $CURSOR ]]
then
# $old_index is the start of the last argument.
# you could move back to it.
elif [[ $old_index -le $CURSOR && $index -gt $CURSOR ]]
then
# $index is the start of the next argument.
# you could move forward to it.
fi
# Obviously both of the above conditions could be true, you would
# have to have a way to tell which one you wanted to test - but since
# you will have two widgets (one forward, one back) you can tell quite easily.
done
До сих пор я показал, как вы можете получить соответствующий индекс для перемещения курсора. Но я не показал вам, как перемещать курсор или как привязывать эти функции к клавишам.
Переменная $ CURSOR может быть обновлена, и если вы это сделаете, вы можете переместить текущую точку вставки. Довольно легко!
Привязка функций к клавишам включает в себя промежуточный этап привязки к виджету:
zle -N WIDGET_NAME FUNCTION_NAME
Затем вы можете привязать виджет к клавише. Вам, вероятно, придется поискать конкретные идентификаторы клавиш, но я обычно просто связываюсь с Ctrl-LETTER, что достаточно просто:
bindkey '^LETTER' WIDGET_NAME
Давайте соберем все это вместе и исправим вашу проблему:
function move_word {
local direction=$1
buffer=$BUFFER
words=${(z)buffer}
index=1
old_index=0
for word in $words[@]
do
if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
then
echo "Something strange happened... no match for current word $word in $buffer"
return 1
fi
old_length=${#buffer}
buffer=${buffer[$MEND,-1]}
new_length=${#buffer}
index=$(($index + $old_length - $new_length))
case "$direction" in
forward)
if [[ $old_index -le $CURSOR && $index -gt $CURSOR ]]
then
CURSOR=$index
return
fi
;;
backward)
if [[ $old_index -lt $CURSOR && $index -ge $CURSOR ]]
then
CURSOR=$old_index
return
fi
;;
esac
old_index=$index
done
case "$direction" in
forward)
CURSOR=${#BUFFER}
;;
backward)
CURSOR=0
;;
esac
}
function move_forward_word {
move_word "forward"
}
function move_backward_word {
move_word "backward"
}
zle -N my_move_backwards move_backward_word
zle -N my_move_forwards move_forward_word
bindkey '^w' my_move_backwards
bindkey '^e' my_move_forwards
Что касается моего собственного тестирования, то, похоже, это делает работу. Возможно, вы захотите изменить ключи, к которым он привязан. Для справки я проверил это с помощью строки:
one 'two three' "four five" "'six seven' eight nine" * **/ **/**/*
^ ^ ^ ^ ^ ^ ^ ^
и он перемещался между каретами. Это не обернуть.