Git Gud at Vim 2: Movement and Editing

This is a write-up of a series I presented to a small group in 2018. To see all the articles, see the link below or find more vim related articles.
Vim is controlled by expressions that take the form of operator + motion. Learning operators and motions gives flexible power to concisely edit text in a repeatable fashion.
Last week we touched on the
idea of buffers, and some basic commands like :e
, :ls
, and :b
that let us
open and manipulate buffers. This week, we are going to talk a little bit about
the heart of vim... editing.
Vim is designed for efficient and repeatable editing. It understands that as you work with a document, much of your time is spent finding just the right place to make a change, making that change, then repeating that same sort of change in a few more places.
Vim is unusual because its commands for a sort of language. Nearly every change you make is a repeatable algorithm that can be re-used! This "language" usually takes the form of an Operator followed by a Motion.
Operators and Motions are orthogonal... if you learn a new motion, it will work
with all the operators you know, and vice versa. Listing all the operators and
motions is the job of the :help
system. Instead, I'm going to list some of my
favorite combinations so that you can maybe see the power that Vim has. There
is often more than one way to do the same task, you can use the tools within
Vim that you know.
There are man pages for listing all the operators and motions available in vim, and more operators and motions available in plugins. Rather than try to list all of them I will group a sampling of them by the steps in a typical editing cycle: First scanning for the right place to change, then placing the cursor in the correct spot, finally changing or adjusting the text.
Scanning
Before you can fix the problem, you have to see it. Usually this will mean scrolling the current window up and down the buffer as you read, or by searching for a pattern that you know is close to the problem.
Scrolling
<c-d>
and <c-u>
.
There are a number of ways to move up and down the buffer. You can use
:<line-number>
to jump to a specific line (if you know where you are
going!). G
jumps to the bottom of the file (good for appending when you know
you will be adding to the bottom,) and gg
jumps to the top. There are a
number of other jump style motions under the prefix of g
, so it's good to
remember that g
= "go."
If I know where I'm going (I have a backtrace with a line number) I'll use
:<number>
, otherwise I typically end up using <c-d>
for "down" and <c-u>
for "up".
Searching
/Layer
(no matches)/layer
(some matches)n
andN
to move back and forth
You can search at any time in normal mode by typing /
followed by a pattern
you are searching for. There are of course regexes and wildcards available to
you. Once you are searching, you can press n
for next to cycle through the
matches, and N
will cycle you in the reverse direction.
Searching has the added benefit of placing the cursor on the match, which makes also an excellent tool for the next phase.
Placing the cursor
The next step after scanning is to place your cursor where you want to make your changes. This step and the actual editing itself are probably the hardest things for people new to vim, so stay with it! Eventually it will feel natural.
2j
to move down two rowsfI
to move to the "I"cw
to "change word"... more on that later!
I will often just use a /
search to place the cursor. This works well if
there's a relatively unique string that you can match against, especially if I
can accomplish this in two characters or less.
If there isn't an apparent or easy search pattern, I will use a combination of
row-wise and column-wise motions to get the same effect. For vertical
(row-wise) motion, I will either jump to a line or I'll use the default motion
keys hjkl
with a count, something like 11j
or 7k
.
For column-wise motion, we have many many options at our disposal, but there
are two that I end up using daily: f
and t
.
f
is for "forward", and both it and it's corresponding reverse motion F
move the cursor directly on top of the next character pressed (called the
target)
t
is for "till", and it does the same thing as f
, execept it stops just
short of the target. It also has a corresponding reverse motion, T
.
With some practice, we should be right on our target and ready to edit!
A Note About Modal Editing
Vim gives us commands all across the keyboard to communicate commands to it.
However, we still have to type sometimes... so it gives us a toggle between two
modes: Normal (command) mode, and Insert mode. We'll talk about a few ways to
enter Insert mode in a minute, but for now, just know that once in Insert
mode, you have to hit ESC
or <c-c>
return to Normal mode.
This is very confusing to some people but it's worth getting used to. We have been talking about the process of editing: scanning the text, placing the cursor, making a change. Of those three steps, only one of them requires inserting text, and then it doesn't always. More often than not you are just removing text, adjusting indentation, changing a delimiter... all tasks that in Vim don't require leaving Normal mode. Therefore, Vim takes the step of making the mode in which we are instructing the editor what to do the default Normal mode, and gives us an Insert mode to enter and leave when required.
Changing, Adjusting, and Adding
d
stands for delete. It deletes in the motion you give it, if you just hit
dd
it'll delete the current line, and D
will delete from the cursor to the
end of the line (roughly equivalent to d$
, where $
is the movement to the
end of a line. Regex fans will notice that that's the part of regex that
matches an end of line, and there's more where that came from.)
There are a number of operators that will enter insert mode.
O | I--i_a--A | o
Here are the main ones I end up using. If the cursor is on the underscore, each
operator listed will move the cursor to the spot listed. o
and O
are
particularly notable, because they start new lines and place the cursor there.
f(
to move to the parensci(
to change inside parens- Type in correct "bar" argument
ESC
to re-enter normal modeA
to put the cursor at the end of the line- Add ":"
The operator I find myself using the most frequently when refactoring or
editing is the c
or change operator. This operator deletes according to your
motion, and places the cursor in the place of the removed text. C
behaves
much the same as D
, except leaving you in insert mode. I have found that
together with a solid use of text-objects, c
probably my most-used operator.
Tweaking
Staying in normal mode is good, it means you have the freedom to keep moving and you are constantly teaching yourself and your editor more things about the text you are editing. There are a bunch of commands that I call "tweaking" commands... they are little actions just to make simple tasks easier.
>
and <
are operators that indent and unindent code. =
will auto-indent
based on your syntax file.
<c-a>
and <c-x>
increment and decrement the character under the cursor...
handy for budging a number or a range of numbers.
There are a bunch of neat extended commands available in plugins and scripts, I'm fond of one that handles encoding and decoding strings, or escaping characters. Combined with the text-object for quotes and single quotes, very powerful!
Text Objects
This family of motions are motions, but they tend to make more sense in the context of an operator. Vim is aware of ranges or blocks in your text... things like parentheses, brackets, braces, quotes, double quotes, paragraphs, sentences, even XML tags. I use these constantly!
They are pretty easy to use too... most text objects can be accessed by using
i<delimiter>
or a<delimiter>
where <delimiter>
is something like ({['"
.
It is one of the easiest to read "sentence" commands... change inside
parentheses becomes ci(
.
There are also motions to move the cursor along text-objects, which can be handy for reading or working with prose.
Step 4. Profit! (with the power of repeat)
The amazing thing about the expressions we've been learning, the orthogonal combinations of operators and motions is that they are scriptable and repeatable. The way we interact with the editor is also the way we can "program" Vim to do a task more than once!
I experienced this power in the course of writing this very blog post. I was
refactoring the examples and putting them inside the <figure>
tags you see
here, and the <modifier-key>
form was being interpreted as an XML tag and
breaking the blog post. Trouble is, I had written dozens of them across the
page.
I remembered seeing an operator that would HTML encode a string, found it in
:help
, (the operator is [x
from vim-unimpaired) and was able to use [xit
to HTML encode the string inside the tag.
Armed with this expression, I searched for "<c-" in the file, hit n
to
move to it, and hit the repeat operator (.
) to fix each occurence in the blog
post. It took mere seconds to do a refactoring activity that would have taken
me probably a half an hour a few years ago when I didn't use vim.
Example: Numbering a list
There's always more than one way to do a task in vim... people who have been using vim for a while will engage in vim golf, a contest to find the fewest keystrokes to accomplish a task. It's partly a competition, partly a way to absorb and learn new operators and motions.
Manually typing
Here I'm just typing in the list like I would when I first starting vim. I use
I
to jump to the beginning of the line and typing in a number.
- use
hjkl
to move the cursor I
to jump to the beginning of the line- type in the new number
- repeat
Using an external command
Here I'm using a trick I learned from Steve Spicer... cat -n
prints out a file with line numbers. This trick is really cool because it works
on any length file with just one line!
-
%!
passes the buffer to an external command and reads from stdout cat -n
takes from stdin and numbers the lines-
<c-v>
starts a visual block so I can select the empty space. d
to delete the selected block.
Using some new vim magic
While researching this talk, I learned that a familiar command extra
features. I knew that <c-a>
learned that a familiar
command extra features. I knew that <c-a>
will increment
the integer under the cursor, but I didn't know that
g<c-a>
would incremement a block of integers and
increment each one by one more.
Therefore I used a search and replace to put a "1." at the beginning of
every line, and used g<c-a>
to increment the line
numbers.
-
%s/^/1. /g
replaces the beginning of every line in the buffer with the string "1." -
<c-v>
allows me to select a visual block from :2 on toG
-
g<c-a>
increments all the integers in the selection by one + each additional line ending.
More Resources
Changelog
-
2022-06-08 11:31:29 -0500Rename articles
-
2022-06-07 15:27:31 -0500Adding Admonitions
-
2020-06-18 14:26:02 -0500Move everything to CST
Don't know why I didn't do that before. It caused _no_ end of
problems. -
2020-06-14 15:51:50 -0500Update articles with series and series partials
After the refactor, needed to move the series metadata to the actual
name. -
2020-06-08 12:17:07 -0500Hack together a series page
I'm basically hacking this into the "tags" system... if you have a tag
named "series-<rest of tag>", it changes the tag page to a "series" page
that also uses the same partial as a series post. -
2020-06-08 10:48:51 -0500Create series partial
I tend to write series blog posts, and I have one coming up... so I have
refactored how I used to do series to test it out. -
2019-11-14 19:21:38 -0600Remove Tag: programming
`code` is the same tag and has more coverage.
-
2018-10-24 11:36:16 -0500Publish Vim Part 2
-
2018-10-24 11:33:57 -0500Write post: Vim movement/editing
-
2018-10-14 17:37:47 -0500Schedule out the second post
-
2018-10-11 20:17:02 -0500Working on movement and editing
-
2018-10-10 15:34:38 -0500More changes to the blog posts
-
2018-10-10 14:54:59 -0500WIP movement article
-
2018-10-10 14:39:29 -0500Working on git gud part 2
-
2018-10-10 13:54:13 -0500Check in outline for part 2