You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
673 lines
17 KiB
673 lines
17 KiB
" 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()
|
|
|