Comment arrêter une macro récursive à la fin de la ligne?

13

Comment puis-je créer une macro récursive pour qu'elle ne s'exécute que jusqu'à la fin de la ligne?

Ou comment exécuter une macro récursive jusqu'à la fin de la ligne uniquement?

DinushanM
la source

Réponses:

11

Il existe probablement une méthode plus simple, mais vous pouvez peut-être essayer ce qui suit.

Imaginons que vous utilisiez le registre qpour enregistrer votre macro récursive.

Au tout début de l'enregistrement, saisissez:

:let a = line('.')

Ensuite, à la toute fin de l'enregistrement, au lieu d'appuyer sur @qpour rendre la macro récursive, tapez la commande suivante:

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

Enfin, terminez l'enregistrement de la macro avec q.

La dernière commande que vous avez tapée rejouera la macro q( exe 'norm @q') mais uniquement si le numéro de ligne actuel ( line('.')) est le même que celui initialement stocké dans la variable a.

La :normalcommande vous permet de taper des commandes normales (comme @q) en mode Ex.
Et la raison pour laquelle la commande est encapsulée dans une chaîne et exécutée par la commande :executeest d'empêcher :normalde consommer (taper) le reste de la commande ( |endif).


Exemple d'utilisation.

Disons que vous avez le tampon suivant:

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

Et vous voulez incrémenter tous les nombres d'une ligne arbitraire avec une macro récursive.

Vous pouvez taper 0pour déplacer votre curseur au début d'une ligne puis démarrer l'enregistrement de la macro:

qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q
  1. qqqefface le contenu du registre qafin que lorsque vous l'appelez initialement lors de la définition de la macro, il n'interfère pas
  2. qq démarre l'enregistrement
  3. :let a=line('.') stocke le numéro de ligne actuel dans la variable a
  4. Ctrl+ aincrémente le nombre sous le curseur
  5. w déplace le curseur sur le numéro suivant
  6. :if line('.')==a|exe 'norm @q'|endif rappelle la macro mais uniquement si le numéro de ligne n'a pas changé
  7. q arrête l'enregistrement

Une fois que vous avez défini votre macro, si vous positionnez votre curseur sur la troisième ligne, appuyez sur 0pour le déplacer au début de la ligne, puis sur @qpour rejouer la macro q, cela ne devrait affecter que la ligne actuelle et pas les autres:

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

Rendre une macro récursive après l'enregistrement

Si vous le souhaitez, vous pouvez rendre votre macro récursive après son enregistrement en utilisant le fait qu'elle est stockée dans une chaîne à l'intérieur d'un registre et que vous pouvez concaténer deux chaînes avec l' .opérateur point .

Cela vous donnerait plusieurs avantages:

  • pas besoin d'effacer le registre avant l'enregistrement, car les caractères @qseront ajoutés dans la macro une fois qu'elle aura été définie et après avoir écrasé le contenu ancien
  • pas besoin de taper quoi que ce soit d'inhabituel pendant l'enregistrement, vous pouvez vous concentrer sur la création d'une macro simple et fonctionnelle
  • possibilité de le tester avant de le rendre récursif pour voir comment il se comporte

Si vous enregistrez votre macro comme d'habitude (de manière non récursive), vous pouvez la rendre récursive par la suite avec la commande suivante:

let @q = @q . "@q"

Ou encore plus court: let @q .= "@q"
.=est un opérateur qui permet d'ajouter une chaîne à une autre.

Cela devrait ajouter les 2 caractères @qà la toute fin de la séquence de touches stockées dans le registre q. Vous pouvez également définir une commande personnalisée:

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

Il définit la commande :RecursiveMacroqui attend le nom d'un registre comme argument (en raison de l' -registerattribut passé à :command).
C'est la même commande qu'auparavant, la seule différence est que vous remplacez chaque occurrence de qpar <reg>. Lorsque la commande sera exécutée, Vim étendra automatiquement chaque occurrence de <reg>avec le nom de registre que vous avez fourni.

Maintenant, tout ce que vous avez à faire est d'enregistrer votre macro comme d'habitude (de manière non récursive), puis de taper :RecursiveMacro qpour rendre la macro stockée dans le registre qrécursive.


