¿Cómo detengo una macro recursiva al final de la línea?

13

¿Cómo puedo crear una macro recursiva para que solo se ejecute hasta el final de la línea?

¿O cómo ejecutar una macro recursiva solo hasta el final de la línea?

DinushanM
fuente

Respuestas:

11

Probablemente haya un método más simple, pero tal vez podría intentar lo siguiente.

Digamos que usará el registro qpara grabar su macro recursiva.

Al comienzo de la grabación, escriba:

:let a = line('.')

Luego, al final de la grabación, en lugar de presionar @qpara hacer que la macro sea recursiva, escriba el siguiente comando:

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

Finalmente finalice la grabación de la macro con q.

El último comando que escribió reproducirá la macro q( exe 'norm @q'), pero solo si el número de línea actual ( line('.')) es el mismo que el inicialmente almacenado en la variable a.

El :normalcomando le permite escribir comandos normales (como @q) desde el modo Ex.
Y la razón por la cual el comando está envuelto en una cadena y ejecutado por el comando :executees para evitar :normalconsumir (escribir) el resto del comando ( |endif).


Ejemplo de uso

Digamos que tiene el siguiente búfer:

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

Y desea incrementar todos los números de una línea arbitraria con una macro recursiva.

Puede escribir 0para mover el cursor al comienzo de una línea y luego comenzar la grabación de la macro:

qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q
  1. qqqborra el contenido del registro qpara que cuando lo llame inicialmente durante la definición de la macro, no interfiera
  2. qq comienza la grabación
  3. :let a=line('.') almacena el número de línea actual dentro de la variable a
  4. Ctrl+ aincrementa el número debajo del cursor
  5. w mueve el cursor al siguiente número
  6. :if line('.')==a|exe 'norm @q'|endif recuerda la macro pero solo si el número de línea no cambió
  7. q detiene la grabación

Una vez que haya definido su macro, si coloca su cursor en la tercera línea, presione 0para moverlo al comienzo de la línea, luego presione @qpara reproducir la macro q, solo debería afectar a la línea actual y no a las otras:

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

Hacer una macro recursiva después de la grabación

Si lo desea, puede hacer que su macro sea recursiva después de su grabación utilizando el hecho de que está almacenada en una cadena dentro de un registro y que puede concatenar dos cadenas con el .operador de punto .

Esto le daría varios beneficios:

  • no es necesario borrar el registro antes de la grabación, porque los caracteres @qse agregarán en la macro después de que se haya definido, y después de que haya sobrescrito cualquier contenido antiguo que haya allí
  • sin necesidad de escribir nada inusual durante la grabación, puede concentrarse en hacer una macro simple y funcional
  • posibilidad de probarlo antes de hacerlo recursivo para ver cómo se comporta

Si graba su macro como de costumbre (no recursivamente), puede hacerlo recursivo después con el siguiente comando:

let @q = @q . "@q"

O incluso más corto: let @q .= "@q"
.=es un operador que permite agregar una cadena a otra.

Esto debería agregar los 2 caracteres @qal final de la secuencia de teclas almacenadas dentro del registro q. También puede definir un comando personalizado:

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

Define el comando :RecursiveMacroque espera el nombre de un registro como argumento (debido al -registeratributo pasado :command).
Es el mismo comando que antes, la única diferencia es que reemplaza cada aparición de qcon <reg>. Cuando se ejecute el comando, Vim expandirá automáticamente cada aparición <reg>con el nombre de registro que proporcionó.

Ahora, todo lo que tiene que hacer es grabar su macro como de costumbre (no recursivamente), luego escriba :RecursiveMacro qpara que la macro almacenada dentro del registro sea qrecursiva.


Podría hacer lo mismo para hacer una macro recursiva con la condición de que permanezca en la línea actual:

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

Es exactamente lo mismo que se describe al comienzo de la publicación, excepto que esta vez lo haces después de la grabación. Simplemente concatena dos cadenas, una antes y otra después de las pulsaciones de teclas que el qregistro contiene actualmente:

  1. let @q = redefine el contenido del registro q
  2. ":let a=line('.')\r"almacena el número de línea actual dentro de la variable aantes de que la macro haga su trabajo,
    \res necesario decirle a Vim que presione Entrar y ejecute el comando, vea :help expr-quoteuna lista de caracteres especiales similares,
  3. . @q .concatena el contenido actual del qregistro con la cadena anterior y la siguiente,
  4. ":if line('.')==a|exe 'norm @q'|endif\r"recuerda la macro qcon la condición de que la línea no haya cambiado

Nuevamente, para guardar algunas teclas, puede automatizar el proceso definiendo el siguiente comando personalizado:

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

Y nuevamente, todo lo que tiene que hacer es grabar su macro como de costumbre (no recursivamente), luego escriba :RecursiveMacroOnLine qpara que la macro almacenada dentro del registro sea qrecursiva con la condición de que permanezca en la línea actual.


Combina los 2 comandos

También puede ajustar :RecursiveMacropara que cubra los 2 casos:

  • hacer una macro recursiva incondicionalmente
  • hacer una macro recursiva con la condición de que permanezca en la línea actual

Para hacer esto, podría pasar un segundo argumento a :RecursiveMacro. Este último simplemente probaría su valor y, dependiendo del valor, ejecutaría uno de los 2 comandos anteriores. Daría algo como esto:

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 (usando continuaciones de línea / barras invertidas para que sea un poco más legible):

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

