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?
Il existe probablement une méthode plus simple, mais vous pouvez peut-être essayer ce qui suit.
Imaginons que vous utilisiez le registre q
pour 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 @q
pour 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 :normal
commande 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 :execute
est d'empêcher :normal
de 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 0
pour 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
qqq
efface le contenu du registre q
afin que lorsque vous l'appelez initialement lors de la définition de la macro, il n'interfère pasqq
démarre l'enregistrement:let a=line('.')
stocke le numéro de ligne actuel dans la variable a
w
déplace le curseur sur le numéro suivant:if line('.')==a|exe 'norm @q'|endif
rappelle la macro mais uniquement si le numéro de ligne n'a pas changéq
arrête l'enregistrementUne fois que vous avez défini votre macro, si vous positionnez votre curseur sur la troisième ligne, appuyez sur 0
pour le déplacer au début de la ligne, puis sur @q
pour 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:
@q
seront ajoutés dans la macro une fois qu'elle aura été définie et après avoir écrasé le contenu ancienSi 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 :RecursiveMacro
qui attend le nom d'un registre comme argument (en raison de l' -register
attribut passé à :command
).
C'est la même commande qu'auparavant, la seule différence est que vous remplacez chaque occurrence de q
par <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 q
pour rendre la macro stockée dans le registre q
ré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 q
registre contient actuellement:
let @q =
redéfinit le contenu du registre q
":let a=line('.')\r"
stocke le numéro de ligne actuel dans la variable a
avant que la macro ne fasse son travail \r
est nécessaire pour dire à Vim d'appuyer sur Entrée et d'exécuter la commande, voir :help expr-quote
pour une liste de caractères spéciaux similaires, . @q .
concatène le contenu actuel du q
registre avec la chaîne précédente et la suivante,":if line('.')==a|exe 'norm @q'|endif\r"
rappelle la macro q
à condition que la ligne ne change pasEncore 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 q
pour rendre la macro stockée dans le registre q
récursive à la condition qu'elle reste sur la ligne actuelle.
Fusionner les 2 commandes
Vous pouvez également modifier :RecursiveMacro
afin qu'il couvre les 2 cas:
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=1
attribut).
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
qq
commence l'enregistrement d'une macro à l'intérieur du registre q
<C-a>
incrémente le nombre sous le curseurw
déplace le curseur sur le numéro suivantq
termine l'enregistrement:RecursiveMacro q 0
rend la macro stockée dans le registre q
récursive mais seulement jusqu'à la fin de la ligne (à cause du deuxième argument 0
)3G
déplace votre curseur sur une ligne arbitraire (3 par exemple)0@q
rejoue la macro récursive depuis le début de la ligneIl 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 1
place de :RecursiveMacro q 0
, la macro q
serait 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
: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?1 2 3 4 5 6 7 8 9 10
, j'obtiens à la2 3 4 5 6 7 8 9 10 12
place de2 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!\d\+
pour décrire des nombres à plusieurs chiffres.:lv ...
commande, la:lla
commande peut être utilisée pour passer à la dernière correspondance et la:lp
commande peut être utilisée pour avancer sur les correspondances dans l'ordre inverse.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
l
commande 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 commandeh
.Donc, en utilisant le même exemple de macro que saginaw :
En panne:
qqq
: Effacer le registre q,qq
: Commencer à enregistrer une macro dans leq
registre,<c-a>
: Incrémente le nombre sous le curseur,lh
: Si nous sommes en fin de ligne, abandonnez la macro. Sinon, ne faites rien.w
: Passer au mot suivant sur la ligne.@q
: Recurseq
: Arrête d'enregistrer.Vous pouvez ensuite exécuter la macro avec la même
0@q
commande 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 avezl
dé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>
,l
et<Right>
), donc si vous avezl
inclus dans votre'whichwrap'
configuration, vous pouvez supprimer celui que vous n'utilisez de la option, par exemple pour :'whichwrap'
<Space>
Vous pouvez ensuite remplacer la
l
commande à l'étape 4 de la macro par une<Space>
commande.la source
virtualedit=onemore
interfère avec l'utilisationl
pour détecter la fin de ligne, mais pas aussi sévèrement quewhichwrap=l
.'ve'