You can kill the background for speed, if you wish.[x]

Monday, August 9, 2010

More Stupid Vim Tricks

Today I had an SVN log that was in descending order by date (most recent at the top), and I wanted it in ascending order (most recent at the bottom). Now there may be a flag on the svn log command to do this, but I thought I'd see what I could do with Vim. One of the most useful tools for doing such things is macros. I read this article a while back about using macros over regexes for many situations, and it's proved to be a very useful technique, and one that looked to be of use here, too. Here's what the svn log looks like:

------------------------------------------------------------------------
r21783 | jbradsha | 2010-08-04 14:41:02 -0700 (Wed, 04 Aug 2010) | 2 lines

Log notes

------------------------------------------------------------------------
r21765 | jbradsha | 2010-08-03 16:09:05 -0700 (Tue, 03 Aug 2010) | 2 lines

Log notes

------------------------------------------------------------------------
r21519 | jbradsha | 2010-07-22 16:51:04 -0700 (Thu, 22 Jul 2010) | 4 lines

Log notes

And so on.

The log notes can be of any length and number of lines, so obviously this wasn't going to be a straightforward task. But it ended up being pretty easy with macros. What I wanted to do was sort those blocks by revision number. To do that, I figured the easiest way is to get each block on one line in a way that I could revert, sort the lines, and then put them back. So I put my cursor on the first character of the first line and did this:

qj 
v
/\n\n---- 
:'<,'> s/[\r\n]/#!#/g
j
q
100@j
:% sort
:% s/#!#/\r/g

Which quite nicely did the trick. Here's what each bit did:
  1. Started recording a macro labelled "j" (for "join")
  2. Started visual selection mode
  3. Searched for two newlines followed by ----: this selects up to the end of the line before the end of the current block (by going two lines before the beginning of the next)
  4. Within that selection, replace all newlines with a unique symbol (#!#), making it all one line ready to be re-exploded afterwards
  5. Move down one line to the beginning of the next block
  6. Stop recording the macro
  7. Run the macro 100 times (I had less than 100 commits to sort)
  8. Sort the now-one-line commits, increasing in number (sort! would reverse the sort)
  9. Replace my delimiters with newlines, restoring the original format
It may sound and look horrendous, but it was pretty straighforward, and macros are a lot easier when you're actually doing them. I just love the power and flexibility you have to do such things in Vim - I can't compare it to emacs, but no other editor allows you nearly this amount of power or flexibility. And if I had wanted to do an even more complex sort, I could have easily piped it out to a command line utility instead of using Vim's built-in sort. But for my purposes, this was plenty.

No comments: