" Insert or delete brackets, parens, quotes in pairs. " Maintainer: JiangMiao <jiangfriend@gmail.com> " Contributor: camthompson " Last Change: 2019-02-02 " Version: 2.0.0 " Homepage: http://www.vim.org/scripts/script.php?script_id=3599 " Repository: https://github.com/jiangmiao/auto-pairs " License: MIT if exists('g:AutoPairsLoaded') || &cp finish end let g:AutoPairsLoaded = 1 if !exists('g:AutoPairs') let g:AutoPairs = {'(':')', '[':']', '{':'}',"'":"'",'"':'"', '```':'```', '"""':'"""', "'''":"'''", "`":"`"} end " default pairs base on filetype func! AutoPairsDefaultPairs() if exists('b:autopairs_defaultpairs') return b:autopairs_defaultpairs end let r = copy(g:AutoPairs) let allPairs = { \ 'vim': {'\v^\s*\zs"': ''}, \ 'rust': {'\w\zs<': '>', '&\zs''': ''}, \ 'php': {'<?': '?>//k]', '<?php': '?>//k]'} \ } for [filetype, pairs] in items(allPairs) if &filetype == filetype for [open, close] in items(pairs) let r[open] = close endfor end endfor let b:autopairs_defaultpairs = r return r endf if !exists('g:AutoPairsMapBS') let g:AutoPairsMapBS = 1 end " Map <C-h> as the same BS if !exists('g:AutoPairsMapCh') let g:AutoPairsMapCh = 1 end if !exists('g:AutoPairsMapCR') let g:AutoPairsMapCR = 1 end if !exists('g:AutoPairsWildClosedPair') let g:AutoPairsWildClosedPair = '' end if !exists('g:AutoPairsMapSpace') let g:AutoPairsMapSpace = 1 end if !exists('g:AutoPairsCenterLine') let g:AutoPairsCenterLine = 1 end if !exists('g:AutoPairsShortcutToggle') let g:AutoPairsShortcutToggle = '<M-p>' end if !exists('g:AutoPairsShortcutFastWrap') let g:AutoPairsShortcutFastWrap = '<M-e>' end if !exists('g:AutoPairsMoveCharacter') let g:AutoPairsMoveCharacter = "()[]{}\"'" end if !exists('g:AutoPairsShortcutJump') let g:AutoPairsShortcutJump = '<M-n>' endif " Fly mode will for closed pair to jump to closed pair instead of insert. " also support AutoPairsBackInsert to insert pairs where jumped. if !exists('g:AutoPairsFlyMode') let g:AutoPairsFlyMode = 0 endif " When skipping the closed pair, look at the current and " next line as well. if !exists('g:AutoPairsMultilineClose') let g:AutoPairsMultilineClose = 1 endif " Work with Fly Mode, insert pair where jumped if !exists('g:AutoPairsShortcutBackInsert') let g:AutoPairsShortcutBackInsert = '<M-b>' endif if !exists('g:AutoPairsSmartQuotes') let g:AutoPairsSmartQuotes = 1 endif " 7.4.849 support <C-G>U to avoid breaking '.' " Issue talk: https://github.com/jiangmiao/auto-pairs/issues/3 " Vim note: https://github.com/vim/vim/releases/tag/v7.4.849 if v:version > 704 || v:version == 704 && has("patch849") let s:Go = "\<C-G>U" else let s:Go = "" endif let s:Left = s:Go."\<LEFT>" let s:Right = s:Go."\<RIGHT>" " unicode len func! s:ulen(s) return len(split(a:s, '\zs')) endf func! s:left(s) return repeat(s:Left, s:ulen(a:s)) endf func! s:right(s) return repeat(s:Right, s:ulen(a:s)) endf func! s:delete(s) return repeat("\<DEL>", s:ulen(a:s)) endf func! s:backspace(s) return repeat("\<BS>", s:ulen(a:s)) endf func! s:getline() let line = getline('.') let pos = col('.') - 1 let before = strpart(line, 0, pos) let after = strpart(line, pos) let afterline = after if g:AutoPairsMultilineClose let n = line('$') let i = line('.')+1 while i <= n let line = getline(i) let after = after.' '.line if !(line =~ '\v^\s*$') break end let i = i+1 endwhile end return [before, after, afterline] endf " split text to two part " returns [orig, text_before_open, open] func! s:matchend(text, open) let m = matchstr(a:text, '\V'.a:open.'\v$') if m == "" return [] end return [a:text, strpart(a:text, 0, len(a:text)-len(m)), m] endf " returns [orig, close, text_after_close] func! s:matchbegin(text, close) let m = matchstr(a:text, '^\V'.a:close) if m == "" return [] end return [a:text, m, strpart(a:text, len(m), len(a:text)-len(m))] endf " add or delete pairs base on g:AutoPairs " AutoPairsDefine(addPairs:dict[, removeOpenPairList:list]) " " eg: " au FileType html let b:AutoPairs = AutoPairsDefine({'<!--' : '-->'}, ['{']) " add <!-- --> pair and remove '{' for html file func! AutoPairsDefine(pairs, ...) let r = AutoPairsDefaultPairs() if a:0 > 0 for open in a:1 unlet r[open] endfor end for [open, close] in items(a:pairs) let r[open] = close endfor return r endf func! AutoPairsInsert(key) if !b:autopairs_enabled return a:key end let b:autopairs_saved_pair = [a:key, getpos('.')] let [before, after, afterline] = s:getline() " Ignore auto close if prev character is \ if before[-1:-1] == '\' return a:key end " check open pairs for [open, close, opt] in b:AutoPairsList let ms = s:matchend(before.a:key, open) let m = matchstr(afterline, '^\v\s*\zs\V'.close) if len(ms) > 0 " process the open pair " remove inserted pair " eg: if the pairs include < > and <!-- --> " when <!-- is detected the inserted pair < > should be clean up let target = ms[1] let openPair = ms[2] if len(openPair) == 1 && m == openPair break end let bs = '' let del = '' while len(before) > len(target) let found = 0 " delete pair for [o, c, opt] in b:AutoPairsList let os = s:matchend(before, o) if len(os) && len(os[1]) < len(target) " any text before openPair should not be deleted continue end let cs = s:matchbegin(afterline, c) if len(os) && len(cs) let found = 1 let before = os[1] let afterline = cs[2] let bs = bs.s:backspace(os[2]) let del = del.s:delete(cs[1]) break end endfor if !found " delete charactor let ms = s:matchend(before, '\v.') if len(ms) let before = ms[1] let bs = bs.s:backspace(ms[2]) end end endwhile return bs.del.openPair.close.s:left(close) end endfor " check close pairs for [open, close, opt] in b:AutoPairsList if close == '' continue end if a:key == g:AutoPairsWildClosedPair || opt['mapclose'] && opt['key'] == a:key " the close pair is in the same line let m = matchstr(afterline, '^\v\s*\V'.close) if m != '' if before =~ '\V'.open.'\v\s*$' && m[0] =~ '\v\s' " remove the space we inserted if the text in pairs is blank return "\<DEL>".s:right(m[1:]) else return s:right(m) end end let m = matchstr(after, '^\v\s*\zs\V'.close) if m != '' if a:key == g:AutoPairsWildClosedPair || opt['multiline'] if b:autopairs_return_pos == line('.') && getline('.') =~ '\v^\s*$' normal! ddk$ end call search(m, 'We') return "\<Right>" else break end end end endfor " Fly Mode, and the key is closed-pairs, search closed-pair and jump if g:AutoPairsFlyMode && a:key =~ '\v[\}\]\)]' if search(a:key, 'We') return "\<Right>" endif endif return a:key endf func! AutoPairsDelete() if !b:autopairs_enabled return "\<BS>" end let [before, after, ig] = s:getline() for [open, close, opt] in b:AutoPairsList let b = matchstr(before, '\V'.open.'\v\s?$') let a = matchstr(after, '^\v\s*\V'.close) if b != '' && a != '' if b[-1:-1] == ' ' if a[0] == ' ' return "\<BS>\<DELETE>" else return "\<BS>" end end return s:backspace(b).s:delete(a) end endfor return "\<BS>" " delete the pair foo[]| <BS> to foo for [open, close, opt] in b:AutoPairsList let m = s:matchend(before, '\V'.open.'\v\s*'.'\V'.close.'\v$') if len(m) > 0 return s:backspace(m[2]) end endfor return "\<BS>" endf " Fast wrap the word in brackets func! AutoPairsFastWrap() let c = @" normal! x let [before, after, ig] = s:getline() if after[0] =~ '\v[\{\[\(\<]' normal! % normal! p else for [open, close, opt] in b:AutoPairsList if close == '' continue end if after =~ '^\s*\V'.open call search(close, 'We') normal! p let @" = c return "" end endfor if after[1:1] =~ '\v\w' normal! e normal! p else normal! p end end let @" = c return "" endf func! AutoPairsJump() call search('["\]'')}]','W') endf func! AutoPairsMoveCharacter(key) let c = getline(".")[col(".")-1] let escaped_key = substitute(a:key, "'", "''", 'g') return "\<DEL>\<ESC>:call search("."'".escaped_key."'".")\<CR>a".c."\<LEFT>" endf func! AutoPairsBackInsert() let pair = b:autopairs_saved_pair[0] let pos = b:autopairs_saved_pair[1] call setpos('.', pos) return pair endf func! AutoPairsReturn() if b:autopairs_enabled == 0 return '' end let b:autopairs_return_pos = 0 let before = getline(line('.')-1) let [ig, ig, afterline] = s:getline() let cmd = '' for [open, close, opt] in b:AutoPairsList if close == '' continue end if before =~ '\V'.open.'\v\s*$' && afterline =~ '^\s*\V'.close let b:autopairs_return_pos = line('.') if g:AutoPairsCenterLine && winline() * 3 >= winheight(0) * 2 " Recenter before adding new line to avoid replacing line content let cmd = "zz" end " If equalprg has been set, then avoid call = " https://github.com/jiangmiao/auto-pairs/issues/24 if &equalprg != '' return "\<ESC>".cmd."O" endif " conflict with javascript and coffee " javascript need indent new line " coffeescript forbid indent new line if &filetype == 'coffeescript' || &filetype == 'coffee' return "\<ESC>".cmd."k==o" else return "\<ESC>".cmd."=ko" endif end endfor return '' endf func! AutoPairsSpace() if !b:autopairs_enabled return "\<SPACE>" end let [before, after, ig] = s:getline() for [open, close, opt] in b:AutoPairsList if close == '' continue end if before =~ '\V'.open.'\v$' && after =~ '^\V'.close if close =~ '\v^[''"`]$' return "\<SPACE>" else return "\<SPACE>\<SPACE>".s:Left end end endfor return "\<SPACE>" endf func! AutoPairsMap(key) " | is special key which separate map command from text let key = a:key if key == '|' let key = '<BAR>' end let escaped_key = substitute(key, "'", "''", 'g') " use expr will cause search() doesn't work execute 'inoremap <buffer> <silent> '.key." <C-R>=AutoPairsInsert('".escaped_key."')<CR>" endf func! AutoPairsToggle() if b:autopairs_enabled let b:autopairs_enabled = 0 echo 'AutoPairs Disabled.' else let b:autopairs_enabled = 1 echo 'AutoPairs Enabled.' end return '' endf func! s:sortByLength(i1, i2) return len(a:i2[0])-len(a:i1[0]) endf func! AutoPairsInit() let b:autopairs_loaded = 1 if !exists('b:autopairs_enabled') let b:autopairs_enabled = 1 end if !exists('b:AutoPairs') let b:AutoPairs = AutoPairsDefaultPairs() end if !exists('b:AutoPairsMoveCharacter') let b:AutoPairsMoveCharacter = g:AutoPairsMoveCharacter end let b:autopairs_return_pos = 0 let b:autopairs_saved_pair = [0, 0] let b:AutoPairsList = [] " buffer level map pairs keys " n - do not map the first charactor of closed pair to close key " m - close key jumps through multi line " s - close key jumps only in the same line for [open, close] in items(b:AutoPairs) let o = open[-1:-1] let c = close[0] let opt = {'mapclose': 1, 'multiline':1} let opt['key'] = c if o == c let opt['multiline'] = 0 end let m = matchlist(close, '\v(.*)//(.*)$') if len(m) > 0 if m[2] =~ 'n' let opt['mapclose'] = 0 end if m[2] =~ 'm' let opt['multiline'] = 1 end if m[2] =~ 's' let opt['multiline'] = 0 end let ks = matchlist(m[2], '\vk(.)') if len(ks) > 0 let opt['key'] = ks[1] let c = opt['key'] end let close = m[1] end call AutoPairsMap(o) if o != c && c != '' && opt['mapclose'] call AutoPairsMap(c) end let b:AutoPairsList += [[open, close, opt]] endfor " sort pairs by length, longer pair should have higher priority let b:AutoPairsList = sort(b:AutoPairsList, "s:sortByLength") for item in b:AutoPairsList let [open, close, opt] = item if open == "'" && open == close let item[0] = '\v(^|\W)\zs''' end endfor for key in split(b:AutoPairsMoveCharacter, '\s*') let escaped_key = substitute(key, "'", "''", 'g') execute 'inoremap <silent> <buffer> <M-'.key."> <C-R>=AutoPairsMoveCharacter('".escaped_key."')<CR>" endfor " Still use <buffer> level mapping for <BS> <SPACE> if g:AutoPairsMapBS " Use <C-R> instead of <expr> for issue #14 sometimes press BS output strange words execute 'inoremap <buffer> <silent> <BS> <C-R>=AutoPairsDelete()<CR>' end if g:AutoPairsMapCh execute 'inoremap <buffer> <silent> <C-h> <C-R>=AutoPairsDelete()<CR>' endif if g:AutoPairsMapSpace " Try to respect abbreviations on a <SPACE> let do_abbrev = "" if v:version == 703 && has("patch489") || v:version > 703 let do_abbrev = "<C-]>" endif execute 'inoremap <buffer> <silent> <SPACE> '.do_abbrev.'<C-R>=AutoPairsSpace()<CR>' end if g:AutoPairsShortcutFastWrap != '' execute 'inoremap <buffer> <silent> '.g:AutoPairsShortcutFastWrap.' <C-R>=AutoPairsFastWrap()<CR>' end if g:AutoPairsShortcutBackInsert != '' execute 'inoremap <buffer> <silent> '.g:AutoPairsShortcutBackInsert.' <C-R>=AutoPairsBackInsert()<CR>' end if g:AutoPairsShortcutToggle != '' " use <expr> to ensure showing the status when toggle execute 'inoremap <buffer> <silent> <expr> '.g:AutoPairsShortcutToggle.' AutoPairsToggle()' execute 'noremap <buffer> <silent> '.g:AutoPairsShortcutToggle.' :call AutoPairsToggle()<CR>' end if g:AutoPairsShortcutJump != '' execute 'inoremap <buffer> <silent> ' . g:AutoPairsShortcutJump. ' <ESC>:call AutoPairsJump()<CR>a' execute 'noremap <buffer> <silent> ' . g:AutoPairsShortcutJump. ' :call AutoPairsJump()<CR>' end if &keymap != '' let l:imsearch = &imsearch let l:iminsert = &iminsert let l:imdisable = &imdisable execute 'setlocal keymap=' . &keymap execute 'setlocal imsearch=' . l:imsearch execute 'setlocal iminsert=' . l:iminsert if l:imdisable execute 'setlocal imdisable' else execute 'setlocal noimdisable' end end endf func! s:ExpandMap(map) let map = a:map let map = substitute(map, '\(<Plug>\w\+\)', '\=maparg(submatch(1), "i")', 'g') let map = substitute(map, '\(<Plug>([^)]*)\)', '\=maparg(submatch(1), "i")', 'g') return map endf func! AutoPairsTryInit() if exists('b:autopairs_loaded') return end " for auto-pairs starts with 'a', so the priority is higher than supertab and vim-endwise " " vim-endwise doesn't support <Plug>AutoPairsReturn " when use <Plug>AutoPairsReturn will cause <Plug> isn't expanded " " supertab doesn't support <SID>AutoPairsReturn " when use <SID>AutoPairsReturn will cause Duplicated <CR> " " and when load after vim-endwise will cause unexpected endwise inserted. " so always load AutoPairs at last " Buffer level keys mapping " comptible with other plugin if g:AutoPairsMapCR if v:version == 703 && has('patch32') || v:version > 703 " VIM 7.3 supports advancer maparg which could get <expr> info " then auto-pairs could remap <CR> in any case. let info = maparg('<CR>', 'i', 0, 1) if empty(info) let old_cr = '<CR>' let is_expr = 0 else let old_cr = info['rhs'] let old_cr = s:ExpandMap(old_cr) let old_cr = substitute(old_cr, '<SID>', '<SNR>' . info['sid'] . '_', 'g') let is_expr = info['expr'] let wrapper_name = '<SID>AutoPairsOldCRWrapper73' endif else " VIM version less than 7.3 " the mapping's <expr> info is lost, so guess it is expr or not, it's " not accurate. let old_cr = maparg('<CR>', 'i') if old_cr == '' let old_cr = '<CR>' let is_expr = 0 else let old_cr = s:ExpandMap(old_cr) " old_cr contain (, I guess the old cr is in expr mode let is_expr = old_cr =~ '\V(' && toupper(old_cr) !~ '\V<C-R>' " The old_cr start with " it must be in expr mode let is_expr = is_expr || old_cr =~ '\v^"' let wrapper_name = '<SID>AutoPairsOldCRWrapper' end end if old_cr !~ 'AutoPairsReturn' if is_expr " remap <expr> to `name` to avoid mix expr and non-expr mode execute 'inoremap <buffer> <expr> <script> '. wrapper_name . ' ' . old_cr let old_cr = wrapper_name end " Always silent mapping execute 'inoremap <script> <buffer> <silent> <CR> '.old_cr.'<SID>AutoPairsReturn' end endif call AutoPairsInit() endf " Always silent the command inoremap <silent> <SID>AutoPairsReturn <C-R>=AutoPairsReturn()<CR> imap <script> <Plug>AutoPairsReturn <SID>AutoPairsReturn au BufEnter * :call AutoPairsTryInit()