14

Is there a dedicated way to merge two blocks of text by interleaving lines, like passing from this:

a1
a2
a3
a4
  b1
  b2
  b3
  b4

to that:

a1
  b1
a2
  b2
a3
  b3
a4
  b4

in a few commands?

EDIT: I really like Sato Katsura's solution, here is how I have implemented it:

function! Interleave()
    " retrieve last selected area position and size
    let start = line(".")
    execute "normal! gvo\<esc>"
    let end = line(".")
    let [start, end] = sort([start, end], "n")
    let size = (end - start + 1) / 2
    " and interleave!
    for i in range(size - 1)
        execute (start + size + i). 'm' .(start + 2 * i)
    endfor
endfunction

" Select your two contiguous, same-sized blocks, and use it to Interleave ;)
vnoremap <pickYourMap> <esc>:call Interleave()<CR>
  • Now I'm curious - what's your use case? Are you block-renaming subtitles for a TV season? – VanLaser Aug 30 '15 at 22:22
  • @VanLaser Haha, I am not. Mostly, I am eye-parsing output from a program, which I need to check for consistency concerning order of creation / then delayed reading of objects. Interleaving blocks makes it easier to match corresponding lines in delayed output blocks. I also sometimes need to interleave lines of codes with repeated, similar instructions for logging or benchmarking. Generating those instructions is easy with macros, then interleaving them with actual code is now just a couple of keystrokes away with this function, which feels great :) – iago-lito Aug 31 '15 at 7:33
  • 1
    @lago-lito - thanks for the answer! Yes, Vim is quite versatile :) Your expression "eye-parsing" made me also think at scroll-binding two Vim windows. – VanLaser Aug 31 '15 at 11:10
  • I'm having trouble using this, how are you selecting the two consecutive blocks? Do they need to be adjacent? – cbcoutinho Mar 16 '18 at 1:08
  • @cbcoutinho Yes they have :) I'm not sure you could select them both otherwise. In the example I've show, I put my cursor on (say) b1, then I hit vip to select the whole chunk, then ,it which is the <map-I've-Picked>. Is it not working on your side? – iago-lito Mar 16 '18 at 9:49
8

There is no dedicated way to do that (as far as I know), but yeah, it can be done with a few commands:

function! Interleave(start, end, where)
    if a:start < a:where
        for i in range(0, a:end - a:start)
            execute a:start . 'm' . (a:where + i)
        endfor
    else
        for i in range(a:end - a:start, 0, -1)
            execute a:end . 'm' . (a:where + i)
        endfor
    endif
endfunction

You can run it with :call Interleave(5, 8, 1). The first parameter is the first line to move, the second one the last line, and the third one where to move them. You probably want to turn on line numbers to see what you're doing (:set number).

This assumes the blocks don't overlap. See :help :move and :help range() to understand how the function works.

There are probably better ways to pick up the two blocks. There is a plugin floating around that is supposed to let you swap two blocks. I can't remember the name of the plugin, but the author (perhaps the famous Dr. Chip?) has put more thought into finding an interface than I did. :)

  • Sweet! I only need two arguments since the two blocks are contiguous and have the same size : start and size. With a homebrew function that retrieves those values from a selection, it'll just be perfect. I'm working on it. :) – iago-lito Aug 29 '15 at 18:10
  • Interesting crosslink? ;) – iago-lito Feb 24 '16 at 9:02
11

Here is another alternative:

:g/^a/+4t .
:+,+5d 

First copy the lines which are 4 lines below to the after the current line (:h :t) then delete the consecutive b lines (:h :d)

Even better is this command:

 :g/^a//^\s*b/m .

Which means, for each line starting with a find the next line starting with 'b' and move it to below the current line.

  • 1
    I got "E16: Invalid range" on the second command. I tried .+,$d instead, and that worked (as did .+,.+4d). – Peter Lewerin Aug 29 '15 at 21:50
  • Not sure why this happens – Christian Brabandt Aug 30 '15 at 7:15
  • 1
    no it doesn't. Read :h :range, you can always use direct numbering instead of a regex search – Christian Brabandt Aug 30 '15 at 18:37
  • 1
    @iago-lito The second trick always works, but you need to change /^\s*b to another :range. e.g. : select 1st block, execute '<,'>g/^/'>+1m. – dedowsdi May 13 at 3:35
  • 1
    @iago-lito It's essentially the same as Christian's answer. Nothing is hardcoded if you visually select the 1st block, '>+1 marks beginning of 2nd block. – dedowsdi May 13 at 7:45
3

If you want to have a little bit of fun with macros and marks you could try something like this:

  • First put a mark (here a) on the line containing a1 with ma

  • Go to the line containing b1 and mark it with mb

  • Start recording a macro in the register you want (here the register q) with qq

  • Insert the folowing in your macro: ddmb'apjma'b

  • Stop recording the macro with q

  • Play it as many times as needed with X@q where X is the number of time to play it.

To detail the macro:

dd mb 'a p j ma 'b
 |  |  | | |    |
 |  |  | | |    go back to line marked `b`
 |  |  | | |
 |  |  | | move of one line and replace the mark `a`
 |  |  | insert the deleted line under the line marked `a`
 |  |  go to line marked `a`
 |  mark the future line to move with `b`
 delete the line to move

Edit As lago-lito mentionned it in the comments this method will overwrite the marks and the buffers.

  • For the marks I don't think that's a real problem: I rarely use all 26 marks in a buffer and I think one will most of the time find 2 free marks.

  • For the buffer it is possible to save it in a temporary variable: Before recording the macro use :let saveReg=getreg('"') to save the register and once the action is done use :call setreg('"', saveReg) to get the register back to its previous state.

Anyway I must admit that this solution is just a quick workaround and isn't optimal: In my opinion Christan's answer is the best one and should be accepted because it doesn't mess with buffers and marks, doesn't force the user to create a function and shows the power of the global command.

  • Interesting. Unfortunately, this overwrites the content of marks and registers, which I may be using ;) – iago-lito Aug 30 '15 at 15:11
  • @lago-lito: indeed it overwrites marks and buffers. For marks I never use all 26 marks in my buffers so I dont't think that's really a problem. For buffers it might be more of a problem, I think you can often find an unused buffer or if you really can't, use a temporary variable and the functions getreg() and setreg() to save your buffer. But I agree that it is not an optimal solution :-) – statox Aug 30 '15 at 18:22
1

I've just seen another similar question and the solution consists of:

Jump to the middle plus one:

Mj

And run:

:,$g/./exe 'm' 2*line('.')-line('$')-1
  • Interesting :) Beware that this interleaves your whole file, though, and not only the selected paragraph! – iago-lito Sep 21 '18 at 9:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.