Support multibytes pairs
This commit is contained in:
parent
4ec359716a
commit
738d1f18a8
@ -16,10 +16,6 @@ if !exists('g:AutoPairs')
|
||||
let g:AutoPairs = {'(':')', '[':']', '{':'}',"'":"'",'"':'"', '`':'`'}
|
||||
end
|
||||
|
||||
if !exists('g:AutoPairsParens')
|
||||
let g:AutoPairsParens = {'(':')', '[':']', '{':'}'}
|
||||
end
|
||||
|
||||
if !exists('g:AutoPairsMapBS')
|
||||
let g:AutoPairsMapBS = 1
|
||||
end
|
||||
@ -95,301 +91,186 @@ let s:Right = s:Go."\<RIGHT>"
|
||||
let g:AutoPairsClosedPairs = {}
|
||||
|
||||
|
||||
function! AutoPairsInsert(key)
|
||||
if !b:autopairs_enabled
|
||||
return a:key
|
||||
end
|
||||
" 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 next_chars = split(after, '\zs')
|
||||
let current_char = get(next_chars, 0, '')
|
||||
let next_char = get(next_chars, 1, '')
|
||||
let prev_chars = split(before, '\zs')
|
||||
let prev_char = get(prev_chars, -1, '')
|
||||
|
||||
let eol = 0
|
||||
if col('$') - col('.') <= 1
|
||||
let eol = 1
|
||||
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
|
||||
|
||||
" Ignore auto close if prev character is \
|
||||
if prev_char == '\'
|
||||
" 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
|
||||
|
||||
" The key is difference open-pair, then it means only for ) ] } by default
|
||||
if !has_key(b:AutoPairs, a:key)
|
||||
let b:autopairs_saved_pair = [a:key, getpos('.')]
|
||||
|
||||
" Skip the character if current character is the same as input
|
||||
if current_char == a:key
|
||||
return s:Right
|
||||
let [before, after] = s:getline()
|
||||
|
||||
" Ignore auto close if prev character is \
|
||||
if before[-1:-1] == '\'
|
||||
return a:key
|
||||
end
|
||||
|
||||
if !g:AutoPairsFlyMode
|
||||
" Skip the character if next character is space
|
||||
if current_char == ' ' && next_char == a:key
|
||||
return s:Right.s:Right
|
||||
" 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
|
||||
|
||||
" Skip the character if closed pair is next character
|
||||
if current_char == ''
|
||||
if g:AutoPairsMultilineClose
|
||||
let next_lineno = line('.')+1
|
||||
let next_line = getline(nextnonblank(next_lineno))
|
||||
let next_char = matchstr(next_line, '\s*\zs.')
|
||||
else
|
||||
let next_char = matchstr(line, '\s*\zs.')
|
||||
" 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
|
||||
if next_char == a:key
|
||||
return "\<ESC>e^a"
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
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 && has_key(b:AutoPairsClosedPairs, a:key)
|
||||
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, 'W')
|
||||
" force break the '.' when jump to different line
|
||||
if search(a:key, 'We')
|
||||
return "\<Right>"
|
||||
endif
|
||||
endif
|
||||
|
||||
" Insert directly if the key is not an open key
|
||||
return a:key
|
||||
end
|
||||
endf
|
||||
|
||||
let open = a:key
|
||||
let close = b:AutoPairs[open]
|
||||
|
||||
if current_char == close && open == close
|
||||
return s: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 repeat(a:key, 4) . repeat(s:Left, 3)
|
||||
end
|
||||
end
|
||||
|
||||
let quotes_num = 0
|
||||
" Ignore comment line for vim file
|
||||
if &filetype == 'vim' && a:key == '"'
|
||||
if before =~ '^\s*$'
|
||||
return a:key
|
||||
end
|
||||
if before =~ '^\s*"'
|
||||
let quotes_num = -1
|
||||
end
|
||||
end
|
||||
|
||||
" Keep quote number is odd.
|
||||
" Because quotes should be matched in the same line in most of situation
|
||||
if g:AutoPairsSmartQuotes && open == close
|
||||
" Remove \\ \" \'
|
||||
let cleaned_line = substitute(line, '\v(\\.)', '', 'g')
|
||||
let n = quotes_num
|
||||
let pos = 0
|
||||
while 1
|
||||
let pos = stridx(cleaned_line, open, pos)
|
||||
if pos == -1
|
||||
break
|
||||
end
|
||||
let n = n + 1
|
||||
let pos = pos + 1
|
||||
endwhile
|
||||
if n % 2 == 1
|
||||
return a:key
|
||||
endif
|
||||
endif
|
||||
|
||||
return open.close.s:Left
|
||||
endfunction
|
||||
|
||||
function! AutoPairsDelete()
|
||||
func! AutoPairsDelete()
|
||||
if !b:autopairs_enabled
|
||||
return "\<BS>"
|
||||
end
|
||||
|
||||
let line = getline('.')
|
||||
let pos = col('.') - 1
|
||||
let current_char = get(split(strpart(line, pos), '\zs'), 0, '')
|
||||
let prev_chars = split(strpart(line, 0, pos), '\zs')
|
||||
let prev_char = get(prev_chars, -1, '')
|
||||
let pprev_char = get(prev_chars, -2, '')
|
||||
|
||||
if pprev_char == '\'
|
||||
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>"
|
||||
end
|
||||
|
||||
" Delete last two spaces in parens, work with MapSpace
|
||||
if has_key(b:AutoPairs, pprev_char) && prev_char == ' ' && current_char == ' '
|
||||
return "\<BS>\<DEL>"
|
||||
endif
|
||||
|
||||
" Delete Repeated Pair eg: '''|''' [[|]] {{|}}
|
||||
if has_key(b:AutoPairs, prev_char)
|
||||
let times = 0
|
||||
let p = -1
|
||||
while get(prev_chars, p, '') == prev_char
|
||||
let p = p - 1
|
||||
let times = times + 1
|
||||
endwhile
|
||||
|
||||
let close = b:AutoPairs[prev_char]
|
||||
let left = repeat(prev_char, times)
|
||||
let right = repeat(close, times)
|
||||
|
||||
let before = strpart(line, pos-times, times)
|
||||
let after = strpart(line, pos, times)
|
||||
if left == before && right == after
|
||||
return repeat("\<BS>\<DEL>", times)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if has_key(b:AutoPairs, prev_char)
|
||||
let close = b:AutoPairs[prev_char]
|
||||
if match(line,'^\s*'.close, col('.')-1) != -1
|
||||
" Delete (|___)
|
||||
let space = matchstr(line, '^\s*', col('.')-1)
|
||||
return "\<BS>". repeat("\<DEL>", len(space)+1)
|
||||
elseif match(line, '^\s*$', col('.')-1) != -1
|
||||
" Delete (|__\n___)
|
||||
let nline = getline(line('.')+1)
|
||||
if nline =~ '^\s*'.close
|
||||
if &filetype == 'vim' && prev_char == '"'
|
||||
" Keep next line's comment
|
||||
return "\<BS>"
|
||||
end
|
||||
|
||||
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
|
||||
" string_chunk cannot use standalone
|
||||
let s:string_chunk = '\v%(\\\_.|[^\1]|[\r\n]){-}'
|
||||
let s:ss_pattern = '\v''' . s:string_chunk . ''''
|
||||
let s:ds_pattern = '\v"' . s:string_chunk . '"'
|
||||
|
||||
func! s:RegexpQuote(str)
|
||||
return substitute(a:str, '\v[\[\{\(\<\>\)\}\]]', '\\&', 'g')
|
||||
endf
|
||||
|
||||
func! s:RegexpQuoteInSquare(str)
|
||||
return substitute(a:str, '\v[\[\]]', '\\&', 'g')
|
||||
endf
|
||||
|
||||
" Search next open or close pair
|
||||
func! s:FormatChunk(open, close)
|
||||
let open = s:RegexpQuote(a:open)
|
||||
let close = s:RegexpQuote(a:close)
|
||||
let open2 = s:RegexpQuoteInSquare(a:open)
|
||||
let close2 = s:RegexpQuoteInSquare(a:close)
|
||||
if open == close
|
||||
return '\v'.open.s:string_chunk.close
|
||||
else
|
||||
return '\v%(' . s:ss_pattern . '|' . s:ds_pattern . '|' . '[^'.open2.close2.']|[\r\n]' . '){-}(['.open2.close2.'])'
|
||||
end
|
||||
endf
|
||||
|
||||
" Fast wrap the word in brackets
|
||||
function! AutoPairsFastWrap()
|
||||
let line = getline('.')
|
||||
let current_char = line[col('.')-1]
|
||||
let next_char = line[col('.')]
|
||||
let open_pair_pattern = '\v[({\[''"]'
|
||||
let at_end = col('.') >= col('$') - 1
|
||||
func! AutoPairsFastWrap()
|
||||
normal! x
|
||||
" Skip blank
|
||||
if next_char =~ '\v\s' || at_end
|
||||
call search('\v\S', 'W')
|
||||
let line = getline('.')
|
||||
let next_char = line[col('.')-1]
|
||||
end
|
||||
|
||||
if has_key(b:AutoPairs, next_char)
|
||||
let followed_open_pair = next_char
|
||||
let inputed_close_pair = current_char
|
||||
let followed_close_pair = b:AutoPairs[next_char]
|
||||
if followed_close_pair != followed_open_pair
|
||||
" TODO replace system searchpair to skip string and nested pair.
|
||||
" eg: (|){"hello}world"} will transform to ({"hello})world"}
|
||||
call searchpair('\V'.followed_open_pair, '', '\V'.followed_close_pair, 'W')
|
||||
let [before, after] = s:getline()
|
||||
if after[0] =~ '\v[\{\[\(]'
|
||||
normal! %
|
||||
normal! p
|
||||
else
|
||||
call search(s:FormatChunk(followed_open_pair, followed_close_pair), 'We')
|
||||
normal! e
|
||||
normal! p
|
||||
end
|
||||
return s:Right.inputed_close_pair.s:Left
|
||||
else
|
||||
normal! he
|
||||
return s:Right.current_char.s:Left
|
||||
end
|
||||
endfunction
|
||||
return ""
|
||||
endf
|
||||
|
||||
function! 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>"
|
||||
func! AutoPairsJump()
|
||||
call search('["\]'')}]','W')
|
||||
endf
|
||||
|
||||
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! AutoPairsMoveCharacter(key)
|
||||
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>"
|
||||
endfunction
|
||||
endf
|
||||
|
||||
function! AutoPairsReturn()
|
||||
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 line = getline('.')
|
||||
let pline = getline(line('.')-1)
|
||||
let prev_char = pline[strlen(pline)-1]
|
||||
let before = getline(line('.')-1)
|
||||
let after = getline('.')
|
||||
let cmd = ''
|
||||
let cur_char = line[col('.')-1]
|
||||
if has_key(b:AutoPairs, prev_char) && b:AutoPairs[prev_char] == cur_char
|
||||
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"
|
||||
@ -410,31 +291,53 @@ function! AutoPairsReturn()
|
||||
return "\<ESC>".cmd."=ko"
|
||||
endif
|
||||
end
|
||||
endfor
|
||||
return ''
|
||||
endfunction
|
||||
endf
|
||||
|
||||
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>".s:Left
|
||||
endif
|
||||
return "\<SPACE>".cmd
|
||||
endfunction
|
||||
func! AutoPairsSpace()
|
||||
if !b:autopairs_enabled
|
||||
return "\<SPACE>"
|
||||
end
|
||||
|
||||
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
|
||||
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 ''
|
||||
endfunction
|
||||
endf
|
||||
|
||||
function! AutoPairsInit()
|
||||
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
|
||||
@ -444,6 +347,9 @@ function! AutoPairsInit()
|
||||
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
|
||||
@ -451,6 +357,8 @@ function! AutoPairsInit()
|
||||
|
||||
" 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)
|
||||
@ -515,15 +423,15 @@ function! AutoPairsInit()
|
||||
end
|
||||
end
|
||||
|
||||
endfunction
|
||||
endf
|
||||
|
||||
function! s:ExpandMap(map)
|
||||
func! s:ExpandMap(map)
|
||||
let map = a:map
|
||||
let map = substitute(map, '\(<Plug>\w\+\)', '\=maparg(submatch(1), "i")', 'g')
|
||||
return map
|
||||
endfunction
|
||||
endf
|
||||
|
||||
function! AutoPairsTryInit()
|
||||
func! AutoPairsTryInit()
|
||||
if exists('b:autopairs_loaded')
|
||||
return
|
||||
end
|
||||
@ -586,7 +494,7 @@ function! AutoPairsTryInit()
|
||||
end
|
||||
endif
|
||||
call AutoPairsInit()
|
||||
endfunction
|
||||
endf
|
||||
|
||||
" Always silent the command
|
||||
inoremap <silent> <SID>AutoPairsReturn <C-R>=AutoPairsReturn()<CR>
|
||||
|
Loading…
Reference in New Issue
Block a user