Refactoring in Vim

Pretty much anything you can do manually in Vim, you can also directly script and turn into some kind of command, function or simple mapping. This is one of its great powers. If you find yourself doing something on a regular basis, you can just make a shortcut to do that complex thing in a couple of keystrokes.

For example, in my job, we do a lot of React, using ES6. There’s tons of legacy code that creates class methods like this:

1
2
3
4
5
class Foo {
doStuff() {
// does some stuff
}
}

Or sometimes just some standalone function like this:

1
2
3
function doStuff() {
// does stuff
}

But sometimes those functions are used as callbacks or in other ways that mess up the scope. So I need to turn them into closures, like this:

1
2
3
4
5
class Foo {
doStuff = () => {
// does some stuff
}
}

Or:

1
2
3
const doStuff = () => {
// does stuff
}

Making those changes is very straightforward. Replace function with const if it exists, find the opening ( and insert the = before it, find the closing ) and insert the => after it. After doing it several times, I came up with a shortcut:

1
nnoremap <Leader>( :s/function/const/e^Mf(i = ^[f)a =>^[

Walking through it. First we replace function with const. :s/function/const/e. The e at the end ignores any errors. In this case, if it doesn’t find the word function. I wanted the single macro to work for regular functions as well as class methods, which don’t use the word function.

The ^M there is a return character. If you were just typing this command in command mode, you’d have to hit enter to do the substitution.

Note! You can’t just type the characters ^ and M here. And you won’t be able to copy and paste the code from this page into your Vim config. ^M is a special control code. You enter it by typing Control-v Control-m.

Now the replacement is done, if it needed to be.

Then we find the opening paren: f(. We go into insert mode and enter the equals sign: i =.

Then we need to escape out of insert mode. That’s done with another escape code: ^[. You get that by typing Control-v Control-[.

Finally, you find the closing paren, append, enter the fat arrow and escape again. f)a =>^[.

I’ve saved this as <Leader>(. Works great.

To create these types of mappings, you can just mentally walk through the steps and try to insert the right characters. But there’s an easier way - with macros.

A macro allows you to record everything you do and then play it back. You start a macro by typing q plus another character. Say, qa to make a macro called a. I did this and then just did the transformation that I’d normally do. Did the function to const substitution, found each paren and inserted stuff before and after.

When you’re done with your macro, hit q again to save it. Now you can re-run that macro by typing @a. By default, though, macros are not saved beyond the session you are in. There are ways to save your current macros and reload them, but I wanted to get the contents of the macro to use in a shortcut.

Macros are stored in registers. You can see the contents of any register or paste it into your buffer.

For macro a, type :reg: a and you’ll see the code that you just recorded. Which is exactly what I just showed you in the above mapping. You can even paste the content of a macro. Just type " plus the register of the macro, a in this case, plus p. So, "ap.

So, once you have a macro and have verified that it does what you want it to do, go into your Vim config, type your mapping, like nnoremap <Leader>( and then paste the macro in, as per the above. Note that it will have all the correct escape characters, etc. Wonderful. You can even edit it further from there if you need to.