I’ve written another new article over on Vimways for the 2018 Vim advent calendar. This is a followup to my previous article, adding some more detail about ways to use Vim’s runtime directory structure.
I’ve written a new article over on Vimways for the 2018 Vim advent calendar. It’s about converting a long and unwieldy
.vimrc into a
~/.vim runtime directory.
Vim’s massive command set in both normal and command mode makes the concept of a
macro especially powerful. Most Vim users, if they ever use macros at all, will
perhaps quickly record an edit to one line, starting with
q, executing with
@q, and otherwise never give the concept much
For slightly more advanced Vim users, macros may be more frequently useful, but they perhaps still believe that macros are a niche Vim function unto themselves, and not really open to the deft manipulation of text that pretty much everything else in Vim is.
As is typical in Vim, the rabbit hole of functionality goes much deeper than most users will ever plumb.
Vanilla Vim macros
Suppose I had a plaintext table loaded into a Vim buffer containing the name, subject matter expertise, birth year, and nationality of a few well-known programmers:
Stallman Richard GNU 1953 USA Wall Larry Perl 1954 USA Moolenar Bram Vim 1961 Netherlands Tridgell Andrew Samba 1967 Australia Matsumoto Yukihiro Ruby 1965 Japan Ritchie Dennis C 1941 USA Thompson Ken Unix 1943 USA
Actually, that’s kind of untidy. Let’s start by formatting it a bit, by running
it through Unix’s
:%!column -t Stallman Richard GNU 1953 USA Wall Larry Perl 1954 USA Moolenar Bram Vim 1961 Netherlands Tridgell Andrew Samba 1967 Australia Matsumoto Yukihiro Ruby 1965 Japan Ritchie Dennis C 1941 USA Thompson Ken Unix 1943 USA
May as well sort it by surname too:
:%!sort -k1 Matsumoto Yukihiro Ruby 1965 Japan Moolenar Bram Vim 1961 Netherlands Ritchie Dennis C 1941 USA Stallman Richard GNU 1953 USA Thompson Ken Unix 1943 USA Tridgell Andrew Samba 1967 Australia Wall Larry Perl 1954 USA
Now, suppose we’ve got the task of replacing the fourth column of this table with the approximate age of the person, which we can get naturally enough by sutracting their birth year from the current year. This is a little awkward to do in pure ex, so we’ll record a macro for doing it on one line.
Experimenting a bit, we find that the following works well:
Broken down, this does the following:
0— Move to the start of the line
3w— Skip three words, in this case to the fourth column
de— Delete to the end of the word
i— Enter insert mode
^R=— Insert the contents of the special
=register, which accepts an expression to evaluate
2012-^R"^M— Enter the expression 2012-(birth year) and press Enter (literal ^M), which completes the operation, and inserts the result
^[— Leave insert mode
0— Return to the start of the line
j— Move down a line
So we record it into a macro
a (to stand for “age”, no other reason) as we fix
up the first line:
qa03wdei^R=2012-^R"^M^[0jq Matsumoto Yukihiro Ruby 47 Japan Moolenar Bram Vim 1961 Netherlands Ritchie Dennis C 1941 USA Stallman Richard GNU 1953 USA Thompson Ken Unix 1943 USA Tridgell Andrew Samba 1967 Australia Wall Larry Perl 1954 USA
This now means that for each line, we can run the macro by just tapping
Matsumoto Yukihiro Ruby 47 Japan Moolenar Bram Vim 51 Netherlands Ritchie Dennis C 71 USA Stallman Richard GNU 59 USA Thompson Ken Unix 69 USA Tridgell Andrew Samba 45 Australia Wall Larry Perl 58 USA
That’s all pretty stock-standard macro stuff that you’ll likely have learned in
other tutorials. The only thing that’s slightly voodoo (and certainly not
vi-compatible) is the arithmetic done with the special
= register. You can
read about that in
:help @=, if you’re curious.
Repeating Vim macros
As a first very simple hint, if you’re running a macro several times, don’t
forget that you can prepend a count to it; in our case,
6@a would have fixed
up all the remaining lines. To take advantage of this, it’s good practice to
compose your macros so that they make sense when run multiple times; in the
example above, note that the end of the macro is moving down onto the next line,
ready to run the macro again if appropriate.
Similarly, if you’ve already run a macro once, you can run the same one again by
just tapping the
@ key twice,
@@. This repeats the last run macro. Again,
you can prepend a count to this,
The true nature of Vim macros
The registers that hold macros are the same registers that you probably use more frequently for operations like deleting, yanking, and pasting. Running a macro is simply instructing Vim to take the contents of a register and execute them as keystrokes, as if you were actually typing them. Thinking about macros this way has a couple of interesting consequences.
First of all, it means you needn’t restrict yourself to the single-keystroke vi commands for Vim when you compose macros. You can include ex commands as well, for example to run a substitution during a macro:
Secondly, and more interestingly, all of the operations that you can apply to
registers in general work with what we normally call macros, with the old
standards, delete, yank, and paste. You can test this with the example macro
demonstrated above, by typing
"ap, which will dump the raw text straight into
the buffer. Also like other registers, it’ll show up in the output of the
All this means is that like everything else in Vim, macros are just text, and all of Vim’s clever tools can be used to work with them.
Editing Vim macros in a buffer
If you’ve used macros even a little bit you’ll be very familiar with how
frustrating they are to record. For some reason, as soon as you press
start recording a macro into register
a, your normal fluency with Vim commands
goes out the window and you keep making mistakes that will make the macro
unsuitable to apply.
So, don’t compose macros on the fly. Just stop doing it. I recommend that if a macro is more than three keystrokes, you should compose it directly in a scratch Vim buffer so that you can test it iteratively.
Here’s an example using the macro above; suppose I realise partway through writing it that I made a mistake in typing 2011 instead of 2012. I finish recording the rest of the macro anyway, and dump the broken keystrokes into a new scratch buffer:
This gives me the contents of the macro in plain text in the buffer:
So now all I have to do is change that bad year to 2012, and then yank the
whole thing back into register
Now I can test it directly with
@a on the appropriate file, and if it’s still
wrong, I just jump back to my scratch buffer and keep fixing it up until it
One potential snag here is that you have to enter keystrokes like Ctrl+R as literal characters, but once you know you can enter any keystroke in Vim literally in insert or command mode by prefixing it with Ctrl+V, that isn’t really a problem. So to enter a literal Ctrl+R in insert mode, you type Ctrl+V, then Ctrl+R.
Running a Vim macro on a set of lines
It’s occasionally handy to be able to run a macro that you’ve got ready on a specific subset of lines of the file, or perhaps just for every line. Fortunately, there’s a way to do this, too.
:normal command, you’re able to run macros from the ex command line:
All it takes is prefixing this with any sort of range definition to allow you to run a macro on any set of lines that you’re able to define.
Run the macro on each line of the whole buffer:
:% normal @a
Between lines 10 and 20:
:10,20 normal @a
On the lines in the current visual selection:
:'<,'> normal @a
On the lines containing the pattern vim:
:g/vim/ normal @a
When you get confident using this,
:norm is a nice abbreviation to use.
Moving a Vim macro into a function
For really useful macros of your own devising, it’s occasionally handy to put it
into a function for use in scripts or keystroke mappings. Here again the
:normal command comes in handy.
Suppose I wanted to keep the age calculation macro defined above for later use on spreadsheets of this kind. I’d start by dumping it into a new buffer:
The macro appears as raw text:
I prefix it with a
:normal call, and wrap a function definition around it:
function! CalculateAge() normal 03wdei^R=2012-^R"^M^[0j endfunction
Then all I need to do is include that in a file that gets loaded during Vim’s
startup, possibly just
.vimrc. I can call it directly from ex:
But given that I wanted it to be a quick-access macro, maybe it’s better to bind
\a, or whatever your chosen
<leader> character is:
nnoremap <leader>a :call CalculateAge()<CR>
Saving a Vim macro
If you want to have a macro always available to you, that is, always loaded into
the appropriate register at startup, that can be done in your
.vimrc file with
a call to
let to fill the register with the literal characters required:
Appending extra keystrokes to a Vim macro
If you just want to tack extra keystrokes onto an existing macro and don’t care
to edit it in a Vim buffer, you can do that by recording into it with its
capital letter equivalent. So, if you wanted to add more keystrokes into the
b, start recording with
qB, and finish with the usual
Recursive Vim macros
If you’re crazy enough to need this, and I never have, there’s an excellent Vim Tip for it. But personally, I think if you need recursion in your text processing then it’s time to bust out a real programming language and not Vimscript to solve your problem.
If the issue for which you think you need recursion is running a macro on every
line of a buffer with an arbitrary number of lines, then you don’t need
recursion; just record a one-line version of the macro and call it with
normal @a to run it on every line.
Vim macro gotchas
Here are a few gotchas which will save you some frustration if you know about them ahead of time:
- When you have a hammer, everything starts to look like a nail. Don’t try to solve problems with macros when there are better solutions in the ex command set, or available in external programs. See the Vim koans page for a couple of examples.
- You need to insert keystrokes like Enter as literal Ctrl+M, so that they
^M. The convenience abbreviations in mappings, like
<CR>, simply don’t work. You’re likely to find yourself chording Ctrl+V a lot.
Macros tend to stop, sometimes for apparently no reason and with no warning messages, as soon as they hit some sort of error. One particularly annoying instance of this is when you’re performing several substitutions in a macro, because if it doesn’t find any instances of a pattern it will throw an error and stop executing. You can prevent this by adding the
eflag to the substitution call:
Thanks to user bairui for suggesting a safer alternative for yanking macro lines from buffers, which I’ve changed. He’s written a whole blog post replying to this one. It’s a good read if you’re interested in going in to even more depth about when to use macros in Vim.
If you can, it’s a good idea to set up your
.vimrc file using
conditionals so that it’s compatible on all of the systems with which you
need to work. Using one
.vimrc file enables you to include it as part of a
centralised set of dotfiles that you can keep under version control.
However, if on a particular machine there’s a special case which means you need
to load some Vim directives for that machine, you can achieve this by way of a
local Vim file kept in
.vimrc.local, only on one particular machine, and
detecting its existence before attempting to load it in your master
file with the following stanza:
if filereadable(glob("~/.vimrc.local")) source ~/.vimrc.local endif
As an example, on one of the nameservers that I manage, I wanted to make sure
that the correct filetype was loaded when editing zone files ending in
.au for New Zealand and Australian domains. The following line in
.vimrc.local did the trick:
autocmd BufNewFile,BufRead *.au,*.nz set filetype=bindzone
.vimrc.local file doesn’t exist on any particular machine, Vim will
simply not attempt to load it on startup.
Besides machine-specific code, this kind of setup may be advisable if you keep
secret or potentially sensitive information in your
.vimrc file that you
wouldn’t want published to a public version control tracker like GitHub, such
as API keys, usernames, machine hostnames, or network paths.
If you work on many machines with varying operating systems (Windows, GNU/Linux,
MacOS X, BSD) and in various upgrade states, particularly if some of the
machines are a lot older or have more minimal or custom installations of Vim,
you might not be using your
.vimrc file on all of them because it includes
features that aren’t available on some other machines, meaning that Vim spits a
lot of errors at you on startup.
This might have prompted you to perhaps keep a simpler
.vimrc file, a
.vimrc, that you put onto your remote machines, and you keep the
.vimrc on your own machine to include lines that use all of the
features only available to you on that machine. If you like to version your
configuration files, maintaining and testing both of the
.vimrc files on all
the machines gets old fast; it would be much better to have a single
file that simply ignored configuration it didn’t understand. There are several
ways to approach this.
Perhaps the best way to manage this is to group all of your configuration items
by Vim feature, and to check for their presence in your
.vimrc file before
attempting to set any of the relevant options. You can do this with the
function. As an example, here’s a stanza from my
if has("spell") set spelllang=en_nz nnoremap <leader>s :set spell!<CR> endif
I set the
spellang option and perform the remapping only if the
feature is actually available.
If an option is dependent on a feature having been compiled into Vim, you can
usually tell by calling
:help on it and looking for a phrase like “not
available when compiled without the
+xyz feature.” You can also view a list
all the features available with
:help feature-list, and see which features
are compiled into a given
vim binary with the
$ vim --version VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Feb 11 2012 03:54:05) Included patches: 1-429 Modified by firstname.lastname@example.org Compiled by email@example.com Huge version with GTK2 GUI. Features included (+) or not (-): +arabic +autocmd +balloon_eval +browse ++builtin_terms +byte_offset +cindent +clientserver +clipboard +cmdline_compl +cmdline_hist +cmdline_info +comments +conceal +cryptv +cscope +cursorbind +cursorshape +dialog_con_gui +diff ...
There are certain special features, like
+unix, that you can use to check
whether the Vim instance running is on a platform suitable for an option or
not. I find this is handy for choosing the correct syntax for specifying fonts
if has("unix") set guifont=Inconsolata\ 14 else set guifont=Consolas:h12 endif
You can check whether an option itself exists rather than a feature with
if exists("&foldenable") set foldenable endif
Check version number
Another way of filtering out options for older versions of Vim is by version
number, which you can perform by checking the value of
example, to only set folding options if you’re working with at least Vim 7, you
if v:version >= 700 set foldenable endif
In this particular case, though, it’s a little clearer and more robust to check
the condition with
if has("folding"), because the version number being recent
enough does not necessarily mean the feature exists. However, one good
application of using version numbers is fixing bugs or unexpected behaviour in
older instances of Vim, to make it behave consistently with newer versions, or
even vice-versa if necessary.
If you can’t find a way to filter by feature or version number, a simple way to
suppress any error messages from a configuration line is to preface it with
silent!. I find this is a nice compact way to call plugin-dependent functions
pathogen#infect(), or custom colorschemes that you can’t be sure
actually exist on the machine:
silent! call pathogen#infect() silent! colorscheme zellner
If you’re not dealing with versions of Vim older than 7.0, another possibility
endtry block. This is handy for setting a default or
fallback option if a call fails, such as selecting colorschemes:
try colorscheme zenburn catch colorscheme torte endtry
This is my least-favoured method of making
.vimrc files degrade gracefully,
as it breaks completely on older instances of Vim.
Thanks to Reddit user bouncingsoul for suggesting the second method which I initially missed.