" LaTeX Box completion setlocal omnifunc=LatexBox_Complete " Wrap {{{ function! s:GetSID() return matchstr(expand(''), '\zs\d\+_\ze.*$') endfunction let s:SID = s:GetSID() function! s:SIDWrap(func) return s:SID . a:func endfunction " }}} " Completion {{{ if !exists('g:LatexBox_completion_close_braces') let g:LatexBox_completion_close_braces = 1 endif if !exists('g:LatexBox_bibtex_wild_spaces') let g:LatexBox_bibtex_wild_spaces = 1 endif if !exists('g:LatexBox_cite_pattern') let g:LatexBox_cite_pattern = '\C\\\a*cite\a*\*\?\(\[[^\]]*\]\)*\_\s*{' endif if !exists('g:LatexBox_ref_pattern') let g:LatexBox_ref_pattern = '\C\\v\?\(eq\|page\|[cC]\)\?ref\*\?\_\s*{' endif if !exists('g:LatexBox_completion_environments') let g:LatexBox_completion_environments = [ \ {'word': 'itemize', 'menu': 'bullet list' }, \ {'word': 'enumerate', 'menu': 'numbered list' }, \ {'word': 'description', 'menu': 'description' }, \ {'word': 'center', 'menu': 'centered text' }, \ {'word': 'figure', 'menu': 'floating figure' }, \ {'word': 'table', 'menu': 'floating table' }, \ {'word': 'equation', 'menu': 'equation (numbered)' }, \ {'word': 'align', 'menu': 'aligned equations (numbered)' }, \ {'word': 'align*', 'menu': 'aligned equations' }, \ {'word': 'document' }, \ {'word': 'abstract' }, \ ] endif if !exists('g:LatexBox_completion_commands') let g:LatexBox_completion_commands = [ \ {'word': '\begin{' }, \ {'word': '\end{' }, \ {'word': '\item' }, \ {'word': '\label{' }, \ {'word': '\ref{' }, \ {'word': '\eqref{eq:' }, \ {'word': '\cite{' }, \ {'word': '\chapter{' }, \ {'word': '\section{' }, \ {'word': '\subsection{' }, \ {'word': '\subsubsection{' }, \ {'word': '\paragraph{' }, \ {'word': '\nonumber' }, \ {'word': '\bibliography' }, \ {'word': '\bibliographystyle' }, \ ] endif if !exists('g:LatexBox_complete_inlineMath') let g:LatexBox_complete_inlineMath = 0 endif if !exists('g:LatexBox_eq_env_patterns') let g:LatexBox_eq_env_patterns = 'equation\|gather\|multiline\|align\|flalign\|alignat\|eqnarray' endif " }}} "LatexBox_kpsewhich {{{ function! LatexBox_kpsewhich(file) let old_dir = getcwd() execute 'lcd ' . fnameescape(LatexBox_GetTexRoot()) let out = system('kpsewhich "' . a:file . '"') " If kpsewhich has found something, it returns a non-empty string with a " newline at the end; otherwise the string is empty if len(out) " Remove the trailing newline let out = fnamemodify(out[:-2], ':p') endif execute 'lcd ' . fnameescape(old_dir) return out endfunction "}}} " Omni Completion {{{ let s:completion_type = '' function! LatexBox_Complete(findstart, base) if a:findstart " return the starting position of the word let line = getline('.') let pos = col('.') - 1 while pos > 0 && line[pos - 1] !~ '\\\|{' let pos -= 1 endwhile let line_start = line[:pos-1] if line_start =~ '\C\\begin\_\s*{$' let s:completion_type = 'begin' elseif line_start =~ '\C\\end\_\s*{$' let s:completion_type = 'end' elseif line_start =~ g:LatexBox_ref_pattern . '$' let s:completion_type = 'ref' elseif line_start =~ g:LatexBox_cite_pattern . '$' let s:completion_type = 'bib' " check for multiple citations let pos = col('.') - 1 while pos > 0 && line[pos - 1] !~ '{\|,' let pos -= 1 endwhile elseif s:LatexBox_complete_inlineMath_or_not() let s:completion_type = 'inlineMath' let pos = s:eq_pos else let s:completion_type = 'command' if line[pos - 1] == '\' let pos -= 1 endif endif return pos else " return suggestions in an array let suggestions = [] if s:completion_type == 'begin' " suggest known environments for entry in g:LatexBox_completion_environments if entry.word =~ '^' . escape(a:base, '\') if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^}') " add trailing '}' let entry = copy(entry) let entry.abbr = entry.word let entry.word = entry.word . '}' endif call add(suggestions, entry) endif endfor elseif s:completion_type == 'end' " suggest known environments let env = LatexBox_GetCurrentEnvironment() if env != '' if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]') call add(suggestions, {'word': env . '}', 'abbr': env}) else call add(suggestions, env) endif endif elseif s:completion_type == 'command' " suggest known commands for entry in g:LatexBox_completion_commands if entry.word =~ '^' . escape(a:base, '\') " do not display trailing '{' if entry.word =~ '{' let entry.abbr = entry.word[0:-2] endif call add(suggestions, entry) endif endfor elseif s:completion_type == 'ref' let suggestions = s:CompleteLabels(a:base) elseif s:completion_type == 'bib' " suggest BibTeX entries let suggestions = LatexBox_BibComplete(a:base) elseif s:completion_type == 'inlineMath' let suggestions = s:LatexBox_inlineMath_completion(a:base) endif if !has('gui_running') redraw! endif return suggestions endif endfunction " }}} " BibTeX search {{{ " find the \bibliography{...} commands " the optional argument is the file name to be searched function! s:FindBibData(...) if a:0 == 0 let file = LatexBox_GetMainTexFile() else let file = a:1 endif if !filereadable(file) return '' endif let bibliography_cmds = [ \ '\\bibliography', \ '\\addbibresource', \ '\\addglobalbib', \ '\\addsectionbib', \ ] let lines = readfile(file) let bibdata_list = [] for cmd in bibliography_cmds let bibdata_list += map(filter(copy(lines), \ 'v:val =~ ''\C' . cmd . '\s*{[^}]\+}'''), \ 'matchstr(v:val, ''\C' . cmd . '\s*{\zs[^}]\+\ze}'')') endfor let bibdata_list += map(filter(copy(lines), \ 'v:val =~ ''\C\\\%(input\|include\)\s*{[^}]\+}'''), \ 's:FindBibData(LatexBox_kpsewhich(matchstr(v:val,' \ . '''\C\\\%(input\|include\)\s*{\zs[^}]\+\ze}'')))') let bibdata_list += map(filter(copy(lines), \ 'v:val =~ ''\C\\\%(input\|include\)\s\+\S\+'''), \ 's:FindBibData(LatexBox_kpsewhich(matchstr(v:val,' \ . '''\C\\\%(input\|include\)\s\+\zs\S\+\ze'')))') return join(bibdata_list, ',') endfunction let s:bstfile = expand(':p:h') . '/vimcomplete' function! LatexBox_BibSearch(regexp) let res = [] " Find data from bib files let bibdata = s:FindBibData() if bibdata != '' " write temporary aux file let tmpbase = LatexBox_GetTexRoot() . '/_LatexBox_BibComplete' let auxfile = tmpbase . '.aux' let bblfile = tmpbase . '.bbl' let blgfile = tmpbase . '.blg' call writefile(['\citation{*}', '\bibstyle{' . s:bstfile . '}', \ '\bibdata{' . bibdata . '}'], auxfile) silent execute '! cd ' shellescape(LatexBox_GetTexRoot()) . \ ' ; bibtex -terse ' \ . fnamemodify(auxfile, ':t') . ' >/dev/null' let lines = split(substitute(join(readfile(bblfile), "\n"), \ '\n\n\@!\(\s\=\)\s*\|{\|}', '\1', 'g'), "\n") for line in filter(lines, 'v:val =~ a:regexp') let matches = matchlist(line, \ '^\(.*\)||\(.*\)||\(.*\)||\(.*\)||\(.*\)') if !empty(matches) && !empty(matches[1]) call add(res, { \ 'key': matches[1], \ 'type': matches[2], \ 'author': matches[3], \ 'year': matches[4], \ 'title': matches[5], \ }) endif endfor call delete(auxfile) call delete(bblfile) call delete(blgfile) endif " Find data from 'thebibliography' environments let lines = readfile(LatexBox_GetMainTexFile()) if match(lines, '\C\\begin{thebibliography}') for line in filter(filter(lines, 'v:val =~ ''\C\\bibitem'''), \ 'v:val =~ a:regexp') let match = matchlist(line, '\\bibitem{\([^}]*\)')[1] call add(res, { \ 'key': match, \ 'type': '', \ 'author': '', \ 'year': '', \ 'title': match, \ }) endfor endif return res endfunction " }}} " BibTeX completion {{{ function! LatexBox_BibComplete(regexp) " treat spaces as '.*' if needed if g:LatexBox_bibtex_wild_spaces "let regexp = substitute(a:regexp, '\s\+', '.*', 'g') let regexp = '.*' . substitute(a:regexp, '\s\+', '\\\&.*', 'g') else let regexp = a:regexp endif let res = [] for m in LatexBox_BibSearch(regexp) let type = m['type'] == '' ? '[-]' : '[' . m['type'] . '] ' let auth = m['author'] == '' ? '' : m['author'][:20] . ' ' let year = m['year'] == '' ? '' : '(' . m['year'] . ')' let w = { 'word': m['key'], \ 'abbr': type . auth . year, \ 'menu': m['title'] } " close braces if needed if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]') let w.word = w.word . '}' endif call add(res, w) endfor return res endfunction " }}} " ExtractLabels {{{ " Generate list of \newlabel commands in current buffer. " " Searches the current buffer for commands of the form " \newlabel{name}{{number}{page}.* " and returns list of [ name, number, page ] tuples. function! s:ExtractLabels() call cursor(1,1) let matches = [] let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' ) while [lblline, lblbegin] != [0,0] let [nln, nameend] = searchpairpos( '{', '', '}', 'W' ) if nln != lblline let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' ) continue endif let curname = strpart( getline( lblline ), lblbegin, nameend - lblbegin - 1 ) " Ignore cref entries (because they are duplicates) if curname =~ "\@cref" continue endif if 0 == search( '{\w*{', 'ce', lblline ) let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' ) continue endif let numberbegin = getpos('.')[2] let [nln, numberend] = searchpairpos( '{', '', '}', 'W' ) if nln != lblline let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' ) continue endif let curnumber = strpart( getline( lblline ), numberbegin, numberend - numberbegin - 1 ) if 0 == search( '\w*{', 'ce', lblline ) let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' ) continue endif let pagebegin = getpos('.')[2] let [nln, pageend] = searchpairpos( '{', '', '}', 'W' ) if nln != lblline let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' ) continue endif let curpage = strpart( getline( lblline ), pagebegin, pageend - pagebegin - 1 ) let matches += [ [ curname, curnumber, curpage ] ] let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' ) endwhile return matches endfunction "}}} " ExtractInputs {{{ " Generate list of \@input commands in current buffer. " " Searches the current buffer for \@input{file} entries and " returns list of all files. function! s:ExtractInputs() call cursor(1,1) let matches = [] let [inline, inbegin] = searchpos( '\\@input{', 'ecW' ) while [inline, inbegin] != [0,0] let [nln, inend] = searchpairpos( '{', '', '}', 'W' ) if nln != inline let [inline, inbegin] = searchpos( '\\@input{', 'ecW' ) continue endif let matches += [ LatexBox_kpsewhich(strpart( getline( inline ), inbegin, inend - inbegin - 1 )) ] let [inline, inbegin] = searchpos( '\\@input{', 'ecW' ) endwhile " Remove empty strings for nonexistant .aux files return filter(matches, 'v:val != ""') endfunction "}}} " LabelCache {{{ " Cache of all labels. " " LabelCache is a dictionary mapping filenames to tuples " [ time, labels, inputs ] " where " * time is modification time of the cache entry " * labels is a list like returned by ExtractLabels " * inputs is a list like returned by ExtractInputs let s:LabelCache = {} "}}} " GetLabelCache {{{ " Extract labels from LabelCache and update it. " " Compares modification time of each entry in the label " cache and updates it, if necessary. During traversal of " the LabelCache, all current labels are collected and " returned. function! s:GetLabelCache(file) if !filereadable(a:file) return [] endif if !has_key(s:LabelCache , a:file) || s:LabelCache[a:file][0] != getftime(a:file) " Open file in temporary split window for label extraction. silent execute '1sp +let\ labels=s:ExtractLabels()|let\ inputs=s:ExtractInputs()|quit! ' . a:file let s:LabelCache[a:file] = [ getftime(a:file), labels, inputs ] endif " We need to create a copy of s:LabelCache[fid][1], otherwise all inputs' " labels would be added to the current file's label cache upon each " completion call, leading to duplicates/triplicates/etc. and decreased " performance. " Also, because we don't anything with the list besides matching copies, " we can get away with a shallow copy for now. let labels = copy(s:LabelCache[a:file][1]) for input in s:LabelCache[a:file][2] let labels += s:GetLabelCache(input) endfor return labels endfunction "}}} " Complete Labels {{{ function! s:CompleteLabels(regex) let labels = s:GetLabelCache(LatexBox_GetAuxFile()) let matches = filter( copy(labels), 'match(v:val[0], "' . a:regex . '") != -1' ) if empty(matches) " also try to match label and number let regex_split = split(a:regex) if len(regex_split) > 1 let base = regex_split[0] let number = escape(join(regex_split[1:], ' '), '.') let matches = filter( copy(labels), 'match(v:val[0], "' . base . '") != -1 && match(v:val[1], "' . number . '") != -1' ) endif endif if empty(matches) " also try to match number let matches = filter( copy(labels), 'match(v:val[1], "' . a:regex . '") != -1' ) endif let suggestions = [] for m in matches let entry = {'word': m[0], 'menu': printf("%7s [p. %s]", '('.m[1].')', m[2])} if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]') " add trailing '}' let entry = copy(entry) let entry.abbr = entry.word let entry.word = entry.word . '}' endif call add(suggestions, entry) endfor return suggestions endfunction " }}} " Complete Inline Math Or Not {{{ " Return 1, when cursor is in a math env: " 1, there is a single $ in the current line on the left of cursor " 2, there is an open-eq-env on/above the current line " (open-eq-env : \(, \[, and \begin{eq-env} ) " Return 0, when cursor is not in a math env function! s:LatexBox_complete_inlineMath_or_not() " switch of inline math completion feature if g:LatexBox_complete_inlineMath == 0 return 0 endif " env names that can't appear in an eq env if !exists('s:LatexBox_doc_structure_patterns') let s:LatexBox_doc_structure_patterns = '\%(' . '\\begin\s*{document}\|' . \ '\\\%(chapter\|section\|subsection\|subsubsection\)\*\?\s*{' . '\)' endif if !exists('s:LatexBox_eq_env_open_patterns') let s:LatexBox_eq_env_open_patterns = ['\\(','\\\['] endif if !exists('s:LatexBox_eq_env_close_patterns') let s:LatexBox_eq_env_close_patterns = ['\\)','\\\]'] endif let notcomment = '\%(\%(\\\@= 0 " find the end of dollar pair let cursor_dollar_pair = matchend(line_start_2_cnum_saved, '\$[^$]\+\$', cursor_dollar_pair) endwhile " find single $ after cursor_dollar_pair let cursor_single_dollar = matchend(line_start_2_cnum_saved, '\$', cursor_dollar_pair) " if single $ is found if cursor_single_dollar >= 0 " check whether $ is in \(...\), \[...\], or \begin{eq}...\end{eq} " check current line, " search for LatexBox_eq_env_close_patterns: \[ and \( let lnum = line('.') for i in range(0, (len(s:LatexBox_eq_env_open_patterns)-1)) call cursor(lnum_saved, cnum_saved) let cnum_close = searchpos(''. s:LatexBox_eq_env_close_patterns[i].'', 'cbW', lnum_saved)[1] let cnum_open = matchend(line_start_2_cnum_saved, s:LatexBox_eq_env_open_patterns[i], cnum_close) if cnum_open >= 0 let s:eq_dollar_parenthesis_bracket_empty = '' let s:eq_pos = cursor_single_dollar - 1 return 1 end endfor " check the lines above " search for s:LatexBox_doc_structure_patterns, and end-of-math-env let lnum -= 1 while lnum > 0 let line = getline(lnum) if line =~ notcomment . '\(' . s:LatexBox_doc_structure_patterns . \ '\|' . '\\end\s*{\(' . g:LatexBox_eq_env_patterns . '\)\*\?}\)' " when s:LatexBox_doc_structure_patterns or g:LatexBox_eq_env_patterns " are found first, complete math, leave with $ at both sides let s:eq_dollar_parenthesis_bracket_empty = '$' let s:eq_pos = cursor_single_dollar break elseif line =~ notcomment . '\\begin\s*{\(' . g:LatexBox_eq_env_patterns . '\)\*\?}' " g:LatexBox_eq_env_patterns is found, complete math, remove $ let s:eq_dollar_parenthesis_bracket_empty = '' let s:eq_pos = cursor_single_dollar - 1 break endif let lnum -= 1 endwhile return 1 else " no $ is found, then search for \( or \[ in current line " 1, whether there is \( call cursor(lnum_saved, cnum_saved) let cnum_parenthesis_close = searchpos('\\)', 'cbW', lnum_saved)[1] let cnum_parenthesis_open = matchend(line_start_2_cnum_saved, '\\(', cnum_parenthesis_close) if cnum_parenthesis_open >= 0 let s:eq_dollar_parenthesis_bracket_empty = '\)' let s:eq_pos = cnum_parenthesis_open return 1 end " 2, whether there is \[ call cursor(lnum_saved, cnum_saved) let cnum_bracket_close = searchpos('\\\]', 'cbW', lnum_saved)[1] let cnum_bracket_open = matchend(line_start_2_cnum_saved, '\\\[', cnum_bracket_close) if cnum_bracket_open >= 0 let s:eq_dollar_parenthesis_bracket_empty = '\]' let s:eq_pos = cnum_bracket_open return 1 end " not inline math completion return 0 endif endfunction " }}} " Complete inline euqation{{{ function! s:LatexBox_inlineMath_completion(regex, ...) if a:0 == 0 let file = LatexBox_GetMainTexFile() else let file = a:1 endif if empty(glob(file, 1)) return '' endif if empty(s:eq_dollar_parenthesis_bracket_empty) let inline_pattern1 = '\$\s*\(' . escape(substitute(a:regex[1:], '^\s\+', '', ""), '\.*^') . '[^$]*\)\s*\$' let inline_pattern2 = '\\(\s*\(' . escape(substitute(a:regex[1:], '^\s\+', '', ""), '\.*^') . '.*\)\s*\\)' else let inline_pattern1 = '\$\s*\(' . escape(substitute(a:regex, '^\s\+', '', ""), '\.*^') . '[^$]*\)\s*\$' let inline_pattern2 = '\\(\s*\(' . escape(substitute(a:regex, '^\s\+', '', ""), '\.*^') . '.*\)\s*\\)' endif let suggestions = [] let line_num = 0 for line in readfile(file) let line_num = line_num + 1 let suggestions += s:LatexBox_inlineMath_mathlist(line,inline_pattern1 , line_num) + s:LatexBox_inlineMath_mathlist( line,inline_pattern2, line_num) " search for included files let included_file = matchstr(line, '^\\@input{\zs[^}]*\ze}') if included_file != '' let included_file = LatexBox_kpsewhich(included_file) call extend(suggestions, s:LatexBox_inlineMath_completion(a:regex, included_file)) endif endfor return suggestions endfunction " }}} " Search for inline maths {{{ " search for $ ... $ and \( ... \) in each line function! s:LatexBox_inlineMath_mathlist(line,inline_pattern, line_num) let col_start = 0 let suggestions = [] while 1 let matches = matchlist(a:line, a:inline_pattern, col_start) if !empty(matches) " show line number of inline math let entry = {'word': matches[1], 'menu': '[' . a:line_num . ']'} if s:eq_dollar_parenthesis_bracket_empty != '' let entry = copy(entry) let entry.abbr = entry.word let entry.word = entry.word . s:eq_dollar_parenthesis_bracket_empty endif call add(suggestions, entry) " update col_start let col_start = matchend(a:line, a:inline_pattern, col_start) else break endif endwhile return suggestions endfunction " }}} " Close Current Environment {{{ function! s:CloseCurEnv() " first, try with \left/\right pairs let [lnum, cnum] = searchpairpos('\C\\left\>', '', '\C\\right\>', 'bnW', 'LatexBox_InComment()') if lnum let line = strpart(getline(lnum), cnum - 1) let bracket = matchstr(line, '^\\left\zs\((\|\[\|\\{\||\|\.\)\ze') for [open, close] in [['(', ')'], ['\[', '\]'], ['\\{', '\\}'], ['|', '|'], ['\.', '|']] let bracket = substitute(bracket, open, close, 'g') endfor return '\right' . bracket endif " second, try with environments let env = LatexBox_GetCurrentEnvironment() if env == '\[' return '\]' elseif env == '\(' return '\)' elseif env != '' return '\end{' . env . '}' endif return '' endfunction " }}} " Wrap Selection {{{ function! s:WrapSelection(wrapper) keepjumps normal! `>a} execute 'keepjumps normal! `o\end{' . env . '}' execute 'keepjumps normal! ` normal! gvgq endif else execute 'keepjumps normal! `>a\end{' . env . '}' execute 'keepjumps normal! ` LatexCloseCurEnv =CloseCurEnv() vnoremap LatexWrapSelection :call WrapSelection('')i vnoremap LatexEnvWrapSelection :call PromptEnvWrapSelection() vnoremap LatexEnvWrapFmtSelection :call PromptEnvWrapSelection(1) nnoremap LatexChangeEnv :call ChangeEnvPrompt() " }}} " vim:fdm=marker:ff=unix:noet:ts=4:sw=4