This is a (somewhat opinionated) survey of some of the most popular vim plugins for auto-pairing, i.e. auto-completion of closing brackets or quotes. I’m not quite satisfied with any of them, so in the end I have to make my own fork (GitHub mirror) and customize it to my liking.

If you are looking for a sensible auto-pairing setup, I am afraid to say that you might have to create your own fork of one of the plugins below as well, since this can be very subjective. However, the issues listed in the survey below should be enough to get you started.

Of course, if you choose to base your setup on jiangmiao’s auto-pairs, the comments in my auto-pairs.vim might be helpful (search for Krasjet ).

1 A survey

The plugins below are not listed in a particular order. I have only experimented with some of the most popular plugins. There are some lesser known ones such as coBra, vim-closer, and vim-endoscope, but I have never tried them, nor do I expect they satisfy my requirements.

Despite some minor annoyances, this plugin is the one I’m most familiar with, which is why my own fork is based on this plugin. However, this plugin has too many mappings that I don’t need, such as <M-e> , <M-n> , and the fly mode. I would prefer to fix pairing issues manually than remembering a keybinding.

Issues

// settings let g:AutoPairsMultilineClose = 0 // before character input: |s (press '(') output: (|)s // inside quote input: '|' (press '[') output: '[|]' // seach for closing pair input: " | " (press '"') output: " "| // closing pair balance input: (((|)) (press ')') output: ((()|) // open pair balance input: ((|))) (press '(') output: (((|)))) // open pair balance input: "str| (press '"') output: "str"|"

I would say I’m pretty impressed with the smart pairing of this plugin. You can even set completion rules based on syntax groups. There are still some minor issues, though. One issue is that I never got the g:pear_tree_smart_backspace working. Also, if g:pear_tree_repeatable_expand is enabled, the closing braces will disappear after a line break, which can be quite annoying.

Issues

// settings let g:pear_tree_smart_openers = 1 let g:pear_tree_smart_closers = 1 let g:pear_tree_smart_backspace = 1 let g:pear_tree_repeatable_expand = 0 // backspace (might be a bug in smart backspace) input: (|) (press '<bs>') output: |) // open pair balance input: ((|))) (press '(') output: (((|))))

One of the biggest drawback is that this plugin has to be installed on top of coc.nvim, which can make your vimrc less portable, but if you are already using coc.nvim for auto-completions, it can be an option. However, do notice that there are several minor issues (but in the case of auto-completion, minor annoyances can be exaggerated).

Issues

// inside quote input: '|' (press '[') output: '[|]' // closing pair balance input: (((|)) (press ')') output: ((()|) // open pair balance input: ((|))) (press '(') output: (((|)))) // multiple character opening (very wierd) input: `|` (press '`') output: ``| (press '`') ```| (press '`') ```|``` (press '`') ```|`````` (press '`') // <cr> mapping input: """|""" (press '<cr>') output: """ |"""

It is possible to set up some snippets for auto pairing, e.g.

