632 lines
16 KiB
VimL
632 lines
16 KiB
VimL
" Insert or delete brackets, parens, quotes in pairs.
|
|
" Maintainer: JiangMiao <jiangfriend@gmail.com>
|
|
" Contributor: camthompson
|
|
" Last Change: 2019-01-15
|
|
" 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
|
|
|
|
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 = copy(g:AutoPairs)
|
|
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 close pairs
|
|
for [open, close, opt] in b:AutoPairsList
|
|
if close == ''
|
|
continue
|
|
end
|
|
if a:key == g:AutoPairsWildClosedPair || opt['mapclose'] && close[0] == a:key
|
|
" the close pair is in the same line
|
|
let m = matchstr(after, '^\v\s*\zs\V'.close)
|
|
if m != ''
|
|
if b:autopairs_return_pos == line('.') && getline('.') =~ '\v^\s*$'
|
|
normal! ddk$
|
|
elseif col('.') > 1
|
|
normal! h
|
|
elseif line('.') > 1
|
|
normal! k$
|
|
else
|
|
return s:right(m)
|
|
break
|
|
end
|
|
call search(m, 'We')
|
|
return "\<Right>"
|
|
break
|
|
end
|
|
end
|
|
endfor
|
|
|
|
" check open pairs
|
|
for [open, close, opt] in b:AutoPairsList
|
|
let ms = s:matchend(before.a:key, open)
|
|
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]
|
|
let bs = ''
|
|
let del = ''
|
|
while len(before) > len(target) && target != before
|
|
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
|
|
|
|
" 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
|
|
return "\<SPACE>\<SPACE>".s:Left
|
|
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 = g:AutoPairs
|
|
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}
|
|
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 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')
|
|
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()
|