auto-pairs/plugin/auto-pairs.vim
2019-01-15 04:21:48 +08:00

505 lines
13 KiB
VimL

" Insert or delete brackets, parens, quotes in pairs.
" Maintainer: JiangMiao <jiangfriend@gmail.com>
" Contributor: camthompson
" Last Change: 2019-01-15
" Version: 1.3.4
" 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: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>"
" Will auto generated {']' => '[', ..., '}' => '{'}in initialize.
let g:AutoPairsClosedPairs = {}
" 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)
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]
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)
for [open, close] in items(a:pairs)
let r[open] = close
endfor
if a:0 > 1
for open in a:1
unlet r[open]
endfor
end
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] = s:getline()
" Ignore auto close if prev character is \
if before[-1:-1] == '\'
return a:key
end
" check close pairs
for [open, close] in b:AutoPairsList
if close[0] == a:key
let m = matchlist(after, '\v^\s*(\V'.close.'\v)')
if len(m) > 0
" skip close pair
call search(m[1], 'We')
return "\<Right>"
end
end
endfor
" check open pairs
let text=before.a:key
for [open, close] in b:AutoPairsList
let m = matchstr(text, '\V'.open.'\v$')
if m != ''
" process the open pair
" remove inserted pair
" if the pairs include < > and <!-- -->
" when <!-- is detected the inserted pair < > should be clean up
let bs = repeat("\<BS>", s:ulen(m)-1)
if bs != ''
call feedkeys(bs)
end
call feedkeys(m.close.s:left(close), "n")
return ""
" return m.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[\}\]\)]'
let n = stridx(after, a:key)
if n != -1
return repeat(s:Right, n+1)
end
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] = s:getline()
for [open, close] in b:AutoPairsList
let b = matchstr(before, '\V'.open.'\v\s*$')
let a = matchstr(after, '^\s*\V'.close)
if b != '' && a != ''
return repeat("\<BS>", s:ulen(b)).repeat("\<DELETE>", s:ulen(a))
end
endfor
return "\<BS>"
endf
" Fast wrap the word in brackets
func! AutoPairsFastWrap()
normal! x
let [before, after] = s:getline()
if after[0] =~ '\v[\{\[\(]'
normal! %
normal! p
else
normal! e
normal! p
end
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()
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 ''
endf
func! AutoPairsReturn()
if b:autopairs_enabled == 0
return ''
end
let before = getline(line('.')-1)
let after = getline('.')
let cmd = ''
for [open, close] in b:AutoPairsList
if before =~ '\V'.open.'\v\s*$' && after =~ '^\s*\V'.close
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] = s:getline()
for [open, close] in b:AutoPairsList
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
let b:AutoPairsClosedPairs = {}
if !exists('b:AutoPairs')
let b:AutoPairs = g:AutoPairs
end
" sort pairs by length, longer pair should have higher priority
let b:AutoPairsList = sort(items(b:AutoPairs), "s:sortByLength")
if !exists('b:AutoPairsMoveCharacter')
let b:AutoPairsMoveCharacter = g:AutoPairsMoveCharacter
end
" buffer level map pairs keys
for [open, close] in items(b:AutoPairs)
let open = open[len(open)-1]
let close = close[0]
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 <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()