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)].
abc def ... 123 45 ...
Is there a nice way to do this without copying and pasting manually line by line?
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)/
work it make it do it makes us harder better faster stronger ~
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:
Finally, in the substitution
s/$/\=remove(l,0)/, we replace the end of each line (
$) with the value of the expression
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
An elegant and concise Ex command solving the issue can be obtained by
:join commands. Assuming that
the first block of lines starts on the first line of the buffer, and
that the cursor is located on the line immediately preceding the first
line of the second block, the command is as follows.
For detailed explanation of this technique, see my answer to
an essentially the same question “How to achieve the “paste -d ‘␣’”
behavior out of the box in Vim?”.
To join blocks of line, you have to do the following steps:
- Go to the third line:
- Enter visual block mode:
- Anchor the cursor to the end of the line (important for lines of differing length):
- Go to the end:
- Cut the block:
- Go to the end of the first line:
- Paste the block here:
The movement is not the best one (I’m not an expert), but it works like you wanted. Hope there will be a shorter version of it.
Here are the prerequisits so this technique works well:
- All lines of the starting block (in the example in the question
def) have the same length XOR
- the first line of the starting block is the longest, and you don’t care about the additional spaces in between) XOR
- The first line of the starting block is not the longest, and you additional spaces to the end.