diff options
Diffstat (limited to 'ftplugin/latex-box/motion.vim')
-rw-r--r-- | ftplugin/latex-box/motion.vim | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/ftplugin/latex-box/motion.vim b/ftplugin/latex-box/motion.vim new file mode 100644 index 00000000..58a6fb42 --- /dev/null +++ b/ftplugin/latex-box/motion.vim @@ -0,0 +1,518 @@ +" LaTeX Box motion functions + +" Motion options {{{ +" Opening and closing patterns +if !exists('g:LatexBox_open_pats') + let g:LatexBox_open_pats = [ '\\{','{','\\(','(','\\\[','\[', + \ '\\begin\s*{.\{-}}', '\\left\s*\%([^\\]\|\\.\|\\\a*\)'] + let g:LatexBox_close_pats = [ '\\}','}','\\)',')','\\\]','\]', + \ '\\end\s*{.\{-}}', '\\right\s*\%([^\\]\|\\.\|\\\a*\)'] +endif +" }}} + +" HasSyntax {{{ +" s:HasSyntax(syntaxName, [line], [col]) +function! s:HasSyntax(syntaxName, ...) + let line = a:0 >= 1 ? a:1 : line('.') + let col = a:0 >= 2 ? a:2 : col('.') + return index(map(synstack(line, col), + \ 'synIDattr(v:val, "name") == "' . a:syntaxName . '"'), + \ 1) >= 0 +endfunction +" }}} + +" Search and Skip Comments {{{ +" s:SearchAndSkipComments(pattern, [flags], [stopline]) +function! s:SearchAndSkipComments(pat, ...) + let flags = a:0 >= 1 ? a:1 : '' + let stopline = a:0 >= 2 ? a:2 : 0 + let saved_pos = getpos('.') + + " search once + let ret = search(a:pat, flags, stopline) + + if ret + " do not match at current position if inside comment + let flags = substitute(flags, 'c', '', 'g') + + " keep searching while in comment + while LatexBox_InComment() + let ret = search(a:pat, flags, stopline) + if !ret + break + endif + endwhile + endif + + if !ret + " if no match found, restore position + call setpos('.', saved_pos) + endif + + return ret +endfunction +" }}} + +" Finding Matching Pair {{{ +function! s:FindMatchingPair(mode) + + if a:mode =~ 'h\|i' + 2match none + elseif a:mode == 'v' + normal! gv + endif + + if LatexBox_InComment() | return | endif + + " open/close pairs (dollars signs are treated apart) + let dollar_pat = '\$' + let notbslash = '\%(\\\@<!\%(\\\\\)*\)\@<=' + let notcomment = '\%(\%(\\\@<!\%(\\\\\)*\)\@<=%.*\)\@<!' + let anymatch = '\(' + \ . join(g:LatexBox_open_pats + g:LatexBox_close_pats, '\|') + \ . '\|' . dollar_pat . '\)' + + let lnum = line('.') + let cnum = searchpos('\A', 'cbnW', lnum)[1] + " if the previous char is a backslash + if strpart(getline(lnum), 0, cnum-1) !~ notbslash . '$' | let cnum = cnum-1 | endif + let delim = matchstr(getline(lnum), '\C^'. anymatch , cnum - 1) + + if empty(delim) || strlen(delim)+cnum-1< col('.') + if a:mode =~ 'n\|v\|o' + " if not found, search forward + let cnum = match(getline(lnum), '\C'. anymatch , col('.') - 1) + 1 + if cnum == 0 | return | endif + call cursor(lnum, cnum) + let delim = matchstr(getline(lnum), '\C^'. anymatch , cnum - 1) + elseif a:mode =~ 'i' + " if not found, move one char bacward and search + let cnum = searchpos('\A', 'bnW', lnum)[1] + " if the previous char is a backslash + if strpart(getline(lnum), 0, cnum-1) !~ notbslash . '$' | let cnum = cnum-1 | endif + let delim = matchstr(getline(lnum), '\C^'. anymatch , cnum - 1) + if empty(delim) || strlen(delim)+cnum< col('.') | return | endif + elseif a:mode =~ 'h' + return + endif + endif + + if delim =~ '^\$' + + " match $-pairs + " check if next character is in inline math + let [lnum0, cnum0] = searchpos('.', 'nW') + if lnum0 && s:HasSyntax('texMathZoneX', lnum0, cnum0) + let [lnum2, cnum2] = searchpos(notcomment . notbslash. dollar_pat, 'nW', line('w$')*(a:mode =~ 'h\|i') , 200) + else + let [lnum2, cnum2] = searchpos('\%(\%'. lnum . 'l\%' . cnum . 'c\)\@!'. notcomment . notbslash . dollar_pat, 'bnW', line('w0')*(a:mode =~ 'h\|i') , 200) + endif + + if a:mode =~ 'h\|i' + execute '2match MatchParen /\%(\%' . lnum . 'l\%' . cnum . 'c\$' . '\|\%' . lnum2 . 'l\%' . cnum2 . 'c\$\)/' + elseif a:mode =~ 'n\|v\|o' + call cursor(lnum2,cnum2) + endif + + else + " match other pairs + for i in range(len(g:LatexBox_open_pats)) + let open_pat = notbslash . g:LatexBox_open_pats[i] + let close_pat = notbslash . g:LatexBox_close_pats[i] + + if delim =~# '^' . open_pat + " if on opening pattern, search for closing pattern + let [lnum2, cnum2] = searchpairpos('\C' . open_pat, '', '\C' + \ . close_pat, 'nW', 'LatexBox_InComment()', + \ line('w$')*(a:mode =~ 'h\|i') , 200) + if a:mode =~ 'h\|i' + execute '2match MatchParen /\%(\%' . lnum . 'l\%' . cnum + \ . 'c' . g:LatexBox_open_pats[i] . '\|\%' + \ . lnum2 . 'l\%' . cnum2 . 'c' + \ . g:LatexBox_close_pats[i] . '\)/' + elseif a:mode =~ 'n\|v\|o' + call cursor(lnum2,cnum2) + if strlen(close_pat)>1 && a:mode =~ 'o' + call cursor(lnum2, matchend(getline('.'), '\C' + \ . close_pat, col('.')-1)) + endif + endif + break + elseif delim =~# '^' . close_pat + " if on closing pattern, search for opening pattern + let [lnum2, cnum2] = searchpairpos('\C' . open_pat, '', + \ '\C\%(\%'. lnum . 'l\%' . cnum . 'c\)\@!' + \ . close_pat, 'bnW', 'LatexBox_InComment()', + \ line('w0')*(a:mode =~ 'h\|i') , 200) + if a:mode =~ 'h\|i' + execute '2match MatchParen /\%(\%' . lnum2 . 'l\%' . cnum2 + \ . 'c' . g:LatexBox_open_pats[i] . '\|\%' + \ . lnum . 'l\%' . cnum . 'c' + \ . g:LatexBox_close_pats[i] . '\)/' + elseif a:mode =~ 'n\|v\|o' + call cursor(lnum2,cnum2) + endif + break + endif + endfor + + endif +endfunction + +" Allow to disable functionality if desired +if !exists('g:LatexBox_loaded_matchparen') + " Disable matchparen autocommands + augroup LatexBox_HighlightPairs + autocmd BufEnter * if !exists("g:loaded_matchparen") || !g:loaded_matchparen | runtime plugin/matchparen.vim | endif + autocmd BufEnter *.tex 3match none | unlet! g:loaded_matchparen | au! matchparen + autocmd! CursorMoved *.tex call s:FindMatchingPair('h') + autocmd! CursorMovedI *.tex call s:FindMatchingPair('i') + augroup END +endif + +" Use LatexBox'es FindMatchingPair as '%' (enable jump between e.g. $'s) +nnoremap <silent> <Plug>LatexBox_JumpToMatch :call <SID>FindMatchingPair('n')<CR> +vnoremap <silent> <Plug>LatexBox_JumpToMatch :call <SID>FindMatchingPair('v')<CR> +onoremap <silent> <Plug>LatexBox_JumpToMatch v:call <SID>FindMatchingPair('o')<CR> + +" }}} + +" select inline math {{{ +" s:SelectInlineMath(seltype) +" where seltype is either 'inner' or 'outer' +function! s:SelectInlineMath(seltype) + + let dollar_pat = '\\\@<!\$' + + if s:HasSyntax('texMathZoneX') + call s:SearchAndSkipComments(dollar_pat, 'cbW') + elseif getline('.')[col('.') - 1] == '$' + call s:SearchAndSkipComments(dollar_pat, 'bW') + else + return + endif + + if a:seltype == 'inner' + normal! l + endif + + if visualmode() ==# 'V' + normal! V + else + normal! v + endif + + call s:SearchAndSkipComments(dollar_pat, 'W') + + if a:seltype == 'inner' + normal! h + endif +endfunction + +vnoremap <silent> <Plug>LatexBox_SelectInlineMathInner + \ :<C-U>call <SID>SelectInlineMath('inner')<CR> +vnoremap <silent> <Plug>LatexBox_SelectInlineMathOuter + \ :<C-U>call <SID>SelectInlineMath('outer')<CR> +" }}} + +" select current environment {{{ +function! s:SelectCurrentEnv(seltype) + let [env, lnum, cnum, lnum2, cnum2] = LatexBox_GetCurrentEnvironment(1) + call cursor(lnum, cnum) + if a:seltype == 'inner' + if env =~ '^\' + call search('\\.\_\s*\S', 'eW') + else + call search('}\(\_\s*\[\_[^]]*\]\)\?\_\s*\S', 'eW') + endif + endif + if visualmode() ==# 'V' + normal! V + else + normal! v + endif + call cursor(lnum2, cnum2) + if a:seltype == 'inner' + call search('\S\_\s*', 'bW') + else + if env =~ '^\' + normal! l + else + call search('}', 'eW') + endif + endif +endfunction +vnoremap <silent> <Plug>LatexBox_SelectCurrentEnvInner :<C-U>call <SID>SelectCurrentEnv('inner')<CR> +vnoremap <silent> <Plug>LatexBox_SelectCurrentEnvOuter :<C-U>call <SID>SelectCurrentEnv('outer')<CR> +" }}} + +" Jump to the next braces {{{ +" +function! LatexBox_JumpToNextBraces(backward) + let flags = '' + if a:backward + normal h + let flags .= 'b' + else + let flags .= 'c' + endif + if search('[][}{]', flags) > 0 + normal l + endif + let prev = strpart(getline('.'), col('.') - 2, 1) + let next = strpart(getline('.'), col('.') - 1, 1) + if next =~ '[]}]' && prev !~ '[][{}]' + return "\<Right>" + else + return '' + endif +endfunction +" }}} + +" Table of Contents {{{ + +" Special UTF-8 conversion +function! s:ConvertBack(line) + let line = a:line + if !exists('g:LatexBox_plaintext_toc') + let line = substitute(line, "\\\\IeC\s*{\\\\'a}", 'á', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`a}", 'à', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^a}", 'à', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨a}", 'ä', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"a}", 'ä', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'e}", 'é', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`e}", 'è', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^e}", 'ê', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨e}", 'ë', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"e}", 'ë', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'i}", 'í', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`i}", 'î', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^i}", 'ì', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨i}", 'ï', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"i}", 'ï', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'o}", 'ó', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`o}", 'ò', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^o}", 'ô', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨o}", 'ö', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"o}", 'ö', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'u}", 'ú', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`u}", 'ù', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^u}", 'û', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨u}", 'ü', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"u}", 'ü', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'A}", 'Á', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`A}", 'À', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^A}", 'À', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨A}", 'Ä', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"A}", 'Ä', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'E}", 'É', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`E}", 'È', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^E}", 'Ê', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨E}", 'Ë', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"E}", 'Ë', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'I}", 'Í', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`I}", 'Î', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^I}", 'Ì', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨I}", 'Ï', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"I}", 'Ï', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'O}", 'Ó', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`O}", 'Ò', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^O}", 'Ô', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨O}", 'Ö', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"O}", 'Ö', 'g') + + let line = substitute(line, "\\\\IeC\s*{\\\\'U}", 'Ú', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\`U}", 'Ù', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\^U}", 'Û', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\¨U}", 'Ü', 'g') + let line = substitute(line, "\\\\IeC\s*{\\\\\"U}", 'Ü', 'g') + else + " substitute stuff like '\IeC{\"u}' (utf-8 umlauts in section heading) + " to plain 'u' + let line = substitute(line, "\\\\IeC\s*{\\\\.\\(.\\)}", '\1', 'g') + endif + return line +endfunction + +function! s:ReadTOC(auxfile, texfile, ...) + let texfile = a:texfile + let prefix = fnamemodify(a:auxfile, ':p:h') + + if a:0 != 2 + let toc = [] + let fileindices = { texfile : [] } + else + let toc = a:1 + let fileindices = a:2 + let fileindices[ texfile ] = [] + endif + + for line in readfile(a:auxfile) + let included = matchstr(line, '^\\@input{\zs[^}]*\ze}') + if included != '' + " append the input TOX to `toc` and `fileindices` + let newaux = prefix . '/' . included + let newtex = fnamemodify(fnamemodify(newaux, ':t:r') . '.tex', ':p') + call s:ReadTOC(newaux, newtex, toc, fileindices) + continue + endif + + " Parse statements like: + " \@writefile{toc}{\contentsline {section}{\numberline {secnum}Section Title}{pagenumber}} + " \@writefile{toc}{\contentsline {section}{\tocsection {}{1}{Section Title}}{pagenumber}} + " \@writefile{toc}{\contentsline {section}{\numberline {secnum}Section Title}{pagenumber}{otherstuff}} + + let line = matchstr(line, + \ '\\@writefile{toc}{\\contentsline\s*\zs.*\ze}\s*$') + if empty(line) + continue + endif + + let tree = LatexBox_TexToTree(line) + + if len(tree) < 3 + " unknown entry type: just skip it + continue + endif + + " parse level + let level = tree[0][0] + " parse page + if !empty(tree[2]) + let page = tree[2][0] + else + let page = '' + endif + " parse section number + if len(tree[1]) > 3 && empty(tree[1][1]) + call remove(tree[1], 1) + endif + if len(tree[1]) > 1 + if !empty(tree[1][1]) + let secnum = LatexBox_TreeToTex(tree[1][1]) + let secnum = s:ConvertBack(secnum) + let secnum = substitute(secnum, '\\\S\+\s', '', 'g') + let secnum = substitute(secnum, '\\\S\+{\(.\{-}\)}', '\1', 'g') + let secnum = substitute(secnum, '^{\+\|}\+$', '', 'g') + endif + let tree = tree[1][2:] + else + let secnum = '' + let tree = tree[1] + endif + " parse section title + let text = LatexBox_TreeToTex(tree) + let text = s:ConvertBack(text) + let text = substitute(text, '^{\+\|}\+$', '', 'g') + + " add TOC entry + call add(fileindices[texfile], len(toc)) + call add(toc, {'file': texfile, + \ 'level': level, + \ 'number': secnum, + \ 'text': text, + \ 'page': page}) + endfor + + return [toc, fileindices] + +endfunction + +function! LatexBox_TOC(...) + + " Check if window already exists + let winnr = bufwinnr(bufnr('LaTeX TOC')) + if winnr >= 0 + if a:0 == 0 + silent execute winnr . 'wincmd w' + else + " Supplying an argument to this function causes toggling instead + " of jumping to the TOC window + if g:LatexBox_split_resize + silent exe "set columns-=" . g:LatexBox_split_width + endif + silent execute 'bwipeout' . bufnr('LaTeX TOC') + endif + return + endif + + " Read TOC + let [toc, fileindices] = s:ReadTOC(LatexBox_GetAuxFile(), + \ LatexBox_GetMainTexFile()) + let calling_buf = bufnr('%') + + " Find closest section in current buffer + let closest_index = s:FindClosestSection(toc,fileindices) + + " Create TOC window and set local settings + if g:LatexBox_split_resize + silent exe "set columns+=" . g:LatexBox_split_width + endif + silent exe g:LatexBox_split_side g:LatexBox_split_width . 'vnew LaTeX\ TOC' + let b:toc = toc + let b:toc_numbers = 1 + let b:calling_win = bufwinnr(calling_buf) + setlocal filetype=latextoc + + " Add TOC entries and jump to the closest section + for entry in toc + call append('$', entry['number'] . "\t" . entry['text']) + endfor + if !g:LatexBox_toc_hidehelp + call append('$', "") + call append('$', "<Esc>/q: close") + call append('$', "<Space>: jump") + call append('$', "<Enter>: jump and close") + call append('$', "s: hide numbering") + endif + 0delete _ + + execute 'normal! ' . (closest_index + 1) . 'G' + + " Lock buffer + setlocal nomodifiable +endfunction + +" Binary search for the closest section +" return the index of the TOC entry +function! s:FindClosestSection(toc, fileindices) + let file = expand('%:p') + if !has_key(a:fileindices, file) + echoe 'Current file is not included in main tex file ' . LatexBox_GetMainTexFile() . '.' + endif + + let imax = len(a:fileindices[file]) + let imin = 0 + while imin < imax - 1 + let i = (imax + imin) / 2 + let tocindex = a:fileindices[file][i] + let entry = a:toc[tocindex] + let titlestr = entry['text'] + let titlestr = escape(titlestr, '\') + let titlestr = substitute(titlestr, ' ', '\\_\\s\\+', 'g') + let [lnum, cnum] = searchpos('\\' . entry['level'] . '\_\s*{' . titlestr . '}', 'nW') + if lnum + let imax = i + else + let imin = i + endif + endwhile + + return a:fileindices[file][imin] +endfunction +" }}} + +" TOC Command {{{ +command! LatexTOC call LatexBox_TOC() +command! LatexTOCToggle call LatexBox_TOC(1) +" }}} + +" vim:fdm=marker:ff=unix:noet:ts=4:sw=4 |