Vous pouvez faire la même chose pour rendre une macro récursive à condition qu'elle reste sur la ligne courante:

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

C'est exactement la même chose que celle décrite au début du post, sauf que cette fois vous le faites après l'enregistrement. Vous venez de concaténer deux chaînes, une avant et une après les frappes que le qregistre contient actuellement:

  1. let @q = redéfinit le contenu du registre q
  2. ":let a=line('.')\r"stocke le numéro de ligne actuel dans la variable aavant que la macro ne fasse son travail
    \rest nécessaire pour dire à Vim d'appuyer sur Entrée et d'exécuter la commande, voir :help expr-quotepour une liste de caractères spéciaux similaires,
  3. . @q .concatène le contenu actuel du qregistre avec la chaîne précédente et la suivante,
  4. ":if line('.')==a|exe 'norm @q'|endif\r"rappelle la macro qà condition que la ligne ne change pas

Encore une fois, pour enregistrer certaines séquences de touches, vous pouvez automatiser le processus en définissant la commande personnalisée suivante:

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

Et encore une fois, tout ce que vous avez à faire est d'enregistrer votre macro comme d'habitude (de manière non récursive), puis de taper :RecursiveMacroOnLine qpour rendre la macro stockée dans le registre qrécursive à la condition qu'elle reste sur la ligne actuelle.


Fusionner les 2 commandes

Vous pouvez également modifier :RecursiveMacroafin qu'il couvre les 2 cas:

  • rendre une macro récursive inconditionnellement,
  • rendre une macro récursive à condition qu'elle reste sur la ligne courante

Pour ce faire, vous pouvez passer un deuxième argument à :RecursiveMacro. Ce dernier testerait simplement sa valeur et, selon la valeur, exécuterait l'une des 2 commandes précédentes. Cela donnerait quelque chose comme ceci:

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 (en utilisant des continuations / antislashs de ligne pour le rendre un peu plus lisible):

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

