18

N.B. This is not a duplicate of How does the "d3fg" command work? Please read it before voting to close!

I've come to a bit of an impasse in my quest to get small deletions work like big ones. The problem I've got is that I can't figure out how to remap commands like d3d. In fact, I can't even understand how they work in the first place, nor find any mention of them in Vim's help.*

d3j is straightforward: it applies the d operator to the 3j motion,

3dd is also straightforward: it runs the dd command with a "count" of 3.

d3d looks like it should be straightforward, it applies the d operator to the… but what is 3d? It's not a motion. If you type 3d on its own, Vim treats it as a count followed by an operator, and waits patiently for further input.

And you can't apply operators to other operators. dd isn't described in the help as a sequence of two operators. It's a separate, two-keystroke command. (cf. dc, which does nothing.)

It also doesn't appear to be the case that the positioning of the count is flexible for commands that require two keystrokes: 2gj moves the cursor down two screen-lines, but g2j discards the first two keypresses entirely and moves the cursor down one line.

So how does it work? Is this just a special case in Vim's internal code? Is there any way I can create a d[count]d mapping?

EDIT: Is it a text object? :h text-objects doesn't include it in the list of "text object selection commands", but then it does include dd in a list of deletions "grouped from small to big objects". And d2aw does delete the word under the cursor and the following one. On the other hand, c2d does nothing.

* I'm sure some mention of them must exist, but I can't find it. I've read :help deleting, :help motion.txt, and tried :helpgrep dNd for all values of N from 1–9.

  • 2
    I think the last d acts as a confirm; So it's the same as d3<CR>. y3y and c3c also work the same ... I can't find any docs on this though in either the Vim :help, vi(1), nvi(1), or the POSIX spec... It does behave the same in vi and nvi btw. – Martin Tournoij Mar 13 '15 at 23:04
  • 1
    In a way, dd is identical to d_. _ brings the cursor to the first non-blank on the ([count]-1)th line down, but it's linewise, so when paired with an operator, it operates on entire lines. For that reason, it makes sense that a count can appear between the two ds. – tommcdo Mar 14 '15 at 0:40
28

Operator-pending mode

Between typing an operator (like d, c, or gU) and a motion (like w, i}, or /foo<CR>), Vim is in Operator-pending mode. You can create mappings for this using :omap and :onoremap.

In my examples, I'm going to map the Operator-pending d to w. This is a random choice, because I don't know what you actually wanted to use it for.

Create an omap

Let's create a mapping for d in Operator pending mode. I'm gonna assume we're writing VimScript here (such as in your .vimrc), so I'll omit leading :s.

onoremap d w

This makes dd act like dw. It also makes d[count]d act like d[count]w. But I bet you didn't want to affect the behaviour of dd (i.e., no [count] supplied).

Expression mappings

Expression mappings let you evaluate a VimScript expression to produce the right-hand-side of the mapping. The expression should evaluate to a string. Expression mappings are denoted by the <expr> keyword.

Vim also has some built-in variables that get populated at various times, and are often useful during mappings. One of them is v:count, which is the [count] supplied to an operator or motion.

Let's put that all together. If no [count] is supplied, v:count will be 0. We can tell our mapping to just use d in that case.

onoremap <expr> d v:count == 0 ? 'd' : 'w'

Now dd behaves like the default (deletes a line), but d[count]d behaves like d[count]w. Cool!

Restrict it to use after the d operator

You might notice something, though. Now cd also behaves like cw. This may not be a problem, but for completeness, let's restrict it so it only works after the d operator.

Vim has another variable, v:operator, which contains the operator most recently used. Let's check that, too, before doing anything special.

onoremap <expr> d (v:count == 0 \|\| v:operator != 'd') ? 'd' : 'w'

NOTE: Since :map commands can be chained with |, we have to escape them in our expression here. So the logical OR operator || becomes \|\|.

Cool, so cd and c[count]d are back to doing nothing, just like they used to. This is good so that other plugins can define cd.

I don't want to just delete some other motion

If you wanted to map d[count]d to something totally different, nothing to do with deleting text, we can make that work too.

When you're typing commands in Normal mode, and you start typing a command by mistake, you can hit <Esc> to cancel that command. The same applies to your mappings.

Let's make d[count]d change the current line to whatever [count] is.

onoremap <expr> d (v:count == 0 \|\| v:operator != 'd') ? 'd' : '<Esc>cc' . v:count . '<Esc>'

The last part of the mapping is a VimScript expression: <Esc>cc' . v:count . '<Esc>'. That's <Esc> to cancel the pending d command, then cc to change the current line, v:count to type the value of the [count] (in Insert mode), and <Esc> to return to Normal mode. Note that we're building a string here using the VimScript concatenation operator, ..


Your actual use case is probably going to be different from what I've outlined here, but hopefully this helps you to get the ball rolling.

Happy Vimming!

  • Great answer! Upvoted. I did link to my use case (also on vi.SE) at the start of the question, but I guess you must have missed it. I basically need to do :omap d d (!), but unfortunately operator-pending mode is never actually reached because of my mapping for d that sets the custom operator function. Hopefully it's possible to tweak things to make d3d work how it should, though. (Let me know if you have any ideas, or feel free to post a complete solution to the other question if you prefer.) If I can't figure it out soon, I'll accept this and post another, more specific question. Thanks! – Rich Mar 14 '15 at 8:22
  • 1
    Actually, on second thoughts, I'm accepting this now. You explained both how to create such a mapping, and also how it works under the hood, which were the two things I asked. – Rich Mar 14 '15 at 8:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.