Come posso interrompere una macro ricorsiva alla fine della riga?


13

Come posso creare una macro ricorsiva in modo che venga eseguita solo fino alla fine della riga?

O come eseguire una macro ricorsiva solo fino alla fine della riga?

Risposte:


11

Probabilmente esiste un metodo più semplice, ma forse potresti provare quanto segue.

Supponiamo che utilizzerai il registro qper registrare la tua macro ricorsiva.

All'inizio della registrazione, digitare:

:let a = line('.')

Quindi, alla fine della registrazione, invece di premere @qper rendere la macro ricorsiva, digitare il comando seguente:

:if line('.') == a | exe 'norm @q' | endif

Finalmente termina la registrazione della macro con q.

L'ultimo comando digitato riprodurrà la macro q( exe 'norm @q') ma solo se il numero di riga corrente ( line('.')) è lo stesso di quello inizialmente memorizzato nella variabile a.

Il :normalcomando consente di digitare normali comandi (come @q) dalla modalità Ex.
E il motivo per cui il comando è racchiuso in una stringa ed eseguito dal comando :executeè impedire :normaldi consumare (digitare) il resto del comando ( |endif).


Esempio di utilizzo

Supponiamo che tu abbia il seguente buffer:

1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4

E vuoi incrementare tutti i numeri da una linea arbitraria con una macro ricorsiva.

È possibile digitare 0per spostare il cursore all'inizio di una riga, quindi avviare la registrazione della macro:

qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q
  1. qqqcancella il contenuto del registro in qmodo che quando lo si chiama inizialmente durante la definizione della macro, non interferirà
  2. qq avvia la registrazione
  3. :let a=line('.') memorizza il numero di riga corrente all'interno della variabile a
  4. Ctrl+ aaumenta il numero sotto il cursore
  5. w sposta il cursore sul numero successivo
  6. :if line('.')==a|exe 'norm @q'|endif richiama la macro ma solo se il numero di riga non è cambiato
  7. q interrompe la registrazione

Dopo aver definito la macro, se si posiziona il cursore sulla terza riga, premere 0per spostarlo all'inizio della riga, quindi premere @qper riprodurre la macro q, dovrebbe interessare solo la riga corrente e non le altre:

1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4

Crea una macro ricorsiva dopo la registrazione

Se vuoi, puoi rendere la tua macro ricorsiva dopo la sua registrazione usando il fatto che è memorizzata in una stringa all'interno di un registro e che puoi concatenare due stringhe con l' .operatore punto .

Questo ti darebbe diversi vantaggi:

  • non è necessario cancellare il registro prima della registrazione, perché i caratteri @qverranno aggiunti nella macro dopo che è stata definita e dopo aver sovrascritto qualsiasi contenuto precedente fosse presente
  • non è necessario digitare nulla di insolito durante la registrazione, potresti concentrarti sulla creazione di una macro semplice e funzionante
  • possibilità di testarlo prima di renderlo ricorsivo per vedere come si comporta

Se registri la tua macro come al solito (in modo non ricorsivo), puoi successivamente renderla ricorsiva con il seguente comando:

let @q = @q . "@q"

O ancora più breve: let @q .= "@q"
.=è un operatore che consente di aggiungere una stringa a un'altra.

Ciò dovrebbe aggiungere i 2 caratteri @qalla fine della sequenza di sequenze di tasti memorizzata nel registro q. È inoltre possibile definire un comando personalizzato:

command! -register RecursiveMacro let @<reg> .= "@<reg>"