Es lo mismo que antes, excepto que esta vez debe proporcionar un segundo argumento para :RecursiveMacro(debido al -nargs=1atributo).
Cuando se ejecute este nuevo comando, Vim se expandirá automáticamente <args>con el valor que proporcionó.
Si este segundo argumento es distinto de cero / verdadero ( if <args>), se ejecutará la primera versión del comando (el que hace que una macro sea recursiva incondicionalmente), de lo contrario, si es cero / falso, se ejecutará la segunda versión (la que hace una macro recursiva con la condición de que permanezca en la línea actual).

Volviendo al ejemplo anterior, daría lo siguiente:

qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
  1. qq comienza la grabación de una macro dentro del registro q
  2. <C-a> incrementa el número debajo del cursor
  3. w mueve el cursor al siguiente número
  4. q termina la grabación
  5. :RecursiveMacro q 0hace que la macro almacenada dentro del registro sea q recursiva pero solo hasta el final de la línea (debido al segundo argumento 0)
  6. 3G mueve el cursor a una línea arbitraria (3 por ejemplo)
  7. 0@q reproduce la macro recursiva desde el principio de la línea

Debería dar el mismo resultado que antes:

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

Pero esta vez no tuvo que escribir los comandos de distracción durante la grabación de su macro, simplemente podría concentrarse en hacer uno que funcione.

Y durante el paso 5, si hubiera pasado un argumento distinto de cero al comando, es decir, si hubiera escrito en :RecursiveMacro q 1lugar de :RecursiveMacro q 0, la macro qse habría vuelto recursiva incondicionalmente, lo que habría dado el siguiente búfer:

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

Esta vez, la macro no se habría detenido al final de la tercera línea, sino al final del búfer.


Para más información, ver:

:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register
Saginaw
fuente
2
La lista de ubicaciones se puede utilizar para avanzar sobre las coincidencias de búsqueda en una macro, siempre que la macro no cambie la posición de las coincidencias, por ejemplo, :lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@qaumentará todos los números en la línea 3. ¿Quizás haya una manera de hacer que esta solución sea menos frágil?
djjcast
@djjcast Podrías publicarlo como respuesta, lo he probado y funciona muy bien. Solo hay un caso que no entiendo, cuando ejecuto la macro en la siguiente línea 1 2 3 4 5 6 7 8 9 10, obtengo en 2 3 4 5 6 7 8 9 10 12lugar de 2 3 4 5 6 7 8 9 10 11. No sé por qué, tal vez escribí algo mal. De todos modos, parece más sofisticado que mi enfoque simple, e involucra expresiones regulares para describir dónde la macro debe mover el cursor, así como una lista de ubicaciones que nunca he visto utilizada de esta manera. ¡Me gusta mucho!
saginaw
@djjcast Lo siento, acabo de entender, el problema surgió simplemente de mi expresión regular, debería haber usado \d\+para describir números de varios dígitos.
saginaw
@djjcast Ah, ahora entiendo lo que quisiste decir cuando dijiste que la macro no debería cambiar la posición de los partidos. Pero no sé cómo resolver este problema. La única idea que tengo es actualizar la lista de ubicaciones desde el interior de la macro, pero no estoy acostumbrado a la lista de ubicaciones, es demasiado complejo para mí, lo siento mucho.
saginaw
1
@saginaw Iterando los partidos en orden inverso parece que resolvería el problema en la mayoría de los casos, ya que parece menos probable que una macro cambie las posiciones de los partidos anteriores. Entonces, después del :lv ...comando, el :llacomando se puede usar para saltar al último partido y el :lpcomando se puede usar para avanzar sobre los partidos en orden inverso.
djjcast
9

Una macro recursiva se detendrá tan pronto como encuentre un comando que falle. Por lo tanto, para detenerse al final de una línea, necesita un comando que fallará al final de la línea.

Por defecto *, el lcomando es dicho comando, por lo que puede usarlo para detener una macro recursiva. Si el cursor no está al final de la línea, entonces solo necesita moverlo hacia atrás con el comando h.

Entonces, usando el mismo ejemplo de macro que saginaw :

qqqqq<c-a>lhw@qq

Desglosado:

  1. qqq: Borrar el registro q,
  2. qq: Comience a grabar una macro en el qregistro,
  3. <c-a>: Incrementa el número debajo del cursor,
  4. lh: Si estamos al final de la línea, aborte la macro. De lo contrario, no hagas nada.
  5. w: Avanza a la siguiente palabra en la línea.
  6. @q: Recurse
  7. q: Para de grabar.

Luego puede ejecutar la macro con el mismo 0@qcomando descrito por saginaw.


* La 'whichwrap'opción le permite definir qué teclas de movimiento se ajustarán a la siguiente línea cuando se encuentre al principio o al final de una línea (Ver :help 'whichwrap'). Si ha lconfigurado esta opción, interrumpirá la solución descrita anteriormente.

Sin embargo, lo más probable es que sólo se utiliza uno de los comandos del modo normal de tres por defecto para el avance de un solo carácter ( <Space>, ly <Right>), por lo que si usted ha lincluido en su 'whichwrap'entorno, puede quitar uno que no utiliza de la 'whichwrap'opción, por ejemplo, para <Space>:

:set whichwrap-=s

Luego puede reemplazar el lcomando en el paso 4 de la macro con un <Space>comando.

Rico
fuente
1
Tenga en cuenta también que la configuración virtualedit=onemoreinterferirá con el uso lpara detectar el final de línea, aunque no tan severamente como whichwrap=l.
Kevin
@ Kevin Buen punto! Actualizaré mi respuesta para mencionar've'
Rich