I'm trying to create a plugin for inserting things like closing quotes, brackets, parenthesis and xml tags. I know about delimitMate, auto-pairs and others, but I'm not satisfied with them.

DelimitMate is kind hard to configure and nice features like <CR> expansion doesn't work by default.

auto-pairs doesn't work with Python in things like b''

To insert a closing tag you have to install another plugin and it doesn't work very well (It's been a long time since I tried).

Everyone is telling that inserting a pairs automatically is surprisingly hard, but I thought about it for a while and it doesn't seem that hard at all.

Basically you need to map every character that can trigger insertion to a function that would search a list of user defined regexes for a pair of regexes that would match text before and after the cursor. If match is found is passed to a function that would return the actual text to insert.

This way for example inserting closing tags, python's """""", f''is trivial. Here's a proof of concept code

fu! RetMatch(matched)
    return '></' . join(reverse(split(a:matched, '.\zs')), '') . '>'

fu! SmartPair()
    " let pairs = {'"': [['""', '"""'], ['[bfr]\?', '"']]}
    let pairs = {'>': [['[^<]\+\ze<', function('RetMatch')]]}
    let reversed_line = join(reverse(split(getline('.')[:col('.')], '.\zs')), '')
    let longest_match_len = -1
    let longest_match = ''
    let Longest_substitution = ''
    for [pattern, Substitution] in pairs['>']
        let [matched, mstart, mend] = matchstrpos(reversed_line, pattern)
        if mstart == 0 && mend - mstart >= longest_match_len
            let longest_match_len = mend - mstart
            let longest_match = matched
            let Longest_substitution = Substitution
    let substitution_type = type(Longest_substitution)
    if longest_match_len >= 0
        if substitution_type == 1
            return '"' . longest_substitution
        elseif substitution_type == 2
            return Longest_substitution(longest_match)
        return '"'

inoremap <expr> " SmartPair()
inoremap <expr> > SmartPair()

Notice how the same approarch makes inserting so called space expansion easy, you just need to define a mapping for space, the regexes pair would be something like '(' to match before the cursor, and ')' to match after and a function that would return resulting text would return two spaces.

I think this also can be achieved by using InsertCharPre...

  • so you have described your approach quite well. I am missing, what exactly your problem is. Commented Aug 26, 2017 at 15:43
  • @ChristianBrabandt didn't new how to move cursor in <expr> mapping. I've done that by copying approach of auto-pairs plugin. The use <silent> <C-R>=... and then append <Left>. It's working now, but maybe there's a cleaner way? Commented Aug 26, 2017 at 16:23

1 Answer 1


For <expr> mappings, you are not allowed to move the cursor (or rather the position of the cursor will be restored afterwards) as it is written in the help.

However what you can do is return the keychars which would move the cursor, e.g. in your expr do something like this:

nmap <expr> , Foobar()
function Foobar()
  "... do some magic
  let result="..."
  return "\<left>\<left>".result

So don't move the cursor directly, but make the result of your expression move the cursor.

  • Strangly It didn't worked when I tried it the first time, but worked with <C-R>=, but now it's working either way. What's the difference between mapping with and <expr> Commented Aug 26, 2017 at 19:49

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.