Thank you for the feedback, please see some of the reddit comments for some fantastic alternative ( and more concise ) solutions by commenters.

Sometimes, as a programmer, you need to edit text in a very specialized way. Say you have a complex, repetitive block of markup that occurs many times in your codebase that you want to reformat. You could match the code you want to replace and then make a decision based on some other content about whether or not to replace it. All of this would be very tedious to do manually. But you can automate this with a macro!

Let Vim be your eyes.

Consider: You have a requirement to add semantic labels to every form element within a Rails application ( using haml in this instance ), but the labels are i18n’d h4 tags with no for field applied, like this:

%h4= t 'financials.operating_costs.company_expenses.question_3'
= f.text_field :amount, :class => 'input-text-right'

So you want this:

= f.label :amount, t('financials.operating_costs.company_expenses.question_3')
= f.text_field :amount, :class => 'input-text-right'

To add an additional layer of complexity to this requirement, you do not want to change the many stand alone headers that match this pattern, ie:


%h4= t 'stuff.we.dont.want.to.change'
%p Sometimes there is stuff we just don't want to change ...

Record a macro!

Macros let you define repeatable blocks of Vim functionality, they essentially let you automate complex repetitive processes.

Editing macros

When building a tool like this using Vims macro system combined with all the built in commands, you are ultimately going to end up with a very complex command set, in this case, something like:

/%h4.*\_s.*\_s.*=\s\?\w*\.\w*/e^Mw"eye?%h4^Mgndaf.label ^["epa,^[wi(^[$a)^[

This is a very complex string of commands, and it is likely that you will make a mistake or need to make some amendments to it to get it right. Say you have a macro assigned to the register a. You can edit the macro as follows.

  1. Type :let @a='
  2. Press Ctrl-R Ctrl-R a to insert the current contents of register a (type Ctrl-R twice to insert the register exactly).
  3. Edit the text as required.
  4. Append an apostrophe (') to finish the command, and press Enter.
  5. Enter :reg a to view the new value in the register.
  6. Type @a to execute the contents of register a.

Handling special characters when editing macros.

It’s quite likely that your command will have special characters ( Escapes or Carriage Returns for example ) and you may well need to add new ones. <Ctrl-v> is your friend here.

To insert an escape while editing the register use: <Ctrl>V<ESC>. So, in literal terms you type CTRL and v together, then press the Escape key.

To insert a carriage return use: <Ctrl>V<CR>. Again, in literal terms you type CTRL and v together, then press the Return key.

Workflow

Use gn

There is a nice Vimcast on the subject.

The power of \_s

The crux of this technique depends on \_s pattern matching atom. This search atom effectively lets you match on multiple lines, its actually more than that but I’m not going to go into it here.

1. The search (/) register

This kind of macro needs to be built up. You can define part of it using a search.

/%h4.*\_s.*=\s\?\w*\.\w*/e

Now we need to bring this into our macro. :let @a=' Then <C-r><C-r>/ to paste the last search into the macro.

But realistically you would want to record the whole thing in realtime and then tweak it. You can do this by accessing the register and recycling it back into itself as shown above in the Editing macros section.

For the sake of brevity I’ve shown the whole macro below, which you can copy into your own Vim for demonstration purposes. But, these will not work unless you replace certain characters with the correct special character counterpart!.

The macro in all its glory!

:let @t='/%h4.*\_s.*=\s\?\w*\.\w*/e^Mw"eye?%h4^Mgndaf.label ^["epa,^[wi(^[$a)^['

Paste the line above into your running Vim while in Normal mode, this will create the macro. Now edit it using the editing technique described above. Replace all instances of ^M with a proper carriage return by pressing <Ctrl-v><Enter> and all instances of ^[ with proper Escapes by pressing <Ctrl-v><ESC>.

This might be quite tricky, and should get you used to editing registers as macros!

And a second version which will match if there is a line between.

:let @v='/%h4.*\_s.*\_s.*=\s\?\w*\.\w*/e^Mw"eye?%h4^Mgndaf.label ^["epa,^[wi(^[$a)^['

Here’s an experimental mode which will match any number of lines between the heading and the form control.

:let @m='/%h4.*\(\_s.*\)\+=\s\?\w*\.\w*/e^Mw"eye?%h4^Mgndaf.label ^["epa,^[wi(^[$a)^['

Project wide application

:args app/views/**
:argdo /<Ctrl-r><Ctrl-r>/