Skip to content

Instantly share code, notes, and snippets.

@romainl
Last active May 15, 2023 07:33
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save romainl/9721c7dd13c30714f568063e03c106dd to your computer and use it in GitHub Desktop.
Save romainl/9721c7dd13c30714f568063e03c106dd to your computer and use it in GitHub Desktop.
What to do with macros?

What to do with macros?

"Macro", you say? Or is that "recording"?

What people refer to as a "macro" is often actually a "recording".

You press qa to start recording your commands into register a, you do your thing, you stop your recording with q, and you play that recording back with @a.

Yes, what you just recorded is a macro, but macros are not necessarily recorded or even stored in registers.

So… what is a macro, then?

A macro is a sequence of commands that is played back instantly and automatically instead of being performed slowly and manually.

A macro is plain text:

2wcefoobar<Esc>

so it can be read, edited, yanked, put, etc.

Because it is "just" plain text, a macro can be created, stored, and played back in many ways.

How to store a macro?

In a register…

… via recording

qa                 " start recording in register a
2wcefoobar<Esc>    " do your thing
q                  " stop recording

… via assignation

" put 2wcefoobar<Esc> in register k
let @k = "2wcefoobar\<Esc>"

Note the use of double quotes that allows us to use \<Esc> rather than the slightly harder to reason about ^[.

… via yanking

" yank visual selection to register z
"zy

In a variable

" put 2wcefoobar\<Esc> in variable myvar
let myvar = "2wcefoobar\<Esc>"

In a mapping

" map <key> to 2wcefoobar<Esc> in normal mode
nnoremap <key> 2wcefoobar<Esc>

In an Ex command

" create an Ex command :Foo that does 2wcefoobar<Esc>
command! Foo normal! 2wcefoobar<Esc>

In a function

" create a function Foo() that does 2wcefoobar<Esc>
function! Foo()
    normal! 2w
    normal! cefoobar
endfunction

How to play a macro back?

Some macros may start from visual mode, others work on lines, others can have nothing to do with editing text, and each macro can be stored in various ways… so how to play a given macro back usually depends on what the macro does, where it is stored, what mode we are in, where the cursor is right now, and where the cursor lands at the end of the playback.

If we forget functions, Ex commands, and mappings for a moment, we basically have two playback methods at our disposal:

  • executing the content of a register right here,
  • executing the content of a register from column 0 of every line in a given range.
Command Description
[n]@<register>               execute content of <register> [n] times
:[range]normal! [n]@<register> execute content of <register> [n] times on every line in [range]

The mechanism is the same whether the macro we want to play back is stored in a register or in a variable. In one case we use the register itself, directly. In the other case we use the expression register that we fill with the content of our variable:

With a register (say q), we would do:

@q
12@q
:7,23norm! @q

With a variable (say foo), we would do:

@=foo<CR>
3@=foo<CR>
:'{,'}norm! @=foo<CR>

How to edit a macro?

Macros being plain text, editing a macro is as simple as putting it into a buffer, editing it, and storing it again in the same box or another one.

Say we recorded this simple macro in register q:

dw

but we figure out that some of the words we want to cut are actually WORDS. The obvious solution is to change dw into dW.

  1. Put the content of register q right here in a new buffer:

     <C-w>n
     "qp
    
  2. Edit it:

     ~
    
  3. Cut it back to register q:

     v0"qd
    

    or:

     :let @q = getline('.')
    

Done.

If you are working with a variable (say foo):

:new
:put=foo
~
:let foo = getline('.')

And that's about all you need to know for your day-to-day use of macros.

Mappings and functions.

Most macros are short-lived because they tend to be contextual by nature but some others are generic enough to find their way in our workflow. Those generally useful macros could certainly be stored and used as-is, but there's certainly an opportunity, here, to make them a little bit smarter and future-proof.

We are going to work with this simple macro that inserts a debugging statement containing the word under the cursor below the current line:

qq
yiwoconsole.log('<C-r>"', <C-r>");<Esc>
q

There are a few potential problems with this macro:

  • the q register is our go-to register for quick recordings so this macro is bound to be overwritten quickly,
  • the unnamed register is polluted,
  • the numbered registers are polluted.

We will start with the first issue, by turning this recording into a mapping. This is done by inserting the following in your vimrc:

nnoremap <F5> <-- there is a space, here

and then putting the content of register q with "qp:

nnoremap <F5> yiwoconsole.log('^R"', ^R");^[<80><fd>a

Hmm… there is quite a bit of junk, here. That, friends, is the dirty secret of macros: yes, they are "just text", so you can yank and put and edit them, but this will often require some massaging and dealing with weird stuff like <fd> or literal control characters like ^R. Case in point, let's clean this up:

nnoremap <F5> yiwoconsole.log('^R"', ^R");^[

or, if you prefer the "angle bracket" notation (I do):

nnoremap <F5> yiwoconsole.log('<C-r>"', <C-r>");<Esc>

Now that we fixed the first issue, it is time to address the rest.

The cleanest way to go about it is to extract the macro into a function that operates on the current word. But first, the extraction:

function! Console() abort
    normal! yiwoconsole.log('<C-r>"', <C-r>");
endfunction
nnoremap <F5> :<C-u>call Console()<CR>

Second, handle the current word:

function! Console() abort
    let cword = expand('<cword>')
    execute "normal! oconsole.log('" . cword . "', " . cword . ");"
endfunction
nnoremap <F5> :<C-u>call Console()<CR>

And we can stop here, with all our issues fixed and a perfectly working and instantaneous mapping.


My Vim-related gists.

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