I would like to type begin! in insert mode and having it immediately replaced by:

begin [cursor here] end

I considered mappings:

:inoremap begin! begin  end<Left><Left><Left><Left>

and abbreviations:

:iabbrev begin! begin  end<Left><Left><Left><Left>

But both have caveats.

(1) The mapping functionally does exactly what I want, but it displays characters typed in a very confusing way. For example, if I type beginning, the apparent contents of my buffer evolves like this:


I would like to see the same thing as if there was no mapping.

(2) The substitution only happens after typing the next character. Furthermore, which characters trigger the substitution and which ones block it is a bit obscure¹, and does not suits my needs (most often, I would need to type an alphabetic character next).

Is there a way to get the behaviour I want?

¹: The substitution is triggered by characters not in the 'iskeyword' option (which includes alphanumeric characters), by <C-]> (which is an awful key for most non-QWERTY layouts), or by escaping from the insert mode.

  • The easiest thing is to just map a different key to <c-]> such as nnoremap <c-b> <c-]>
    – Mass
    Aug 4, 2018 at 22:50

3 Answers 3


As a follow-up: an actual answer is to use a snippet plugin, such as UltiSnips. Snippet plugins are:

  • already done;
  • designed precisely for that kind of things;
  • much easier to set up (just write templates as plain text in some config file);
  • much more comfortable to use (you can have several holes, placeholders and so on);
  • much more powerful (you can run arbitrary Python scripts);
  • fast (UltiSnips is, at least).

Crafting a square wheel was a waste of time. ;-)


I found a kind of solution by reading :help abbreviations:

fun! EatBlank()
  let c = nr2char(getchar(0))
  return (c =~ '\s' || c == "\r") ? '' : c

iabbrev <buffer> begin! begin  end<Left><Left><Left><Left><C-R>=EatBlank()<CR>

This will consume the next character if it is a blank character (including <CR>). This way, I can type a space to trigger the substitution, and it will not be inserted. Not exactly what I was looking for, but good enough.

After hacking for a while, I could achieve the exact behaviour I was looking for, by making ! the character that triggers the substitution instead of being part of the abbreviation.

fun! BeginEnd()
  let c = nr2char(getchar(0))
  return (c == '!') ? "begin  end\<Left>\<Left>\<Left>\<Left>" : 'begin' . c

iabbrev <buffer> <expr> begin BeginEnd()

Here's an alternative solution that triggers on a plain ! and then inspects the characters before the cursor to decide whether to insert an exclamation point or your expansion:

inoremap <expr> ! getline('.')[getpos('.')[2] - 6 : getpos('.')[2] - 2] == 'begin' ? "  end\<Left>\<Left>\<Left>\<Left>" : '!'

I think I prefer yours :).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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