Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge two multi-line blocks of text in Vim?

Tags:

vim

I’d like to merge two blocks of lines in Vim, i.e., take lines k through l and append them to lines m through n. If you prefer a pseudocode explanation: [line[k+i] + line[m+i] for i in range(min(l-k, n-m)+1)].

For example,

abc def ...  123 45 ... 

should become

abc123 def45 

Is there a nice way to do this without copying and pasting manually line by line?

like image 681
ThiefMaster Avatar asked May 25 '12 19:05

ThiefMaster


People also ask

How do I convert multiple lines to one line in Vim?

When you want to merge two lines into one, position the cursor anywhere on the first line, and press J to join the two lines. J joins the line the cursor is on with the line below. Repeat the last command ( J ) with the . to join the next line with the current line.

How do I go down multiple lines in vim?

In normal mode or in insert mode, press Alt-j to move the current line down, or press Alt-k to move the current line up. After visually selecting a block of lines (for example, by pressing V then moving the cursor down), press Alt-j to move the whole block down, or press Alt-k to move the block up.


1 Answers

You can certainly do all this with a single copy/paste (using block-mode selection), but I'm guessing that's not what you want.

If you want to do this with just Ex commands

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

will transform

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

into

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

UPDATE: An answer with this many upvotes deserves a more thorough explanation.

In Vim, you can use the pipe character (|) to chain multiple Ex commands, so the above is equivalent to

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

Many Ex commands accept a range of lines as a prefix argument - in the above case the 5,8 before the del and the 1,4 before the s/// specify which lines the commands operate on.

del deletes the given lines. It can take a register argument, but when one is not given, it dumps the lines to the unnamed register, @", just like deleting in normal mode does. let l=split(@") then splits the deleted lines into a list, using the default delimiter: whitespace. To work properly on input that had whitespace in the deleted lines, like:

more than  hour  our  never  ever after work is over ~ 

we'd need to specify a different delimiter, to prevent "work is" from being split into two list elements: let l=split(@","\n").

Finally, in the substitution s/$/\=remove(l,0)/, we replace the end of each line ($) with the value of the expression remove(l,0). remove(l,0) alters the list l, deleting and returning its first element. This lets us replace the deleted lines in the order in which we read them. We could instead replace the deleted lines in reverse order by using remove(l,-1).

like image 68
rampion Avatar answered Sep 20 '22 14:09

rampion