auto-pairs/plugin/auto-pairs.vim
2012-05-14 15:22:43 +08:00

354 lines
9.0 KiB
VimL

" Insert or delete brackets, parens, quotes in pairs.
" Maintainer: JiangMiao <jiangfriend@gmail.com>
" Contributor: camthompson
" Last Change: 2012-05-14
" Version: 1.2.0
" Homepage: http://www.vim.org/scripts/script.php?script_id=3599
" Repository: https://github.com/jiangmiao/auto-pairs
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
let g:AutoExtraPairs = copy(g:AutoPairs)
let g:AutoExtraPairs['<'] = '>'
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 = '<M-p>'
end
if !exists('g:AutoPairsShortcutFastWrap')
let g:AutoPairsShortcutFastWrap = '<M-e>'
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 = 1
endif
" Work with Fly Mode, insert pair where jumped
if !exists('g:AutoPairsShortcutBackInsert')
let g:AutoPairsShortcutBackInsert = '<M-b>'
endif
" Will auto generated {']' => '[', ..., '}' => '{'}in initialize.
let g:AutoPairsClosedPairs = {}
function! AutoPairsInsert(key)
if !b:autopairs_enabled
return a:key
end
let line = getline('.')
let prev_char = line[col('.')-2]
let current_char = line[col('.')-1]
let next_char = line[col('.')]
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(g:AutoPairs, a:key)
" Skip the character if next character is space
if current_char == ' ' && next_char == a:key
return "\<Right>\<Right>"
end
" Skip the character if closed pair is next character
if current_char == ''
let next_lineno = line('.')+1
let next_line = getline(nextnonblank(next_lineno))
let next_char = matchstr(next_line, '\s*\zs.')
if next_char == a:key
return "\<ESC>e^a"
endif
endif
" Skip the character if current character is the same as input
if current_char == a:key
return "\<Right>"
end
" Fly Mode, and the key is closed-pairs, search closed-pair and jump
if g:AutoPairsFlyMode && has_key(g:AutoPairsClosedPairs, a:key)
let b:autopairs_saved_pair = [a:key, getpos('.')]
" Use 's' flag to overwritee '' or not is a question
if(search(a:key, 'W'))
return "\<Right>"
end
endif
" Input directly if the key is not an open key
return a:key
end
let open = a:key
let close = g:AutoPairs[open]
if current_char == close && open == close
return "\<Right>"
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 a:key
end
end
return open.close."\<Left>"
endfunction
function! AutoPairsDelete()
let line = getline('.')
let current_char = line[col('.')-1]
let prev_char = line[col('.')-2]
let pprev_char = line[col('.')-3]
if pprev_char == '\'
return "\<BS>"
end
" Delete last two spaces in parens, work with MapSpace
if has_key(g:AutoPairs, pprev_char) && prev_char == ' ' && current_char == ' '
return "\<BS>\<DEL>"
endif
if has_key(g:AutoPairs, prev_char)
let close = g:AutoPairs[prev_char]
if match(line,'^\s*'.close, col('.')-1) != -1
let space = matchstr(line, '^\s*', col('.')-1)
return "\<BS>". repeat("\<DEL>", len(space)+1)
else
let nline = getline(line('.')+1)
if nline =~ '^\s*'.close
let space = matchstr(nline, '^\s*')
return "\<BS>\<DEL>". repeat("\<DEL>", len(space)+1)
end
end
end
return "\<BS>"
endfunction
function! AutoPairsJump()
call search('["\]'')}]','W')
endfunction
" Fast wrap the word in brackets
function! AutoPairsFastWrap()
let line = getline('.')
let current_char = line[col('.')-1]
let next_char = line[col('.')]
" Ignore EOL
if col('.') == col('$')
return ''
end
normal! x
if next_char =~ '\s'
call search('\S', 'W')
let next_char = getline('.')[col('.')-1]
end
if has_key(g:AutoExtraPairs, next_char)
let close = g:AutoExtraPairs[next_char]
call search(close, 'W')
return "\<RIGHT>".current_char."\<LEFT>"
else
if next_char =~ '\w'
execute "normal! he"
end
execute "normal! a".current_char
return ""
end
endfunction
function! AutoPairsMap(key)
let escaped_key = substitute(a:key, "'", "''", 'g')
execute 'inoremap <buffer> <silent> '.a:key." <C-R>=AutoPairsInsert('".escaped_key."')<CR>"
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()
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(g:AutoPairs, prev_char) && g:AutoPairs[prev_char] == cur_char
if g:AutoPairsCenterLine && winline() * 1.5 >= winheight(0)
let cmd = " \<C-O>zz\<ESC>cl"
end
" conflict with javascript and coffee
" javascript need indent new line
" coffeescript forbid indent new line
if &filetype == 'coffeescript' || &filetype == 'coffee'
return "\<ESC>k==o".cmd
else
return "\<ESC>=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 = "\<SPACE>\<LEFT>"
endif
return "\<SPACE>".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
" buffer level map pairs keys
for [open, close] in items(g:AutoPairs)
call AutoPairsMap(open)
if open != close
call AutoPairsMap(close)
end
let g:AutoPairsClosedPairs[close] = open
endfor
" Still use <buffer> level mapping for <BS> <SPACE>
if g:AutoPairsMapBS
execute 'inoremap <buffer> <silent> <expr> <BS> AutoPairsDelete()'
end
if g:AutoPairsMapSpace
execute 'inoremap <buffer> <silent> <expr> <SPACE> AutoPairsSpace()'
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
endfunction
function! AutoPairsForceInit()
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 didn't support <Plug>AutoPairsReturn
" when use <Plug>AutoPairsReturn will cause <Plug> wasn't expanded
"
" supertab didn't support <SID>AutoPairsReturn
" when use <SID>AutoPairsReturn will cause Duplicated <CR>
"
" and when load after vim-endwise will cause endwise duplicated closed. so always
" load AutoPairs at last
" Buffer level keys mapping
" comptible with other plugin
if g:AutoPairsMapCR
let old_cr = maparg('<CR>', 'i')
if old_cr == ''
let old_cr = '<CR>'
endif
if old_cr !~ 'AutoPairsReturn'
execute 'imap <buffer> <CR> '.old_cr.'<SID>AutoPairsReturn'
end
endif
call AutoPairsInit()
endfunction
" Always silent the command
inoremap <silent> <SID>AutoPairsReturn <C-R>=AutoPairsReturn()<CR>
imap <script> <Plug>AutoPairsReturn <SID>AutoPairsReturn
au BufEnter * :call AutoPairsForceInit()