From b45d4066ed3fe41ebb7a79bcf3892160727b6b98 Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Thu, 6 Aug 2015 09:52:45 -0700 Subject: [vim] add autopair --- vim/plugin/auto-pairs.vim | 537 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 vim/plugin/auto-pairs.vim (limited to 'vim/plugin') diff --git a/vim/plugin/auto-pairs.vim b/vim/plugin/auto-pairs.vim new file mode 100644 index 0000000..0ffaf9a --- /dev/null +++ b/vim/plugin/auto-pairs.vim @@ -0,0 +1,537 @@ +" Insert or delete brackets, parens, quotes in pairs. +" Maintainer: JiangMiao +" Contributor: camthompson +" Last Change: 2013-07-13 +" Version: 1.3.2 +" 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 + +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: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 + + +" 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 "\" + end + + if !g:AutoPairsFlyMode + " Skip the character if next character is space + if current_char == ' ' && next_char == a:key + return "\\" + 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) + if search(a:key, 'W') + 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 "\" + 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("\", 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."\" +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 "\".inputed_close_pair."\" + else + normal he + return "\".current_char."\" + 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! 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 + " Use \ instead of \cl will cause the placeholder deleted + " incorrect. because zz won't leave Normal mode. + " Use \ is a bit wierd. the character before cursor need to be deleted. + let cmd = " \zz\cl" + end + + " If equalprg has been set, then avoid call = + " https://github.com/jiangmiao/auto-pairs/issues/24 + if &equalprg != '' + return "\O".cmd + endif + + " conflict with javascript and coffee + " javascript need indent new line + " coffeescript forbid indent new line + if &filetype == 'coffeescript' || &filetype == 'coffee' + return "\k==o".cmd + else + return "\=ko".cmd + 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 = "\\" + 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 + let b:autopairs_enabled = 1 + let b:AutoPairsClosedPairs = {} + + if !exists('b:AutoPairs') + let b:AutoPairs = g:AutoPairs + 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 + + " Still use level mapping for + if g:AutoPairsMapBS + " Use instead of for issue #14 sometimes press BS output strange words + execute 'inoremap =AutoPairsDelete()' + execute 'inoremap =AutoPairsDelete()' + end + + 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