Combinar varias líneas (dos bloques) en Vim

323

Me gustaría fusionar dos bloques de líneas en Vim, es decir, tomar líneas n..my agregarlas a líneas a..b. Si prefiere una explicación de pseudocódigo:[a[i] + b[i] for i in min(len(a), len(b))]

Ejemplo:

abc
def
...

123
45
...

debe convertirse

abc123
def45

¿Hay una buena manera de hacer esto sin copiar y pegar manualmente?

ThiefMaster
fuente
Entonces ... ¿quieres unir líneas alternas? Es decir, ¿quieres unir línea xcon unirse x+2?
Larsks
1
No, tengo dos bloques separados. Pseudocode-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster
2
Vea una pregunta similar (¡y una respuesta!) Aquí
NWS

Respuestas:

880

Ciertamente puede hacer todo esto con una sola copia / pegar (usando la selección de modo de bloque), pero supongo que eso no es lo que quiere.

Si quieres hacer esto con solo comandos Ex

:5,8del | let l=split(@") | 1,4s/$/\=remove(l,0)/

transformará

work it 
make it 
do it 
makes us 
harder
better
faster
stronger
~

dentro

work it harder
make it better
do it faster
makes us stronger
~

ACTUALIZACIÓN: Una respuesta con tantos votos a favor merece una explicación más completa.

En Vim, puede usar el carácter de canalización ( |) 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, 5,8antes dely 1,4antes de s///especificar en qué líneas operan los comandos.

delelimina las líneas dadas. Puede tomar un argumento de registro, pero cuando no se proporciona uno, volca las líneas en el 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, utilizando el delimitador predeterminado: espacios en blanco. Para funcionar correctamente en la entrada que tenía espacios en blanco en las líneas eliminadas, como:

more than 
hour 
our 
never 
ever
after
work is
over
~

tendríamos que especificar un delimitador diferente, para evitar que "el trabajo es" de ser dividida 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 mediante el uso remove(l,-1).

rampion
fuente
1
hmm ... solo tengo que presionar enter una vez. Y no insertará un espacio entre las dos mitades. Si hay algo de espacio final en las líneas (como en el ejemplo "trabajarlo"), todavía estará allí. Puede deshacerse de cualquier espacio final utilizando en s/\s*$/lugar de s/$/.
rampion
1
Gracias por la explicación. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlsparece ser aún mejor ya que limpia el historial de búsqueda después de la ejecución. Y no muestra el mensaje "x más / menos líneas" que requiere que presione enter.
ThiefMaster
11
Si quieres referencia vim completo para esa respuesta: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Benoit
16
Asegurarme de que personas como tú quieran publicar respuestas como esta es la razón por la que me convertí en moderador. Buen show :)
Tim Post
1
Hay un problema si no hay un espacio en blanco después de las primeras 4 oraciones.
Reman
58

Un comando elegante y concisa Ex solución del problema puede ser obtenida mediante la combinación de los :global, :movey :joinlos comandos. Suponiendo que el primer bloque de líneas comienza en la primera línea del búfer y que el cursor se encuentra en la línea que precede inmediatamente 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, vea mi respuesta a una pregunta similar ¿ Comportamiento depegar Vim -d” fuera de la caja? ".

ib.
fuente
E16: Invalid Range- Pero funciona de todos modos. Al eliminar el 1,funciona sin el error.
ThiefMaster
Y lástima que no pueda aceptar más de una respuesta; de lo contrario, la suya también tendría una marca verde.
ThiefMaster
3
¡Muy agradable! No sabía acerca de :movey :join!, ni qué ''significaba como argumento de rango ( :help '') y qué +y -significaba como modificadores de rango ( :help range). ¡Gracias!
rampion
@ThiefMaster: el comando está diseñado para usarse cuando los dos rangos de líneas a unir son adyacentes, de modo que el cursor se encuentra inicialmente en la última línea del primer bloque que precede inmediatamente a la primera línea del segundo bloque. (Consulte la respuesta y la pregunta vinculadas). El comando funciona según lo previsto, incluso si el número de líneas en los bloques difiere, aunque en ese caso da un mensaje de error. Se pueden suprimir los mensajes de error anteponiendo sil!el comando.
ib.
2
@ib .: Creo que sería una buena idea incluir la explicación detallada en esta respuesta también.
ThiefMaster
44

Para unir bloques de línea, debe realizar los siguientes pasos:

  1. Ir a la tercera línea: jj
  2. Ingrese al modo de bloqueo visual: CTRL-v
  3. Fije el cursor al final de la línea (importante para líneas de diferente longitud): $
  4. Ve al final: CTRL-END
  5. Cortar el bloque: x
  6. Ir al final de la primera línea: kk$
  7. Pega el bloque aquí: p

El movimiento no es el mejor (no soy un experto), pero funciona como quisieras. Espero que haya una versión más corta de la misma.

Estos son los requisitos previos para que esta técnica funcione bien:

  • Todas las líneas del bloque inicial (en el ejemplo en la pregunta abcy def) tienen la misma longitud XOR
  • la primera línea del bloque inicial es la más larga, y no te importan los espacios adicionales en el medio) XOR
  • La primera línea del bloque de inicio no es la más larga, y tiene espacios adicionales hasta el final.