snippet ( "()" iA ($1)$0 endsnippet

Since you can use regex and scripting in UltiSnips, it can be very powerful. However, there is no mechanism for deleting pairs, which can be very critical to some people.

This plugin is extremely customizable. The number of options can be very overwhelming (with regular expressions). The smart pairing rules are also fairly good.

Issues

However, there is one critical issue. I don’t think it support multi-byte opening/ closing pair very well. For example,

// setting let delimitMate_expand_cr = 1 let delimitMate_balance_matchpairs = 1 let delimitMate_nesting_quotes = [ '"' , '`' ] let delimitMate_quotes = "\" ' ` * **"

// multi-byte input: | (press '**') output: ***|* // multi-byte input: | (press '*', wait) output: *|* (press '*', wait) **|

It will add a imap for ** , which will cause some delay when you type * . In addition, there are some other problems,

// inside quote input: '|' (press '[') output: '[|]' // closing pair balance input: (((|)) (press ')') output: ((()|) // <cr> mapping input: """|""" (press '<cr>') output: """ |"""

The completion and deletion are based on rules, which can be very customizable, but the default rules are not very smart, so it can take some time to properly set it up.

Issues

// before character input: |s (press '(') output: (|)s // inside quote input: '|' (press '[') output: '[|]' // open pair balance input: ((|))) (press '(') output: (((|)))) // open pair balance input: "str| (press '"') output: "str"|" // <cr> mapping input: """|""" (press '<cr>') output: """ |"""

General suggestions

Plugins can’t read your mind, so don’t expect a plugin that can handle all the edge cases correctly.

However, I do think it is possible to figure out a most probable solution for yourself, i.e. a solution that is most likely to be correct for you in most contexts. This can be a interesting machine learning problem, but you can also do it manually. This is why I recommend creating your own fork, or pick a highly customizable plugin, such as delimitMate and lexima.vim and make your rules.

Consider an example, say you want to add a quotation mark before and after Hello , and now your cursor is before H I might actually prefer to use vim-surround instead of going into insert mode in this case.

|Hello

Should the plugin add a closing quote in this case? Maybe it’s good to ask yourself:

Do I need a closing quote when a character is immediately after the cursor?

The question can be more specific:

Do I need a closing quote when an alphanumeric character is immediately after the cursor?

If the first answer that comes to mind is no, then add a rule to fix it (see below). Even though the solution can’t cover all the cases, where a closing quote is needed, for me, it doesn’t hurt to type one manually, but your responses might be different.

If the answer is unsure, people can have different solutions. For me, if I’m questioning whether I do need a completion or not, e.g. inside a quote, then I would choose to add a rule and inhibit the completion, since I would rather type the closing quote manually. But still, this is my preference, and yours can differ.

If the answer is yes, try come up with other instances that follow the same pattern, or just leave it as it is and wait until you notice a problem in the future.

The final result of your customization can’t read your mind, but your mind can affect the probability of correctness (to you) and the probability of frustration, which I think is better than nothing.

2 My own fork

I’m not quite satisfied with any of the plugins, so I have to make a fork to fix some common issues and make the auto-completion rules more sensible. However, I don’t expect anyone else to use this plugin, so no options are currently provided. If you want to know how to use the plugin, please read the original readme.

The source code of my fork can be found in the sink or on GitHub.

2.1 Installation

With vim-plug

Plug 'Krasjet/auto.pairs'

2.2 Main changes

Principles: auto-completion and jump should be as lazy as possible, because correcting one completion mistake would take more keystrokes than closing the pair manually.

2.2.1 Check next character

Only insert the closing pair if the next character is a space or a non-string closing character.

input: | s (press '(') output: (|) s input: |s (press '(') output: (|s input: (|) (press '(') output: ((|)) input: (|) (press '[') output: ([|]) input: '|' (press '[') output: '[|'

All the quote characters can be set as a global variable g:AutoPairsQuoteClosingChar or buffer variable b:AutoPairsQuoteClosingChar .

let g :AutoPairsQuoteClosingChar = [ '"' , "'" , '`' ] let b :AutoPairsQuoteClosingChar = [ '"' , "'" , '`' ]

If the next character of the cursor is any of these, auto-completion will be inhibited.

There is also variables g:AutoPairsNextCharWhitelist and b:AutoPairsNextCharWhitelist to whitelist certain characters (or strings) such that trigger the auto-completion when appeared as the next character.

let g :AutoPairsNextCharWhitelist = [] let b :AutoPairsNextCharWhitelist = [ '.' , ',' ]

If the next character(s) of the cursor is any of these, auto-completion will be triggered. I would usually add . and , to the whitelist for LaTeX and markdown files.

In summary, auto-completion will only be triggered if

the next character is a space or the next character is not from the closing pair of a quote or the next character is in the whitelist

2.2.2 Do not skip space

Do not search for the closing pair if spaces are in between.

input: " | " (press '"') output: " "| " input: " |" (press '"') output: " "|

2.2.3 Closing pair balance

Only jump across the closing pair if pairs are balanced.

input: (((|)) (press ')') output: (((|))) input: (((|))) (press ')') output: ((()|))

2.2.4 Open pair balance

Do not complete the closing pair until pairs are balanced.

input: ((|))) (press '(') output: (((|))) input: (((|))) (press '(') output: ((((|)))) input: "str| (press '"') output: "str"|

The balance check for single quote ( ' ) can be turned on or off using the variable g:AutoPairsSingleQuoteBalanceCheck and b:AutoPairsSingleQuoteBalanceCheck

let g :AutoPairsSingleQuoteBalanceCheck = 1 let b :AutoPairsSingleQuoteBalanceCheck = 0

It is turned on by default, but you might want to use ftplugin or autocmd to turn it off for text files (LaTeX, markdown, etc.). Consider

input: There's a '| (press "'") // single quote balance check on output: There's a ''|' // single quote balance check off output: There's a ''|

However, for code files,

input: ['a', |] (press "'") // single quote balance check on output: ['a', '|'] // single quote balance check off output: ['a', '|]

The balance check for a opening pair can be turned off using g:AutoPairsOpenBalanceBlacklist and b:AutoPairsOpenBalanceBlacklist

let g :AutoPairsOpenBalanceBlacklist = [] let b :AutoPairsOpenBalanceBlacklist = [ '{' ]

This is a temporary workaround for if-else clause in C-like languages.

input: } else | (press '{') // with open balance check off output: } else {|} // with open balance check on output: } else {|

Nothing is in the blacklist by default, but you can use ftplugin or autocmd to turn off the open balance check for { .

2.3 Side note

The default value of g:AutoPairsMultilineClose has been changed to 0. If you want to enable it, set

let g :AutoPairsMultilineClose = 1

but be cautious that this fork might break it (and the fly mode, which is also disabled by default), but I haven’t tested them, and I don’t need them anyways.

More to be added when I encounter more problems.

3 License

The plugin is licensed under the MIT license.

The survey is licensed under CC-BY-NC 4.0. © 2020 Krasjet.