This was a tough one as:
- I couldn't get
getpos('``<')
to work
searchpair*()
don't work when the opening and the closing signs are not identical. Here, two cases:
- with strings,
va'
and va"
can be used
- with other possible pairs like
_
and *
in markdown, *
and |
in vim help files, we have to use search()
twice
searchpairpos()
returns a byte offset and not a column position, this means that we need to count characters in UTF-8 documents -- as I didn't want to use setpos()
but instead move the cursor in a redoable way
- sometimes we have pairs like angle-brackets that can be used for other things (like ordered comparisons), and they can introduce new jump points (I have no generic solution for this one).
Note that I've integrated your idea in lh-brackets where I'm maintaining the list of registered pairs automatically.
I've ended up with two functions: one that searches for the compatible pairs (used to define block), and one called from various mappings to actually move the cursor. NB: I won't reproduce the code from lh-vim-lib functions used:
" Function: s:outer_blocks() {{{2
function! s:outer_blocks() abort
let crt_pairs = s:GetPairs(0) " global pairs
call extend(copy(crt_pairs), s:GetPairs(1), 'force') " buffer-local ones
let matches = {}
for p in crt_pairs
if p[0] != p[1] " searchpos doesn't work in that case
let pos = searchpairpos(p[0], '', p[1], 'cWn', "lh#syntax#is_a_comment('.')")
call s:Verbose('Testing searchpos(%1) -> %2', p, pos)
elseif p[0] =~ '["'']'
" Stuff which can be checked with vi', vi"
let crt_pos = getpos('.')
let cleanup = lh#on#exit()
\.restore('@a')
\.register('call setpos(".", '.string(crt_pos).')')
try
let @a = ''
silent! exe 'normal! "aya'.p[0]
" 2 chars are to be expected for open and close
if lh#encoding#strlen(@a) >= lh#encoding#strlen(p[0].p[1])
" In two steps because it may fail
" -- it shouldn't though thanks to len(@a) >= 2 * len(open)
exe 'normal! v'
silent! exe 'normal! a'.p[0]
silent! exe "normal! \<esc>"
let pos = getpos('.')[1:2]
else
let pos = [0,0]
endif
if 0
" getpos doesn't seem to work...
let pos = getpos('`>')[1:2]
if getpos('`<')[1:2] == pos
let pos = [0,0]
endif
endif
call s:Verbose('Testing va%1 -> %2 - %3', p[0], pos, @a)
finally
call cleanup.finalize()
endtry
else
let pos = searchpos(p[0], 'cWnb', 'lh#syntax#is_a_comment(".")')
if pos != [0,0]
let pos = searchpos(p[1], 'cWn', 'lh#syntax#is_a_comment(".")')
endif
call s:Verbose('Testing /%1 -> %2', p[0], pos)
endif
if pos != [0,0]
let matches[p[0]] = pos
endif
endfor
call s:Verbose('Containing bracket pairs: %1', matches)
return matches
endfunction
"------------------------------------------------------------------------
" Function: lh#brackets#jump_outside(param) {{{2
" In this flavour, we don't expect to be just before the current closing
" character. Instead, search for the next character that closes the current
" scope.
function! lh#brackets#jump_outside(param) abort
let mode = get(a:param, 'mode')
let matches = s:outer_blocks()
if empty(matches)
call s:Verbose('The cursor doesn''t belong to any block')
return ''
endif
let m2 = map(values(matches), '[0]+v:val')
call sort(m2, 'lh#position#compare')
let crt_pos = getpos('.')
let dest = m2[0]+[0]
" call assert_true(lh#position#is_before(crt_pos, dest))
" Insert mode, :map-<expr>
if mode == 'i' && crt_pos[1] == dest[1] " same-line
" Need to know how many characters does this really represent, not the
" number of bytes!
let text = lh#position#extract(crt_pos, dest)
let offset = lh#encoding#strlen(text)+1
" produces a redo-able move sequence
return lh#map#_move_cursor_on_the_current_line(offset)
else
call setpos('.', dest)
return mode == 'i' ? "\<Right>" : "a"
endif
endfunction
inoremap <silent> <Plug>MarkersJumpOutside <C-R>=lh#brackets#jump_outside({'mode': 'i'})<cr>
nnoremap <silent> <Plug>MarkersJumpOutside @=lh#brackets#jump_outside({'mode': 'n'})<cr>
xnoremap <silent> <Plug>MarkersJumpOutside <C-\><C-N>@=lh#brackets#jump_outside({'mode': 'x'})<cr>
smap <silent> <Plug>MarkersJumpOutside <C-\><C-N>a<Plug>MarkersJumpOutside
searchpair()
on more than one delimiter at a time, there are two options: 1- Use[([{<"]
and[)\]}>"]
, but it may give odd result on unbalanced pairs. 2- Run as manysearchpairpos('{', '}', 'n')
as required (every time with a different brackets pair), and see which one give the closest result. Use that one to jump past what you're looking for.searchpair*()
function don't work with equal{start}
and{end}
parameters. This means that while you could use it to detect the end of the current outer block, if won't work to detect the end of the current string.({[<
but not for single and double quotes. Too bad.