Definisce il comando :RecursiveMacroche attende il nome di un registro come argomento (a causa -registerdell'attributo passato a :command).
È lo stesso comando di prima, l'unica differenza è che sostituisci ogni ricorrenza di qcon <reg>. Quando il comando verrà eseguito, Vim espande automaticamente ogni ricorrenza di <reg>con il nome del registro fornito.

Ora, tutto quello che devi fare è registrare la tua macro come al solito (non ricorsivamente), quindi digitare :RecursiveMacro qper rendere qricorsiva la macro memorizzata nel registro .


Puoi fare la stessa cosa per rendere una macro ricorsiva a condizione che rimanga sulla riga corrente:

let @q = ":let a=line('.')\r" . @q . ":if line('.')==a|exe 'norm @q'|endif\r"

È esattamente la stessa cosa descritta all'inizio del post, tranne che questa volta lo fai dopo la registrazione. Basta concatenare due stringhe, una prima e una dopo le sequenze di tasti qattualmente contenute nel registro:

  1. let @q = ridefinisce il contenuto del registro q
  2. ":let a=line('.')\r"memorizza il numero di riga corrente all'interno della variabile aprima che la macro faccia il suo lavoro
    \rè necessario per dire a Vim di premere Invio ed eseguire il comando, vedere :help expr-quoteun elenco di caratteri speciali simili,
  3. . @q .concatena il contenuto corrente del qregistro con la stringa precedente e quella successiva,
  4. ":if line('.')==a|exe 'norm @q'|endif\r"richiama la macro qa condizione che la linea non sia cambiata

Ancora una volta, per salvare alcune sequenze di tasti, è possibile automatizzare il processo definendo il seguente comando personalizzato:

command! -register RecursiveMacroOnLine let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r"

E ancora, tutto ciò che devi fare è registrare la tua macro come al solito (in modo non ricorsivo), quindi digitare :RecursiveMacroOnLine qper rendere qricorsiva la macro memorizzata nel registro a condizione che rimanga sulla riga corrente.


Unisci i 2 comandi

Puoi anche modificare in :RecursiveMacromodo che copra i 2 casi:

  • fare una macro ricorsiva incondizionatamente,
  • rendere una macro ricorsiva a condizione che rimanga sulla riga corrente

Per fare questo, potresti passare un secondo argomento a :RecursiveMacro. Quest'ultimo testerebbe semplicemente il suo valore e, a seconda del valore, eseguirà uno dei 2 comandi precedenti. Darebbe qualcosa del genere:

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

Oppure (usando continuazioni di riga / barre rovesciate per renderlo un po 'più leggibile):

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

È lo stesso di prima, tranne che questa volta è necessario fornire un secondo argomento a :RecursiveMacro(a causa -nargs=1dell'attributo).
Quando verrà eseguito questo nuovo comando, Vim si espanderà automaticamente <args>con il valore fornito.
Se questo secondo argomento è diverso da zero / true ( if <args>) verrà eseguita la prima versione del comando (quella che rende una macro ricorsiva incondizionatamente), altrimenti se è zero / falso verrà eseguita la seconda versione (quella che rende una macro ricorsiva a condizione che rimanga sulla riga corrente).

Quindi, tornando all'esempio precedente, darebbe la seguente cosa:

qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
  1. qq inizia la registrazione di una macro all'interno del registro q
  2. <C-a> incrementa il numero sotto il cursore
  3. w sposta il cursore sul numero successivo
  4. q termina la registrazione
  5. :RecursiveMacro q 0rende la macro memorizzata all'interno del registro q ricorsiva ma solo fino alla fine della riga (a causa del secondo argomento 0)
  6. 3G sposta il cursore su una linea arbitraria (ad esempio 3)
  7. 0@q riproduce la macro ricorsiva dall'inizio della riga

Dovrebbe dare lo stesso risultato di prima:

1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4

Ma questa volta non è stato necessario digitare i comandi di distrazione durante la registrazione della macro, è possibile concentrarsi semplicemente sul crearne uno funzionante.

E durante il passaggio 5, se al comando fosse stato passato un argomento diverso da zero, ovvero se si fosse digitato :RecursiveMacro q 1invece di :RecursiveMacro q 0, la macro qsarebbe diventata ricorsiva incondizionatamente, il che avrebbe fornito il seguente buffer:

1 2 3 4
1 2 3 4
2 3 4 5
2 3 4 5

Questa volta la macro non si sarebbe fermata alla fine della 3a riga ma alla fine del buffer.


Per ulteriori informazioni, vedere:

:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register

2
L'elenco delle posizioni può essere utilizzato per avanzare sulle corrispondenze di ricerca in una macro, purché la macro non cambi la posizione delle corrispondenze, ad esempio :lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@qaumenterà tutti i numeri sulla riga 3. Forse c'è un modo per rendere questa soluzione meno fragile?
DJJcast,

@djjcast Potresti postarlo come risposta, l'ho provato e funziona davvero alla grande. C'è solo un caso che non capisco, quando eseguo la macro sulla riga seguente 1 2 3 4 5 6 7 8 9 10, ottengo 2 3 4 5 6 7 8 9 10 12invece di 2 3 4 5 6 7 8 9 10 11. Non so perché, forse ho sbagliato a scrivere qualcosa. Comunque sembra più sofisticato del mio semplice approccio, e implica regex per descrivere dove la macro dovrebbe spostare il cursore, così come un elenco di posizioni che non ho mai visto usato in questo modo. Mi piace un sacco!
saginaw,

@djjcast Spiacente, ho appena capito, il problema è venuto semplicemente dal mio regex, avrei dovuto usare \d\+per descrivere numeri di più cifre.
saginaw,

@djjcast Ah ora capisco cosa intendevi quando hai detto che la macro non dovrebbe cambiare la posizione delle partite. Ma non so come risolvere questo problema. L'unica idea che ho sarebbe quella di aggiornare l'elenco delle posizioni dall'interno della macro ma non sono abituato all'elenco delle posizioni, è troppo complesso per me, mi dispiace davvero.
Saginaw

1
@saginaw L'iterazione delle partite in ordine inverso sembra risolvere il problema nella maggior parte dei casi poiché sembra che sia meno probabile che una macro cambi le posizioni delle partite precedenti. Quindi, dopo il :lv ...comando, il :llacomando può essere usato per saltare all'ultima partita e il :lpcomando può avanzare sulle partite in ordine inverso.
DJJcast,

9

Una macro ricorsiva si interromperà non appena incontrerà un comando non riuscito. Pertanto, per interrompere alla fine di una riga, è necessario un comando che non riuscirà alla fine della riga.

Per impostazione predefinita *, il lcomando è un tale comando, quindi è possibile utilizzarlo per interrompere una macro ricorsiva. Se il cursore non si trova alla fine della riga, è sufficiente spostarlo indietro in seguito con il comando h.

Quindi, usando la stessa macro di esempio di saginaw :

qqqqq<c-a>lhw@qq

Suddiviso:

  1. qqq: Cancella il registro q,
  2. qq: Inizia a registrare una macro nel qregistro,
  3. <c-a>: Incrementa il numero sotto il cursore,
  4. lh: Se siamo alla fine della riga, interrompere la macro. Altrimenti, non fare nulla.
  5. w: Passa alla parola successiva sulla riga.
  6. @q: Ricorso
  7. q: Interrompe la registrazione.

È quindi possibile eseguire la macro con lo stesso 0@qcomando descritto da saginaw.


* L' 'whichwrap'opzione ti consente di definire quali tasti di movimento verranno spostati sulla riga successiva quando ti trovi all'inizio o alla fine di una riga (Vedi :help 'whichwrap'). Se hai limpostato questa opzione, la soluzione sopra descritta verrà interrotta.

Tuttavia, è probabile che si usa solo uno dei comandi in modalità normale di tre predefiniti per avanzare un singolo carattere ( <Space>, l, e <Right>), quindi se avete lincluso nel vostro 'whichwrap'ambiente, è possibile rimuovere uno che non si usa dal 'whichwrap'opzione, ad es. per <Space>:

:set whichwrap-=s

Quindi è possibile sostituire il lcomando nel passaggio 4 della macro con un <Space>comando.


1
Si noti inoltre che l'impostazione virtualedit=onemoreinterferirà con l'utilizzo lper rilevare la fine della linea, sebbene non in modo così grave whichwrap=l.
Kevin,

@Kevin Ottimo punto! Aggiornerò la mia risposta per menzionarla've'
Rich
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.