Friday, September 16, 2011

Refactoring with Vim Macros

Automation is what computers do best but we often forget to take advantage of this fact. I don't know about you but I love to take shortcuts when it gets me to the same place. In practice though, it often takes a little extra work up front, but the small amount of effort comprises the majority of the end result.

There are some refactoring commands that you may wish to repeat in the Vim editor but don't want to make a permanent mapping for because they're only needed temporarily for the current file. And a complicated regular expression would take you too long to get right - especially with Vim's non-standard regex engine - and you may not feel it safe to run even if you did manage to figure it out.

Fortunately, Vim also supports macros. Knowing just the Vim commands that you already know, plus a couple of commands to record and play them, you can create powerful macros with very little effort.

To record a macro in Vim, type qq to begin recording, followed by the series of commands to record, then "q" to stop recording. You then type @q to execute the recorded macro.

Let's start with a very simple example. Say you have a file with a bunch of lines:

    line1
    line2
    line3
    line4
    ...

and you want to change the first letter of each line to be a capital letter. You might normally do this all manually by going to the beginning of each line and typing ~ (tilde in vim toggles the case of the character under the cursor). Then you might type j repeatedly to scroll down to each line and then type . to repeat the command. I've seen people do stuff like this on lines with 50+ lines and they just sit there like a robot typing the same thing over and over. Well, it doesn't have to be that way. Instead, you could just go to line1 and type:

    qq0~j<ESC>q

That will run the command on just the current line number and position you to the next line so you can easily run the macro. Note that the 0 (zero) I added to the macro is there to jump to the beginning of the line so we always only capitalize the first letter. Now you're ready to repeat the macro on the rest of the lines by typing:

    3@q

The '3' is for the number of times to repeat the macro.

As another example say you need to edit an PHP file that has a bunch of ugly, yet similar, lines like:

    if (@$FOO) someFunction($foo);

and you want to refactor these lines to be like:

    if (isset($foo) && !empty($foo)) {
      someFunction($foo);
    }


to do this, you could type the following (Don't worry if you don't understand it yet, I'll break it down in a minute):

  0f@xyeiisset(<ESC>f)i) && !empty(<ESC>pa)<ESC>la {<ENTER><ESC>o}<ESC>

so now all you need to do to this record it is add two q's to the beginning and one q to the end, like so:

    qq0f@xyeiisset(<ESC>f)i) && !empty(<ESC>pa)<ESC>la {<ENTER><ESC>o}<ESC>q

Now you can put your cursor anywhere on a line containing the if statement you want to refactor and type @q and it will run the macro. To repeat the macro, go to another line and type @@.

Okay, let's break down the command. It may need to be tweaked depending on your Vim setup, but on my set up it works like this:

    qq begins recording
    0 jumps to the beginning of the line
    f@ jumps to the @ sign.
    x deletes the @ sign
    ye yanks from the cursor to the end of the word (copies $foo)
    i puts you in INSERT mode
    isset( inserts the text "isset("
    <ESC> escapes you back to normal mode
    f) jumps to to the closing parenthesis
    i) && !empty( inserts the text ") && !empty("
    <ESC> escapes you back to normal mode
    p pastes the text "$foo"
    a) inserts the closing parenthesis
    <ESC> escapes you back to normal mode
    la moves your cursor passed the ")" and into insert mode
    { inserts the opening brace
    <ENTER< inserts a newline, pushing "somefunc($foo)" to the next line
    <ESC> escapes you back to normal mode
    o jumps your cursor to the next line and puts you in insert mode
    } inserts a closing brace (which should auto-indent depending on your setup)
    <ESC> escapes you back to normal mode
    q ends recording

This may seem intimidating on first glance and make you think it's more trouble than it's worth but the beauty of Vim is how quickly you can type commands. You just need to try it a few times to get the hang of it, trust me.

In the above examples I used qq to begin recording. This is just a convention for single macros. You can actually store multiple macros at the same time by storing them in different registers. For example qa begins recording to the register a, which you can then run with @a. You could then create another macro with qb and run with @b, and so on.

You can even run a macro on a specific range of lines, say 20 to 30, by doing:

    :20,30norm! @q

Similarly, you could visually select just the lines you want to run the macro on and then type"

    :norm @q

If you remember this simple strategy of recording each series of commands you plan to repeat, then you're golden. If you're going to have to type the commands either way, then recording them is very little effort compared to manually typing the commands over and over.

No comments:

Post a Comment

Followers