Wednesday, March 14, 2018

Vim (for non-programmers) Part Three: Refactoring my _vimrc File; Chapter Five: Correct Easy Link Addition (Correcting My Misreading of Steve Losh)

As we've seen in previous entries, I've decided to refactor my somewhat creaky, bloated _vimrc file. (For one thing, it was more or less organized chronologically, which makes a certain kind of sense—it certainly reflects an increasing sophistication (or anyway complexity) to my efforts—but also is obviously an insane way to organize configuration settings for anything. For another, I have a tendency to want to hold on to everything I ever tried, so I have, for example, an entire section called "Experiments That Failed Too Many Times".)

Part of the refactoring process, though, involves more than just reordering elements in the file: it involves examining those elements and determining if they could stand some improving.

One of the most vital resources for customizing your _vimrc is Steve Losh's Learn Vimscript the Hard Way. While I was working my way through (the early parts of) this book, I fell in love with a mapping he suggested for surrounding a word with double quotes. (As we know, mappings are a way to tell Vim, "When I type X, please act as though I typed Y". To keep from typing X accidentally, it's common to use a "leader" key followed by a short mnemonic. Because Vim is incredibly complicated, you have to specify what mode the mapping operates in, and you have to specify whether or not the mapping is recursive. This means a mapping is almost impossible to parse visually, because it has the form:
MODE_MAPPING_IS_FOR I_TYPE_THIS YOU_ACT_AS_THOUGH_I_TYPED_THIS
Which, given Vim's predilection for being hella terse, and the obvious utility to tell it "When I type something very short and easy to type, act as though I typed something very long and difficult to type", makes reading mappings difficult.)

viw<ESC>a"<ESC>bi"<ESC>lel
viw
– visually select the current word (the word the cursor is on)
<ESC> – exit visual mode / return to normal mode
a – enter insert mode after the character the cursor is on
" – insert a double quote <ESC> – exit insert mode / return to normal mode
b – move to beginning of word the cursor is on
i – enter insert mode before the character the cursor is on
" – insert a double quote
<ESC> – exit insert mode / return to normal mode
l – move cursor right one character (back onto the word)
e – move to end of word
l – move cursor right one character (onto the closing double quote)

As an exercise, he suggested a more advanced mapping that added double quotes around the last thing that had been visually suggested. I was quite taken with this approach, and, reasoning that HTML tags came in pairs just like quotation marks, I adapted his mappings so that I could quickly add hyperlinks to the word the cursor was on, or to the last thing I had visually suggested. The chunk of my _vimrc containing these mappings read like so (please be impressed with my incredible commitment to code documentation via comment):

" make the word under the cursor a hyperlink to URL from system clipboard
" 18jan2016
" mapping <leader>a to:
" select the current word with viw
" wrap the current word in an <a href> tag
" by moving to the end of the word and adding </a> to close the tag
" then moving to the beginning of the WORD and adding <a href="">
" moving back to the beginning of the WORD to move to the quotes
" and populate the quotes with the contents of the + register
" mostly inspired by Steve Losh
" edited 27dec2016
" :nnoremap <leader>a viw<esc>a</a><esc>Bi<a href=""><esc>Bci"<esc>a<c-r>+<esc>
:nnoremap <leader>a viw<esc>a</a><esc>Bi<a href="<c-r>+<esc>a"><esc>


" make the last visually selected text a hyperlink to URL from system clipboard
" 18jan2016
" mapping <leader>v to:
" select the last visual selection with `< and `>
" wrap that selection in an <a href> tag
" by moving to the end of the visual selection and adding </a> to close the tag
" then moving to the beginning of the visual selection and adding <a href="">
" moving back to the beginning of the visual selection to move to the quotes
" and populate the quotes with the contents of the + register
" mostly inspired by Steve Losh
:nnoremap <leader>v `><esc>a</a><esc>`<i<a href=""><esc>Bci"<esc>a<c-r>+<esc>

This allowed me to use two keystrokes—<leader>v or <leader>a—to add links to my text. This is an extremely handy shortcut—especially when working here in Reviewiera, where it's often the case that to make the case that some stuff is better than some other stuff, it is helpful to link to stuff, and then again to other stuff.

