diff options
Diffstat (limited to '')
-rw-r--r-- | autoload/vimtex/text_obj.vim | 447 | ||||
-rw-r--r-- | autoload/vimtex/text_obj/cmdtargets.vim | 85 | ||||
-rw-r--r-- | autoload/vimtex/text_obj/envtargets.vim | 110 | ||||
-rw-r--r-- | autoload/vimtex/text_obj/targets.vim | 49 |
4 files changed, 691 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 diff --git a/autoload/vimtex/text_obj/cmdtargets.vim b/autoload/vimtex/text_obj/cmdtargets.vim new file mode 100644 index 00000000..611285c7 --- /dev/null +++ b/autoload/vimtex/text_obj/cmdtargets.vim @@ -0,0 +1,85 @@ +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#cmdtargets#new(args) " {{{1 + return { + \ 'genFuncs': { + \ 'c': function('vimtex#text_obj#cmdtargets#current'), + \ 'n': function('vimtex#text_obj#cmdtargets#next'), + \ 'l': function('vimtex#text_obj#cmdtargets#last'), + \ }, + \ 'modFuncs': { + \ 'i': [function('vimtex#text_obj#cmdtargets#inner'), + \ function('targets#modify#drop')], + \ 'a': [function('targets#modify#keep')], + \ 'I': [function('vimtex#text_obj#cmdtargets#inner'), + \ function('targets#modify#shrink')], + \ 'A': [function('targets#modify#expand')], + \ }} +endfunction + +" }}}1 +function! vimtex#text_obj#cmdtargets#current(args, opts, state) " {{{1 + let target = s:select(a:opts.first ? 1 : 2) + call target.cursorE() " keep going from right end + return target +endfunction + +" }}}1 +function! vimtex#text_obj#cmdtargets#next(args, opts, state) " {{{1 + if targets#util#search('\\\S*{', 'W') > 0 + return targets#target#withError('no target') + endif + + let oldpos = getpos('.') + let target = s:select(1) + call setpos('.', oldpos) + return target +endfunction + +" }}}1 +function! vimtex#text_obj#cmdtargets#last(args, opts, state) " {{{1 + " Move to the last non-surrounding cmd + if targets#util#search('\\\S\+{\_.\{-}}', 'bWe') > 0 + return targets#target#withError('no target') + endif + + let oldpos = getpos('.') + let target = s:select(1) + call setpos('.', oldpos) + return target +endfunction + +" }}}1 +function! vimtex#text_obj#cmdtargets#inner(target, args) " {{{1 + if a:target.state().isInvalid() + return + endif + + call a:target.cursorS() + silent! normal! f{ + call a:target.setS() +endfunction + +" }}}1 + +function! s:select(count) " {{{1 + " Try to select command + silent! execute 'keepjumps normal v'.a:count."\<Plug>(vimtex-ac)v" + let target = targets#target#fromVisualSelection() + + if target.sc == target.ec && target.sl == target.el + return targets#target#withError('tex_cmd select') + endif + + return target +endfunction + +" }}}1 + +endif diff --git a/autoload/vimtex/text_obj/envtargets.vim b/autoload/vimtex/text_obj/envtargets.vim new file mode 100644 index 00000000..10e3ae93 --- /dev/null +++ b/autoload/vimtex/text_obj/envtargets.vim @@ -0,0 +1,110 @@ +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#envtargets#new(args) " {{{1 + return { + \ 'genFuncs': { + \ 'c': function('vimtex#text_obj#envtargets#current'), + \ 'n': function('vimtex#text_obj#envtargets#next'), + \ 'l': function('vimtex#text_obj#envtargets#last'), + \ }, + \ 'modFuncs': { + \ 'i': [function('vimtex#text_obj#envtargets#inner'), function('targets#modify#drop')], + \ 'a': [function('targets#modify#keep')], + \ 'I': [function('vimtex#text_obj#envtargets#inner'), function('targets#modify#shrink')], + \ 'A': [function('vimtex#text_obj#envtargets#expand')], + \ }} +endfunction + +" }}}1 +function! vimtex#text_obj#envtargets#current(args, opts, state) " {{{1 + let target = s:select(a:opts.first ? 1 : 2) + call target.cursorE() " keep going from right end + return target +endfunction + +" }}}1 +function! vimtex#text_obj#envtargets#next(args, opts, state) " {{{1 + if targets#util#search('\\begin{.*}', 'W') > 0 + return targets#target#withError('no target') + endif + + let oldpos = getpos('.') + let target = s:select(1) + call setpos('.', oldpos) + return target +endfunction + +" }}}1 +function! vimtex#text_obj#envtargets#last(args, opts, state) " {{{1 + if targets#util#search('\\end{.*}', 'bW') > 0 + return targets#target#withError('no target') + endif + + let oldpos = getpos('.') + let target = s:select(1) + call setpos('.', oldpos) + return target +endfunction + +" }}}1 +function! vimtex#text_obj#envtargets#inner(target, args) " {{{1 + call a:target.cursorS() + call a:target.searchposS('\\begin{.*}', 'Wce') + call a:target.cursorE() + call a:target.searchposE('\\end{.*}', 'bWc') +endfunction + +" }}}1 +function! vimtex#text_obj#envtargets#expand(target, args) " {{{1 + " Based on targets#modify#expand() from + " $VIMMRUNTIME/autoload/targets/modify.vim + + " Add outer whitespace + if a:0 == 0 || a:1 ==# '>' + call a:target.cursorE() + let [line, column] = searchpos('\S\|$', '') + if line > a:target.el || (line > 0 && column-1 > a:target.ec) + " non whitespace or EOL after trailing whitespace found + " not counting whitespace directly after end + return a:target.setE(line, column-1) + endif + endif + + if a:0 == 0 || a:1 ==# '<' + call a:target.cursorS() + let [line, column] = searchpos('\S', 'b') + if line < a:target.sl + return a:target.setS(line+1, 0) + elseif line > 0 + " non whitespace before leading whitespace found + return a:target.setS(line, column+1) + endif + " only whitespace in front of start + " include all leading whitespace from beginning of line + let a:target.sc = 1 + endif +endfunction + +" }}}1 + +function! s:select(count) " {{{1 + " Try to select environment + silent! execute 'keepjumps normal v'.a:count."\<Plug>(vimtex-ae)v" + let target = targets#target#fromVisualSelection() + + if target.sc == target.ec && target.sl == target.el + return targets#target#withError('tex_env select') + endif + + return target +endfunction + +" }}}1 + +endif diff --git a/autoload/vimtex/text_obj/targets.vim b/autoload/vimtex/text_obj/targets.vim new file mode 100644 index 00000000..6bbdf710 --- /dev/null +++ b/autoload/vimtex/text_obj/targets.vim @@ -0,0 +1,49 @@ +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#targets#enabled() abort " {{{1 + return exists('g:loaded_targets') + \ && ( (type(g:loaded_targets) == type(0) && g:loaded_targets) + \ || (type(g:loaded_targets) == type('') && !empty(g:loaded_targets))) + \ && ( g:vimtex_text_obj_variant ==# 'auto' + \ || g:vimtex_text_obj_variant ==# 'targets') +endfunction + +" }}}1 +function! vimtex#text_obj#targets#init() abort " {{{1 + let g:vimtex_text_obj_variant = 'targets' + + " Create intermediate mappings + omap <expr> <plug>(vimtex-targets-i) targets#e('o', 'i', 'i') + xmap <expr> <plug>(vimtex-targets-i) targets#e('x', 'i', 'i') + omap <expr> <plug>(vimtex-targets-a) targets#e('o', 'a', 'a') + xmap <expr> <plug>(vimtex-targets-a) targets#e('x', 'a', 'a') + + augroup vimtex_targets + autocmd! + autocmd User targets#sources call s:init_sources() + autocmd User targets#mappings#plugin call s:init_mappings() + augroup END +endfunction + +" }}}1 + +function! s:init_mappings() abort " {{{1 + call targets#mappings#extend({'e': {'tex_env': [{}]}}) + call targets#mappings#extend({'c': {'tex_cmd': [{}]}}) +endfunction + +" }}}1 +function! s:init_sources() abort " {{{1 + call targets#sources#register('tex_env', function('vimtex#text_obj#envtargets#new')) + call targets#sources#register('tex_cmd', function('vimtex#text_obj#cmdtargets#new')) +endfunction + +" }}}1 + +endif |