¿Cómo fusionar dos bloques de texto de varias líneas en Vim?
Me gustaría fusionar dos bloques de líneas en Vim, es decir, tomar las líneas k a l y agregarlas a las líneas m a n . Si prefiere una explicación en pseudocódigo: [line[k+i] + line[m+i] for i in range(min(l-k, n-m)+1)]
.
Por ejemplo,
abc
def
...
123
45
...
debe convertirse
abc123
def45
¿Existe una buena manera de hacer esto sin copiar y pegar manualmente línea por línea?
Ciertamente puedes hacer todo esto con una sola copia y pegado (usando la selección en modo de bloque), pero supongo que eso no es lo que quieres.
Si quieres hacer esto solo con comandos Ex
:5,8del | let l=split(@") | 1,4s/$/\=remove(l,0)/
se transformará
work it
make it
do it
makes us
harder
better
faster
stronger
~
en
work it harder
make it better
do it faster
makes us stronger
~
ACTUALIZACIÓN: Una respuesta con tantos votos positivos merece una explicación más detallada.
En Vim, puede usar el carácter de barra vertical ( |
) para encadenar múltiples comandos Ex, por lo que lo anterior es equivalente a
:5,8del
:let l=split(@")
:1,4s/$/\=remove(l,0)/
Muchos comandos Ex aceptan un rango de líneas como argumento de prefijo; en el caso anterior, antes 5,8
de del
y 1,4
antes de s///
especifican en qué líneas operan los comandos.
del
elimina las líneas dadas. Puede tomar un argumento de registro, pero cuando no se proporciona ninguno, vuelca las líneas al registro sin nombre, @"
tal como lo hace la eliminación en modo normal. let l=split(@")
luego divide las líneas eliminadas en una lista, usando el delimitador predeterminado: espacios en blanco. Para trabajar correctamente en entradas que tenían espacios en blanco en las líneas eliminadas, como:
more than
hour
our
never
ever
after
work is
over
~
necesitaríamos especificar un delimitador diferente para evitar que "el trabajo es" se divida en dos elementos de la lista: let l=split(@","\n")
.
Finalmente, en la sustitución s/$/\=remove(l,0)/
, reemplazamos el final de cada línea ( $
) con el valor de la expresión remove(l,0)
. remove(l,0)
altera la lista l
, eliminando y devolviendo su primer elemento. Esto nos permite reemplazar las líneas eliminadas en el orden en que las leemos. En su lugar, podríamos reemplazar las líneas eliminadas en orden inverso usando remove(l,-1)
.
Se puede obtener un comando Ex elegante y conciso que resuelva el problema combinando los comandos :global
, :move
y :join
. Suponiendo que el primer bloque de líneas comienza en la primera línea del búfer y que el cursor está ubicado en la línea inmediatamente anterior a la primera línea del segundo bloque, el comando es el siguiente.
:1,g/^/''+m.|-j!
Para obtener una explicación detallada de esta técnica, consulte mi respuesta a esencialmente la misma pregunta " ¿Cómo lograr el comportamiento "pegar -d '␣'" listo para usar en Vim? ”.
Para unir bloques de línea, debes realizar los siguientes pasos:
- Vaya a la tercera línea:
jj
- Ingrese al modo de bloqueo visual:
CTRL-v
- Ancle el cursor al final de la línea (importante para líneas de diferente longitud):
$
- Ve al final:
CTRL-END
- Cortar el bloque:
x
- Ir al final de la primera línea:
kk$
- Pega el bloque aquí:
p
El movimiento no es el mejor (no soy un experto), pero funciona como querías. Espero que haya una versión más corta.
Estos son los requisitos previos para que esta técnica funcione bien:
- Todas las líneas del bloque inicial (en el ejemplo de la pregunta
abc
ydef
) tienen la misma longitud XOR - la primera línea del bloque inicial es la más larga y no te importan los espacios adicionales entre medio) XOR
- La primera línea del bloque inicial no es la más larga y hay espacios adicionales hasta el final.
Así es como lo haría (con el cursor en la primera línea):
qama:5<CR>y$'a$p:5<CR>dd'ajq3@a
Necesitas saber dos cosas:
- El número de línea en el que comienza la primera línea del segundo grupo (5 en mi caso), y
- el número de líneas en cada grupo (3 en mi ejemplo).
Esto es lo que está pasando:
qa
registra todo hasta el siguienteq
en un "búfer" en formatoa
.ma
crea una marca en la línea actual.:5<CR>
pasa al siguiente grupo.y$
tira del resto de la línea.'a
vuelve a la marca establecida anteriormente.$p
pega al final de la línea.:5<CR>
regresa a la primera línea del segundo grupo.dd
lo elimina.'a
vuelve a la marca.jq
baja una línea y deja de grabar.3@a
repite la acción para cada línea (3 en mi caso)
Como se mencionó en otra parte, la selección de bloques es el camino a seguir. Pero también puedes utilizar cualquier variante de:
:!tail -n -6 % | paste -d '\0' % - | head -n 5
Este método se basa en la línea de comando de UNIX. La paste
utilidad fue creada para manejar este tipo de combinación de líneas.
PASTE(1) BSD General Commands Manual PASTE(1)
NAME
paste -- merge corresponding or subsequent lines of files
SYNOPSIS
paste [-s] [-d list] file ...
DESCRIPTION
The paste utility concatenates the corresponding lines of the given input files, replacing all but the last file's newline characters with a single tab character,
and writes the resulting lines to standard output. If end-of-file is reached on an input file while other input files still contain data, the file is treated as if
it were an endless source of empty lines.