mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-24 09:30:04 +08:00
979 lines
27 KiB
VimL
979 lines
27 KiB
VimL
" vim match-up - even better matching
|
|
"
|
|
" Maintainer: Andy Massimino
|
|
" Email: a@normed.space
|
|
"
|
|
|
|
scriptencoding utf-8
|
|
|
|
let s:save_cpo = &cpo
|
|
set cpo&vim
|
|
|
|
function! matchup#matchparen#init_module() " {{{1
|
|
if !g:matchup_matchparen_enabled | return | endif
|
|
|
|
call matchup#matchparen#enable()
|
|
|
|
nnoremap <silent> <plug>(matchup-hi-surround)
|
|
\ :<c-u>call matchup#matchparen#highlight_surrounding()<cr>
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! matchup#matchparen#enable() " {{{1
|
|
let g:matchup_matchparen_enabled = 1
|
|
|
|
if g:matchup_matchparen_deferred
|
|
\ && (!has('timers') || !exists('*timer_pause')
|
|
\ || has('nvim') && !has('nvim-0.2.1'))
|
|
let g:matchup_matchparen_deferred = 0
|
|
echohl WarningMsg
|
|
echom "match-up's deferred highlighting feature is "
|
|
\ . 'not supported in your vim version'
|
|
echohl None
|
|
endif
|
|
|
|
augroup matchup_matchparen
|
|
autocmd!
|
|
autocmd CursorMoved,CursorMovedI *
|
|
\ call s:matchparen.highlight_deferred()
|
|
autocmd WinEnter * call s:matchparen.highlight(1)
|
|
autocmd TextChanged,TextChangedI *
|
|
\ call s:matchparen.highlight_deferred()
|
|
if has('patch-8.0.1494')
|
|
autocmd TextChangedP * call s:matchparen.highlight_deferred()
|
|
endif
|
|
autocmd BufReadPost * call s:matchparen.transmute_reset()
|
|
autocmd WinLeave,BufLeave * call s:matchparen.clear()
|
|
autocmd InsertEnter,InsertChange * call s:matchparen.highlight(1, 1)
|
|
autocmd InsertLeave * call s:matchparen.highlight(1)
|
|
augroup END
|
|
|
|
if has('vim_starting')
|
|
" prevent this from autoloading during timer callback at startup
|
|
if g:matchup_matchparen_deferred
|
|
call matchup#pos#val(0,0)
|
|
endif
|
|
|
|
" prevent loading the delim module at vim startup
|
|
let w:last_changedtick = 2
|
|
let w:last_cursor = [0,1,1,0,1]
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:pi_paren_sid() " {{{1
|
|
if s:pi_paren_sid >= 0
|
|
return s:pi_paren_sid
|
|
endif
|
|
|
|
let s:pi_paren_sid = 0
|
|
if get(g:, 'loaded_matchparen')
|
|
let l:pat = '\%#=1\V'.expand('$VIM').'\m.\+matchparen\.vim$'
|
|
if v:version >= 800
|
|
" execute() was added in 7.4.2008
|
|
" :filter was introduced in 7.4.2244 but I have not tested it there
|
|
let l:lines = split(execute("filter '".l:pat."' scriptnames"), '\n')
|
|
else
|
|
let l:lines = matchup#util#command('scriptnames')
|
|
call filter(l:lines, 'v:val =~# l:pat')
|
|
endif
|
|
let s:pi_paren_sid = matchstr(get(l:lines, 0), '\d\+\ze: ')
|
|
if !exists('*<SNR>'.s:pi_paren_sid.'_Highlight_Matching_Pair')
|
|
let s:pi_paren_sid = 0
|
|
endif
|
|
endif
|
|
if s:pi_paren_sid
|
|
let s:pi_paren_fcn = function('<SNR>'.s:pi_paren_sid
|
|
\ .'_Highlight_Matching_Pair')
|
|
endif
|
|
return s:pi_paren_sid
|
|
endfunction
|
|
|
|
let s:pi_paren_sid = -1
|
|
|
|
" }}}1
|
|
|
|
function! matchup#matchparen#disable() " {{{1
|
|
let g:matchup_matchparen_enabled = 0
|
|
call s:matchparen.clear()
|
|
silent! autocmd! matchup_matchparen
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! matchup#matchparen#toggle(...) " {{{1
|
|
let g:matchup_matchparen_enabled = a:0 > 0
|
|
\ ? a:1
|
|
\ : !g:matchup_matchparen_enabled
|
|
call matchup#matchparen#reload()
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! matchup#matchparen#reload() " {{{1
|
|
if g:matchup_matchparen_enabled
|
|
call matchup#matchparen#enable()
|
|
call s:matchparen.highlight(1)
|
|
else
|
|
call matchup#matchparen#disable()
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! matchup#matchparen#update() " {{{1
|
|
call s:matchparen.highlight(1)
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
let s:matchparen = {}
|
|
|
|
function! s:matchparen.clear() abort dict " {{{1
|
|
if exists('w:matchup_match_id_list')
|
|
for l:id in w:matchup_match_id_list
|
|
silent! call matchdelete(l:id)
|
|
endfor
|
|
unlet! w:matchup_match_id_list
|
|
endif
|
|
|
|
if exists('t:match_popup') && (exists('*win_gettype')
|
|
\ ? win_gettype() !=# 'popup' : &buftype !=# 'terminal')
|
|
call popup_hide(t:match_popup)
|
|
elseif has('nvim')
|
|
call s:close_floating_win()
|
|
endif
|
|
|
|
if exists('w:matchup_oldstatus')
|
|
let &l:statusline = w:matchup_oldstatus
|
|
unlet w:matchup_oldstatus
|
|
if exists('#User#MatchupOffscreenLeave')
|
|
doautocmd <nomodeline> User MatchupOffscreenLeave
|
|
endif
|
|
endif
|
|
if exists('w:matchup_statusline')
|
|
unlet w:matchup_statusline
|
|
endif
|
|
|
|
let w:matchup_need_clear = 0
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:timer_callback(win_id, timer_id) abort " {{{1
|
|
if a:win_id != win_getid()
|
|
call timer_pause(a:timer_id, 1)
|
|
return
|
|
endif
|
|
|
|
" if we timed out, do a highlight and pause the timer
|
|
let l:elapsed = 1000*s:reltimefloat(reltime(w:matchup_pulse_time))
|
|
if l:elapsed >= s:show_delay
|
|
call timer_pause(a:timer_id, 1)
|
|
if exists('#TextYankPost') && !has('patch-8.1.0192')
|
|
" workaround crash with autocmd trigger during regex match (#3175)
|
|
let l:save_ei = &eventignore
|
|
try
|
|
set eventignore+=TextYankPost
|
|
call s:matchparen.highlight()
|
|
finally
|
|
let &eventignore = l:save_ei
|
|
endtry
|
|
else
|
|
call s:matchparen.highlight()
|
|
endif
|
|
elseif w:matchup_need_clear && exists('w:matchup_hi_time')
|
|
" if highlighting becomes too stale, clear it
|
|
let l:elapsed = 1000*s:reltimefloat(reltime(w:matchup_hi_time))
|
|
if l:elapsed >= s:hide_delay
|
|
call s:matchparen.clear()
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:matchparen.fade(level, pos, token) abort dict " {{{1
|
|
""
|
|
" fade feature: remove highlights after a certain time
|
|
" {level}
|
|
" = 0: prepare for possible loss of cursor support
|
|
" = 1: new highlights are coming (cancel prior fade)
|
|
" = 2: end of new highlights
|
|
" {pos} [lnum, column] of current match
|
|
" {token} in/out saves state between calls
|
|
"
|
|
" returns 1 if highlighting should be canceled
|
|
|
|
if !g:matchup_matchparen_deferred || !exists('w:matchup_fade_timer')
|
|
if a:level <= 0
|
|
call s:matchparen.clear()
|
|
endif
|
|
return 0
|
|
endif
|
|
|
|
" jumping between windows
|
|
if a:level == 0 && win_getid() != get(s:, 'save_win')
|
|
call timer_pause(w:matchup_fade_timer, 1)
|
|
if exists('w:matchup_fade_pos')
|
|
unlet w:matchup_fade_pos
|
|
endif
|
|
call s:matchparen.clear()
|
|
let s:save_win = win_getid()
|
|
endif
|
|
|
|
" highlighting might be stale
|
|
if a:level == 0
|
|
if exists('w:matchup_fade_pos')
|
|
let a:token.save_pos = w:matchup_fade_pos
|
|
unlet w:matchup_fade_pos
|
|
endif
|
|
if !w:matchup_need_clear
|
|
call timer_pause(w:matchup_fade_timer, 1)
|
|
endif
|
|
return 0
|
|
endif
|
|
|
|
" prepare for new highlighting
|
|
if a:level == 1
|
|
" if token has no save_pos, cursor was previously off of a match
|
|
if !has_key(a:token, 'save_pos') || a:pos != a:token.save_pos
|
|
" clear immediately
|
|
call timer_pause(w:matchup_fade_timer, 1)
|
|
call s:matchparen.clear()
|
|
return 0
|
|
endif
|
|
let w:matchup_fade_pos = a:token.save_pos
|
|
return 1
|
|
endif
|
|
|
|
" new highlighting is active
|
|
if a:level == 2 && a:pos != get(w:, 'matchup_fade_pos', [])
|
|
" init fade request
|
|
let w:matchup_fade_pos = a:pos
|
|
let w:matchup_fade_start = reltime()
|
|
call timer_pause(w:matchup_fade_timer, 0)
|
|
endif
|
|
|
|
return 0
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:fade_timer_callback(win_id, timer_id) abort " {{{1
|
|
if a:win_id != win_getid()
|
|
call timer_pause(a:timer_id, 1)
|
|
return
|
|
endif
|
|
|
|
if !exists('w:matchup_fade_start') || !w:matchup_need_clear
|
|
call timer_pause(a:timer_id, 1)
|
|
return
|
|
endif
|
|
|
|
let l:elapsed = 1000*s:reltimefloat(reltime(w:matchup_fade_start))
|
|
if l:elapsed >= s:fade_time
|
|
call s:matchparen.clear()
|
|
call timer_pause(a:timer_id, 1)
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
" function! s:reltimefloat(time) {{{1
|
|
if exists('*reltimefloat')
|
|
function! s:reltimefloat(time)
|
|
return reltimefloat(a:time)
|
|
endfunction
|
|
else
|
|
function! s:reltimefloat(time)
|
|
return str2float(reltimestr(a:time))
|
|
endfunction
|
|
endif
|
|
|
|
" }}}1
|
|
|
|
function! s:matchparen.highlight_deferred() abort dict " {{{1
|
|
if !get(b:, 'matchup_matchparen_deferred',
|
|
\ g:matchup_matchparen_deferred)
|
|
return s:matchparen.highlight()
|
|
endif
|
|
|
|
if !exists('w:matchup_timer')
|
|
let s:show_delay = g:matchup_matchparen_deferred_show_delay
|
|
let s:hide_delay = g:matchup_matchparen_deferred_hide_delay
|
|
let w:matchup_timer = timer_start(s:show_delay,
|
|
\ function('s:timer_callback', [ win_getid() ]),
|
|
\ {'repeat': -1})
|
|
if !exists('w:matchup_need_clear')
|
|
let w:matchup_need_clear = 0
|
|
endif
|
|
let s:fade_time = g:matchup_matchparen_deferred_fade_time
|
|
if s:fade_time > 0
|
|
let w:matchup_fade_timer = timer_start(s:fade_time,
|
|
\ function('s:fade_timer_callback', [ win_getid() ]),
|
|
\ {'repeat': -1})
|
|
call timer_pause(w:matchup_fade_timer, 1)
|
|
endif
|
|
endif
|
|
|
|
" keep the timer alive with a heartbeat
|
|
let w:matchup_pulse_time = reltime()
|
|
|
|
" if the timer is paused, some time has passed
|
|
if timer_info(w:matchup_timer)[0].paused
|
|
" unpause the timer
|
|
call timer_pause(w:matchup_timer, 0)
|
|
|
|
" set the hi time to the pulse time
|
|
let w:matchup_hi_time = w:matchup_pulse_time
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:matchparen.highlight(...) abort dict " {{{1
|
|
if !g:matchup_matchparen_enabled | return | endif
|
|
|
|
if has('vim_starting') | return | endif
|
|
|
|
if !g:matchup_matchparen_pumvisible && pumvisible() | return | endif
|
|
|
|
if !get(b:, 'matchup_matchparen_enabled', 1)
|
|
\ && get(b:, 'matchup_matchparen_fallback', 1) && s:pi_paren_sid()
|
|
return call(s:pi_paren_fcn, [])
|
|
endif
|
|
|
|
if !get(b:, 'matchup_matchparen_enabled', 1) | return | endif
|
|
|
|
let l:force_update = a:0 >= 1 ? a:1 : 0
|
|
let l:changing_insert = a:0 >= 2 ? a:2 : 0
|
|
let l:real_mode = l:changing_insert ? v:insertmode : mode()
|
|
|
|
if !l:force_update
|
|
\ && exists('w:last_changedtick') && exists('w:last_cursor')
|
|
\ && matchup#pos#equal(w:last_cursor, matchup#pos#get_cursor())
|
|
\ && w:last_changedtick == b:changedtick
|
|
return
|
|
endif
|
|
let w:last_changedtick = b:changedtick
|
|
let w:last_cursor = matchup#pos#get_cursor()
|
|
|
|
call matchup#perf#tic('matchparen.highlight')
|
|
|
|
" request eventual clearing of stale matches
|
|
let l:token = {}
|
|
call self.fade(0, [], l:token)
|
|
|
|
let l:modes = g:matchup_matchparen_nomode
|
|
if get(g:, 'matchup_matchparen_novisual', 0) " deprecated option name
|
|
let l:modes .= "vV\<c-v>"
|
|
endif
|
|
if stridx(l:modes, l:real_mode) >= 0
|
|
return
|
|
endif
|
|
|
|
" prevent problems in visual block mode at the end of a line
|
|
if get(matchup#pos#get_cursor(), 4, 0) == 2147483647
|
|
return
|
|
endif
|
|
|
|
" don't get matches when inside a closed fold
|
|
if foldclosed(line('.')) > -1
|
|
return
|
|
endif
|
|
|
|
" give up when cursor is far into a very long line
|
|
if &synmaxcol && col('.') > &synmaxcol
|
|
return
|
|
endif
|
|
|
|
" in insert mode, cursor is treated as being one behind
|
|
let l:insertmode = l:real_mode ==# 'i'
|
|
|
|
" start the timeout period
|
|
let l:timeout = l:insertmode
|
|
\ ? get(b:, 'matchup_matchparen_insert_timeout',
|
|
\ g:matchup_matchparen_insert_timeout)
|
|
\ : get(b:, 'matchup_matchparen_timeout',
|
|
\ g:matchup_matchparen_timeout)
|
|
call matchup#perf#timeout_start(l:timeout)
|
|
|
|
let l:current = matchup#delim#get_current('all', 'both_all',
|
|
\ { 'insertmode': l:insertmode,
|
|
\ 'stopline': g:matchup_matchparen_stopline,
|
|
\ 'highlighting': 1, })
|
|
call matchup#perf#toc('matchparen.highlight', 'get_current')
|
|
if empty(l:current)
|
|
if get(b:, 'matchup_matchparen_deferred',
|
|
\ g:matchup_matchparen_deferred)
|
|
\ && get(b:, 'matchup_matchparen_hi_surround_always',
|
|
\ g:matchup_matchparen_hi_surround_always)
|
|
call s:highlight_surrounding(l:insertmode)
|
|
endif
|
|
return
|
|
endif
|
|
|
|
let l:corrlist = matchup#delim#get_matching(l:current,
|
|
\ { 'stopline': g:matchup_matchparen_stopline,
|
|
\ 'highlighting': 1, })
|
|
call matchup#perf#toc('matchparen.highlight', 'get_matching')
|
|
if empty(l:corrlist) | return | endif
|
|
|
|
if g:matchup_transmute_enabled
|
|
if !exists('w:matchup_matchparen_context')
|
|
let w:matchup_matchparen_context = {
|
|
\ 'normal': {
|
|
\ 'current': {},
|
|
\ 'corrlist': [],
|
|
\ },
|
|
\ 'prior': {},
|
|
\ 'counter': 0,
|
|
\}
|
|
endif
|
|
|
|
let w:matchup_matchparen_context.counter += 1
|
|
|
|
if !l:insertmode
|
|
let w:matchup_matchparen_context.prior
|
|
\ = copy(w:matchup_matchparen_context.normal)
|
|
|
|
let w:matchup_matchparen_context.normal.current = l:current
|
|
let w:matchup_matchparen_context.normal.corrlist = l:corrlist
|
|
endif
|
|
|
|
" if transmuted, highlight again (will reset timeout)
|
|
if matchup#transmute#tick(l:insertmode)
|
|
" no force_update here because it would screw up prior
|
|
return s:matchparen.highlight(0, l:changing_insert)
|
|
endif
|
|
endif
|
|
|
|
if !has_key(l:current, 'match_index')
|
|
\ || len(l:corrlist) <= (l:current.side ==# 'mid' ? 2 : 1)
|
|
\ && !g:matchup_matchparen_singleton
|
|
" TODO this doesn't catch every case, needs refactor
|
|
" TODO singleton doesn't work right for mids
|
|
return
|
|
endif
|
|
|
|
" prepare for (possibly) new highlights
|
|
let l:pos = [l:current.lnum, l:current.cnum]
|
|
if self.fade(1, l:pos, l:token)
|
|
return
|
|
endif
|
|
|
|
" store flag meaning highlighting is active
|
|
let w:matchup_need_clear = 1
|
|
|
|
" disable off-screen when scrolling with j/k
|
|
let l:scrolling = get(g:matchup_matchparen_offscreen, 'scrolloff', 0)
|
|
\ && winheight(0) > 2*&scrolloff
|
|
\ && (line('.') == line('w$')-&scrolloff
|
|
\ && line('$') != line('w$')
|
|
\ || line('.') == line('w0')+&scrolloff)
|
|
|
|
" show off-screen matches
|
|
let l:method = get(g:matchup_matchparen_offscreen, 'method', '')
|
|
if !empty(l:method) && l:method !=# 'none'
|
|
\ && !l:current.skip && !l:scrolling
|
|
\ && winheight(0) > 0
|
|
call s:do_offscreen(l:current, l:method)
|
|
endif
|
|
|
|
" add highlighting matches
|
|
call s:add_matches(l:corrlist, l:current)
|
|
|
|
" highlight the background between parentheses
|
|
if g:matchup_matchparen_hi_background >= 1
|
|
call s:highlight_background(l:corrlist)
|
|
endif
|
|
|
|
" new highlights done, request fade away
|
|
call self.fade(2, l:pos, l:token)
|
|
|
|
call matchup#perf#toc('matchparen.highlight', 'end')
|
|
endfunction
|
|
|
|
function s:matchparen.transmute_reset() abort dict
|
|
if g:matchup_transmute_enabled
|
|
call matchup#transmute#reset()
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:do_offscreen(current, method) " {{{1
|
|
let l:offscreen = {}
|
|
|
|
if !has_key(a:current, 'links') | return | endif
|
|
|
|
" prefer to show close
|
|
if a:current.links.open.lnum < line('w0')
|
|
let l:offscreen = a:current.links.open
|
|
endif
|
|
if a:current.links.close.lnum > line('w$')
|
|
let l:offscreen = a:current.links.close
|
|
endif
|
|
|
|
if empty(l:offscreen) | return | endif
|
|
|
|
if a:method ==# 'status'
|
|
call s:do_offscreen_statusline(l:offscreen, 0)
|
|
elseif a:method ==# 'status_manual'
|
|
call s:do_offscreen_statusline(l:offscreen, 1)
|
|
elseif a:method ==# 'popup' && winheight(0) > 1
|
|
if has('nvim')
|
|
call s:do_offscreen_popup_nvim(l:offscreen)
|
|
elseif exists('*popup_create')
|
|
call s:ensure_match_popup()
|
|
call s:do_offscreen_popup(l:offscreen)
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:do_offscreen_statusline(offscreen, manual) " {{{1
|
|
let l:opts = {}
|
|
if a:manual
|
|
let l:opts.compact = 1
|
|
endif
|
|
let [l:sl, l:lnum] = matchup#matchparen#status_str(a:offscreen, l:opts)
|
|
if s:ensure_scroll_timer() && !a:manual
|
|
let l:sl .= '%{matchup#matchparen#scroll_update('.l:lnum.')}'
|
|
endif
|
|
|
|
let w:matchup_statusline = l:sl
|
|
if !exists('w:matchup_oldstatus')
|
|
let w:matchup_oldstatus = &l:statusline
|
|
endif
|
|
if !a:manual
|
|
let &l:statusline = w:matchup_statusline
|
|
endif
|
|
|
|
if exists('#User#MatchupOffscreenEnter')
|
|
doautocmd <nomodeline> User MatchupOffscreenEnter
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:ensure_match_popup() abort " {{{1
|
|
if !exists('*popup_create') || exists('t:match_popup')
|
|
return
|
|
endif
|
|
|
|
" create a popup and store its winid
|
|
let l:opts = {'hidden': v:true}
|
|
if has_key(g:matchup_matchparen_offscreen, 'highlight')
|
|
let l:opts.highlight = g:matchup_matchparen_offscreen.highlight
|
|
endif
|
|
let t:match_popup = popup_create('', l:opts)
|
|
|
|
if !has('patch-8.1.1406')
|
|
" in case 'hidden' in popup_create-usage is unimplemented
|
|
call popup_hide(t:match_popup)
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:do_offscreen_popup(offscreen) " {{{1
|
|
" screen position of top-left corner of current window
|
|
let [l:row, l:col] = win_screenpos(winnr())
|
|
let l:height = winheight(0) " height of current window
|
|
let l:adjust = matchup#quirks#status_adjust(a:offscreen)
|
|
let l:lnum = a:offscreen.lnum + l:adjust
|
|
let l:line = l:lnum < line('.') ? l:row : l:row + l:height - 1
|
|
|
|
" if popup would overlap with cursor
|
|
if l:line == winline() | return | endif
|
|
|
|
call popup_move(t:match_popup, {
|
|
\ 'line': l:line,
|
|
\ 'col': l:col,
|
|
\ 'maxheight': 1,
|
|
\})
|
|
|
|
" set popup text
|
|
let l:text = ''
|
|
if &number || &relativenumber
|
|
let l:text = printf('%*S ', wincol()-virtcol('.')-1, l:lnum)
|
|
endif
|
|
|
|
" replace tab indent with spaces
|
|
" (popup window doesn't follow tabstop option of current buffer)
|
|
let l:linestr = getline(l:lnum)
|
|
let l:indent = repeat(' ', strdisplaywidth(matchstr(l:linestr, '^\s\+')))
|
|
let l:linestr = substitute(l:linestr, '^\s\+', l:indent, '')
|
|
|
|
let l:text .= l:linestr . ' '
|
|
if l:adjust
|
|
let l:text .= '… ' . a:offscreen.match . ' '
|
|
endif
|
|
call setbufline(winbufnr(t:match_popup), 1, l:text)
|
|
call popup_show(t:match_popup)
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:do_offscreen_popup_nvim(offscreen) " {{{1
|
|
if exists('*nvim_open_win')
|
|
" neovim floating window
|
|
call s:close_floating_win()
|
|
|
|
let l:lnum = a:offscreen.lnum
|
|
let [l:row, l:anchor] = l:lnum < line('.')
|
|
\ ? [0, 'NW'] : [winheight(0), 'SW']
|
|
if l:row == winline() | return | endif
|
|
|
|
" Set default width and height for now.
|
|
let s:float_id = nvim_open_win(bufnr('%'), v:false, {
|
|
\ 'relative': 'win',
|
|
\ 'anchor': l:anchor,
|
|
\ 'row': l:row,
|
|
\ 'col': 0,
|
|
\ 'width': 42,
|
|
\ 'height': &previewheight,
|
|
\ 'focusable': v:false,
|
|
\})
|
|
|
|
if has_key(g:matchup_matchparen_offscreen, 'highlight')
|
|
call nvim_win_set_option(s:float_id, 'winhighlight',
|
|
\ 'Normal:' . g:matchup_matchparen_offscreen.highlight)
|
|
endif
|
|
|
|
if &relativenumber
|
|
call nvim_win_set_option(s:float_id, 'number', v:true)
|
|
call nvim_win_set_option(s:float_id, 'relativenumber', v:false)
|
|
endif
|
|
|
|
call s:populate_floating_win(a:offscreen)
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:populate_floating_win(offscreen) " {{{1
|
|
let l:adjust = matchup#quirks#status_adjust(a:offscreen)
|
|
let l:lnum = a:offscreen.lnum + l:adjust
|
|
let l:body = getline(l:lnum, a:offscreen.lnum)
|
|
let l:body_length = len(l:body)
|
|
let l:height = min([l:body_length, &previewheight])
|
|
|
|
if exists('*nvim_open_win')
|
|
" neovim floating win
|
|
let width = max(map(copy(l:body), 'strdisplaywidth(v:val)'))
|
|
let l:width += wincol()-virtcol('.')
|
|
call nvim_win_set_width(s:float_id, l:width + 1)
|
|
if &winminheight != 1
|
|
let l:save_wmh = &winminheight
|
|
let &winminheight = 1
|
|
call nvim_win_set_height(s:float_id, l:height)
|
|
let &winminheight = l:save_wmh
|
|
else
|
|
call nvim_win_set_height(s:float_id, l:height)
|
|
endif
|
|
call nvim_win_set_cursor(s:float_id, [l:lnum, 0])
|
|
call nvim_win_set_option(s:float_id, 'wrap', v:false)
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:close_floating_win() " {{{1
|
|
if !exists('s:float_id')
|
|
return
|
|
endif
|
|
if win_id2win(s:float_id) > 0
|
|
call nvim_win_close(s:float_id, 0)
|
|
endif
|
|
let s:float_id = 0
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! MatchupStatusOffscreen() " {{{1
|
|
return substitute(get(w:, 'matchup_statusline', ''),
|
|
\ '%<\|%#\w*#', '', 'g')
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! matchup#matchparen#highlight_surrounding() abort " {{{1
|
|
call matchup#perf#timeout_start(500)
|
|
call s:highlight_surrounding()
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:highlight_surrounding(...) " {{{1
|
|
let l:opts = { 'local': 0, 'matches': [] }
|
|
let l:delims = matchup#delim#get_surrounding('delim_all', 1, l:opts)
|
|
let l:open = l:delims[0]
|
|
if empty(l:open) | return | endif
|
|
|
|
let l:corrlist = l:opts.matches
|
|
if empty(l:corrlist) | return | endif
|
|
|
|
" store flag meaning highlighting is active
|
|
let w:matchup_need_clear = 1
|
|
|
|
" add highlighting matches
|
|
call s:add_matches(l:corrlist)
|
|
|
|
" highlight the background between parentheses
|
|
if g:matchup_matchparen_hi_background >= 2
|
|
call s:highlight_background(l:corrlist)
|
|
endif
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:highlight_background(corrlist) " {{{1
|
|
let [l:lo1, l:lo2] = [a:corrlist[0], a:corrlist[-1]]
|
|
|
|
let l:inclusive = 1
|
|
if l:inclusive
|
|
call s:add_background_matches_1(
|
|
\ l:lo1.lnum,
|
|
\ l:lo1.cnum,
|
|
\ l:lo2.lnum,
|
|
\ l:lo2.cnum + matchup#delim#end_offset(l:lo2))
|
|
else
|
|
call s:add_background_matches_1(
|
|
\ l:lo1.lnum,
|
|
\ l:lo1.cnum + matchup#delim#end_offset(l:lo1) + 1,
|
|
\ l:lo2.lnum,
|
|
\ l:lo2.cnum - 1)
|
|
endif
|
|
endfunction
|
|
|
|
"}}}1
|
|
|
|
function! s:format_gutter(lnum, ...) " {{{1
|
|
let l:opts = a:0 ? a:1 : {}
|
|
let l:padding = wincol()-virtcol('.')
|
|
let l:sl = ''
|
|
|
|
let l:direction = a:lnum < line('.')
|
|
if &number || &relativenumber
|
|
let l:nw = max([strlen(line('$')), &numberwidth-1])
|
|
let l:linenr = a:lnum " distinct for relativenumber
|
|
|
|
if &relativenumber
|
|
let l:linenr = abs(l:linenr-line('.'))
|
|
endif
|
|
|
|
let l:sl = printf('%'.(l:nw).'s', l:linenr)
|
|
if l:direction && !get(l:opts, 'noshowdir', 0)
|
|
let l:sl = '%#Search#' . l:sl . '∆%#Normal#'
|
|
else
|
|
let l:sl = '%#CursorLineNr#' . l:sl . ' %#Normal#'
|
|
endif
|
|
let l:padding -= l:nw + 1
|
|
endif
|
|
|
|
if empty(l:sl) && l:direction && !get(l:opts, 'noshowdir', 0)
|
|
let l:sl = '%#Search#∆%#Normal#'
|
|
let l:padding -= 1 " OK if this is negative
|
|
if l:padding == -1 && indent(a:lnum) == 0
|
|
let l:padding = 0
|
|
endif
|
|
endif
|
|
|
|
" possible fold column, up to &foldcolumn characters
|
|
let l:fdcstr = ''
|
|
if &foldcolumn
|
|
let l:fdc = max([1, &foldcolumn-1])
|
|
let l:fdl = foldlevel(a:lnum)
|
|
let l:fdcstr = l:fdl <= l:fdc ? repeat('|', l:fdl)
|
|
\ : join(range(l:fdl-l:fdc+1, l:fdl), '')
|
|
let l:padding -= len(l:fdcstr)
|
|
let l:fdcstr = '%#FoldColumn#' . l:fdcstr . '%#Normal#'
|
|
elseif empty(l:sl)
|
|
let l:sl = '%#Normal#'
|
|
endif
|
|
|
|
" add remaining padding (this handles rest of fdc and scl)
|
|
let l:sl = l:fdcstr . repeat(' ', l:padding) . l:sl
|
|
return l:sl
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! matchup#matchparen#status_str(offscreen, ...) abort " {{{1
|
|
let l:opts = a:0 ? a:1 : {}
|
|
let l:adjust = matchup#quirks#status_adjust(a:offscreen)
|
|
let l:lnum = a:offscreen.lnum + l:adjust
|
|
let l:line = getline(l:lnum)
|
|
|
|
let l:sl = ''
|
|
let l:trimming = 0
|
|
if get(l:opts, 'compact', 0)
|
|
let l:trimming = 1
|
|
else
|
|
let l:sl = s:format_gutter(l:lnum, l:opts)
|
|
endif
|
|
|
|
if has_key(l:opts, 'width')
|
|
" TODO subtract the gutter from above
|
|
let l:room = l:opts.width
|
|
else
|
|
let l:room = min([300, winwidth(0)]) - (wincol()-virtcol('.'))
|
|
endif
|
|
let l:room -= l:adjust ? 3+strdisplaywidth(a:offscreen.match) : 0
|
|
let l:lasthi = ''
|
|
for l:c in range(min([l:room, strlen(l:line)]))
|
|
if !l:adjust && a:offscreen.cnum <= l:c+1 && l:c+1 <= a:offscreen.cnum
|
|
\ - 1 + strlen(a:offscreen.match)
|
|
let l:wordish = a:offscreen.match !~? '^[[:punct:]]\{1,3\}$'
|
|
" TODO: we can't overlap groups, this might not be totally correct
|
|
let l:curhi = l:wordish ? 'MatchWord' : 'MatchParen'
|
|
elseif char2nr(l:line[l:c]) < 32
|
|
let l:curhi = 'SpecialKey'
|
|
else
|
|
let l:curhi = synIDattr(synID(l:lnum, l:c+1, 1), 'name')
|
|
if empty(l:curhi)
|
|
let l:curhi = 'Normal'
|
|
endif
|
|
endif
|
|
let l:sl .= (l:curhi !=# l:lasthi ? '%#'.l:curhi.'#' : '')
|
|
if l:trimming && l:line[l:c] !~ '\s'
|
|
let l:trimming = 0
|
|
endif
|
|
if l:trimming
|
|
elseif l:line[l:c] ==# "\t"
|
|
let l:sl .= repeat(' ', strdisplaywidth(strpart(l:line, 0, 1+l:c))
|
|
\ - strdisplaywidth(strpart(l:line, 0, l:c)))
|
|
elseif char2nr(l:line[l:c]) < 32
|
|
let l:sl .= strtrans(l:line[l:c])
|
|
elseif l:line[l:c] == '%'
|
|
let l:sl .= '%%'
|
|
else
|
|
let l:sl .= l:line[l:c]
|
|
endif
|
|
let l:lasthi = l:curhi
|
|
endfor
|
|
let l:sl = substitute(l:sl, '\s\+$', '', '') . '%<%#Normal#'
|
|
if l:adjust
|
|
let l:sl .= '%#LineNr# … %#Normal#'
|
|
\ . '%#MatchParen#' . a:offscreen.match . '%#Normal#'
|
|
endif
|
|
|
|
return [l:sl, l:lnum]
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:ensure_scroll_timer() " {{{1
|
|
if has('timers') && exists('*timer_pause')
|
|
if !exists('s:scroll_timer')
|
|
let s:scroll_timer = timer_start(50,
|
|
\ 'matchup#matchparen#scroll_callback',
|
|
\ { 'repeat': -1 })
|
|
call timer_pause(s:scroll_timer, 1)
|
|
endif
|
|
endif
|
|
|
|
return exists('s:scroll_timer')
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! matchup#matchparen#scroll_callback(tid) " {{{1
|
|
call timer_pause(a:tid, 1)
|
|
call s:matchparen.highlight(1)
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! matchup#matchparen#scroll_update(lnum) " {{{1
|
|
if line('w0') <= a:lnum && a:lnum <= line('w$')
|
|
\ && exists('s:scroll_timer')
|
|
call timer_pause(s:scroll_timer, 0)
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
function! s:add_matches(corrlist, ...) " {{{1
|
|
if !exists('w:matchup_match_id_list')
|
|
let w:matchup_match_id_list = []
|
|
endif
|
|
|
|
" if MatchwordCur is undefined and MatchWord links to MatchParen
|
|
" (as default), behave like MatchWordCur is the same as MatchParenCur
|
|
" otherwise, MatchWordCur is the same as MatchWord
|
|
if a:0
|
|
let l:mwc = hlexists('MatchWordCur') ? 'MatchWordCur'
|
|
\ : (synIDtrans(hlID('MatchWord')) == hlID('MatchParen')
|
|
\ ? 'MatchParenCur' : 'MatchWord')
|
|
endif
|
|
|
|
for l:corr in a:corrlist
|
|
let l:wordish = l:corr.match !~? '^[[:punct:]]\{1,3\}$'
|
|
|
|
if a:0 && l:corr.match_index == a:1.match_index
|
|
let l:group = l:wordish ? l:mwc : 'MatchParenCur'
|
|
else
|
|
let l:group = l:wordish ? 'MatchWord' : 'MatchParen'
|
|
endif
|
|
|
|
if exists('*matchaddpos')
|
|
call add(w:matchup_match_id_list, matchaddpos(l:group,
|
|
\ [[l:corr.lnum, l:corr.cnum, strlen(l:corr.match)]], 0))
|
|
else
|
|
call add(w:matchup_match_id_list, matchadd(l:group,
|
|
\ '\%'.(l:corr.lnum).'l\%'.(l:corr.cnum).'c'
|
|
\ . '.\+\%<'.(l:corr.cnum+strlen(l:corr.match)+1).'c', 0))
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:add_background_matches_1(line1, col1, line2, col2) " {{{1
|
|
if a:line1 == a:line2 && a:col1 > a:col2
|
|
return
|
|
endif
|
|
|
|
let l:priority = -1
|
|
|
|
if a:line1 == a:line2
|
|
let l:match = '\%'.(a:line1).'l\&'
|
|
\ . '\%'.(a:col1).'c.*\%'.(a:col2).'c.'
|
|
else
|
|
let l:match = '\%>'.(a:line1).'l\(.\+\|^$\)\%<'.(a:line2).'l'
|
|
\ . '\|\%'.(a:line1).'l\%>'.(a:col1-1).'c.\+'
|
|
\ . '\|\%'.(a:line2).'l.\+\%<'.(a:col2+1).'c.'
|
|
endif
|
|
|
|
call add(w:matchup_match_id_list,
|
|
\ matchadd('MatchBackground', l:match, l:priority))
|
|
endfunction
|
|
|
|
" }}}1
|
|
function! s:add_background_matches_2(line1, col1, line2, col2) " {{{1
|
|
if a:line1 == a:line2 && a:col1 > a:col2
|
|
return
|
|
endif
|
|
|
|
let l:priority = -1
|
|
|
|
let l:curline = a:line1
|
|
while l:curline <= a:line2
|
|
let l:endline = min([l:curline+7, a:line2])
|
|
let l:list = range(l:curline, l:endline)
|
|
if l:curline == a:line1
|
|
let l:list[0] = [a:line1, a:col1,
|
|
\ l:curline == a:line2 ? (a:col2-a:col1+1)
|
|
\ : strlen(getline(a:line1))]
|
|
endif
|
|
if l:endline == a:line2 && l:curline != a:line2
|
|
let l:list[-1] = [a:line2, 1, a:col2]
|
|
endif
|
|
|
|
call add(w:matchup_match_id_list,
|
|
\ matchaddpos('MatchBackground', l:list, l:priority))
|
|
let l:curline = l:endline+1
|
|
endwhile
|
|
endfunction
|
|
|
|
" }}}1
|
|
|
|
let &cpo = s:save_cpo
|
|
|
|
" vim: fdm=marker sw=2
|
|
|