A thread on the Ruby Parley email list caught my attention a while back. The
author mentioned that he had accidentally hit
ff in Vim, when he had meant to
gg to go to the top of the file. This lead him to think about what all
the duplicated letter commands do in Vim.
What I find really interesting about this line of thought isn't what the specific commands do, but instead the patterns in them, and how Vim parses and executes the commands.
I came up with four totally distinct ways that Vim interprets duplicate patterns. (which conveniently each get a section below!)
The first major group of the duplicated-letter commands I saw was simply where Vim interpreted them as duplicated commands.
jj is an excellent instance of this. The first
j says to go down a line. And
j says to go down a second line. Two totally separate commands.
Not much more to say here...
A second major grouping appears after filtering out the simply duplicated single-letter commands. This is the group of commands that take a motion afterward.
d is an excellent prototype to examine. If you simply hit
d in Vim. Nothing
visible happens. Vim knows you're about to delete something, but is waiting for
you to tell it what. You tell it with a motion.
dw - Delete word
d$ - Delete until end of line
di) - Delete inner parenthesis (see also: a refresher on Text Objects)
So when we type
d, then Vim waits for a motion, just as every time above. But
if we hit
d again, it knows to delete the entire line. BUT WAIT!
d is not a
motion command. How does it know what to delete?
It turns out this is a clever standard that Vim uses. Every command that I've run across (I'd guess all, but I am not 100% sure) that waits for a motion interprets its own keystroke again as an instruction to operate on the entire line.
dd - Deletes the entire line
>> - Indents the entire line
But if we try to mix the two:
>d - Whoops, Vim doesn't know what to do with this, and beeps at you.
d> - No luck again. Only confused beeps from our editor.
So only duplicates have this behavior.
This is another distinct way that Vim could interpret a duplicated letter.
tt is the best example of this style of commands. The first
t is the "until"
command. But Vim needs to know "until WHAT", and so it waits for an argument.
When it hears the second
t it can finish the command. But that second
not a motion, it's not another command, it's the argument to the command.
@@ is similar.
@ is the command to run a macro. But any register could be
holding a macro in it, so it waits for you to tell it which macro to run. In
this case, it borrows the thought process from
dd and it special cases the
@ argument (which is not a register), it executes a
smart default, which in this case is rerunning the last macro.
gg doesn't match any of the previous categories.
g isn't a command by
itself. It's not waiting for an argument, or a motion. Instead, it's a whole
namespace of distinct commands. If you go to
:help g, you get an entire
section of distinct commands.
Cool enough, some of those namespaced commands in turn follow the rules laid out above. For example
g? takes a motion to rot13 some text.
g?g? rot13s the current line.
reformat text does the same, 'gqgq' operates on the current line.
So back to
gg. What's up here? It's just a single command inside the
namespace, the way the unnamespaced
k are. Vim parses it as a single
entity, not as any fancy combination.
z is a similar namespace to 'g', holding such amazing commands as
center the viewport around your cursor (moves the window, but not your cursor
inside the text)
Vim has its own logic. It's consistent in ways I hadn't even realized before writing this article. Send me some feedback below in the comments, or via email at email@example.com. I'd love to know if I missed anything in this analysis.