I've written the following python script to be called with a shortcut in vim:

:imap <c-t> <c-r>=system('./script.py')<cr>

It works, and it's quite cool. However, I'd like to understand why it works. For example, what are the following items for:

  • <c-r>
  • =
  • system('...')

Why not just do the same thing with:

:imap <c-t> <c-o>:!./script.py<cr>

It comes down to two things: how we get to where we can call an external command and where the output of that external command goes.

I'm going to just use a simple external command, uname, rather than a python script but the concepts are the same.

Let's try:

 imap <c-t> <c-o>:!uname<cr>

Well, this runs the command, clearly. But nothing gets inserted into the buffer. That's because :! doesn't do any redirection of the command's output. Your other mapping

imap <c-t> <c-r>=system('uname')<cr>

uses system(). One of that function's primary purposes is to capture output from whatever command it is asked to run.. The rest of it <c-r>= is how we pull data into the buffer from Insert mode. What it does is say "insert the contents of register = into the buffer". But register = is special...the "expression register" and calls for entering an expression after which it will be evaluated and the result will be the register's "value"...and that's what is inserted into the buffer.

In the first command OTOH <c-o> is used to activate Normal mode temporarily after which command line mode is entered and from there we can use :! to execute the external. So it's a bit roundabout. But so is the <c-r>= method, arguably.

Well, then, if we don't mind <c-o> and find it easier to understand than <c-r>=, can we still get command output into the buffer that way? Yes, all we need to do is replace the :! command with :r ! which does the same thing but copies the called command's output into the buffer...

imap <c-t> <c-o>:r !uname<cr>

The <C-r> keystroke in Insert mode is used to insert the contents of a register, so <C-r>a will insert the contents of register "a and <C-r>" will insert the contents of the default register. See :help i_CTRL-R.

The expression register is a bit special, in that inserting it (or otherwise reading from it in another context) switches to a special expression input (on the last line, where you typically have the Ex command line or you enter search patterns following a / or ? command.) It will then accept an expression, evaluate it, and use it as the value of the register. There's even a direct tag for the usage of that register with <C-r> in the documentation, see :help i_CTRL-R_=.

When you use it in a mapping, the mapping gets to type the expression. The <CR> at the end is still needed, in order to actually let Vim know that the expression is over and it can go ahead and evaluate it. (Same as if you were to write a Normal mode mapping that starts a : command, or a / search, you also need a <CR> on those to execute them.) The documentation for the expression register mentions the need for <CR>, see :help quote_= for that documentation.

Why not just do the same thing with:

:imap <c-t> <c-o>:!./script.py<cr>

They're not really the same.

:!command<CR> will only run the external command, but not insert its output into the current buffer. The output from the command only goes to the screen, to display to the user, it's not incorporated into the current buffer.

<C-r>=system('command')<CR> is inserting the output of that system command at the current cursor position, running the external command without any specific input coming through stdin.

There's :.!command<CR>, which will on the other hand filter the current line through the external command, replacing it with the output of the external command. Or :r !command<CR>, which will append the output of the external command into the current buffer, but starting on the next line (and not on the cursor position.)

  • I see, with doing the :r! approach, is it possible to get it at the current cursor position, for example doing an :<up>J (or some other way)?
    – David542
    May 31 '20 at 3:20
  • @David542 Ex commands are typically linewise... There might be a way, but it's probably not an obvious one. How about if the cursor is in the middle of a line?
    – filbranden
    May 31 '20 at 3:22
  • 1
    I think I finally understood what the visual syntax of '<,'> is -- when in visual mode it sets a marker for the start ('<) and end ('>) of the visual selection, and then so it's just giving a range from the start to the end...is that right?
    – David542
    May 31 '20 at 4:44
  • 1
    @David542 Yes, and those are "line" ranges (starting with ', the ones starting with backquote will take you to the column where selection started/ended.) So from visual mode it's trivial to turn it into a range of lines for an Ex command such as :[range]! filter, in that case :'<,'>!.
    – filbranden
    May 31 '20 at 6:11
  • 1
    @David542 Yes. And replacing the lines in the range with the output of the command. If you think of.the external command as grep then I guess you'll see why it makes sense to call it a "filter".
    – filbranden
    May 31 '20 at 6:55

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.