Como posso criar uma macro recursiva para que seja executada apenas até o final da linha?
Ou como executar uma macro recursiva até o final da linha?
Provavelmente existe um método mais simples, mas talvez você possa tentar o seguinte.
Digamos que você usará o registro q
para gravar sua macro recursiva.
No início da gravação, digite:
:let a = line('.')
Em seguida, no final da gravação, em vez de pressionar @q
para tornar a macro recursiva, digite o seguinte comando:
:if line('.') == a | exe 'norm @q' | endif
Finalmente termine a gravação da macro com q
.
O último comando digitado reproduzirá a macro q
( exe 'norm @q'
), mas apenas se o número da linha atual ( line('.')
) for o mesmo que o inicialmente armazenado na variável a
.
O :normal
comando permite digitar comandos normais (como @q
) no modo Ex.
E a razão pela qual o comando é agrupado em uma string e executado pelo comando :execute
é evitar :normal
consumir (digitando) o restante do comando ( |endif
).
Exemplo de uso.
Digamos que você tenha o seguinte buffer:
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
E você deseja incrementar todos os números de uma linha arbitrária com uma macro recursiva.
Você pode digitar 0
para mover o cursor para o início de uma linha e iniciar a gravação da macro:
qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q
qqq
limpa o conteúdo do registro q
para que quando você o chamar inicialmente durante a definição da macro, ele não interfiraqq
inicia a gravação:let a=line('.')
armazena o número da linha atual dentro da variável a
w
move o cursor para o próximo número:if line('.')==a|exe 'norm @q'|endif
chama a macro, mas somente se o número da linha não mudouq
para a gravaçãoDepois de definir sua macro, se você posicionar o cursor na terceira linha, pressione 0
para movê-lo para o início da linha e pressione @q
para reproduzir a macro q
, ela deve afetar apenas a linha atual e não as outras:
1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4
Tornar uma macro recursiva após a gravação
Se desejar, você pode tornar sua macro recursiva após a gravação usando o fato de que ela está armazenada em uma string dentro de um registro e que você pode concatenar duas strings com o .
operador de ponto .
Isso daria vários benefícios:
@q
serão adicionados à macro após a definição e após a substituição de qualquer conteúdo antigo existente.Se você gravar sua macro normalmente (não recursivamente), poderá torná-la recursiva posteriormente com o seguinte comando:
let @q = @q . "@q"
Ou ainda mais curto: let @q .= "@q"
.=
é um operador que permite acrescentar uma string a outra.
Isso deve adicionar os 2 caracteres @q
no final da sequência de pressionamentos de teclas armazenados no registro q
. Você também pode definir um comando personalizado:
command! -register RecursiveMacro let @<reg> .= "@<reg>"
Ele define o comando :RecursiveMacro
que aguarda o nome de um registro como argumento (devido ao -register
atributo passado para :command
).
É o mesmo comando de antes, a única diferença é que você substitui cada ocorrência de q
por <reg>
. Quando o comando será executado, o Vim expandirá automaticamente todas as ocorrências <reg>
com o nome do registro que você forneceu.
Agora, tudo o que você precisa fazer é gravar sua macro como de costume (não recursivamente) e depois digitar :RecursiveMacro q
para tornar a macro armazenada dentro do registro q
recursiva.
Você pode fazer o mesmo para tornar uma macro recursiva na condição de permanecer na linha atual:
let @q = ":let a=line('.')\r" . @q . ":if line('.')==a|exe 'norm @q'|endif\r"
É exatamente a mesma coisa descrita no início do post, exceto que desta vez você faz isso após a gravação. Você apenas concatena duas strings, uma antes e outra depois de qualquer tecla que o q
registro contenha atualmente:
let @q =
redefine o conteúdo do registro q
":let a=line('.')\r"
armazena o número da linha atual dentro da variável a
antes que a macro faça seu trabalho, \r
é necessário dizer ao Vim para pressionar Enter e executar o comando, veja :help expr-quote
uma lista de caracteres especiais semelhantes, . @q .
concatena o conteúdo atual do q
registro com a sequência anterior e a seguinte,":if line('.')==a|exe 'norm @q'|endif\r"
recorda a macro q
com a condição de que a linha não tenha mudadoNovamente, para salvar algumas teclas, você pode automatizar o processo, definindo o seguinte comando personalizado:
command! -register RecursiveMacroOnLine let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r"
E, novamente, tudo o que você precisa fazer é gravar sua macro como de costume (de forma não recursiva) e depois digitar :RecursiveMacroOnLine q
para tornar a macro armazenada dentro de um registro q
recursiva, desde que permaneça na linha atual.
Mesclar os 2 comandos
Você também pode ajustar :RecursiveMacro
para cobrir os 2 casos:
Para fazer isso, você pode passar um segundo argumento para :RecursiveMacro
. O último simplesmente testaria seu valor e, dependendo do valor, executaria um dos 2 comandos anteriores. Daria algo assim:
command! -register -nargs=1 RecursiveMacro if <args> | let @<reg> .= "@<reg>" | else | let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r" | endif
Ou (usando continuações de linha / barras invertidas para torná-lo um pouco mais legível):
command! -register -nargs=1 RecursiveMacro
\ if <args> |
\ let @<reg> .= "@<reg>" |
\ else |
\ let @<reg> = ":let a = line('.')\r" .
\ @<reg> .
\ ":if line('.')==a | exe 'norm @<reg>' | endif\r" |
\ endif
É o mesmo de antes, exceto que desta vez você deve fornecer um segundo argumento para :RecursiveMacro
(por causa do -nargs=1
atributo).
Quando esse novo comando for executado, o Vim será expandido automaticamente <args>
com o valor que você forneceu.
Se esse segundo argumento for diferente de zero / verdadeiro ( if <args>
), a primeira versão do comando será executada (aquela que torna uma macro recursiva incondicionalmente); caso contrário, se for zero / falso, a segunda versão será executada (a que cria uma macro recursiva na condição de permanecer na linha atual).
Voltando ao exemplo anterior, daria o seguinte:
qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
qq
inicia a gravação de uma macro dentro do registro q
<C-a>
incrementa o número sob o cursorw
move o cursor para o próximo númeroq
termina a gravação:RecursiveMacro q 0
torna a macro armazenada dentro do registro q
recursiva, mas apenas até o final da linha (devido ao segundo argumento 0
)3G
move o cursor para uma linha arbitrária (3 por exemplo)0@q
repete a macro recursiva desde o início da linhaDeve dar o mesmo resultado que antes:
1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4
Mas desta vez você não precisou digitar os comandos de distração durante a gravação da sua macro, basta concentrar-se em criar um comando funcional.
E durante a etapa 5, se você tivesse passado um argumento diferente de zero para o comando, ou seja, se você tivesse digitado em :RecursiveMacro q 1
vez de :RecursiveMacro q 0
, a macro q
se tornaria recursiva incondicionalmente, o que daria o seguinte buffer:
1 2 3 4
1 2 3 4
2 3 4 5
2 3 4 5
Desta vez, a macro não teria parado no final da 3ª linha, mas no final do buffer.
Para mais informações, veja:
:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register
:lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@q
, aumentará todos os números na linha 3. Talvez haja uma maneira de tornar essa solução menos frágil?1 2 3 4 5 6 7 8 9 10
, recebo em2 3 4 5 6 7 8 9 10 12
vez de2 3 4 5 6 7 8 9 10 11
. Não sei por que, talvez eu tenha digitado algo errado. De qualquer forma, parece mais sofisticado do que minha abordagem simples, e envolve expressões regulares para descrever para onde a macro deve mover o cursor, bem como uma lista de locais que nunca vi usada dessa maneira. Eu gosto muito!\d\+
para descrever números de vários dígitos.:lv ...
comando, o:lla
comando pode ser usado para pular para a última partida e o:lp
comando para avançar nas partidas na ordem inversa.Uma macro recursiva será interrompida assim que encontrar um comando que falhar. Portanto, para parar no final de uma linha, você precisa de um comando que falhará no final da linha.
Por padrão *, o
l
comando é um comando desse tipo; portanto, você pode usá-lo para interromper uma macro recursiva. Se o cursor é não no final da linha, então você só precisa movê-lo novamente depois com o comandoh
.Portanto, usando o mesmo exemplo de macro que saginaw :
Quebrado:
qqq
: Limpe o registro q,qq
: Comece a gravar uma macro noq
registro,<c-a>
: Incrementa o número sob o cursor,lh
: Se estivermos no final da linha, aborte a macro. Caso contrário, não faça nada.w
: Avança para a próxima palavra na linha.@q
: Recursoq
: Pare de gravar.Você pode então executar a macro com o mesmo
0@q
comando descrito por saginaw.* A
'whichwrap'
opção permite definir quais teclas de movimento serão quebradas para a próxima linha quando você estiver no início ou no final de uma linha (consulte:help 'whichwrap'
). Se vocêl
definiu esta opção, ela interromperá a solução descrita acima.No entanto, é provável que você só pode usar um dos comandos do modo normal de três padrão para avançar um único caractere (
<Space>
,l
e<Right>
), então se você tiverl
incluído na sua'whichwrap'
configuração, você pode remover um que você não usar a partir do'whichwrap'
opção, por exemplo, para<Space>
:Em seguida, você pode substituir o
l
comando na etapa 4 da macro por um<Space>
comando.fonte
virtualedit=onemore
interferirá no usol
para detectar o final de linha, embora não tão severamente quantowhichwrap=l
.'ve'