Friday, July 01, 2011

Conditionally applying macro recordings.

I make and use recordings in Vim all the time. I cannot imagine my daily work-a-day life without them. The most common reason for using recordings is to perform a complex modification of a semi-formatted dataset: SQL result sets, CSV, TSV, XML, HTML, etc...

As with most things in Vim when you find some new way feature, you find that you probably already had the parts laying before you, but hadn't quite had the 'manual' to put all the parts together. Recently I found a new way to do so conditionally, that I thought I'd share; its one of those methods that has probably been staring me in the face for-ever, and I just didn't see it.

So. What am I talking about? Conditional Macros. Lets break it down:

Macros

Plain and simple. Here is a simple problem that I think requires a macro. Suppose you have some lines of text like the following:

List A: 108 unique colors, 387 total colors
List B: 266 unique colors, 1343 total colors
List C: 361 unique colors, 2554 total colors
List D: 174 unique colors, 1221 total colors
List E: 301 unique colors, 2665 total colors

Suppose you wanted to compute the difference between the total colors and unique colors. So, in the case of the first line this would be 387 minus 108. You can do this once, sure. But suppose there are a couple thousand lines, and you want it all done, and done in Vim. No problem. You would record a macro like so:
  1. start your macro recording to register m (qm)
  2. go to the beginning of the line (^)
  3. move up to the first number (WW)
  4. store the first number in a register ("iyw)
  5. go to the second number (f, )
  6. store the second number in a register("oyw)
  7. compute the difference between the numbers (:let @s=@o-@i[carriage return])
  8. print out the result at the end of the line ($a == [control r]s[control c])
  9. move to the next line (j)
  10. stop recording (q)
If you were to examine register m, you would see:

^WW"iywf, "oyw:let @s=@o-@i[carriage return]$a == [control r]s[control c]j

And if you executed this macro (@m) for each line you would get something like this:

List A: 108 unique colors, 387 total colors == 279
List B: 266 unique colors, 1343 total colors == 1077
List C: 361 unique colors, 2554 total colors == 2193
List D: 174 unique colors, 1221 total colors == 1047
List E: 301 unique colors, 2665 total colors == 2364

To do several lines you would replay the macro by typing something like 5@m to do it five times, etc.

Conditional

When conditional modification pops to mind, I think of the global command (:g//). Whenever there is a match for the global command, it executes some arbitrary commds. So suppose you had text like the following:

...
...
Uninteresting line
List A: 108 unique colors, 387 total colors
Uninteresting line
Uninteresting line
List B: 266 unique colors, 1343 total colors
Uninteresting line
List C: 361 unique colors, 2554 total colors
Uninteresting line
Uninteresting line
...
...

You could just delete all the uninteresting lines like so:

:g/^Uninteresting line$/delete

Conditional Macros

Now, you could just delete everything not interesting and then run your macro on whatever is left. But a lot of times, you might find that you are interested in 'fixing' some lines but leaving the rest of the text unchanged. You can do this by combining the global command with a macro. So suppose you had:

...
...
Mildly interesting line
List A: 108 unique colors, 387 total colors
Mildly interesting line
Mildly interesting line
List B: 266 unique colors, 1343 total colors
...
...

To execute the macro you created, but only for the interesting lines you can combine the macro with the global command:

:g/^List/norm @m

Which would yield the desired result:

...
...
Mildly interesting line
List A: 108 unique colors, 387 total colors == 279
Mildly interesting line
Mildly interesting line
List B: 266 unique colors, 1343 total colors == 1077
...
...

Wednesday, March 23, 2011

New indenthl syntax plugin

I've still been getting comments on my original indenthl post, lo four or so years ago. Back when I wrote it I was an advocate of the 'set noet' (no expanded tabs - a tab means a tab!) form of consistency. Although I still prefer this method for its individual control (I like 2 char tabs, you like 4 char tabs, some fool likes 8 char tabs) I've noticed at my job people mix spaces and tabs (no we're not a python house, although we were a jython house for a while). The result: UGLY. Anyway, I'm beginning to see some of the virtues of a 'set et' world.

Thus saying, I've done some IndentHL syntax updates. It now has a couple new features (and settings!):
  • Error highlighting: if you have a line indented with a combination of tabs and spaces then this will highlight it as an error.
  • 'follow the expandtab' setting: If you turn off the expandtab setting then the syntax plugin will highlight anything that isn't TAB'd out as error prone. If you specify et then the syntax plugin will do its same 'indent level highlighting' it did before for spaces (using your shiftwidth setting), and it'll highlight in red anything thats tabbed.
  • A setting to turn off error highlighting: maybe you don't care about consistency. Well, you can turn it off.
  • I originally designed three different highlight modes (see my original post). You can now pick which one you want (as a setting).

Also, I moved this syntax project over to github. Of course, its over at vim.org as well.