mliebelt
fuente
Woah, eso es interesante! Nunca hubiera pensado que funcionaría de esa manera.
voithos
13
Esto funciona como se desea solo porque abcy deftienen la misma longitud. La selección de bloque mantendrá las sangrías del texto eliminado, por lo que si el cursor se encuentra en una línea corta al colocar el texto, las líneas se insertan entre las letras en las más largas, y los espacios se añaden a las más cortas si el cursor era más largo uno.
Izkata
De hecho ... ¿Hay alguna manera de hacerlo correctamente usando block copy & paste? Después de todo, esta es la forma más fácil de hacerlo (especialmente si no puede buscar las formas más complicadas aquí por alguna razón).
ThiefMaster
2
Como @Izkata dice que te encontrarás con el problema de que el texto se inserte entre las líneas más largas. Para solucionarlo, solo agregue más espacios al final de la primera línea para hacerlo más largo y luego pegue el bloque de texto. Una vez hecho esto, comprimir múltiples espacios en uno es tan simple como:%s/ \+/ /g
Khaja Minhajuddin
1
set ve=alldebería ayudar, consulte vimdoc.sourceforge.net/htmldoc/options.html#'virtualedit '
Ben
19

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:

  • qagraba todo hasta el siguiente qen un "búfer" en a.
  • ma crea una marca en la línea actual.
  • :5<CR> va al siguiente grupo.
  • y$ tira del resto de la línea.
  • 'a vuelve a la marca, establecida anteriormente.
  • $p pastas al final de la línea.
  • :5<CR> vuelve a la primera línea del segundo grupo.
  • dd lo borra
  • 'a vuelve a la marca.
  • jq baja una línea y detiene la grabación.
  • 3@a repite la acción para cada línea (3 en mi caso)
Kenneth Ballenegger
fuente
1
Tienes que presionar [Enter]después de las :5dos veces que lo escribes o esto no funcionará.
Shawn J. Goff
1
Estoy en gvim ¿Hay alguna forma de copiar y pegar ese comando en GVim? ^ V pega automáticamente en modo de inserción (lo cual tiene sentido, eso es lo que la gente suele querer) incluso si estoy actualmente en modo normal (?). Lo intenté :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@apero parece que solo se ejecuta q.
ThiefMaster
1
ThiefMaster: Intenta :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, donde los ^Mcaracteres se escriben presionando CTRL-V y luego Enter.
rampion
8

Como se mencionó en otra parte, la selección de bloques es el camino a seguir. Pero también puede usar cualquier variante de:

:!tail -n -6 % | paste -d '\0' % - | head -n 5

Este método se basa en la línea de comandos de UNIX. La pasteutilidad se creó para manejar este tipo de fusió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.
kevinlawler
fuente
Usar la selección de bloque no es el único camino a seguir. Tampoco es el más simple. El paste -dcomportamiento deseado ( similar) se puede implementar mediante un breve comando Vim, como se muestra en mi respuesta .
ib.
3
Además de eso, estoy en Windows, por lo que la solución implicaría abrir una conexión SSH a mi máquina Linux y pegar desde el editor en la terminal y viceversa.
ThiefMaster
3

Los datos de muestra son los mismos que los de rampion.

:1,4s/$/\=getline(line('.')+4)/ | 5,8d
kev
fuente
3

No creo que sea demasiado complicado. Simplemente establecería virtualedit en
( :set virtualedit=all)
Seleccione el bloque 123 y todo lo siguiente.
Ponlo después de la primera columna:

abc    123
def    45
...    ...

y elimine el espacio múltiple entre 1 espacio:

:%s/\s\{2,}/ /g
Animar
fuente
La pregunta en realidad no requiere espacios, haría algo como gvV:'<,'>s/\s+//g(vim debería insertarlo automáticamente '<,'>para que no necesite escribirlo manualmente).
Ben
2

Usaría repeticiones complejas :)

Dado este:

aaa
bbb
ccc

AAA
BBB
CCC

Con el cursor en la primera línea, presione lo siguiente:

qa}jdd''pkJxjq

y luego presione @a(y luego puede usar @@) tantas veces como sea necesario.

Deberías terminar con:

aaaAAA
bbbBBB
cccCCC

(Además de una nueva línea).

Explicacion:

  • qa comienza a grabar una repetición compleja en a

  • } salta a la siguiente línea vacía

  • jdd elimina la siguiente línea

  • '' vuelve a la posición anterior al último salto

  • p pegue la línea eliminada debajo de la actual

  • kJ agregue la línea actual al final de la anterior

  • xeliminar el espacio que se Jagrega entre las líneas combinadas; puedes omitir esto si quieres el espacio

  • j pasa a la siguiente línea

  • q finalizar la compleja grabación repetida

Después de eso, usaría @apara ejecutar la repetición compleja almacenada en a, y luego puede usar @@para volver a ejecutar la última repetición compleja ejecutada.

Gerardo Marset
fuente
1

Puede haber muchas formas de lograr esto. Combinaré dos bloques de texto usando cualquiera de los siguientes dos métodos.

supongamos que el primer bloque está en la línea 1 y el segundo bloque comienza en la línea 10 con la posición inicial del cursor en la línea número 1.

(\ n significa presionar la tecla Intro).

1. abc
   def
   ghi        

10. 123
    456
    789

con una macro usando los comandos: copiar, pegar y unir.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

con una macro usando los comandos, mueva una línea en el enésimo número de línea y únase.

qcqqc: 10m. \ nkJjq2 @ c

dvk317960
fuente