Git Gud at Vim 2: Movement and Editing

vim icon

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

Scanning using <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

  1. /Layer (no matches)
  2. /layer (some matches)
  3. n and N 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.

  1. 2j to move down two rows
  2. fI to move to the "I"
  3. 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.

  1. f( to move to the parens
  2. ci( to change inside parens
  3. Type in correct "bar" argument
  4. ESC to re-enter normal mode
  5. A to put the cursor at the end of the line
  6. 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.

  1. use hjkl to move the cursor
  2. I to jump to the beginning of the line
  3. type in the new number
  4. 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!

  1. %! passes the buffer to an external command and reads from stdout
  2. cat -n takes from stdin and numbers the lines
  3. <c-v>starts a visual block so I can select the empty space.
  4. 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.

  1. %s/^/1. /g replaces the beginning of every line in the buffer with the string "1."
  2. <c-v> allows me to select a visual block from :2 on to G
  3. 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 -0500
    Rename articles

  • 2022-06-07 15:27:31 -0500
    Adding Admonitions

  • 2020-06-18 14:26:02 -0500
    Move 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 -0500
    Update 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 -0500
    Hack 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 -0500
    Create 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 -0600
    Remove Tag: programming

    `code` is the same tag and has more coverage.

  • 2018-10-24 11:36:16 -0500
    Publish Vim Part 2

  • 2018-10-24 11:33:57 -0500
    Write post: Vim movement/editing

  • 2018-10-14 17:37:47 -0500
    Schedule out the second post

  • 2018-10-11 20:17:02 -0500
    Working on movement and editing

  • 2018-10-10 15:34:38 -0500
    More changes to the blog posts

  • 2018-10-10 14:54:59 -0500
    WIP movement article

  • 2018-10-10 14:39:29 -0500
    Working on git gud part 2

  • 2018-10-10 13:54:13 -0500
    Check in outline for part 2