C'est la même chose qu'avant, sauf que cette fois vous devez fournir un 2ème argument à :RecursiveMacro(à cause de l' -nargs=1attribut).
Lorsque cette nouvelle commande sera exécutée, Vim se développera automatiquement <args>avec la valeur que vous avez fournie.
Si ce 2e argument est non nul / vrai ( if <args>) la première version de la commande sera exécutée (celle qui rend une macro récursive inconditionnellement), sinon si elle est nulle / fausse alors la deuxième version sera exécutée (celle qui fait une macro récursive à condition qu'elle reste sur la ligne courante).

Donc, pour revenir à l'exemple précédent, cela donnerait la chose suivante:

qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
  1. qq commence l'enregistrement d'une macro à l'intérieur du registre q
  2. <C-a> incrémente le nombre sous le curseur
  3. w déplace le curseur sur le numéro suivant
  4. q termine l'enregistrement
  5. :RecursiveMacro q 0rend la macro stockée dans le registre q récursive mais seulement jusqu'à la fin de la ligne (à cause du deuxième argument 0)
  6. 3G déplace votre curseur sur une ligne arbitraire (3 par exemple)
  7. 0@q rejoue la macro récursive depuis le début de la ligne

Il devrait donner le même résultat qu'auparavant:

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

Mais cette fois, vous n'avez pas eu à taper les commandes distrayantes pendant l'enregistrement de votre macro, vous pouvez simplement vous concentrer sur la création d'une macro fonctionnelle.

Et au cours de l'étape 5, si vous aviez passé un argument non nul à la commande, c'est-à-dire si vous aviez tapé à la :RecursiveMacro q 1place de :RecursiveMacro q 0, la macro qserait devenue récursive inconditionnellement, ce qui aurait donné le tampon suivant:

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

Cette fois, la macro ne se serait pas arrêtée à la fin de la 3ème ligne mais à la fin du tampon.


Pour plus d'informations, voir:

:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register
saginaw
la source
2
La liste des emplacements peut être utilisée pour avancer sur les correspondances de recherche dans une macro, tant que la macro ne change pas la position des correspondances, par exemple :lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@q, incrémentera tous les nombres sur la ligne 3. Peut-être existe-t-il un moyen de rendre cette solution moins fragile?
djjcast
@djjcast Vous pouvez le poster comme réponse, je l'ai essayé et ça marche vraiment bien. Il n'y a qu'un seul cas que je ne comprends pas, lorsque j'exécute la macro sur la ligne suivante 1 2 3 4 5 6 7 8 9 10, j'obtiens à la 2 3 4 5 6 7 8 9 10 12place de 2 3 4 5 6 7 8 9 10 11. Je ne sais pas pourquoi, j'ai peut-être mal écrit quelque chose. Quoi qu'il en soit, cela semble plus sophistiqué que mon approche simple, et cela implique des expressions rationnelles pour décrire où la macro doit déplacer le curseur, ainsi qu'une liste d'emplacement que je n'ai jamais vu utilisée de cette façon. Je l'aime beaucoup!
saginaw
@djjcast Désolé, je viens de comprendre, le problème vient simplement de mon expression régulière, j'aurais dû utiliser \d\+pour décrire des nombres à plusieurs chiffres.
saginaw
@djjcast Ah maintenant je comprends ce que tu voulais dire quand tu as dit que la macro ne devait pas changer la position des correspondances. Mais je ne sais pas comment résoudre ce problème. La seule idée que j'aurais serait de mettre à jour la liste des emplacements depuis l'intérieur de la macro mais je n'ai pas l'habitude de la liste des emplacements, c'est trop complexe pour moi, vraiment désolé.
saginaw
1
@saginaw Itérer sur les correspondances dans l'ordre inverse semble résoudre le problème dans la plupart des cas, car il semble qu'il est moins probable qu'une macro change la position des correspondances précédentes. Ainsi, après la :lv ...commande, la :llacommande peut être utilisée pour passer à la dernière correspondance et la :lpcommande peut être utilisée pour avancer sur les correspondances dans l'ordre inverse.
djjcast
9

Une macro récursive s'arrête dès qu'elle rencontre une commande qui échoue. Par conséquent, pour vous arrêter à la fin d'une ligne, vous avez besoin d'une commande qui échouera à la fin de la ligne.

Par défaut *, la lcommande est une telle commande, vous pouvez donc l'utiliser pour arrêter une macro récursive. Si le curseur n'est pas à la fin de la ligne, il vous suffit ensuite de le reculer avec la commande h.

Donc, en utilisant le même exemple de macro que saginaw :

qqqqq<c-a>lhw@qq

En panne:

  1. qqq: Effacer le registre q,
  2. qq: Commencer à enregistrer une macro dans le qregistre,
  3. <c-a>: Incrémente le nombre sous le curseur,
  4. lh: Si nous sommes en fin de ligne, abandonnez la macro. Sinon, ne faites rien.
  5. w: Passer au mot suivant sur la ligne.
  6. @q: Recurse
  7. q: Arrête d'enregistrer.

Vous pouvez ensuite exécuter la macro avec la même 0@qcommande que celle décrite par saginaw.


* L' 'whichwrap'option vous permet de définir les touches de mouvement qui passeront à la ligne suivante lorsque vous êtes au début ou à la fin d'une ligne (voir :help 'whichwrap'). Si vous avez ldéfini cette option, cela rompra la solution décrite ci-dessus.

Cependant, il est probable que vous utilisez seulement l' une des trois commandes par défaut en mode normal pour faire avancer un seul caractère ( <Space>, let <Right>), donc si vous avez linclus dans votre 'whichwrap'configuration, vous pouvez supprimer celui que vous n'utilisez de la option, par exemple pour :'whichwrap'<Space>

:set whichwrap-=s

Vous pouvez ensuite remplacer la lcommande à l'étape 4 de la macro par une <Space>commande.

Riches
la source
1
Notez également que ce paramètre virtualedit=onemoreinterfère avec l'utilisation lpour détecter la fin de ligne, mais pas aussi sévèrement que whichwrap=l.
Kevin
@Kevin Bon point! Je mettrai à jour ma réponse pour mentionner've'
Rich