" Insert or delete brackets, parens, quotes in pairs. " Maintainer: JiangMiao " Contributor: camthompson " Last Change: 2017-06-17 " Version: 1.3.3 " 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 if !exists('g:AutoPairsParens') let g:AutoPairsParens = {'(':')', '[':']', '{':'}'} end if !exists('g:AutoPairsMapBS') let g:AutoPairsMapBS = 1 end " Map 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:AutoPairsMapSpace') let g:AutoPairsMapSpace = 1 end if !exists('g:AutoPairsCenterLine') let g:AutoPairsCenterLine = 1 end if !exists('g:AutoPairsShortcutToggle') let g:AutoPairsShortcutToggle = '' end if !exists('g:AutoPairsShortcutFastWrap') let g:AutoPairsShortcutFastWrap = '' end if !exists('g:AutoPairsMoveCharacter') let g:AutoPairsMoveCharacter = "()[]{}\"'" end if !exists('g:AutoPairsShortcutJump') let g:AutoPairsShortcutJump = '' 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 = '' endif if !exists('g:AutoPairsSmartQuotes') let g:AutoPairsSmartQuotes = 1 endif " 7.4.849 support 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 = "\U" else let s:Go = "" endif let s:Left = s:Go."\" let s:Right = s:Go."\" " Will auto generated {']' => '[', ..., '}' => '{'}in initialize. let g:AutoPairsClosedPairs = {} function! AutoPairsInsert(key) if !b:autopairs_enabled return a:key end let line = getline('.') let pos = col('.') - 1 let before = strpart(line, 0, pos) let after = strpart(line, pos) let next_chars = split(after, '\zs') let current_char = get(next_chars, 0, '') let next_char = get(next_chars, 1, '') let prev_chars = split(before, '\zs') let prev_char = get(prev_chars, -1, '') let eol = 0 if col('$') - col('.') <= 1 let eol = 1 end " Ignore auto close if prev character is \ if prev_char == '\' return a:key end " The key is difference open-pair, then it means only for ) ] } by default if !has_key(b:AutoPairs, a:key) let b:autopairs_saved_pair = [a:key, getpos('.')] " Skip the character if current character is the same as input if current_char == a:key return s:Right end if !g:AutoPairsFlyMode " Skip the character if next character is space if current_char == ' ' && next_char == a:key return s:Right.s:Right end " Skip the character if closed pair is next character if current_char == '' if g:AutoPairsMultilineClose let next_lineno = line('.')+1 let next_line = getline(nextnonblank(next_lineno)) let next_char = matchstr(next_line, '\s*\zs.') else let next_char = matchstr(line, '\s*\zs.') end if next_char == a:key return "\e^a" endif endif endif " Fly Mode, and the key is closed-pairs, search closed-pair and jump if g:AutoPairsFlyMode && has_key(b:AutoPairsClosedPairs, a:key) let n = stridx(after, a:key) if n != -1 return repeat(s:Right, n+1) end if search(a:key, 'W') " force break the '.' when jump to different line return "\" endif endif " Insert directly if the key is not an open key return a:key end let open = a:key let close = b:AutoPairs[open] if current_char == close && open == close return s:Right end " Ignore auto close ' if follows a word " MUST after closed check. 'hello|' if a:key == "'" && prev_char =~ '\v\w' return a:key end " support for ''' ``` and """ if open == close " The key must be ' " ` let pprev_char = line[col('.')-3] if pprev_char == open && prev_char == open " Double pair found return repeat(a:key, 4) . repeat(s:Left, 3) end end let quotes_num = 0 " Ignore comment line for vim file if &filetype == 'vim' && a:key == '"' if before =~ '^\s*$' return a:key end if before =~ '^\s*"' let quotes_num = -1 end end " Keep quote number is odd. " Because quotes should be matched in the same line in most of situation if g:AutoPairsSmartQuotes && open == close " Remove \\ \" \' let cleaned_line = substitute(line, '\v(\\.)', '', 'g') let n = quotes_num let pos = 0 while 1 let pos = stridx(cleaned_line, open, pos) if pos == -1 break end let n = n + 1 let pos = pos + 1 endwhile if n % 2 == 1 return a:key endif endif return open.close.s:Left endfunction function! AutoPairsDelete() if !b:autopairs_enabled return "\" end let line = getline('.') let pos = col('.') - 1 let current_char = get(split(strpart(line, pos), '\zs'), 0, '') let prev_chars = split(strpart(line, 0, pos), '\zs') let prev_char = get(prev_chars, -1, '') let pprev_char = get(prev_chars, -2, '') if pprev_char == '\' return "\" end " Delete last two spaces in parens, work with MapSpace if has_key(b:AutoPairs, pprev_char) && prev_char == ' ' && current_char == ' ' return "\\" endif " Delete Repeated Pair eg: '''|''' [[|]] {{|}} if has_key(b:AutoPairs, prev_char) let times = 0 let p = -1 while get(prev_chars, p, '') == prev_char let p = p - 1 let times = times + 1 endwhile let close = b:AutoPairs[prev_char] let left = repeat(prev_char, times) let right = repeat(close, times) let before = strpart(line, pos-times, times) let after = strpart(line, pos, times) if left == before && right == after return repeat("\\", times) end end if has_key(b:AutoPairs, prev_char) let close = b:AutoPairs[prev_char] if match(line,'^\s*'.close, col('.')-1) != -1 " Delete (|___) let space = matchstr(line, '^\s*', col('.')-1) return "\". repeat("\", len(space)+1) elseif match(line, '^\s*$', col('.')-1) != -1 " Delete (|__\n___) let nline = getline(line('.')+1) if nline =~ '^\s*'.close if &filetype == 'vim' && prev_char == '"' " Keep next line's comment return "\" end let space = matchstr(nline, '^\s*') return "\\". repeat("\", len(space)+1) end end end return "\" endfunction function! AutoPairsJump() call search('["\]'')}]','W') endfunction " string_chunk cannot use standalone let s:string_chunk = '\v%(\\\_.|[^\1]|[\r\n]){-}' let s:ss_pattern = '\v''' . s:string_chunk . '''' let s:ds_pattern = '\v"' . s:string_chunk . '"' func! s:RegexpQuote(str) return substitute(a:str, '\v[\[\{\(\<\>\)\}\]]', '\\&', 'g') endf func! s:RegexpQuoteInSquare(str) return substitute(a:str, '\v[\[\]]', '\\&', 'g') endf " Search next open or close pair func! s:FormatChunk(open, close) let open = s:RegexpQuote(a:open) let close = s:RegexpQuote(a:close) let open2 = s:RegexpQuoteInSquare(a:open) let close2 = s:RegexpQuoteInSquare(a:close) if open == close return '\v'.open.s:string_chunk.close else return '\v%(' . s:ss_pattern . '|' . s:ds_pattern . '|' . '[^'.open2.close2.']|[\r\n]' . '){-}(['.open2.close2.'])' end endf " Fast wrap the word in brackets function! AutoPairsFastWrap() let line = getline('.') let current_char = line[col('.')-1] let next_char = line[col('.')] let open_pair_pattern = '\v[({\[''"]' let at_end = col('.') >= col('$') - 1 normal! x " Skip blank if next_char =~ '\v\s' || at_end call search('\v\S', 'W') let line = getline('.') let next_char = line[col('.')-1] end if has_key(b:AutoPairs, next_char) let followed_open_pair = next_char let inputed_close_pair = current_char let followed_close_pair = b:AutoPairs[next_char] if followed_close_pair != followed_open_pair " TODO replace system searchpair to skip string and nested pair. " eg: (|){"hello}world"} will transform to ({"hello})world"} call searchpair('\V'.followed_open_pair, '', '\V'.followed_close_pair, 'W') else call search(s:FormatChunk(followed_open_pair, followed_close_pair), 'We') end return s:Right.inputed_close_pair.s:Left else normal! he return s:Right.current_char.s:Left end endfunction function! AutoPairsMap(key) " | is special key which separate map command from text let key = a:key if key == '|' let key = '' end let escaped_key = substitute(key, "'", "''", 'g') " use expr will cause search() doesn't work execute 'inoremap '.key." =AutoPairsInsert('".escaped_key."')" endfunction function! AutoPairsToggle() if b:autopairs_enabled let b:autopairs_enabled = 0 echo 'AutoPairs Disabled.' else let b:autopairs_enabled = 1 echo 'AutoPairs Enabled.' end return '' endfunction function! AutoPairsMoveCharacter(key) let c = getline(".")[col(".")-1] let escaped_key = substitute(a:key, "'", "''", 'g') return "\\:call search("."'".escaped_key."'".")\a".c."\" endfunction function! AutoPairsReturn() if b:autopairs_enabled == 0 return '' end let line = getline('.') let pline = getline(line('.')-1) let prev_char = pline[strlen(pline)-1] let cmd = '' let cur_char = line[col('.')-1] if has_key(b:AutoPairs, prev_char) && b:AutoPairs[prev_char] == cur_char 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 "\".cmd."O" endif " conflict with javascript and coffee " javascript need indent new line " coffeescript forbid indent new line if &filetype == 'coffeescript' || &filetype == 'coffee' return "\".cmd."k==o" else return "\".cmd."=ko" endif end return '' endfunction function! AutoPairsSpace() let line = getline('.') let prev_char = line[col('.')-2] let cmd = '' let cur_char =line[col('.')-1] if has_key(g:AutoPairsParens, prev_char) && g:AutoPairsParens[prev_char] == cur_char let cmd = "\".s:Left endif return "\".cmd endfunction function! AutoPairsBackInsert() if exists('b:autopairs_saved_pair') let pair = b:autopairs_saved_pair[0] let pos = b:autopairs_saved_pair[1] call setpos('.', pos) return pair endif return '' endfunction function! AutoPairsInit() let b:autopairs_loaded = 1 if !exists('b:autopairs_enabled') let b:autopairs_enabled = 1 end let b:AutoPairsClosedPairs = {} if !exists('b:AutoPairs') let b:AutoPairs = g:AutoPairs end if !exists('b:AutoPairsMoveCharacter') let b:AutoPairsMoveCharacter = g:AutoPairsMoveCharacter end " buffer level map pairs keys for [open, close] in items(b:AutoPairs) call AutoPairsMap(open) if open != close call AutoPairsMap(close) end let b:AutoPairsClosedPairs[close] = open endfor for key in split(b:AutoPairsMoveCharacter, '\s*') let escaped_key = substitute(key, "'", "''", 'g') execute 'inoremap =AutoPairsMoveCharacter('".escaped_key."')" endfor " Still use level mapping for if g:AutoPairsMapBS " Use instead of for issue #14 sometimes press BS output strange words execute 'inoremap =AutoPairsDelete()' end if g:AutoPairsMapCh execute 'inoremap =AutoPairsDelete()' endif if g:AutoPairsMapSpace " Try to respect abbreviations on a let do_abbrev = "" if v:version == 703 && has("patch489") || v:version > 703 let do_abbrev = "" endif execute 'inoremap '.do_abbrev.'=AutoPairsSpace()' end if g:AutoPairsShortcutFastWrap != '' execute 'inoremap '.g:AutoPairsShortcutFastWrap.' =AutoPairsFastWrap()' end if g:AutoPairsShortcutBackInsert != '' execute 'inoremap '.g:AutoPairsShortcutBackInsert.' =AutoPairsBackInsert()' end if g:AutoPairsShortcutToggle != '' " use to ensure showing the status when toggle execute 'inoremap '.g:AutoPairsShortcutToggle.' AutoPairsToggle()' execute 'noremap '.g:AutoPairsShortcutToggle.' :call AutoPairsToggle()' end if g:AutoPairsShortcutJump != '' execute 'inoremap ' . g:AutoPairsShortcutJump. ' :call AutoPairsJump()a' execute 'noremap ' . g:AutoPairsShortcutJump. ' :call AutoPairsJump()' end endfunction function! s:ExpandMap(map) let map = a:map let map = substitute(map, '\(\w\+\)', '\=maparg(submatch(1), "i")', 'g') return map endfunction function! 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 AutoPairsReturn " when use AutoPairsReturn will cause isn't expanded " " supertab doesn't support AutoPairsReturn " when use AutoPairsReturn will cause Duplicated " " 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 info " then auto-pairs could remap in any case. let info = maparg('', 'i', 0, 1) if empty(info) let old_cr = '' let is_expr = 0 else let old_cr = info['rhs'] let old_cr = s:ExpandMap(old_cr) let old_cr = substitute(old_cr, '', '' . info['sid'] . '_', 'g') let is_expr = info['expr'] let wrapper_name = 'AutoPairsOldCRWrapper73' endif else " VIM version less than 7.3 " the mapping's info is lost, so guess it is expr or not, it's " not accurate. let old_cr = maparg('', 'i') if old_cr == '' let old_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' " The old_cr start with " it must be in expr mode let is_expr = is_expr || old_cr =~ '\v^"' let wrapper_name = 'AutoPairsOldCRWrapper' end end if old_cr !~ 'AutoPairsReturn' if is_expr " remap to `name` to avoid mix expr and non-expr mode execute 'inoremap