Skip to content

Instantly share code, notes, and snippets.

@jacobat
Last active November 6, 2016 11:16
Show Gist options
  • Save jacobat/6579236fd0d2645d0971c8644949d64a to your computer and use it in GitHub Desktop.
Save jacobat/6579236fd0d2645d0971c8644949d64a to your computer and use it in GitHub Desktop.
Description of automating a big refactoring using a macro and the quick list in vim

Macros and the quick list in vim

The problem

Yesterday I was looking at some Ruby code that I wanted to change. For context here is the gist of it:

module UserInstrumentation
  class StatsdSubscriber < SomeLibrary
    ...
    attach_to "some_topic"
    ...
  end
end

This same pattern played out in a big number of files.

There was a few variations on the topic. The attach_to statement was located at different places in different files with some at the top and others further down the file. Also some files had multiple classes with attach_to like:

module UserInstrumentation
  class StatsdSubscriber < SomeLibrary
    [some number of lines]
    attach_to "topic"
    ...
  end

  class LogSubscriber < SomeLibrary
    [some number of lines]
    attach_to 'topic'
    ...
  end
end  

What I wanted to do was to replace the attach_to call of every file with a single initializer file containing direct calls to Rails subscription mechanism like this:

ActiveSupport::Notifications.subscriber(/^\w\.topic$/, UserInstrumentation::StatsdSubscriber)

The solution

I could have of course opted for the manual solution and dutifully copied every single module name, class name, and topic into the initializer file. But I wondered if perhaps I could find a faster way doing the same using some of vim's more powerful features. This is what I came up with.

The solution comes in two parts. The first part is setting up a macro for doing this manipulation for a single attach_to and the second part is running this macro on all instances in the source code.

First up the macro. Before starting the macro I stored the following in register a:

ActiveSupport::Notifications.subscriber(/^\w\.TOPIC$/, MODULE::CLASS)

The macro recorded in register q then went like this:

  • Move to the first letter of the topic name, store everything until either a single- or a double-quote in register t for topic.
  • Search backwards for a line where the first word is class.
  • Move to the beginning of the first word after class and copy it to register c for class.
  • Search backwards for a line with the word module.
  • Move to the beginning of the first word after module and copy it to register m for module.
  • Switch to the initializer file.
  • Paste register a, ie. the line stored above.
  • Search for TOPIC and replace the word with the content of register t.
  • Search for MODULE and replace the word with the content of register m.
  • Search for CLASS and replace the word with the content of register c.

As you might imagine it took a few tries to get the macro fully correct.

After recording the macro I wanted to execute it on every instance of attach_to I could find in the app directory. To do this I used Ack to populate the quick list and then execute the macro for every item in the list:

:Ack attach_to app
:cdo execute "normal! @r<cr>"

Conclusion

Did I succeed in my goal of performing the edit faster than if I had opted for doing the edits manually? I don't know. It took a while to get the macro correct. My guess would be that I used roughly the same amount of time, perhaps a bit more. But I consider this an investment. The next time I see a change I need to do to a large number of files I know the mechanics and I will be able to do it faster -- probably a lot faster than a manual edit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment