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')), '') . '>'
endfu
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
endif
endfor
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)
endif
else
return '"'
endif
endfu
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
...
<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?