Automatically append closing characters

From Vim Tips Wiki

Jump to: navigation, search
Tip 630 Printable Monobook Previous Next

created 2004 · complexity basic · author Andrzej Cuber · version 6.0

This tip is deprecated for the following reasons:

The undo/redo workarounds contained here were broken by Vim version 7.4. A fix has been included as of 7.4.849 to support this without workarounds; this tip needs updating to reflect the new method.

This tip discusses methods to automatically add closing characters (such as adding "}" after typing "{").

[edit] A simple solution

[edit] Basic pair completion

Using the following mappings, when you type an open brace, a closing brace is automatically inserted on the same line after the cursor. If you quickly press Enter after the open brace (to begin a code block), the closing brace will be inserted on the line below the cursor. If you quickly press the open brace key again after the open brace, nothing extra will be inserted—you will just get a single open brace. Finally, if you quickly type an open and close brace, Vim will not do anything special.

inoremap {      {}<Left>
inoremap {<CR>  {<CR>}<Esc>O
inoremap {{     {
inoremap {}     {}

One thing to be aware of with these mappings is that they will interrupt your undo sequence, as documented at :help ins-special-special. This means that after using these mappings and inserting more text between the braces, pressing u will only undo the text inserted between the braces. Similarly, pressing . (repeat) will only insert the same text.

[edit] Skipping over the closing character

Similar mappings for other "paired" characters can be made from the above with trivial modifications, but characters like brackets and parentheses which often require text after them might instead benefit from something like the following, which automatically closes all groups, and skips over existing closing characters if another one is typed immediately before:

inoremap        (  ()<Left>
inoremap <expr> )  strpart(getline('.'), col('.')-1, 1) == ")" ? "\<Right>" : ")"

This solution works by looking at the character just after the cursor (which is at the byte index given by cursor column - 1), and simply moving the cursor if it is the closing character. If the character after the cursor is not the closing character, it inserts the closing character. The mapping is fairly straightforward, but will not work in versions prior to Vim 7.0 which introduced <expr> mappings. You will need to modify this slightly for older versions of Vim.

In the case of single or double quotes, since the closing character is the same as the opening one, the mapping is done slightly different:

inoremap <expr> ' strpart(getline('.'), col('.')-1, 1) == "\'" ? "\<Right>" : "\'\'\<Left>"

If the character just after the cursor is not a single quote, we insert two single quotes instead of only one.

Note that like the insertion of the closing character, skipping the closing character in this way will break your undo sequence.

[edit] Expanding the simple solution

[edit] More on undo/redo/repeat

Without interrupting the undo/redo/repeat sequence, there is not an easy way to move the cursor in insert mode or to insert text without moving the cursor. In Vim 7.4, there is no known way to In Vim 7.4.849 you can move the cursor in insert mode and leave the undo sequence intact with the CTRL-G_U sequence. But, it is possible in Vim 7.3 and below, using setpos() or setline() within a function called from an insertion of the expression register, for example:

" Incomplete example moving the cursor after insertion.
" DO NOT USE, use a plugin instead.
function! MoveLeft()
  let newpos = getpos('.')
  let newpos[2] -= 1
  if (newpos[2] < 1)
    let newpos[2] = 1
  call setpos('.', newpos)
  return ""
inoremap ( ()<C-R>=MoveLeft()<CR>


" Incomplete example inserting text without moving cursor.
" DO NOT USE, use a plugin instead.
function! InsertClosing(char)
  let line = getline('.')
  let colm = col('.')
  let colmIdx = colm - 1
  call setline('.', line[:colmIdx].a:char.line[colmIdx+1:])
  return ""
inoremap ( (<C-R>=InsertClosing(')')<CR>

The expression register is used instead of an <expr> mapping, because moving the cursor or changing text is not allowed within an <expr> mapping.

Unfortunately, even though the undo sequence is unbroken, without a complicated set of other mappings, the text inserted or cursor movement accomplished within the expression mapping will not be reflected in the text inserted by pressing '.'. Using the first method (moving the cursor without breaking undo) you would insert "abc(def)" and repeat using . to get "abc()def". Using the second method (inserting text without moving the cursor) you would insert "abc(def)" (with the closing ')' added automatically) and repeat only "abc(def" (without the automatic closing character).

If you want auto-insertion of closing characters which does not break your undo sequence, you should consider one of the plugins which has implemented this already, but you will also need to stick with Vim 7.3 or earlier.

[edit] Add the closing brace only at the end of the line

Automatically inserting closing braces can be confusing when editing text. The following function inserts the closing brace only when the cursor is at the end of the line. As a consequence, the closing brace doesn't often get in your way, although that might be a question of personal preference.

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('[', ']')

[edit] Managing paired character sequences

For adding multiple-character pairs such as C-style comments, you may want to find another way to prevent the mapping from taking effect, such as typing the mapleader (usually '\' – see :help mapleader) character first as below:

inoremap /*          /**/<Left><Left>
inoremap /*<Space>   /*<Space><Space>*/<Left><Left><Left>
inoremap /*<CR>      /*<CR>*/<Esc>O
inoremap <Leader>/*  /*

Similar mappings might be useful for quotes, but they might get in your way depending on the type of file you're editing. Some languages use duplicate single-quotes a lot, and some pair the backtick with the quote. For these situations, you might want to put similar commands into language-specific files. For example, this quote-completer for GNU M4 might live in ~/.vim/after/ftplugin/m4.vim. :help after-directory

inoremap `      `')<Left><Left>
inoremap `<CR>  `<CR>'<Esc>O
inoremap ``     `
inoremap `'     `'

[edit] More advanced solutions

If you want something more complex and configurable, there are a number of different scripts that accomplish this task.

[edit] Plugins

The auto-insertion of matching brackets is provided by these plugins:

  • delimitMate by Israel Chauca Fuentes (configurable, and doesn't break undo/redo/repeat in Vim 7.3 or lower, but breaks iabbr). Latest version on GitHub.
  • AutoClose by Karl Guertin (auto-closes specific hard-coded characters, and doesn't break undo/redo/repeat in Vim 7.3 or lower)
  • AutoClose by Thiago Alves (configurable, but breaks undo/redo/repeat)
  • auto-pairs Auto Pairs by Miao Jiang (configurable, but breaks undo/redo/repeat)
  • ClosePairs by Edoardo Vacchi (configurable, but breaks undo/redo/repeat)
  • smartinput by Kana Natsuno (configurable, but breaks undo/redo/repeat)
  • Luc Hermitte has some very advanced and smart abbreviations. Those scripts give advanced brace-handling features like markers support (placeholders in another terminology), several things can be easily tweaked (whether we want newlines or not before the curly-brackets, ...), the abbreviations are buffer-relative (which is necessary to have "{" expand into different things according to the filetype of the buffer edited), context-sensitive (the abbreviations are not expanded within comments or string contexts) and more. lh-brackets also provides surrounding, and a smart <BS> that deletes both characters from an empty pair. See also lh-cpp, the C & C++ ftplugin suite built on top of it, where ; can close all pending ). lh-brackets supports redo/repeat on vim versions >= 7.4-849.
  • Srinath Avadhanula's imaps.vim is used by Latex-Suite to provide a similar bracketing system.
  • Marcn Szamotulski's Automatic LaTeX Plugin is closing open brackets within the omnicompletion mechanism, it also closes opened LaTeX environments.
  • The popular surround.vim provides an insert-mode mapping to insert pairs of any delimiter defined by a text object together, if you can remember to use it instead of just typing the opening delimiter. surround.vim integrates with the repeat.vim plugin to provide some support for the repeat command for its normal-mode commands, but the insert-mode command will still break the undo sequence.

[edit] ReplaceCurly script

This script operates only on braces, but is smarter about detecting when it should act. It will not take effect when editing comments, strings and lines containing the the word "new." (This is useful for array initialization, e.g. string[] myArray = new string[] {"a", "b"}.)

imap { <Esc>:call ReplaceCurly()<CR>"_cl
function! ReplaceCurly()
  imap { {
  " only replace outside of comments or strings (which map to constant)
  let elesyn = synIDtrans(synID(line("."), col(".") - 1, 0))
  if elesyn != hlID('Comment') && elesyn != hlID('Constant') && match(getline("."), "\\<new\\>") < 0
    exe "normal a{"
    " need to add a spare character (x) to position the cursor afterwards
    exe "normal ox"
    exe "normal o}"
    exe "normal kw"
    " need to add a spare character (x) to position the cursor afterwards
    exe "normal a{x"
  imap { <Esc>:let word= ReplaceCurly()<CR>"_cl

"Surround code with braces
nmap <Leader>{} O{<Esc>ddj>>ddkP
vmap <Leader>{} <Esc>o{<Esc>ddgv>gvdp

[edit] Backwards-compatible closing brace skip

If you need a solution for inserting a closing brace automatically in a pre-7.0 Vim, you can slightly modify the <expr> mapping given above to use the expression register instead:

inoremap ) <C-R>=strpart(getline('.'), col('.')-1, 1) == ")" ? "\<lt>Right>" : ")"<CR>

Note that we needed to treat the "\<Right>" text differently so that it is expanded when the expression register is evaluated instead of when the mapping is executed.

[edit] See also

[edit] Comments

20150813: Tested on Debian Unstable with Vim 7.4.712 -> Works perfect!

Personal tools