1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 09:30:04 +08:00
SpaceVim/bundle/vim-matchup/autoload/matchup/delim.vim
2020-06-13 14:06:35 +08:00

919 lines
26 KiB
VimL

" vim match-up - even better matching
"
" Maintainer: Andy Massimino
" Email: a@normed.space
"
let s:save_cpo = &cpo
set cpo&vim
function! matchup#delim#get_next(type, side, ...) " {{{1
return s:get_delim(extend({
\ 'direction' : 'next',
\ 'type' : a:type,
\ 'side' : a:side,
\}, get(a:, '1', {})))
endfunction
" }}}1
function! matchup#delim#get_prev(type, side, ...) " {{{1
return s:get_delim(extend({
\ 'direction' : 'prev',
\ 'type' : a:type,
\ 'side' : a:side,
\}, get(a:, '1', {})))
endfunction
" }}}1
function! matchup#delim#get_current(type, side, ...) " {{{1
return s:get_delim(extend({
\ 'direction' : 'current',
\ 'type' : a:type,
\ 'side' : a:side,
\}, get(a:, '1', {})))
endfunction
" }}}1
function! matchup#delim#get_matching(delim, ...) " {{{1
if empty(a:delim) || !has_key(a:delim, 'lnum') | return {} | endif
let l:opts = a:0 && type(a:1) == type({}) ? a:1 : {}
let l:stopline = get(l:opts, 'stopline', s:stopline)
" get all the matching position(s)
" *important*: in the case of mid, we search up before searching down
" this gives us a context object which we use for the other side
" TODO: what if no open is found here?
let l:matches = []
let l:save_pos = matchup#pos#get_cursor()
for l:down in {'open': [1], 'close': [0], 'mid': [0,1]}[a:delim.side]
call matchup#pos#set_cursor(a:delim)
" second iteration: [] refers to the current match
if !empty(l:matches)
call add(l:matches, [])
endif
let l:res = a:delim.get_matching(l:down, l:stopline)
if l:res[0][1] > 0
call extend(l:matches, l:res)
elseif l:down
let l:matches = []
endif
endfor
call matchup#pos#set_cursor(l:save_pos)
if a:delim.side ==# 'open'
call insert(l:matches, [])
endif
if a:delim.side ==# 'close'
call add(l:matches, [])
endif
" create the match result(s)
let l:matching_list = []
for l:i in range(len(l:matches))
if empty(l:matches[l:i])
let a:delim.match_index = l:i
call add(l:matching_list, a:delim)
continue
end
let [l:match, l:lnum, l:cnum] = l:matches[l:i]
let l:matching = copy(a:delim)
let l:matching.class = copy(a:delim.class)
let l:matching.lnum = l:lnum
let l:matching.cnum = l:cnum
let l:matching.match = l:match
let l:matching.side = l:i == 0 ? 'open'
\ : l:i == len(l:matches)-1 ? 'close' : 'mid'
let l:matching.class[1] = 'FIXME'
let l:matching.match_index = l:i
call add(l:matching_list, l:matching)
endfor
" set up links between matches
for l:i in range(len(l:matching_list))
let l:c = l:matching_list[l:i]
let l:c.links = {}
let l:c.links.next = l:matching_list[(l:i+1) % len(l:matching_list)]
let l:c.links.prev = l:matching_list[l:i-1]
let l:c.links.open = l:matching_list[0]
let l:c.links.close = l:matching_list[-1]
endfor
return l:matching_list
endfunction
" }}}1
function! matchup#delim#get_surrounding(type, ...) " {{{1
call matchup#perf#tic('delim#get_surrounding')
let l:save_pos = matchup#pos#get_cursor()
let l:pos_val_cursor = matchup#pos#val(l:save_pos)
let l:pos_val_last = l:pos_val_cursor
let l:pos_val_open = l:pos_val_cursor - 1
let l:count = a:0 >= 1 ? a:1 : 1
let l:counter = l:count
" third argument specifies local any block, otherwise,
" provided count == 0 refers to local any block
let l:opts = a:0 >= 2 ? a:2 : {}
let l:local = get(l:opts, 'local', l:count == 0 ? 1 : 0)
let l:delimopts = {}
let s:invert_skip = 0 " TODO: this logic is still bad
if matchup#delim#skip() " TODO: check for insert mode (?)
let l:delimopts.check_skip = 0
endif
" TODO: pin skip
if get(l:opts, 'check_skip', 0)
let l:delimopts.check_skip = 1
endif
" keep track of the outermost pair found so far
" returned when g:matchup_delim_count_fail = 1
let l:best = []
while l:pos_val_open < l:pos_val_last
let l:open = matchup#delim#get_prev(a:type,
\ l:local ? 'open_mid' : 'open', l:delimopts)
if empty(l:open) | break | endif
" if configured, we may still accept this match
if matchup#perf#timeout_check() && !g:matchup_delim_count_fail
break
endif
let l:matches = matchup#delim#get_matching(l:open, 1)
" TODO: getting one match result here is surely wrong
if len(l:matches) == 1
let l:matches = []
endif
if has_key(l:opts, 'matches')
let l:opts.matches = l:matches
endif
if len(l:matches)
let l:close = l:local ? l:open.links.next : l:open.links.close
let l:pos_val_try = matchup#pos#val(l:close)
\ + matchup#delim#end_offset(l:close)
endif
if len(l:matches) && l:pos_val_try >= l:pos_val_cursor
if l:counter <= 1
" restore cursor and accept
call matchup#pos#set_cursor(l:save_pos)
call matchup#perf#toc('delim#get_surrounding', 'accept')
return [l:open, l:close]
endif
let l:counter -= 1
let l:best = [l:open, l:close]
else
let l:pos_val_last = l:pos_val_open
let l:pos_val_open = matchup#pos#val(l:open)
endif
if l:open.lnum == 1 && l:open.cnum == 1
break
endif
call matchup#pos#set_cursor(matchup#pos#prev(l:open))
endwhile
if !empty(l:best) && g:matchup_delim_count_fail
call matchup#pos#set_cursor(l:save_pos)
call matchup#perf#toc('delim#get_surrounding', 'bad_count')
return l:best
endif
" restore cursor and return failure
call matchup#pos#set_cursor(l:save_pos)
call matchup#perf#toc('delim#get_surrounding', 'fail')
return [{}, {}]
endfunction
" }}}1
function! matchup#delim#get_surround_nearest(open, ...) " {{{1
" finds the first consecutive pair whose start
" positions surround pos (default to the cursor)
let l:cur_pos = a:0 ? a:1 : matchup#pos#get_cursor()
let l:pos_val_cursor = matchup#pos#val(l:cur_pos)
let l:pos_val_open = matchup#pos#val(a:open)
let l:pos_val_prev = l:pos_val_open
let l:delim = a:open.links.next
let l:pos_val_next = matchup#pos#val(l:delim)
while l:pos_val_next > l:pos_val_open
let l:end_offset = matchup#delim#end_offset(l:delim)
if l:pos_val_prev <= l:pos_val_cursor
\ && l:pos_val_next + l:end_offset >= l:pos_val_cursor
return [l:delim.links.prev, l:delim]
endif
let l:pos_val_prev = l:pos_val_next
let l:delim = l:delim.links.next
let l:pos_val_next = matchup#pos#val(l:delim)
endwhile
return [{}, {}]
endfunction
" }}}1
function! matchup#delim#jump_target(delim) " {{{1
let l:save_pos = matchup#pos#get_cursor()
" unicode note: technically wrong, but works in practice
" since the cursor snaps back to start of multi-byte chars
let l:column = a:delim.cnum
let l:column += strlen(a:delim.match) - 1
if strlen(a:delim.match) < 2
return l:column
endif
for l:tries in range(strlen(a:delim.match)-1)
call matchup#pos#set_cursor(a:delim.lnum, l:column)
let l:delim_test = matchup#delim#get_current('all', 'both_all')
if l:delim_test.class[0] ==# a:delim.class[0]
break
endif
let l:column -= 1
endfor
call matchup#pos#set_cursor(l:save_pos)
return l:column
endfunction
" }}}1
function! matchup#delim#end_offset(delim) " {{{1
return max([0, match(a:delim.match, '.$')])
endfunction
" }}}1
function! matchup#delim#end_pos(delim) abort " {{{1
return [a:delim.lnum, a:delim.cnum + matchup#delim#end_offset(a:delim)]
endfunction
" }}}1
function! s:get_delim(opts) " {{{1
" arguments: {{{2
" opts = {
" 'direction' : 'next' | 'prev' | 'current'
" 'type' : 'delim_tex'
" | 'delim_all'
" | 'all'
" 'side' : 'open' | 'close'
" | 'both' | 'mid'
" | 'both_all' | 'open_mid'
" }
"
" }}}2
" returns: {{{2
" delim = {
" type : 'delim'
" lnum : line number
" cnum : column number
" match : the actual text match
" augment : how to match a corresponding open
" groups : dict of captured groups
" side : 'open' | 'close' | 'mid'
" class : [ c1, c2 ] identifies the kind of match_words
" regexone : the regex item, like \1foo
" regextwo : the regex_capture item, like \(group\)foo
" }
"
" }}}2
if !get(b:, 'matchup_delim_enabled', 0)
return {}
endif
call matchup#perf#tic('s:get_delim')
let l:save_pos = matchup#pos#get_cursor()
call matchup#loader#refresh_match_words()
" this contains all the patterns for the specified type and side
let l:re = b:matchup_delim_re[a:opts.type][a:opts.side]
let l:cursorpos = col('.')
let l:insertmode = get(a:opts, 'insertmode', 0)
if l:cursorpos > 1 && l:insertmode
let l:cursorpos -= 1
endif
if l:cursorpos > strlen(getline('.'))
\ && stridx("vV\<c-v>", mode()) > -1
let l:cursorpos -= 1
endif
let s:invert_skip = 0
if a:opts.direction ==# 'current'
let l:check_skip = get(a:opts, 'check_skip',
\ g:matchup_delim_noskips >= 2
\ || g:matchup_delim_noskips >= 1
\ && getline(line('.'))[l:cursorpos-1] =~ '[^[:punct:]]')
if l:check_skip && matchup#delim#skip(line('.'), l:cursorpos)
return {}
endif
else
" check skip if cursor is not currently in skip
let l:check_skip = get(a:opts, 'check_skip',
\ !matchup#delim#skip(line('.'), l:cursorpos)
\ || g:matchup_delim_noskips >= 2)
endif
let a:opts.cursorpos = l:cursorpos
" for current, we want to find matches that end after the cursor
" note: we expect this to give false-positives with \ze
if a:opts.direction ==# 'current'
let l:re .= '\%>'.(l:cursorpos).'c'
" let l:re = '\%<'.(l:cursorpos+1).'c' . l:re
endif
" allow overlapping delimiters
" without this, the > in <tag> would not be found
if b:matchup_delim_re[a:opts.type]._engine_info.has_zs[a:opts.side]
let l:save_cpo = &cpo
noautocmd set cpo-=c
else
" faster than changing cpo but doesn't work right with \zs
let l:re .= '\&'
endif
" move cursor one left for searchpos if necessary
let l:need_restore_cursor = 0
if l:insertmode
call matchup#pos#set_cursor(line('.'), col('.')-1)
let l:need_restore_cursor = 1
endif
" stopline may depend on the current action
let l:stopline = get(a:opts, 'stopline', s:stopline)
" in the first pass, we get matching line and column numbers
" this is intended to be as fast as possible, with no capture groups
" we look for a match on this line (if direction == current)
" or forwards or backwards (if direction == next or prev)
" for current, we actually search leftwards from the cursor
while 1
let l:to = matchup#perf#timeout()
let [l:lnum, l:cnum] = a:opts.direction ==# 'next'
\ ? searchpos(l:re, 'cnW', line('.') + l:stopline, l:to)
\ : a:opts.direction ==# 'prev'
\ ? searchpos(l:re, 'bcnW',
\ max([line('.') - l:stopline, 1]), l:to)
\ : searchpos(l:re, 'bcnW', line('.'), l:to)
if l:lnum == 0 | break | endif
" note: the skip here should not be needed
" in 'current' mode, but be explicit
if a:opts.direction !=# 'current'
\ && (l:check_skip || g:matchup_delim_noskips == 1
\ && getline(l:lnum)[l:cnum-1] =~ '[^[:punct:]]')
\ && matchup#delim#skip(l:lnum, l:cnum)
\ && (a:opts.direction ==# 'prev' ? (l:lnum > 1 || l:cnum > 1)
\ : (l:lnum < line('$') || l:cnum < len(getline('$'))))
" invalid match, move cursor and keep looking
call matchup#pos#set_cursor(a:opts.direction ==# 'next'
\ ? matchup#pos#next(l:lnum, l:cnum)
\ : matchup#pos#prev(l:lnum, l:cnum))
let l:need_restore_cursor = 1
continue
endif
break
endwhile
" restore cpo if necessary
" note: this messes with cursor position
if exists('l:save_cpo')
noautocmd let &cpo = l:save_cpo
let l:need_restore_cursor = 1
endif
" restore cursor
if l:need_restore_cursor
call matchup#pos#set_cursor(l:save_pos)
endif
call matchup#perf#toc('s:get_delim', 'first_pass')
" nothing found, leave now
if l:lnum == 0
call matchup#perf#toc('s:get_delim', 'nothing_found')
return {}
endif
if matchup#perf#timeout_check()
return {}
endif
let l:skip_state = 0
if !l:check_skip && (!&synmaxcol || l:cnum <= &synmaxcol)
" XXX: workaround an apparent obscure vim bug where the
" reported syntax id is incorrect on the first synID() call
call matchup#delim#skip(l:lnum, l:cnum)
if matchup#perf#timeout_check()
return {}
endif
let l:skip_state = matchup#delim#skip(l:lnum, l:cnum)
endif
" now we get more data about the match in this position
" there may be capture groups which need to be stored
" result stub, to be filled by the parser when there is a match
let l:result = {
\ 'lnum' : l:lnum,
\ 'cnum' : l:cnum,
\ 'type' : '',
\ 'match' : '',
\ 'augment' : '',
\ 'groups' : '',
\ 'side' : '',
\ 'class' : [],
\ 'regexone' : '',
\ 'regextwo' : '',
\ 'skip' : l:skip_state,
\}
for l:type in s:types[a:opts.type]
let l:parser_result = l:type.parser(l:lnum, l:cnum, a:opts)
if !empty(l:parser_result)
let l:result = extend(l:parser_result, l:result, 'keep')
break
endif
endfor
call matchup#perf#toc('s:get_delim', 'got_results')
return empty(l:result.type) ? {} : l:result
endfunction
" }}}1
function! s:parser_delim_new(lnum, cnum, opts) " {{{1
let l:cursorpos = a:opts.cursorpos
let l:found = 0
let l:sides = matchup#loader#sidedict()[a:opts.side]
let l:rebrs = b:matchup_delim_lists[a:opts.type].regex_capture
" use b:match_ignorecase
let l:ic = get(b:, 'match_ignorecase', 0) ? '\c' : '\C'
" loop through all (index, side) pairs,
let l:ns = len(l:sides)
let l:found = 0
for l:i in range(len(l:rebrs)*l:ns)
let l:side = l:sides[ l:i % l:ns ]
if l:side ==# 'mid'
let l:res = l:rebrs[l:i / l:ns].mid_list
if empty(l:res) | continue | end
else
let l:res = [ l:rebrs[l:i / l:ns][l:side] ]
if empty(l:res[0]) | continue | end
endif
" if pattern may contain \zs, extra processing is required
let l:extra_info = l:rebrs[l:i / l:ns].extra_info
let l:has_zs = get(l:extra_info, 'has_zs', 0)
let l:mid_id = 0
for l:re in l:res
let l:mid_id += 1
" check whether hlend needs to be handled
let l:id = l:side ==# 'mid' ? l:mid_id : l:side ==# 'open' ? 0 : -1
let l:extra_entry = l:rebrs[l:i / l:ns].extra_list[l:id]
let l:has_hlend = has_key(l:extra_entry, 'hlend')
if l:has_hlend && get(a:opts, 'highlighting', 0)
let l:re = s:process_hlend(l:re, l:cursorpos)
endif
" prepend the column number and append the cursor column
" to anchor the match; we don't use {start} for matchlist
" because there may be zero-width look behinds
let l:re_anchored = l:ic . s:anchor_regex(l:re, a:cnum, l:has_zs)
" for current we want the first match which the cursor is inside
if a:opts.direction ==# 'current'
let l:re_anchored .= '\%>'.(l:cursorpos).'c'
endif
let l:matches = matchlist(getline(a:lnum), l:re_anchored)
if empty(l:matches) | continue | endif
" reject matches which the cursor is outside of
" this matters only for \ze
if !l:has_hlend && a:opts.direction ==# 'current'
\ && a:cnum + strlen(l:matches[0]) <= l:cursorpos
continue
endif
" if pattern contains \zs we need to re-check the starting column
if l:has_zs && match(getline(a:lnum), l:re_anchored) != a:cnum-1
continue
endif
let l:found = 1
break
endfor
if !l:found | continue | endif
break
endfor
" reset ignorecase (defunct)
if !l:found
return {}
endif
let l:match = l:matches[0]
let l:list = b:matchup_delim_lists[a:opts.type]
let l:thisre = l:list.regex[l:i / l:ns]
let l:thisrebr = l:list.regex_capture[l:i / l:ns]
let l:augment = {}
" these are the capture groups indexed by their 'open' id
let l:groups = {}
let l:id = 0
if l:side ==# 'open'
" XXX we might as well store all the groups...
"for l:br in keys(l:thisrebr.need_grp)
for l:br in range(1,9)
if empty(l:matches[l:br]) | continue | endif
let l:groups[l:br] = l:matches[l:br]
endfor
else
let l:id = (l:side ==# 'close')
\ ? len(l:thisrebr.mid_list)+1
\ : l:mid_id
if has_key(l:thisrebr.grp_renu, l:id)
for [l:br, l:to] in items(l:thisrebr.grp_renu[l:id])
let l:groups[l:to] = l:matches[l:br]
endfor
endif
" fill in augment pattern
" TODO all the augment patterns should match,
" but checking might be too slow
if has_key(l:thisrebr.aug_comp, l:id)
let l:aug = l:thisrebr.aug_comp[l:id][0]
let l:augment.str = matchup#delim#fill_backrefs(
\ l:aug.str, l:groups, 0)
let l:augment.unresolved = deepcopy(l:aug.outputmap)
endif
endif
let l:result = {
\ 'type' : 'delim_tex',
\ 'match' : l:match,
\ 'augment' : l:augment,
\ 'groups' : l:groups,
\ 'side' : l:side,
\ 'class' : [(l:i / l:ns), l:id],
\ 'get_matching' : s:basetypes['delim_tex'].get_matching,
\ 'regexone' : l:thisre,
\ 'regextwo' : l:thisrebr,
\ 'midmap' : get(l:list, 'midmap', {}),
\ 'highlighting' : get(a:opts, 'highlighting', 0),
\}
return l:result
endfunction
" }}}1
function! s:get_matching_delims(down, stopline) dict " {{{1
" called as: a:delim.get_matching(...)
" called from: matchup#delim#get_matching <- matchparen, motion
" from: matchup#delim#get_surrounding <- matchparen, motion, text_obj
call matchup#perf#tic('get_matching_delims')
" first, we figure out what the furthest match is, which will be
" either the open or close depending on the direction
let [l:re, l:flags, l:stopline] = a:down
\ ? [self.regextwo.close, 'W', line('.') + a:stopline]
\ : [self.regextwo.open, 'bW', max([line('.') - a:stopline, 1])]
" these are the anchors for searchpairpos
let l:open = self.regexone.open " TODO is this right? BADLOGIC
let l:close = self.regexone.close
" if we're searching up, we anchor by the augment, if it exists
if !a:down && !empty(self.augment)
let l:open = self.augment.str
endif
" TODO temporary workaround for BADLOGIC
if a:down && self.side ==# 'mid'
let l:open = self.regextwo.open
endif
" turn \(\) into \%(\) for searchpairpos
let l:open = matchup#loader#remove_capture_groups(l:open)
let l:close = matchup#loader#remove_capture_groups(l:close)
" fill in back-references
" TODO: BADLOGIC2: when going up we don't have these groups yet..
" the second anchor needs to be mid/self for mid self
let l:open = matchup#delim#fill_backrefs(l:open, self.groups, 0)
let l:close = matchup#delim#fill_backrefs(l:close, self.groups, 0)
let s:invert_skip = self.skip
if empty(b:matchup_delim_skip)
let l:skip = 'matchup#delim#skip_default()'
else
let l:skip = 'matchup#delim#skip0()'
endif
" disambiguate matches for languages like julia, matlab, ruby, etc
if !empty(self.midmap)
let l:midmap = self.midmap.elements
if self.side ==# 'mid'
let l:idx = filter(range(len(l:midmap)),
\ 'self.match =~# l:midmap[v:val][1]')
else
let l:syn = synIDattr(synID(self.lnum, self.cnum, 0), 'name')
let l:idx = filter(range(len(l:midmap)),
\ 'l:syn =~# l:midmap[v:val][0]')
endif
if len(l:idx)
let l:valid = l:midmap[l:idx[0]]
let l:skip = printf('matchup#delim#skip1(%s, %s)',
\ string(l:midmap[l:idx[0]]), string(l:skip))
else
let l:skip = printf('matchup#delim#skip2(%s, %s)',
\ string(self.midmap.strike), string(l:skip))
endif
endif
if matchup#perf#timeout_check() | return [['', 0, 0]] | endif
" improves perceptual performance in insert mode
if mode() ==# 'i' || mode() ==# 'R'
if !g:matchup_matchparen_deferred
sleep 1m
endif
endif
" use b:match_ignorecase
let l:ic = get(b:, 'match_ignorecase', 0) ? '\c' : '\C'
let l:open = l:ic . l:open
let l:close = l:ic . l:close
let [l:lnum_corr, l:cnum_corr] = searchpairpos(l:open, '', l:close,
\ 'n'.l:flags, l:skip, l:stopline, matchup#perf#timeout())
call matchup#perf#toc('get_matching_delims', 'initial_pair')
" if nothing found, bail immediately
if l:lnum_corr == 0
" reset ignorecase (defunct)
return [['', 0, 0]]
endif
" when highlighting, respect hlend
let l:extra_entry = self.regextwo.extra_list[a:down ? -1 : 0]
if self.highlighting && has_key(l:extra_entry, 'hlend')
let l:re = s:process_hlend(l:re, -1)
endif
" get the match and groups
let l:has_zs = self.regextwo.extra_info.has_zs
let l:re_anchored = l:ic . s:anchor_regex(l:re, l:cnum_corr, l:has_zs)
let l:matches = matchlist(getline(l:lnum_corr), l:re_anchored)
let l:match_corr = l:matches[0]
" reset ignorecase (defunct)
" store these in these groups
if a:down
" let l:id = len(self.regextwo.mid_list)+1
" for [l:from, l:to] in items(self.regextwo.grp_renu[l:id])
" let self.groups[l:to] = l:matches[l:from]
" endfor
else
for l:to in range(1,9)
if !has_key(self.groups, l:to) && !empty(l:matches[l:to])
let self.groups[l:to] = l:matches[l:to]
endif
endfor
endif
call matchup#perf#toc('get_matching_delims', 'get_matches')
" fill in additional groups
let l:mids = matchup#loader#remove_capture_groups(self.regexone.mid)
let l:mids = matchup#delim#fill_backrefs(l:mids, self.groups, 1)
" if there are no mids, we're done
if empty(l:mids)
return [[l:match_corr, l:lnum_corr, l:cnum_corr]]
endif
let l:re = l:mids
" when highlighting, respect hlend
if get(self.regextwo.extra_info, 'mid_hlend') && self.highlighting
let l:re = s:process_hlend(l:re, -1)
endif
" use b:match_ignorecase
let l:mid = l:ic . l:mids
let l:re = l:ic . l:re
let l:list = []
while 1
if matchup#perf#timeout_check() | break | endif
let [l:lnum, l:cnum] = searchpairpos(l:open, l:mids, l:close,
\ l:flags, l:skip, l:lnum_corr, matchup#perf#timeout())
if l:lnum <= 0 | break | endif
if a:down
if l:lnum > l:lnum_corr || l:lnum == l:lnum_corr
\ && l:cnum >= l:cnum_corr | break | endif
else
if l:lnum < l:lnum_corr || l:lnum == l:lnum_corr
\ && l:cnum <= l:cnum_corr | break | endif
endif
let l:re_anchored = s:anchor_regex(l:re, l:cnum, l:has_zs)
let l:matches = matchlist(getline(l:lnum), l:re_anchored)
if empty(l:matches)
" this should never happen
continue
endif
let l:match = l:matches[0]
call add(l:list, [l:match, l:lnum, l:cnum])
endwhile
" reset ignorecase (defunct)
call add(l:list, [l:match_corr, l:lnum_corr, l:cnum_corr])
if !a:down
call reverse(l:list)
endif
return l:list
endfunction
" }}}1
function! matchup#delim#skip(...) " {{{1
if a:0 >= 2
let [l:lnum, l:cnum] = [a:1, a:2]
else
let [l:lnum, l:cnum] = matchup#pos#get_cursor()[1:2]
endif
if empty(get(b:, 'matchup_delim_skip', ''))
return matchup#util#in_comment_or_string(l:lnum, l:cnum)
\ ? !s:invert_skip : s:invert_skip
endif
let s:eff_curpos = [l:lnum, l:cnum]
execute 'return' (s:invert_skip ? '!(' : '(') b:matchup_delim_skip ')'
endfunction
function! matchup#delim#skip_default()
return matchup#util#in_comment_or_string(line('.'), col('.'))
\ ? !s:invert_skip : s:invert_skip
endfunction
function! matchup#delim#skip0()
let s:eff_curpos = [line('.'), col('.')]
execute 'return' (s:invert_skip ? '!(' : '(') b:matchup_delim_skip ')'
endfunction
""
" advanced mid/syntax skip when found in midmap
" {val} is a 2 element array of allowed [syntax, words]
" {def} is the fallback skip expression
function! matchup#delim#skip1(val, def)
if getline('.')[col('.')-1:] =~# '^'.a:val[1]
return eval(a:def)
endif
let l:s = synIDattr(synID(line('.'),col('.'), 0), 'name')
return l:s !~# a:val[0] || eval(a:def)
endfunction
""
" advanced mid/syntax skip when word is not in midmap
" {strike} pattern of disallowed mid words
" {def} is the fallback skip expression
function! matchup#delim#skip2(strike, def)
return getline('.')[col('.')-1:] =~# '^' . a:strike || eval(a:def)
endfunction
let s:invert_skip = 0
let s:eff_curpos = [1, 1]
" effective column/line
function! s:effline(expr)
return a:expr ==# '.' ? s:eff_curpos[0] : line(a:expr)
endfunction
function! s:effcol(expr)
return a:expr ==# '.' ? s:eff_curpos[1] : col(a:expr)
endfunction
function! s:geteffline(expr)
return a:expr ==# '.' ? getline(s:effline(a:expr)) : getline(a:expr)
endfunction
" }}}1
function! matchup#delim#fill_backrefs(re, groups, warn) " {{{1
return substitute(a:re, g:matchup#re#backref,
\ '\=s:get_backref(a:groups, submatch(1), a:warn)', 'g')
" \ '\=get(a:groups, submatch(1), "")', 'g')
endfunction
function! s:get_backref(groups, bref, warn)
if !has_key(a:groups, a:bref)
if a:warn
echohl WarningMsg
echo 'match-up: requested invalid backreference \'.a:bref
echohl None
endif
return ''
endif
return '\V'.escape(get(a:groups, a:bref), '\').'\m'
endfunction
"}}}1
function! s:anchor_regex(re, cnum, method) " {{{1
if a:method
" trick to re-match at a particular column
" handles the case where pattern contains \ze, \zs, and assertions
" but doesn't work with overlapping matches and is possibly slower
return '\%<'.(a:cnum+1).'c\%('.a:re.'\)\%>'.(a:cnum).'c'
else
" fails to match with \zs
return '\%'.(a:cnum).'c\%('.a:re.'\)'
endif
endfunction
" }}}1
function! s:process_hlend(re, cursorpos) " {{{1
" first replace all \ze with \%>{cursorpos}c
let l:re = substitute(a:re, g:matchup#re#ze,
\ a:cursorpos < 0 ? '' : '\\%>'.a:cursorpos.'c', 'g')
" next convert hlend mark to \ze
return substitute(l:re, '\V\\%(hlend\\)\\{0}', '\\ze', 'g')
endfunction
" }}}1
" initialize script variables
let s:stopline = get(g:, 'matchup_delim_stopline', 1500)
let s:basetypes = {
\ 'delim_tex': {
\ 'parser' : function('s:parser_delim_new'),
\ 'get_matching' : function('s:get_matching_delims'),
\ },
\}
let s:types = {
\ 'all' : [ s:basetypes.delim_tex ],
\ 'delim_all' : [ s:basetypes.delim_tex ],
\ 'delim_tex' : [ s:basetypes.delim_tex ],
\}
let &cpo = s:save_cpo
" vim: fdm=marker sw=2