šŸ‘©ā€šŸ’» chrismanbrown.gitlab.io

Registers

a vim story

2021-03-21

I was recently complaining/bragging about a complex bit of text editing I was doing for a project. The gist of it was converting an OCR rip of a PDF, full of conversion defects and artifacts, into <abbr title=ā€œā€œJasonā€ā€œ>JSON. So, yuck.

Throughout the course of this task, I started collecting lots of little snippets and macros. Currently, of my 26 named vim registers (A-Z), Iā€™ve reserved "a and "b for general use, and it looks like "u is empty. Thatā€™s it. The other 23 registers are snippets of JSON or special characters saved for quick insertion (ā„¢, Ā®, Ā½, etc), or macros for transforming stuff, expanding snippets, and moving stuff around.

This is a quick post about how I use those registers, and also a few other random vim tips that I used during this process.

Contents

  1. Snippets
  2. Macros
  3. Other stuff
  4. Conclusion
  5. Appendix

Snippets

I have several templates like this that I need to create constantly:

{
  hasImages: true,
  imageUrls: [
    ""
  ],
  noteIds: [
    0
  ]
}

So I would yank that to a register by visually selecting it (if your cursor is on a curly bracket, V%) and then "zy. "z = ā€œto register zā€, y = yank. Yank to register z.

Then later I could pop it out with "zp. (Register z, ā€œputā€).

I later referred to these ā€œtemplatesā€ in macros that were basically ā€œperform this text expansion on these lines of code.ā€ In this example, the macro would paste that template and then fill in the URLs or the note IDs.

This is of course a perfect use case for a snippet library like ultisnips or coc-snippets. Except I havenā€™t figured out how to expand a snippet yet inside of a macro. So I went with this more manual method of text expansion, and it worked fine. At the cost of a few registers.

Macros

My macros started getting long and complex.

For a long time I could just :reg to see all my registers and their contents, and I could eyeball them and kind of remember what did what. But after a while, it started to require more and more effort to tell them all apart at a glance.

So I added a text file to my gitignore and started dumping macros there with notes and explanations, and also the name of the register where that macro currently lives. So now I had a place I could look up, ā€œWhereā€™s the one that does x?ā€

That way I could also record over macros Iā€™m not using right now, but which might come in handy later.

That file, by the way, currently includes one very long macro with the note, ā€œDonā€™t remember what this one is. Looks important though.ā€

Macros are recorded to registers, just like snippets or whatever are saved to registers. Everything you can do with a register you can do with your macro: yank it and put it, append to it. And of course, execute it.

My approach to creating macros usually looks something like this.

  1. Start recording to the q register: qq.
  2. Do stuff
  3. Stop recording: q.
  4. Undo all my changes.
  5. Play back the macro: @q. (I think of q as my ā€œquickā€ register because qq is so fast to start recording. I have @q aliased to Q for (almost) equally quick playback.)
  6. See if it does the thing.

Often I need to tweak or debug a macro. In that case, Iā€™ll dump it ("qp) and squint really hard at it to try to make sense of the sequence of keystrokes.

For example, hereā€™s one of my longer ones:

:let @a=''^M/hasimages^Mddk$x3k$hhyi"^L/^R"^Mf|lyt| g/^R"/y A^M^H^H"zpjpdd=i[kf[%k$x

Iā€™ll describe what this does step by step below. For now, at a high level, it deletes some JSON, yanks some text, jumps to a second buffer and does a look up, jumps to a third buffer and yanks a bunch of URLs based on that lookup, goes back to the first buffer, and finally inserts and formats those URLs.

So it does a lot. And sometimes I needed to adjust that macro so it deletes an extra line, or inserts an extra comma somewhere, or whatever.

To do that, I would paste the macro and basically read it and execute it by hand step-by-step. When I got to the part that didnā€™t work the way I wanted it to, Iā€™d go fix that part of the macro and then yank it back to the register ("qdd).

And the run it (@q) and see if it works.

Hot tip: macros will halt if they encounter an error. A common error for me is searching for some text that doesnā€™t exist. Sometimes I want the macro to continue even if it fails. (Think, search for and replace a string, if it exists, in all these files. You donā€™t want to stop replacing the first time you donā€™t find that string.) This is a great use case for the e search flag, which causes vim to silently consume any search errors: s/something/another thing/e

One thing Iā€™m not sure about is whether using the new nvim lua API would make procedures like this more readable and reusable. I certainly didnā€™t have the time in the moment to dive into that.

Other stuff

Some other things I used and skills I sharpened during this work.

Working with many files

When something was renamed, or if the JSON schema changed, I would do a :bufdo to play a macro in all loaded files: :bufdo normal @q. Or do a simple replacement: :bufdo %s/old/new/

Iā€™ve since then learned that you can use vimgrep and the quickfix list in a similar way. Not sure if itā€™d be better or faster, but something to play around with later:

Iā€™m also interested in using this the next time I have merge conflicts to find everything I need to address: :grep HEAD ./*

Norm

Not the guy from Cheers. Or from Weekend Update.

I did a lot of :{range}norm {commands} to execute commands on a selection. e.g., visually select a number of lines, and :\<,\>norm d2w to ā€œdelete 2 wordsā€. (I had a lot of lines that started with a ton of whitespace, and a dash, some more whitespace. This got rid of all that.)

Expression in a substitution

I had an off by one incrementing error over a large block of text: all the 4ā€™s needed to be 3ā€™s, all the 3ā€™s needed to be 2ā€™s etc.

Visually select the block, and :s/\d\+/\=submatch(0)-1/.

Neat.

Conclusion

I guess thatā€™s it. This post is mostly for ~mieum, who said theyā€™d be interested in reading about how I use vim registers.

Registers are one of the things for me, along with the movement ā€œlanguageā€, that make vim ā€œvimā€. I simply love their versitility and power.

Appendix

Resources

That macro

Okay.

:let @a=''^M/hasimages^Mddk$x3k$hhyi"^L/^R"^Mf|lyt| g/^R"/y A^M^H^H"zpjpdd=i[kf[%k$x

Here we go.

The end. That was a wacky macro.