vimtex/autoload/latex/complete.vim
Karl Yngve Lervåg 407e8d19e9 Removed .sty extension / absolute paths for cite
Removed the .sty extension, which caused some confusion for the root
detection routine. Not entirely sure what caused the issue, but gf to
.sty seems like a border-line enough case anyway.

Also, I've modified the bibsearch function to better work with larger
projects. It prepends the path of whichever file links to a bib file
to that bib file's path, allowing us to correctly complete citations
even if the bib file and current file are in two different directories
and the main file linking to the bibfile is in a different directory
to the current file.

This could possibly be modified to check for absolute paths in
\bibliography commands, but I don't believe anyone is using those?

Note: This patch was authored by Karl Moritz Hermann (see #8).
2014-02-02 22:04:47 +01:00

331 lines
9.1 KiB
VimL

" {{{1 latex#complete#init
function! latex#complete#init(initialized)
if g:latex_complete_enabled
setlocal omnifunc=latex#complete#omnifunc
endif
endfunction
" {{{1 latex#complete#omnifunc
let s:completion_type = ''
function! latex#complete#omnifunc(findstart, base)
if a:findstart
"
" First call: Find start of text to be completed
"
" Note: g:latex_complete_patterns is a dictionary where the keys are the
" types of completion and the values are the patterns that must match for
" the given type. Currently, it completes labels (e.g. \ref{...), bibtex
" entries (e.g. \cite{...) and commands (e.g. \...).
"
let pos = col('.') - 1
let line = getline('.')[:pos-1]
for [type, pattern] in items(g:latex_complete_patterns)
if line =~ pattern . '$'
let s:completion_type = type
while pos > 0
if line[pos - 1] =~ '{\|,' || line[pos-2:pos-1] == ', '
return pos
else
let pos -= 1
endif
endwhile
return -2
endif
endfor
else
"
" Second call: Find list of matches
"
if s:completion_type == 'ref'
return latex#complete#labels(a:base)
elseif s:completion_type == 'bib'
return latex#complete#bibtex(a:base)
endif
endif
endfunction
" {{{1 latex#complete#labels
function! latex#complete#labels(regex)
let labels = s:labels_get(g:latex#data[b:latex.id].aux())
let matches = filter(copy(labels), 'v:val[0] =~ ''' . a:regex . '''')
" Try to match label and number
if empty(matches)
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),
\ 'v:val[0] =~ ''' . base . ''' &&' .
\ 'v:val[1] =~ ''' . number . '''')
endif
endif
" Try to match number
if empty(matches)
let matches = filter(copy(labels), 'v:val[1] =~ ''' . a:regex . '''')
endif
let suggestions = []
for m in matches
let entry = {
\ 'word': m[0],
\ 'menu': printf("%7s [p. %s]", '('.m[1].')', m[2])
\ }
if g:latex_complete_close_braces && !s:next_chars_match('^\s*[,}]')
let entry = copy(entry)
let entry.abbr = entry.word
let entry.word = entry.word . '}'
endif
call add(suggestions, entry)
endfor
return suggestions
endfunction
" {{{1 latex#complete#bibtex
function! latex#complete#bibtex(regexp)
let res = []
let s:type_length = 4
for m in s:bibtex_search(a:regexp)
let type = m['type'] == '' ? '[-]' : '[' . m['type'] . '] '
let auth = m['author'] == '' ? '' : m['author'][:20] . ' '
let year = m['year'] == '' ? '' : '(' . m['year'] . ')'
" Align the type entry and fix minor annoyance in author list
let type = printf('%-' . s:type_length . 's', type)
let auth = substitute(auth, '\~', ' ', 'g')
let w = {
\ 'word': m['key'],
\ 'abbr': type . auth . year,
\ 'menu': m['title']
\ }
" Close braces if desired
if g:latex_complete_close_braces && !s:next_chars_match('^\s*[,}]')
let w.word = w.word . '}'
endif
call add(res, w)
endfor
return res
endfunction
" }}}1
" {{{1 s:bibtex_search
let s:bstfile = expand('<sfile>:p:h') . '/vimcomplete'
let s:type_length = 0
function! s:bibtex_search(regexp)
let res = []
" Find data from external bib files
let bibdata = join(s:bibtex_find_bibs(), ',')
if bibdata != ''
let tmp = {
\ 'aux' : 'tmpfile.aux',
\ 'bbl' : 'tmpfile.bbl',
\ 'blg' : 'tmpfile.blg',
\ }
" Write temporary aux file
call writefile([
\ '\citation{*}',
\ '\bibstyle{' . s:bstfile . '}',
\ '\bibdata{' . bibdata . '}',
\ ], tmp.aux)
" Create temporary bbl file
silent execute '!bibtex -terse ' . tmp.aux . ' >/dev/null'
if !has('gui_running')
redraw!
endif
" Parse temporary bbl file
let lines = split(substitute(join(readfile(tmp.bbl), "\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])
let s:type_length = max([s:type_length, len(matches[2]) + 3])
call add(res, {
\ 'key': matches[1],
\ 'type': matches[2],
\ 'author': matches[3],
\ 'year': matches[4],
\ 'title': matches[5],
\ })
endif
endfor
call delete(tmp.aux)
call delete(tmp.bbl)
call delete(tmp.blg)
endif
" Find data from 'thebibliography' environments
let lines = readfile(g:latex#data[b:latex.id].tex)
if match(lines, '\C\\begin{thebibliography}') >= 0
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
" {{{1 s:bibtex_find_bibs
function! s:bibtex_find_bibs(...)
if a:0
let file = a:1
else
let file = g:latex#data[b:latex.id].tex
endif
if !filereadable(file)
return ''
endif
let lines = readfile(file)
let bibdata_list = []
"
" Search for added bibliographies
" * Parse commands such as \bibliography{file1,file2.bib,...}
" * This also removes the .bib extensions
"
let bibsearch = '''\v\C\\'
let bibsearch .= '(bibliography|add(bibresource|globalbib|sectionbib))'
let bibsearch .= '\m\s*{\zs[^}]\+\ze}'''
let file_root = fnamemodify(file, ':p:h') . '/'
for entry in map(filter(copy(lines),
\ 'v:val =~ ' . bibsearch),
\ 'matchstr(v:val, ' . bibsearch . ')')
let bibdata_list += map(split(entry, ','), 'file_root . '
\ . 'fnamemodify(v:val, '':r'')')
endfor
if g:latex_complete_recursive_bib
"
" Recursively search included files
"
let incsearch = '''\C\\'
let incsearch .= '\%(input\|include\)'
let incsearch .= '\s*{\zs[^}]\+\ze}'''
for entry in map(filter(lines,
\ 'v:val =~ ' . incsearch),
\ 'matchstr(v:val, ' . incsearch . ')')
let bibdata_list += s:bibtex_find_bibs(latex#util#kpsewhich(entry))
endfor
endif
return bibdata_list
endfunction
" {{{1 s:labels_cache
"
" s:label_cache is a dictionary that maps filenames to tuples of the form
"
" [ time, labels, inputs ]
"
" where time is modification time of the cache entry, labels is a list like
" returned by extract_labels, and inputs is a list like returned by
" s:extract_inputs.
"
let s:label_cache = {}
" {{{1 s:labels_get
function! s:labels_get(file)
"
" s:labels_get compares modification time of each entry in the label cache
" and updates it if necessary. During traversal of the label cache, all
" current labels are collected and returned.
"
if !filereadable(a:file)
return []
endif
" Open file in temporary split window for label extraction.
if !has_key(s:label_cache , a:file)
\ || s:label_cache[a:file][0] != getftime(a:file)
let s:label_cache[a:file] = [
\ getftime(a:file),
\ s:labels_extract(a:file),
\ s:labels_extract_inputs(a:file),
\ ]
endif
" We need to create a copy of s:label_cache[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:label_cache[a:file][1])
for input in s:label_cache[a:file][2]
let labels += s:labels_get(input)
endfor
return labels
endfunction
" {{{1 s:labels_extract
function! s:labels_extract(file)
"
" Searches file for commands of the form
"
" \newlabel{name}{{number}{page}.*}.*
"
" and returns a list of [name, number, page] tuples.
"
let matches = []
let lines = readfile(a:file)
let lines = filter(lines, 'v:val =~ ''\\newlabel{''')
let lines = filter(lines, 'v:val !~ ''@cref''')
let lines = map(lines, 'latex#util#convert_back(v:val)')
for line in lines
let tree = latex#util#tex2tree(line)
if !empty(tree[2][0])
call add(matches, [
\ latex#util#tree2tex(tree[1][0]),
\ latex#util#tree2tex(tree[2][0][0]),
\ latex#util#tree2tex(tree[2][1][0]),
\ ])
endif
endfor
return matches
endfunction
" {{{1 s:labels_extract_inputs
function! s:labels_extract_inputs(file)
"
" Searches file for \@input{file} entries and returns list of all files.
"
let matches = []
for line in filter(readfile(a:file), 'v:val =~ ''\\@input{''')
call add(matches, matchstr(line, '{\zs.*\ze}'))
endfor
return matches
endfunction
" }}}1
" {{{1 s:next_chars_match
function! s:next_chars_match(regex)
return strpart(getline('.'), col('.') - 1) =~ a:regex
endfunction
" }}}1
" vim:fdm=marker:ff=unix