123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- " 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()
|