diff options
Diffstat (limited to 'autoload/vimtex/text_obj.vim')
-rw-r--r-- | autoload/vimtex/text_obj.vim | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/autoload/vimtex/text_obj.vim b/autoload/vimtex/text_obj.vim new file mode 100644 index 00000000..27d1a8b6 --- /dev/null +++ b/autoload/vimtex/text_obj.vim @@ -0,0 +1,447 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1 + +" vimtex - LaTeX plugin for Vim +" +" Maintainer: Karl Yngve LervÄg +" Email: karl.yngve@gmail.com +" + +function! vimtex#text_obj#init_buffer() abort " {{{1 + if !g:vimtex_text_obj_enabled | return | endif + + " Note: I've permitted myself long lines here to make this more readable. + for [l:map, l:name, l:opt] in [ + \ ['c', 'commands', ''], + \ ['d', 'delimited', 'delim_all'], + \ ['e', 'delimited', 'env_tex'], + \ ['$', 'delimited', 'env_math'], + \ ['P', 'sections', ''], + \ ['m', 'items', ''], + \] + let l:optional = empty(l:opt) ? '' : ',''' . l:opt . '''' + execute printf('xnoremap <silent><buffer> <plug>(vimtex-i%s) :<c-u>call vimtex#text_obj#%s(1, 1%s)<cr>', l:map, l:name, l:optional) + execute printf('xnoremap <silent><buffer> <plug>(vimtex-a%s) :<c-u>call vimtex#text_obj#%s(0, 1%s)<cr>', l:map, l:name, l:optional) + execute printf('onoremap <silent><buffer> <plug>(vimtex-i%s) :<c-u>call vimtex#text_obj#%s(1, 0%s)<cr>', l:map, l:name, l:optional) + execute printf('onoremap <silent><buffer> <plug>(vimtex-a%s) :<c-u>call vimtex#text_obj#%s(0, 0%s)<cr>', l:map, l:name, l:optional) + endfor +endfunction + +" }}}1 + +function! vimtex#text_obj#commands(is_inner, mode) abort " {{{1 + let l:obj = {} + let l:pos_save = vimtex#pos#get_cursor() + if a:mode + call vimtex#pos#set_cursor(getpos("'>")) + endif + + " Get the delimited text object positions + for l:count in range(v:count1) + if !empty(l:obj) + call vimtex#pos#set_cursor(vimtex#pos#prev(l:obj.cmd_start)) + endif + + let l:obj_prev = l:obj + let l:obj = {} + + let l:cmd = vimtex#cmd#get_current() + if empty(l:cmd) | break | endif + + let l:pos_start = copy(l:cmd.pos_start) + let l:pos_end = l:cmd.pos_end + + if a:is_inner + let l:pos_end.lnum = l:pos_start.lnum + let l:pos_end.cnum = l:pos_start.cnum + strlen(l:cmd.name) - 1 + let l:pos_start.cnum += 1 + endif + + if a:mode + \ && vimtex#pos#equal(l:pos_start, getpos("'<")) + \ && vimtex#pos#equal(l:pos_end, getpos("'>")) + let l:pos_old = l:cmd.pos_start + call vimtex#pos#set_cursor(vimtex#pos#prev(l:pos_old)) + + let l:cmd = vimtex#cmd#get_current() + if empty(l:cmd) | break | endif + + if vimtex#pos#smaller(l:pos_old, l:cmd.pos_end) + let l:pos_start = l:cmd.pos_start + let l:pos_end = l:cmd.pos_end + + if a:is_inner + let l:pos_end.lnum = l:pos_start.lnum + let l:pos_end.cnum = l:pos_start.cnum + strlen(l:cmd.name) - 1 + let l:pos_start.cnum += 1 + endif + endif + endif + + let l:obj = { + \ 'pos_start' : l:pos_start, + \ 'pos_end' : l:pos_end, + \ 'cmd_start' : l:cmd.pos_start, + \} + endfor + + if empty(l:obj) + if empty(l:obj_prev) || g:vimtex_text_obj_variant ==# 'targets' + if a:mode + normal! gv + else + call vimtex#pos#set_cursor(l:pos_save) + endif + return + endif + let l:obj = l:obj_prev + endif + + call vimtex#pos#set_cursor(l:pos_start) + normal! v + call vimtex#pos#set_cursor(l:pos_end) +endfunction + +" }}}1 +function! vimtex#text_obj#delimited(is_inner, mode, type) abort " {{{1 + let l:object = {} + let l:prev_object = {} + let l:pos_save = vimtex#pos#get_cursor() + let l:startpos = getpos("'>") + + " Get the delimited text object positions + for l:count in range(v:count1) + if !empty(l:object) + let l:pos_next = vimtex#pos#prev( + \ a:is_inner ? l:object.open : l:object.pos_start) + + if a:mode + let l:startpos = l:pos_next + else + call vimtex#pos#set_cursor(l:pos_next) + endif + endif + + if a:mode + let l:object = s:get_sel_delimited_visual(a:is_inner, a:type, l:startpos) + else + let [l:open, l:close] = vimtex#delim#get_surrounding(a:type) + let l:object = empty(l:open) + \ ? {} : s:get_sel_delimited(l:open, l:close, a:is_inner) + endif + + if empty(l:object) + if !empty(l:prev_object) && g:vimtex_text_obj_variant !=# 'targets' + let l:object = l:prev_object + break + endif + + if a:mode + normal! gv + else + call vimtex#pos#set_cursor(l:pos_save) + endif + return + endif + + let l:prev_object = l:object + endfor + + " Handle empty inner objects + if vimtex#pos#smaller(l:object.pos_end, l:object.pos_start) + if v:operator ==# 'y' && !a:mode + return + endif + + if index(['c', 'd'], v:operator) >= 0 + call vimtex#pos#set_cursor(l:object.pos_start) + normal! ix + endif + + let l:object.pos_end = l:object.pos_start + endif + + " Apply selection + execute 'normal!' l:object.select_mode + call vimtex#pos#set_cursor(l:object.pos_start) + normal! o + call vimtex#pos#set_cursor(l:object.pos_end) +endfunction + +" }}}1 +function! vimtex#text_obj#sections(is_inner, mode) abort " {{{1 + let l:pos_save = vimtex#pos#get_cursor() + call vimtex#pos#set_cursor(vimtex#pos#next(l:pos_save)) + + " Get section border positions + let [l:pos_start, l:pos_end, l:type] + \ = s:get_sel_sections(a:is_inner, '') + if empty(l:pos_start) + call vimtex#pos#set_cursor(l:pos_save) + return + endif + + " Increase visual area if applicable + if a:mode + \ && visualmode() ==# 'V' + \ && getpos("'<")[1] == l:pos_start[0] + \ && getpos("'>")[1] == l:pos_end[0] + let [l:pos_start_new, l:pos_end_new, l:type] + \ = s:get_sel_sections(a:is_inner, l:type) + if !empty(l:pos_start_new) + let l:pos_start = l:pos_start_new + let l:pos_end = l:pos_end_new + endif + endif + + " Repeat for count + for l:count in range(v:count1 - 1) + let [l:pos_start_new, l:pos_end_new, l:type] + \ = s:get_sel_sections(a:is_inner, l:type) + + if empty(l:pos_start_new) | break | endif + let l:pos_start = l:pos_start_new + let l:pos_end = l:pos_end_new + endfor + + " Apply selection + call vimtex#pos#set_cursor(l:pos_start) + normal! V + call vimtex#pos#set_cursor(l:pos_end) +endfunction + +" }}}1 +function! vimtex#text_obj#items(is_inner, mode) abort " {{{1 + let l:pos_save = vimtex#pos#get_cursor() + + " Get section border positions + let [l:pos_start, l:pos_end] = s:get_sel_items(a:is_inner) + if empty(l:pos_start) + call vimtex#pos#set_cursor(l:pos_save) + return + endif + + " Apply selection + execute 'normal!' (v:operator ==# ':') ? visualmode() : 'v' + call vimtex#pos#set_cursor(l:pos_start) + normal! o + call vimtex#pos#set_cursor(l:pos_end) +endfunction + +" }}}1 + +function! s:get_sel_delimited_visual(is_inner, type, startpos) abort " {{{1 + if a:is_inner + call vimtex#pos#set_cursor(vimtex#pos#next(a:startpos)) + let [l:open, l:close] = vimtex#delim#get_surrounding(a:type) + if !empty(l:open) + let l:object = s:get_sel_delimited(l:open, l:close, a:is_inner) + + " Select next pair if we reached the same selection + if (l:object.select_mode ==# 'v' + \ && getpos("'<")[1:2] == l:object.pos_start + \ && getpos("'>")[1:2] == l:object.pos_end) + \ || (l:object.select_mode ==# 'V' + \ && getpos("'<")[1] == l:object.pos_start[0] + \ && getpos("'>")[1] == l:object.pos_end[0]) + call vimtex#pos#set_cursor(vimtex#pos#prev(l:open.lnum, l:open.cnum)) + let [l:open, l:close] = vimtex#delim#get_surrounding(a:type) + if empty(l:open) | return {} | endif + return s:get_sel_delimited(l:open, l:close, a:is_inner) + endif + endif + endif + + call vimtex#pos#set_cursor(a:startpos) + let [l:open, l:close] = vimtex#delim#get_surrounding(a:type) + if empty(l:open) | return {} | endif + let l:object = s:get_sel_delimited(l:open, l:close, a:is_inner) + if a:is_inner | return l:object | endif + + " Select next pair if we reached the same selection + if (l:object.select_mode ==# 'v' + \ && getpos("'<")[1:2] == l:object.pos_start + \ && getpos("'>")[1:2] == l:object.pos_end) + \ || (l:object.select_mode ==# 'V' + \ && getpos("'<")[1] == l:object.pos_start[0] + \ && getpos("'>")[1] == l:object.pos_end[0]) + call vimtex#pos#set_cursor(vimtex#pos#prev(l:open.lnum, l:open.cnum)) + let [l:open, l:close] = vimtex#delim#get_surrounding(a:type) + if empty(l:open) | return {} | endif + return s:get_sel_delimited(l:open, l:close, a:is_inner) + endif + + return l:object +endfunction + +" }}}1 +function! s:get_sel_delimited(open, close, is_inner) abort " {{{1 + " Determine if operator is linewise + let l:linewise = index(g:vimtex_text_obj_linewise_operators, v:operator) >= 0 + + let [l1, c1, l2, c2] = [a:open.lnum, a:open.cnum, a:close.lnum, a:close.cnum] + + " Adjust the borders + if a:is_inner + if has_key(a:open, 'env_cmd') && !empty(a:open.env_cmd) + let l1 = a:open.env_cmd.pos_end.lnum + let c1 = a:open.env_cmd.pos_end.cnum+1 + else + let c1 += len(a:open.match) + endif + let c2 -= 1 + + let l:is_inline = (l2 - l1) > 1 + \ && match(strpart(getline(l1), c1), '^\s*$') >= 0 + \ && match(strpart(getline(l2), 0, c2), '^\s*$') >= 0 + + if l:is_inline + let l1 += 1 + let c1 = strlen(matchstr(getline(l1), '^\s*')) + 1 + let l2 -= 1 + let c2 = strlen(getline(l2)) + if c2 == 0 && !l:linewise + let l2 -= 1 + let c2 = len(getline(l2)) + 1 + endif + elseif c2 == 0 + let l2 -= 1 + let c2 = len(getline(l2)) + 1 + endif + else + let c2 += len(a:close.match) - 1 + + let l:is_inline = (l2 - l1) > 1 + \ && match(strpart(getline(l1), 0, c1-1), '^\s*$') >= 0 + \ && match(strpart(getline(l2), 0, c2), '^\s*$') >= 0 + endif + + return { + \ 'open' : a:open, + \ 'close' : a:close, + \ 'pos_start' : [l1, c1], + \ 'pos_end' : [l2, c2], + \ 'is_inline' : l:is_inline, + \ 'select_mode' : l:is_inline && l:linewise + \ ? 'V' : (v:operator ==# ':') ? visualmode() : 'v', + \} +endfunction + +" }}}1 +function! s:get_sel_sections(is_inner, type) abort " {{{1 + let l:pos_save = vimtex#pos#get_cursor() + let l:min_val = get(s:section_to_val, a:type) + + " Get the position of the section start + while 1 + let l:pos_start = searchpos(s:section_search, 'bcnW') + if l:pos_start == [0, 0] | return [[], [], ''] | endif + + let l:sec_type = matchstr(getline(l:pos_start[0]), s:section_search) + let l:sec_val = s:section_to_val[l:sec_type] + + if !empty(a:type) + if l:sec_val >= l:min_val + call vimtex#pos#set_cursor(vimtex#pos#prev(l:pos_start)) + else + call vimtex#pos#set_cursor(l:pos_save) + break + endif + else + break + endif + endwhile + + " Get the position of the section end + while 1 + let l:pos_end = searchpos(s:section_search, 'nW') + if l:pos_end == [0, 0] + let l:pos_end = [line('$')+1, 1] + break + endif + + let l:cur_val = s:section_to_val[ + \ matchstr(getline(l:pos_end[0]), s:section_search)] + if l:cur_val <= l:sec_val + let l:pos_end[0] -= 1 + break + endif + + call vimtex#pos#set_cursor(l:pos_end) + endwhile + + " Adjust for inner text object + if a:is_inner + call vimtex#pos#set_cursor(l:pos_start[0]+1, l:pos_start[1]) + let l:pos_start = searchpos('\S', 'cnW') + call vimtex#pos#set_cursor(l:pos_end) + let l:pos_end = searchpos('\S', 'bcnW') + elseif l:sec_val ==# 'document' + let l:pos_start = [l:pos_start[0]+1, l:pos_start[1]] + endif + + return [l:pos_start, l:pos_end, l:sec_type] +endfunction + +" }}}1 +function! s:get_sel_items(is_inner) abort " {{{1 + let l:pos_cursor = vimtex#pos#get_cursor() + + " Find previous \item + call vimtex#pos#set_cursor(l:pos_cursor[0], 1) + let l:pos_start = searchpos('^\s*\\item\S*', 'bcnWz') + if l:pos_start == [0, 0] | return [[], []] | endif + + " Find end of current \item + call vimtex#pos#set_cursor(l:pos_start) + let l:pos_end = searchpos('\ze\n\s*\%(\\item\|\\end{itemize}\)', 'nW') + if l:pos_end == [0, 0] + \ || vimtex#pos#val(l:pos_cursor) > vimtex#pos#val(l:pos_end) + return [[], []] + endif + + " Adjust for outer text object + if a:is_inner + let l:pos_start[1] = searchpos('^\s*\\item\S*\s\?', 'cne')[1] + 1 + let l:pos_end[1] = col([l:pos_end[0], '$']) - 1 + endif + + return [l:pos_start, l:pos_end] +endfunction + +" }}}1 + + +" {{{1 Initialize module + +" Pattern to match section/chapter/... +let s:section_search = '\v%(%(\\@<!%(\\\\)*)@<=\%.*)@<!\s*\\\zs(' + \ . join([ + \ '%(sub)?paragraph>', + \ '%(sub)*section>', + \ 'chapter>', + \ 'part>', + \ 'appendix>', + \ '%(front|back|main)matter>', + \ '%(begin|end)\{\zsdocument\ze\}' + \ ], '|') + \ .')' + +" Dictionary to give values to sections in order to compare them +let s:section_to_val = { + \ 'document': 0, + \ 'frontmatter': 1, + \ 'mainmatter': 1, + \ 'appendix': 1, + \ 'backmatter': 1, + \ 'part': 1, + \ 'chapter': 2, + \ 'section': 3, + \ 'subsection': 4, + \ 'subsubsection': 5, + \ 'paragraph': 6, + \ 'subparagraph': 7, + \} + +" }}}1 + +endif |