vimtex/autoload/vimtex/latexmk.vim
Karl Yngve Lervåg 5771ed00aa Added option to fix quickfix with noautochdir
- Use `let g:vimtex_quickfix_fix_paths = 1` to enable
- Also added test files.

Fixes #256
2015-11-20 09:47:51 +01:00

572 lines
16 KiB
VimL

" vimtex - LaTeX plugin for Vim
"
" Maintainer: Karl Yngve Lervåg
" Email: karl.yngve@gmail.com
"
function! vimtex#latexmk#init_options() " {{{1
call vimtex#util#set_default('g:vimtex_latexmk_enabled', 1)
call vimtex#util#set_default('g:vimtex_latexmk_build_dir', '')
if !g:vimtex_latexmk_enabled | return | endif
call vimtex#util#set_default('g:vimtex_latexmk_background', 0)
call vimtex#util#set_default('g:vimtex_latexmk_callback', 1)
call vimtex#util#set_default('g:vimtex_latexmk_continuous', 1)
call vimtex#util#set_default('g:vimtex_latexmk_file_line_error', 1)
call vimtex#util#set_default('g:vimtex_latexmk_options', '')
call vimtex#util#set_default('g:vimtex_latexmk_progname',
\ get(v:, 'progpath', get(v:, 'progname')))
call vimtex#util#set_default('g:vimtex_quickfix_autojump', '0')
call vimtex#util#set_default('g:vimtex_quickfix_ignore_all_warnings', 0)
call vimtex#util#set_default('g:vimtex_quickfix_ignored_warnings', [])
call vimtex#util#set_default('g:vimtex_quickfix_mode', '2')
call vimtex#util#set_default('g:vimtex_quickfix_open_on_warning', '1')
call vimtex#util#set_default('g:vimtex_quickfix_fix_paths', '0')
endfunction
" }}}1
function! vimtex#latexmk#init_script() " {{{1
if !g:vimtex_latexmk_enabled | return | endif
call s:check_system_compatibility()
" Ensure that all latexmk processes are stopped when a latex project is
" closed and when vim exits
if g:vimtex_latexmk_continuous
augroup vimtex_latexmk
autocmd!
autocmd VimLeave * call vimtex#latexmk#stop_all()
autocmd User VimtexQuit call s:stop_before_leaving()
augroup END
endif
" Add autocommand to fix paths in quickfix
if g:vimtex_quickfix_fix_paths
augroup vimtex_latexmk_fix_dirs
au!
au QuickFixCmdPost c*file call s:fix_quickfix_paths()
augroup END
endif
endfunction
" }}}1
function! vimtex#latexmk#init_buffer() " {{{1
if !g:vimtex_latexmk_enabled | return | endif
" Set compiler (this defines the errorformat)
compiler latexmk
" Initialize system PID
call s:latexmk_init_pid()
" Define commands
command! -buffer VimtexCompile call vimtex#latexmk#compile()
command! -buffer -bang VimtexCompileSS call vimtex#latexmk#compile_ss(<q-bang> == "!")
command! -buffer VimtexCompileToggle call vimtex#latexmk#toggle()
command! -buffer VimtexCompileOutput call vimtex#latexmk#output()
command! -buffer VimtexStop call vimtex#latexmk#stop()
command! -buffer VimtexStopAll call vimtex#latexmk#stop_all()
command! -buffer VimtexErrors call vimtex#latexmk#errors()
command! -buffer -bang VimtexClean call vimtex#latexmk#clean(<q-bang> == "!")
command! -buffer -bang VimtexStatus call vimtex#latexmk#status(<q-bang> == "!")
command! -buffer VimtexLacheck call vimtex#latexmk#lacheck()
" Define mappings
nnoremap <buffer> <plug>(vimtex-compile) :call vimtex#latexmk#compile()<cr>
nnoremap <buffer> <plug>(vimtex-compile-ss) :call vimtex#latexmk#compile_ss(0)<cr>
nnoremap <buffer> <plug>(vimtex-compile-toggle) :call vimtex#latexmk#toggle()<cr>
nnoremap <buffer> <plug>(vimtex-compile-output) :call vimtex#latexmk#output()<cr>
nnoremap <buffer> <plug>(vimtex-stop) :call vimtex#latexmk#stop()<cr>
nnoremap <buffer> <plug>(vimtex-stop-all) :call vimtex#latexmk#stop_all()<cr>
nnoremap <buffer> <plug>(vimtex-errors) :call vimtex#latexmk#errors()<cr>
nnoremap <buffer> <plug>(vimtex-clean) :call vimtex#latexmk#clean(0)<cr>
nnoremap <buffer> <plug>(vimtex-clean-full) :call vimtex#latexmk#clean(1)<cr>
nnoremap <buffer> <plug>(vimtex-status) :call vimtex#latexmk#status(0)<cr>
nnoremap <buffer> <plug>(vimtex-status-all) :call vimtex#latexmk#status(1)<cr>
nnoremap <buffer> <plug>(vimtex-lacheck) :call vimtex#latexmk#lacheck()<cr>
endfunction
" }}}1
function! vimtex#latexmk#callback(status) " {{{1
if get(s:, 'silence_next_callback', 0)
let s:silence_next_callback = 0
return
endif
call vimtex#latexmk#errors_open(0)
redraw!
if g:vimtex_view_enabled
\ && has_key(b:vimtex.viewer, 'latexmk_callback')
call b:vimtex.viewer.latexmk_callback()
endif
call vimtex#echo#status(['latexmk compile: ',
\ a:status ? ['VimtexSuccess', 'success'] : ['VimtexWarning', 'fail']])
return ''
endfunction
" }}}1
function! vimtex#latexmk#clean(full) " {{{1
if b:vimtex.pid
silent call vimtex#latexmk#stop()
let l:restart = 1
let s:silence_next_callback = 1
endif
"
" Run latexmk clean process
"
if has('win32')
let cmd = 'cd /D "' . b:vimtex.root . '" & '
else
let cmd = 'cd ' . vimtex#util#shellescape(b:vimtex.root) . '; '
endif
let cmd .= 'latexmk'
if g:vimtex_latexmk_build_dir !=# ''
let cmd .= ' -outdir=' . g:vimtex_latexmk_build_dir
endif
let cmd .= a:full ? ' -C ' : ' -c '
let cmd .= vimtex#util#shellescape(b:vimtex.base)
call vimtex#util#execute({'cmd' : cmd})
let b:vimtex.cmd_latexmk_clean = cmd
if get(l:, 'restart', 0)
silent call vimtex#latexmk#compile()
endif
call vimtex#echo#status(['latexmk clean: ',
\ ['VimtexSuccess', 'finished' . (a:full ? ' (full)' : '')]])
endfunction
" }}}1
function! vimtex#latexmk#lacheck() " {{{1
compiler lacheck
silent lmake %
lwindow
silent redraw!
wincmd p
compiler latexmk
endfunction
" }}}1
function! vimtex#latexmk#toggle() " {{{1
if b:vimtex.pid
call vimtex#latexmk#stop()
else
call vimtex#latexmk#compile()
endif
endfunction
" }}}1
function! vimtex#latexmk#compile() " {{{1
if b:vimtex.pid
call vimtex#echo#status(['latexmk compile: ',
\ ['VimtexWarning', 'already running for `' . b:vimtex.base . "'"]])
return
endif
" Build command line and start latexmk
let exe = s:latexmk_build_cmd()
if !g:vimtex_latexmk_continuous && !g:vimtex_latexmk_background
let exe.bg = 0
let exe.silent = 0
endif
call vimtex#util#execute(exe)
if g:vimtex_latexmk_continuous
call s:latexmk_set_pid()
call vimtex#echo#status(['latexmk compile: ',
\ ['VimtexSuccess', 'started continuous mode']])
else
call vimtex#echo#status(['latexmk compile: ',
\ ['VimtexSuccess', 'compiling ...']])
endif
endfunction
" }}}1
function! vimtex#latexmk#compile_ss(verbose) " {{{1
if b:vimtex.pid
call vimtex#echo#status(['latexmk compile: ',
\ ['VimtexWarning', 'already running for `' . b:vimtex.base . "'"]])
return
endif
let l:vimtex_latexmk_continuous = g:vimtex_latexmk_continuous
let g:vimtex_latexmk_continuous = 0
let exe = s:latexmk_build_cmd()
if a:verbose
let exe.bg = 0
let exe.silent = 0
endif
call vimtex#util#execute(exe)
let g:vimtex_latexmk_continuous = l:vimtex_latexmk_continuous
endfunction
" }}}1
function! vimtex#latexmk#errors() " {{{1
if s:open_quickfix_window
let s:open_quickfix_window = 0
cclose
else
call vimtex#latexmk#errors_open(1)
endif
endfunction
" }}}1
function! vimtex#latexmk#errors_open(force) " {{{1
if !exists('b:vimtex') | return | endif
cclose
let log = b:vimtex.log()
if empty(log)
if a:force
call vimtex#echo#status(['latexmk errors: ',
\ ['VimtexWarning', 'No log file found']])
endif
return
endif
" Save root name for fixing quickfix paths
let s:vimtex_tmp_path = b:vimtex.root
if g:vimtex_quickfix_autojump
execute 'cfile ' . fnameescape(log)
else
execute 'cgetfile ' . fnameescape(log)
endif
if empty(getqflist())
if a:force
call vimtex#echo#status(['latexmk errors: ',
\ ['VimtexSuccess', 'No errors!']])
endif
return
endif
"
" There are two options that determine when to open the quickfix window. If
" forced, the quickfix window is always opened when there are errors or
" warnings (forced typically imply that the functions is called from the
" normal mode mapping). Else the behaviour is based on the settings.
"
let s:open_quickfix_window = a:force
\ || (g:vimtex_quickfix_mode > 0
\ && (g:vimtex_quickfix_open_on_warning
\ || s:log_contains_error(log)))
if s:open_quickfix_window
botright cwindow
if g:vimtex_quickfix_mode == 2
wincmd p
endif
redraw!
endif
endfunction
let s:open_quickfix_window = 0
" }}}1
function! vimtex#latexmk#output() " {{{1
if has_key(b:vimtex, 'tmp')
let tmp = b:vimtex.tmp
else
call vimtex#echo#status(['vimtex: ', ['VimtexWarning', 'No output exists']])
return
endif
" Create latexmk output window
if bufnr(tmp) >= 0
silent exe 'bwipeout' . bufnr(tmp)
endif
silent exe 'split ' . tmp
" Better automatic update
augroup vimtex_tmp_update
autocmd!
autocmd BufEnter * silent! checktime
autocmd CursorHold * silent! checktime
autocmd CursorHoldI * silent! checktime
autocmd CursorMoved * silent! checktime
autocmd CursorMovedI * silent! checktime
augroup END
silent exe 'autocmd! BufDelete ' . tmp . ' augroup! vimtex_tmp_update'
" Set some mappings
nnoremap <buffer> <silent> q :bwipeout<cr>
" Set some buffer options
setlocal autoread
setlocal nomodifiable
endfunction
" }}}1
function! vimtex#latexmk#status(detailed) " {{{1
if a:detailed
let running = 0
for data in values(g:vimtex_data)
if data.pid
if !running
call vimtex#echo#status(['latexmk status: ',
\ ['VimtexSuccess', "running\n"]])
call vimtex#echo#status([['None', ' pid '],
\ ['None', "file\n"]])
let running = 1
endif
let name = data.tex
if len(name) >= winwidth('.') - 20
let name = '...' . name[-winwidth('.')+23:]
endif
call vimtex#echo#status([
\ ['None', printf(' %-6s ', data.pid)],
\ ['None', name . "\n"]])
endif
endfor
if !running
call vimtex#echo#status(['latexmk status: ',
\ ['VimtexWarning', 'not running']])
endif
else
if b:vimtex.pid
call vimtex#echo#status(['latexmk status: ',
\ ['VimtexSuccess', 'running']])
else
call vimtex#echo#status(['latexmk status: ',
\ ['VimtexWarning', 'not running']])
endif
endif
endfunction
" }}}1
function! vimtex#latexmk#stop() " {{{1
if b:vimtex.pid
call s:latexmk_kill(b:vimtex)
call vimtex#echo#status(['latexmk compile: ',
\ ['VimtexSuccess', 'stopped (' . b:vimtex.base . ')']])
else
call vimtex#echo#status(['latexmk compile: ',
\ ['VimtexWarning', 'no process to stop (' . b:vimtex.base . ')']])
endif
endfunction
" }}}1
function! vimtex#latexmk#stop_all() " {{{1
for data in values(g:vimtex_data)
if data.pid
call s:latexmk_kill(data)
endif
endfor
endfunction
" }}}1
" Helper function(s) for building the latexmk command
function! vimtex#latexmk#add_option(name, value) " {{{1
if has('win32')
return ' -e "$' . a:name . ' = ''' . a:value . '''"'
else
return ' -e ''$' . a:name . ' = "' . a:value . '"'''
endif
endfunction
"}}}1
" Helper functions for latexmk command
function! s:latexmk_build_cmd() " {{{1
let exe = {}
let exe.null = 0
" Note: We don't send output to /dev/null, but rather to a temporary file,
" which allows inspection of latexmk output
let tmp = tempname()
if has('win32')
let cmd = 'cd /D "' . b:vimtex.root . '"'
let cmd .= ' && set max_print_line=2000 & latexmk'
else
let cmd = 'cd ' . vimtex#util#shellescape(b:vimtex.root)
if fnamemodify(&shell, ':t') ==# 'fish'
let cmd .= '; and set max_print_line 2000; and latexmk'
else
let cmd .= ' && max_print_line=2000 latexmk'
endif
endif
let cmd .= ' -verbose -pdf ' . g:vimtex_latexmk_options
if g:vimtex_latexmk_file_line_error
let cmd .= ' -e ' . vimtex#util#shellescape(
\ '$pdflatex =~ s/ / -file-line-error /')
endif
if g:vimtex_latexmk_build_dir !=# ''
let cmd .= ' -outdir=' . g:vimtex_latexmk_build_dir
endif
if g:vimtex_latexmk_continuous
let cmd .= ' -pvc'
if get(s:, 'silence_next_callback', 0)
let cmd .= ' -view=none'
endif
endif
if g:vimtex_latexmk_callback && has('clientserver')
let success = '\"' . g:vimtex_latexmk_progname . '\"'
let success .= ' --servername ' . v:servername
let success .= ' --remote-expr \"vimtex\#latexmk\#callback(1)\"'
let failed = '\"' . g:vimtex_latexmk_progname . '\"'
let failed .= ' --servername ' . v:servername
let failed .= ' --remote-expr \"vimtex\#latexmk\#callback(0)\"'
let cmd .= vimtex#latexmk#add_option('success_cmd', success)
let cmd .= vimtex#latexmk#add_option('failure_cmd', failed)
let s:first_callback = 1
endif
if g:vimtex_view_enabled
\ && has_key(b:vimtex.viewer, 'latexmk_append_argument')
let cmd .= b:vimtex.viewer.latexmk_append_argument()
endif
let cmd .= ' ' . vimtex#util#shellescape(b:vimtex.base)
if g:vimtex_latexmk_continuous || g:vimtex_latexmk_background
if has('win32')
let cmd .= ' >' . tmp
let cmd = 'cmd /s /c "' . cmd . '"'
else
let cmd .= ' >' . tmp . ' 2>&1'
endif
elseif has('win32')
let cmd = 'cmd /c "' . cmd . '"'
endif
let exe.cmd = cmd
let b:vimtex.cmd_latexmk_compile = cmd
let b:vimtex.tmp = tmp
return exe
endfunction
" }}}1
function! s:latexmk_init_pid() " {{{1
"
" First see if the PID is already defined
"
let b:vimtex.pid = get(b:vimtex, 'pid', 0)
"
" If the PID is 0, then search for existing processes
"
if b:vimtex.pid ==# 0
if has('win32')
"
" PASS - don't know how to do this on Windows yet.
"
return
else
"
" Use pgrep combined with /proc/PID/cwd to search for existing process
"
for l:pid in split(system(
\ 'pgrep -f "^perl.*latexmk.*' . b:vimtex.base . '"'), "\n")
let path = resolve('/proc/' . l:pid . '/cwd') . '/' . b:vimtex.base
if path ==# b:vimtex.tex
let b:vimtex.pid = str2nr(l:pid)
return
endif
endfor
endif
endif
endfunction
function! s:latexmk_set_pid() " {{{1
if has('win32')
let pidcmd = 'tasklist /fi "imagename eq latexmk.exe"'
let pidinfo = split(system(pidcmd), '\n')[-1]
let b:vimtex.pid = str2nr(split(pidinfo,'\s\+')[1])
else
let b:vimtex.pid = str2nr(system('pgrep -nf "^perl.*latexmk"')[:-2])
endif
endfunction
function! s:latexmk_kill(data) " {{{1
let exe = {}
let exe.null = 0
if has('win32')
let exe.cmd = 'taskkill /PID ' . a:data.pid . ' /T /F'
else
let exe.cmd = 'kill ' . a:data.pid
endif
call vimtex#util#execute(exe)
let a:data.pid = 0
endfunction
" }}}1
function! s:fix_quickfix_paths() " {{{1
let qflist = getqflist()
for i in qflist
let file = s:vimtex_tmp_path . '/'
\ . substitute(bufname(i.bufnr), '^\.\/', '', '')
if !bufexists(file) && filereadable(fnameescape(file))
execute 'badd' fnamemodify(file, ':~:.')
endif
let i.bufnr = bufnr(file)
endfor
call setqflist(qflist)
endfunction
" }}}1
function! s:stop_before_leaving() " {{{1
if b:vimtex.pid > 0
call s:latexmk_kill(b:vimtex)
call vimtex#echo#status(['latexmk compile: ',
\ ['VimtexSuccess', 'stopped (' . b:vimtex.base . ')']])
endif
endfunction
function! s:log_contains_error(logfile) " {{{1
let lines = readfile(a:logfile)
let lines = filter(lines, 'v:val =~# ''^.*:\d\+: ''')
let lines = uniq(map(lines, 'matchstr(v:val, ''^.*\ze:\d\+:'')'))
let lines = map(lines, 'fnamemodify(v:val, '':p'')')
let lines = filter(lines, 'filereadable(v:val)')
return len(lines) > 0
endfunction
function! s:check_system_compatibility() " {{{1
"
" Check for required executables
"
if has('win32')
let required = ['latexmk']
else
let required = ['latexmk', 'pgrep']
endif
let missing = filter(required, '!executable(v:val)')
"
" Disable latexmk if required programs are missing
"
if len(missing) > 0
call vimtex#echo#warning('vimtex warning: ')
call vimtex#echo#warning(' vimtex#latexmk was not initialized', 'None')
for cmd in missing
call vimtex#echo#warning(' ' . cmd . ' is not executable', 'None')
endfor
let g:vimtex_latexmk_enabled = 0
endif
endfunction
" }}}1
" vim: fdm=marker sw=2