5

Is there a good way to reverse a set of paragraphs (navigable via cursor motion { and })? My practical use case is reversing the order of blocks of latex code (ie, reversing the order of jobs in a resume from oldest-to-newest to newest-to-oldest).

Example code:

\begin{rSubsection}{Example 1}{}{Redacted}{}
\item Stuff I did
\item Also did other things
\end{rSubsection}

\begin{rSubsection}{Example 2}{}{Redacted}{}
\item Stuff I did
\item Also did other things
\end{rSubsection}

\begin{rSubsection}{Example 3}{}{Redacted}{}
\item Stuff I did
\item Also did other things
\end{rSubsection}

\begin{rSubsection}{Example 4}{}{Redacted}{}
\item Stuff I did
\item Also did other things
\end{rSubsection}

3 Answers 3

4

If this wasn't something that I did frequently enough to set up a mapping/custom command, I'd probably just use a macro to automate my normal edit commands:

O<Esc>mfGo<Esc>qqmt'fd}mf'tPq3@q

Broken down, the steps look like this

  1. O<Esc>: Navigate to the line above the first paragraph. Your sample doesn't actually include such a line, so we're adding one.
  2. mf: Set a "from" mark.
  3. Go<Esc>: Navigate to the line below the last paragraph. Again, your sample doesn't include one, so we're adding it.
  4. qq: Start recording a macro into the "q register.
  5. mt: Set a "to" mark.
  6. 'f: Jump to the "from" mark.
  7. d}: Delete the paragraph.
  8. mf: We just deleted our "from mark", so reset it.
  9. 't: Jump to the "to" mark.
  10. P: Paste the deleted paragraph above the current line (and mark).
  11. q: Stop recording.
  12. 3@q: Replay the macro three times. 2@q would also work, but it would leave the cursor after the (new) first paragraph instead of before it. Alternatively, if you don't want to count how many paragraphs need to be sorted, you could just replay it once with @q, and then mash @@ until the paragraphs are all sorted.

If you need to remove the lines we added in steps 1 & 3, you can do so with the commands ddGdd.

Golfers could probably shorten the number of keystrokes required, but doing it straightforwardly like this is easier in real-life scenarios.

Similarly, I would personally find this much easier to come up with on the fly than the initial global command in user786441's otherwise excellent answer.

Here's another version that performs the same edits as in @SergioAraujo's excellent command-line answer (see the dapggP in the middle), but uses a mark instead of a search to find the next paragraph to operate on:

 Go<Esc>gg}maqqjdapggP'aq2@qdd
1
  • Ah ha! I knew there was a way to do it with a macro. I had just forgotten all about marks. Thanks for reminding me! Feb 8, 2018 at 11:32
3

You could try these 3 commands:

g/^\\begin/,/^\\end/-s/$/\="\<c-a>"/|?^\\begin?,+j!
g/^/m0
%s/\%u0001/\r/g

The first one appends a literal C-a character at the end of every line in your paragraphs, and joins all the lines in each paragraph.

Now that your paragraphs have been reduced to single lines, the second command can reverse their order.

Finally, the third command splits the lines back into paragraphs, by removing the literal C-a characters, and replacing them with newlines.


The first command can be explained like this:

g/^\\begin/,/^\\end/-s/$/\="\<c-a>"/|?^\\begin?,+j!
└────────┤ └─────────────────────┤   └────────────┤
         │                       │                └ and this one (B)
         │                       └ execute this command (A)
         └ for every line beginning with `\begin`

(A) can be broken down further:

,/^\\end/-s/$/\="\<c-a>"/
│└──────┤│└─┤ └────────┤
│       ││  │          └ with a literal `C-a`
│       ││  │
│       ││  └ replaces any end-of-line
│       ││
│       │└ … and move up one line
│       │
│       └ down to the next line beginning with `\end` …
│
└ from the current line (because there's nothing before the comma; you could write a dot; here it's implicit)

This is a substitution command whose:

  • range is ,/^\\end/-
  • pattern is $
  • replacement is \<c-a>

It replaces any end-of-line with a literal C-a.

(B) can be broken down like this:

?^\\begin?,+j!
└────────┤ │││
         │ ││└ without adding/removing any space
         │ ││
         │ │└ join all the lines inside the range
         │ │
         │ └ down to the line below the current line (`+` = `.+1`)
         │
         └ from the previous line beginning with `\begin`

The 2nd command can be explained like this:

g/^/m0
└─┤ └┤
  │  └ move it at the top
  │
  └ for every line in the file

The 3rd command can be explained like this:

%s/\%u0001/\r/g
└┤ └─────┤ └┤ │
 │       │  │ └ globally (i.e. all the occurrences on a line; not just the 1st)
 │       │  │
 │       │  └ with a newline
 │       │
 │       └ replace a `C-a` (`0001` is its unicode)
 │
 └ for every line in the file

For more information, see:

:help :g
:help :s
:help :m
:help :j

Alternatively, I haven't used any of them, but you could try the AdvancedSorters plugin (the ingo-library being a dependency), or this snippet of code.

3

Another one line solution:

:$put _ | g/Example [2-4]/normal! dapggP

One line solution:

:$pu _ | 16,20m0 | 16,20m5 | 16,20m10

I have thought a lot until reach this elegant solution (one-liner above). In this solution, we are adding a new lind at the end :$pu _, then, we are getting the last lines and moving them to the line 0, 5 and 10 respectively

Other way:

:$pu _ | normal gg
:16,$m.-1 | normal j
2@:

(old solution) This solution will invert the blocks order

:$put _ ..................... add a new line at the end
4k .........................  move to the begining of {example 4}
5:m-0 ......................  move next 5 lines to line zero
}}j ........................  move to {example 3}
5:m-11 .....................  move block to 11 lines before
}j .........................  move to the {example 2}
5:m-6 ......................  finishes the magic

Notice: When you type 5:m-0 for example vim will show :.,.+4m-0

OBS: if this block is inside a bigger one you have to use your Relative numbers to do the trick :set nu rnu

If you want mappings to move blocks

"" Move lines in all modes 
vnoremap J :m '>+1<CR>gv=gv
vnoremap K :m '<-2<CR>gv=gv
inoremap <A-j> <Esc>:m+<CR>==gi
inoremap <A-k> <Esc>:m-2<CR>==gi
nnoremap <A-j> :m+<CR>
nnoremap <A-k> :m-2<CR>
1
  • 1
    I like your newest one-liner!
    – Rich
    Feb 16, 2018 at 9:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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