The one big problem I had with this mapping is that it didn't work if the line had quotes in it, because my clever ci" motion "selects the text from the previous quote until the next quote" (see :help a"). This meant that when I wanted to add my links to, say, a paragraph where I had mentioned the title of something, I had to:

  1. Enter some line breaks before and after the text I wanted to add links to
  2. Then add the links
  3. Then remove the line breaks

This pretty severely compromised the whole point of having a quick-keystroke method for adding links. And because I didn't look up the behavior of ci" until I was typing this up, I wasn't entirely certain what the glitch was! All I knew was that I had had to create a workaround when the line before whatever I wanted to add a link to included double quotes.

Today, in a meeting, I decided this was no longer an acceptable way to go about my business, so I put the matter to some thought, and decided:

  • Steve Losh was wrong and his approach to selecting a word then moving the cursor around manually wasn't the best
  • The way to craft this mapping properly would leverage Vim's multiple registers (registers are basically clipboards with names: the ones you can assign are a, b, c ... z, and this was something I abused a lot in my early days using Vim)

I sketched it out on paper before trying anything on the laptop, and, oddly, it turned out to work pretty much exactly as designed. Here's what it looks like:

:nnoremap <leader>a "zdiwi<a href="<CTRL-r>+"<CTRL-z</a><ESC>

As I was working on it and testing it, I realized that the problem wasn't Steve Losh at all, the problem was all me! But now I have a couple extremely sexy mappings that work when I personally need them to work. Just for grins, let's break them down and see what they do.

:nnoremap <leader>a "zdiwi<a href="<CTRL-r>+"<CTRL-z</a><ESC> "z – into the z buffer ... diw – delete the word the cursor is on (not including any surrounding whitespace) i – enter insert mode <a href=" – type <a href=" <CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers) + – the + register is the system clipboard, where we assume the URL is <CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers) z – paste the z register in (this is the word we deleted at first) </a> – close the html tag <ESC> – exit insert mode / return to normal mode

What I like about this is that it works left-to-right, in a more or less sensible way. It basically does exactly what I would do if I were typing: the bulk of the mapping is just banging away in insert mode; typing in the <a href=", then using <CTRL-r>+ to drop in the URL from the clipboard is precisely how I tended to add links before I started fiddling around with mappings in the first place. Also, since I rarely use named registers, I think it's okay to have this mapping clobber the z register. However, as a best practice slash approximation to idiomatically written Vimscript, I think the following mapping is probably better:

:nnoremap <leader>a diwi<a href="<c-r>+"><c-r>"</a><esc>

This just deletes the word, which by default places it into the unnamed register (see :help quotequote), which can be accessed by calling it by the name '"'. (By this point in the series, we should be far beyond being surprised or upset by trivialities like the fact that you can name an unnamed register...)

The visual mapping is a little bit trickier, but only a little bit. It uses exactly the same left-to-right approach, but leverages a fancy little command: gv, which in normal mode re-selects the last visual selection. NOTE: the g prefix in normal mode does some seriously under-known shit. In February of 2015, Tim Chase, one of the heroes on the Vim list, dropped this little gem:

Also, just in case you need it, "g&" is an obscure "across all lines in the file, repeat the last substitution with the same flags" command, even if it's several items back in your command-line history.

Like, seriously, what the hell, Vim? That's a LOT of power to pack into two keystrokes. Anyway, let's check out visual-mode link-adding.

:nnoremap <leader>v gvdi<a href="<c-r>+"><c-r>"</a><esc>
gv – re-select the last visual selection
d – delete what's selected
i – enter insert mode
<a href=" – type <a href="
<CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers)
+ – the + register is the system clipboard, where we assume the URL is
<CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers)
" – paste the so-called unnamed register in (this is the word we deleted at first)
</a> – close the html tag
<ESC> – exit insert mode / return to normal mode

Again, this is a simpler approach than Losh's, which goes right-to-left, then has to jump back to the right when it's done. Literally everything else he says and does makes a hell of a lot more sense than anything else I say or do, of course.

Anyway, that's two mappings refactored! I only have the entire rest of my _vimrc to go.

No comments: