Como faço para parar uma macro recursiva no final da linha?


13

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?

Respostas:


11

Provavelmente existe um método mais simples, mas talvez você possa tentar o seguinte.

Digamos que você usará o registro qpara 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 @qpara 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 :normalcomando 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 :normalconsumir (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 0para 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
  1. qqqlimpa o conteúdo do registro qpara que quando você o chamar inicialmente durante a definição da macro, ele não interfira
  2. qq inicia a gravação
  3. :let a=line('.') armazena o número da linha atual dentro da variável a
  4. Ctrl+ aincrementa o número sob o cursor
  5. w move o cursor para o próximo número
  6. :if line('.')==a|exe 'norm @q'|endif chama a macro, mas somente se o número da linha não mudou
  7. q para a gravação

Depois de definir sua macro, se você posicionar o cursor na terceira linha, pressione 0para movê-lo para o início da linha e pressione @qpara 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:

  • não é necessário limpar o registro antes da gravação, porque os caracteres @qserão adicionados à macro após a definição e após a substituição de qualquer conteúdo antigo existente.
  • sem necessidade de digitar nada incomum durante a gravação, você pode se concentrar em criar uma macro simples e funcional
  • possibilidade de testá-lo antes de torná-lo recursivo para ver como ele se comporta

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 @qno 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 :RecursiveMacroque aguarda o nome de um registro como argumento (devido ao -registeratributo passado para :command).
É o mesmo comando de antes, a única diferença é que você substitui cada ocorrência de qpor <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 qpara tornar a macro armazenada dentro do registro qrecursiva.


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 qregistro contenha atualmente:

  1. let @q = redefine o conteúdo do registro q
  2. ":let a=line('.')\r"armazena o número da linha atual dentro da variável aantes que a macro faça seu trabalho,
    \ré necessário dizer ao Vim para pressionar Enter e executar o comando, veja :help expr-quoteuma lista de caracteres especiais semelhantes,
  3. . @q .concatena o conteúdo atual do qregistro com a sequência anterior e a seguinte,
  4. ":if line('.')==a|exe 'norm @q'|endif\r"recorda a macro qcom a condição de que a linha não tenha mudado

Novamente, 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 qpara tornar a macro armazenada dentro de um registro qrecursiva, desde que permaneça na linha atual.


Mesclar os 2 comandos

Você também pode ajustar :RecursiveMacropara cobrir os 2 casos:

  • faça uma macro recursiva incondicionalmente,
  • faça uma macro recursiva na condição de permanecer na linha atual

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=1atributo).
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
  1. qq inicia a gravação de uma macro dentro do registro q
  2. <C-a> incrementa o número sob o cursor
  3. w move o cursor para o próximo número
  4. q termina a gravação
  5. :RecursiveMacro q 0torna a macro armazenada dentro do registro q recursiva, mas apenas até o final da linha (devido ao segundo argumento 0)
  6. 3G move o cursor para uma linha arbitrária (3 por exemplo)
  7. 0@q repete a macro recursiva desde o início da linha

Deve 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 1vez de :RecursiveMacro q 0, a macro qse 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

2
A lista de locais pode ser usada para avançar nas correspondências de pesquisa em uma macro, desde que a macro não mude a posição das correspondências, por exemplo :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?
djjcast

@ djjcast Você pode publicá-lo como resposta, eu tentei e funciona muito bem. Há apenas um caso que não entendo; quando executo a macro na linha a seguir 1 2 3 4 5 6 7 8 9 10, recebo em 2 3 4 5 6 7 8 9 10 12vez 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!
Saginaw

@djjcast Desculpe, eu acabei de entender, o problema veio simplesmente do meu regex, eu deveria ter usado \d\+para descrever números de vários dígitos.
precisa

@djjcast Ah, agora entendo o que você quis dizer quando disse que a macro não deveria mudar a posição das partidas. Mas não sei como resolver esse problema. A única idéia que tenho seria atualizar a lista de locais de dentro da macro, mas não estou acostumada à lista de locais, é muito complexo para mim, desculpe.
Saginaw

1
@saginaw A repetição das partidas em ordem inversa parece resolver o problema na maioria dos casos, pois parece menos provável que uma macro mude as posições das partidas anteriores. Portanto, após o :lv ...comando, o :llacomando pode ser usado para pular para a última partida e o :lpcomando para avançar nas partidas na ordem inversa.
djjcast

9

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 lcomando é 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 comando h.

Portanto, usando o mesmo exemplo de macro que saginaw :

qqqqq<c-a>lhw@qq

Quebrado:

  1. qqq: Limpe o registro q,
  2. qq: Comece a gravar uma macro no qregistro,
  3. <c-a>: Incrementa o número sob o cursor,
  4. lh: Se estivermos no final da linha, aborte a macro. Caso contrário, não faça nada.
  5. w: Avança para a próxima palavra na linha.
  6. @q: Recurso
  7. q: Pare de gravar.

Você pode então executar a macro com o mesmo 0@qcomando 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ê ldefiniu 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>, le <Right>), então se você tiver lincluí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>:

:set whichwrap-=s

Em seguida, você pode substituir o lcomando na etapa 4 da macro por um <Space>comando.


1
Observe também que a configuração virtualedit=onemoreinterferirá no uso lpara detectar o final de linha, embora não tão severamente quanto whichwrap=l.
22719 Kevin

@ Kevin Good point! Vou atualizar a minha resposta para mencionar #'ve'
2346 Rich Rich
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.