Eu sei que você está usando o bash, e eu não estou confiante de que o que você pergunta é possível no bash. O que vou mostrar é como implementar o recurso solicitado no ZSH. (ZSH é um pouco como um bash melhorado - se você mudar, então você ainda deve permanecer proficiente).
No ZSH existe o ZSH Line Editor (abreviado para zle). Isso fornece todas as teclas de movimento como comandos ligáveis, bem como o bash. Para onde vai mais longe, está a capacidade de definir comandos personalizados. Um comando personalizado é qualquer função do shell que foi transformada em um widget.
Essas funções podem executar outros comandos e também obter acesso a diversas variáveis que são de interesse para o seu problema. Os que eu vou falar são:
- $ BUFFER - esta é a linha inteira que você está editando no momento
- $ CURSOR - esta é a posição da inserção na linha atual
Existem também outras disponíveis, como:
- $ LBUFFER - isso é tudo antes do cursor
- $ RBUFFER - isso é tudo depois do cursor
Agora acontece que o ZSH não só é capaz de fornecer associações de teclas personalizadas, como também tem um conjunto de operações muito mais abrangente que você pode executar em variáveis. Um dos que é interessante para esse problema é:
- z - Divida o resultado da expansão em palavras usando a análise de shell para encontrar as palavras, ou seja, levando em conta qualquer cotação no valor.
Você pode atribuir o $ BUFFER expandido diretamente a uma variável, assim:
line=${(z)BUFFER}
(a linha é agora uma matriz, mas irritantemente esta matriz começa no índice 1, ao contrário do bash!)
Isso não executará nenhuma expansão de caracteres globbing, portanto, ele retornará uma matriz dos argumentos reais em sua linha atual. Depois de ter isso, você está interessado na posição do ponto inicial de cada palavra no buffer. Infelizmente você pode ter vários espaços entre duas palavras, assim como palavras repetidas. A melhor coisa que posso pensar neste momento é remover cada palavra sendo considerada a partir do buffer atual como você a considera. Algo como:
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
Estamos quase lá agora! O que é necessário é uma maneira de ver qual palavra é a última palavra antes do cursor, e qual palavra é o começo da próxima palavra após o cursor.
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
Até agora, mostrei como você pode derivar o índice apropriado para o cursor para mover. Mas eu não mostrei a você como mover o cursor ou como vincular essas funções a chaves.
A variável $ CURSOR pode ser atualizada e, se você fizer isso, poderá mover o ponto de inserção atual. Muito fácil!
A vinculação de funções a chaves envolve uma etapa intermediária de vinculação a um widget primeiro:
zle -N WIDGET_NAME FUNCTION_NAME
Você pode vincular o widget a uma chave. Você provavelmente terá que procurar os identificadores de chave específicos, mas eu costumo apenas ligar para Ctrl-LETTER, o que é bastante fácil:
bindkey '^LETTER' WIDGET_NAME
Vamos colocar tudo isso junto e corrigir seu problema:
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
No que diz respeito aos meus testes, isso parece fazer o trabalho. Você provavelmente desejará alterar as chaves às quais ele se liga. Para referência, testei com a linha:
one 'two three' "four five" "'six seven' eight nine" * **/ **/**/*
^ ^ ^ ^ ^ ^ ^ ^
e navegou entre os carets. Não quebra.