I'm already aware of existing solution to automatically "surround" sentences/word/etc (eg: auto-surround, vim-surround, etc) but those (as far as i know) don't support using normal strings instead of characters (eg: {}, ()...).

I did found this snippet which seems to do what i want:

function! ConditionalPairMap(open, close)
  let line = getline('.')
  let col = col('.')
  if col < col('$') || stridx(line, a:close, col + 1) != -1
    return a:open
    return a:open . a:close . repeat("\<left>", len(a:close))
inoremap <expr> ( ConditionalPairMap('(', ')')
inoremap <expr> { ConditionalPairMap('{', '}')
inoremap <expr> [ ConditionalPairMap('[', ']')

Only problem is that I'm not sure how to make this work with normal strings. Here what i tried using the above function:

inoremap <expr> ( ConditionalPairMap('te', '.te')

I mainly want to use those as custom "marker" or "header" for certain block of text. The above closing and opening marker are just example.

Additionally, I'm aware that by using normal or any string as either opening/closing surrounding will be confusing when writing normal text, that's why I'd prefer if it would only trigger the closing marker when there a newline in front of it, like so:

te # no newline yet

If there a newline after it, place the closing after it:

te #a newline placed here

PS: This is mainly to be used in Insert mode.

Any way to do this?

  • 1
    Well, surround supports some string types (function calls, latex environments, xml tags, just to name a few), and lets you build your own.
    – D. Ben Knoble
    Commented May 10, 2021 at 1:25
  • 1
    It's hard to cover this topic in a too generic way, since often details change the experience significantly... If what you're after could be narrowed to "insert closing tag in its own line when pressing Enter on a line beginning with the open tag", then something like this might work: inoremap <expr> <CR> getline('.') =~# '^\s*te\>' ? '<CR>.te<C-o>O' : '<CR>'
    – filbranden
    Commented May 10, 2021 at 2:32
  • 1
    YEP, that works great :D @filbranden Feel free to post this as answer if you want. Btw, any way to do this without necessarily using a regex? so i could use a different opening and closing string more easily. Otherwise, it's perfect Commented May 10, 2021 at 2:35
  • 2
    Your question also seems to mix up the vim-surround plug-in, which implements Normal-mode actions to surround existing contents, with plug-ins such as "auto-pairs" (and many others similar to it), which will add matching brackets while you insert them in Insert-mode. These are actually quite different from each other and have different trade offs... You mentioned "surround" at the top of your question, but your examples are much closer to what "auto-pairs" does...
    – filbranden
    Commented May 10, 2021 at 2:37
  • 1
    Nice! I'll post that as an answer. Let me look into making it more general to cover open/close tags from a Dict...
    – filbranden
    Commented May 10, 2021 at 2:38

1 Answer 1


Since you're looking at pairing open/close tags across multiple lines (enclosing a block) and in your case it makes sense to insert the closing tag when breaking the line, my suggestion is to add a mapping to the Enter key in Insert mode and then use an <expr> to decide whether we need to add the closing tag. You can make that decision based on the contents of the current line, more specifically the first word you find on the line.

A very simple example for the exact case you described:

inoremap <expr> <CR> getline('.') =~# '^\s*te\>'
      \ ? '<CR>.te<C-o>O'
      \ : '<CR>'

It's using a regexp match on the current line to check whether the current line starts with te, possibly after some whitespace, then followed by a word boundary. In that case, it adds a <CR> to break the line, then adds the closing .te, and then uses the <C-o> sequence to enter Normal mode temporarily (<Esc> would have worked the same here), following by an O Normal-mode command, to insert a new line above the current one and start Insert mode there.

If the line doesn't start with the expected tag, then we simply insert a <CR>, to act as if there was no mapping for the Enter key.

You can extend this idea to define a Dictionary of pairs of open/close tags and use a function to look them up and match them. I'm posting an example below. It relies on the opening tags being a full word (followed by whitespace) on their own. But as this is Vimscript, you can decide to make it as complex as you wish, to cover corner cases that matter to you.

let g:tag_pairs = {
  \ 'te': '.te',
  \ 'if': 'endif',
  \ }

function! AddClosingPair()
  let firstword = get(split(getline('.')), 0)
  let closing = get(g:tag_pairs, firstword, '')
  if closing != ''
    return "\r".closing."\<C-o>O\<C-t>"
  return "\r"

inoremap <expr> <CR> AddClosingPair()

Just update the g:tag_pairs Dictionary to cover more tag pairs. In this example, I also included an \<C-t> at the end of the sequence triggered by the tag pairs, to add indentation to the block opened by it. (That would be more appropriately handled in an 'indentexpr', but maybe it makes sense to keep it in your use case, so I included it.)


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.