diff options
Diffstat (limited to 'autoload/vimtex/compiler')
| -rw-r--r-- | autoload/vimtex/compiler/arara.vim | 218 | ||||
| -rw-r--r-- | autoload/vimtex/compiler/latexmk.vim | 700 | ||||
| -rw-r--r-- | autoload/vimtex/compiler/latexrun.vim | 250 | ||||
| -rw-r--r-- | autoload/vimtex/compiler/tectonic.vim | 255 | 
4 files changed, 1423 insertions, 0 deletions
| diff --git a/autoload/vimtex/compiler/arara.vim b/autoload/vimtex/compiler/arara.vim new file mode 100644 index 00000000..8234b77d --- /dev/null +++ b/autoload/vimtex/compiler/arara.vim @@ -0,0 +1,218 @@ +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#compiler#arara#init(options) abort " {{{1 +  let l:compiler = deepcopy(s:compiler) + +  call l:compiler.init(extend(a:options, +        \ get(g:, 'vimtex_compiler_arara', {}), 'keep')) + +  return l:compiler +endfunction + +" }}}1 + +let s:compiler = { +      \ 'name' : 'arara', +      \ 'backend' : has('nvim') ? 'nvim' +      \                         : v:version >= 800 ? 'jobs' : 'process', +      \ 'root' : '', +      \ 'target' : '', +      \ 'target_path' : '', +      \ 'background' : 1, +      \ 'output' : tempname(), +      \ 'options' : ['--log'], +      \} + +function! s:compiler.init(options) abort dict " {{{1 +  call extend(self, a:options) + +  if !executable('arara') +    call vimtex#log#warning('arara is not executable!') +    throw 'vimtex: Requirements not met' +  endif + +  call extend(self, deepcopy(s:compiler_{self.backend})) + +  " Processes run with the new jobs api will not run in the foreground +  if self.backend !=# 'process' +    let self.background = 1 +  endif +endfunction + +" }}}1 + +function! s:compiler.build_cmd() abort dict " {{{1 +  let l:cmd = 'arara' + +  for l:opt in self.options +    let l:cmd .= ' ' . l:opt +  endfor + +  return l:cmd . ' ' . vimtex#util#shellescape(self.target) +endfunction + +" }}}1 +function! s:compiler.cleanup() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.pprint_items() abort dict " {{{1 +  let l:configuration = [] + +  if self.backend ==# 'process' +    call add(l:configuration, ['background', self.background]) +  endif + +  call add(l:configuration, ['arara options', self.options]) + +  let l:list = [] +  call add(l:list, ['backend', self.backend]) +  if self.background +    call add(l:list, ['output', self.output]) +  endif + +  if self.target_path !=# b:vimtex.tex +    call add(l:list, ['root', self.root]) +    call add(l:list, ['target', self.target_path]) +  endif + +  call add(l:list, ['configuration', l:configuration]) + +  if has_key(self, 'process') +    call add(l:list, ['process', self.process]) +  endif + +  if has_key(self, 'job') +    call add(l:list, ['cmd', self.cmd]) +  endif + +  return l:list +endfunction + +" }}}1 + +function! s:compiler.clean(...) abort dict " {{{1 +  call vimtex#log#warning('Clean not implemented for arara') +endfunction + +" }}}1 +function! s:compiler.start(...) abort dict " {{{1 +  call self.exec() + +  if self.background +    call vimtex#log#info('Compiler started in background') +  else +    call vimtex#compiler#callback(!vimtex#qf#inquire(self.target)) +  endif +endfunction + +" }}}1 +function! s:compiler.start_single() abort dict " {{{1 +  call self.start() +endfunction + +" }}}1 +function! s:compiler.stop() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.is_running() abort dict " {{{1 +  return 0 +endfunction + +" }}}1 +function! s:compiler.kill() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.get_pid() abort dict " {{{1 +  return 0 +endfunction + +" }}}1 + +let s:compiler_process = {} +function! s:compiler_process.exec() abort dict " {{{1 +  let self.process = vimtex#process#new() +  let self.process.name = 'arara' +  let self.process.background = self.background +  let self.process.workdir = self.root +  let self.process.output = self.output +  let self.process.cmd = self.build_cmd() +  call self.process.run() +endfunction + +" }}}1 + +let s:compiler_jobs = {} +function! s:compiler_jobs.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] +  let l:options = { +        \ 'out_io' : 'file', +        \ 'err_io' : 'file', +        \ 'out_name' : self.output, +        \ 'err_name' : self.output, +        \} + +  let s:cb_target = self.target_path !=# b:vimtex.tex ? self.target_path : '' +  let l:options.exit_cb = function('s:callback') + +  call vimtex#paths#pushd(self.root) +  let self.job = job_start(l:cmd, l:options) +  call vimtex#paths#popd() +endfunction + +" }}}1 +function! s:callback(ch, msg) abort " {{{1 +  call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target)) +endfunction + +" }}}1 + +let s:compiler_nvim = {} +function! s:compiler_nvim.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] + +  let l:shell = { +        \ 'on_stdout' : function('s:callback_nvim_output'), +        \ 'on_stderr' : function('s:callback_nvim_output'), +        \ 'on_exit' : function('s:callback_nvim_exit'), +        \ 'cwd' : self.root, +        \ 'target' : self.target_path, +        \ 'output' : self.output, +        \} + +  let self.job = jobstart(l:cmd, l:shell) +endfunction + +" }}}1 +function! s:callback_nvim_output(id, data, event) abort dict " {{{1 +  if !empty(a:data) +    call writefile(filter(a:data, '!empty(v:val)'), self.output, 'a') +  endif +endfunction + +" }}}1 +function! s:callback_nvim_exit(id, data, event) abort dict " {{{1 +  let l:target = self.target !=# b:vimtex.tex ? self.target : '' +  call vimtex#compiler#callback(!vimtex#qf#inquire(l:target)) +endfunction + +" }}}1 + +endif diff --git a/autoload/vimtex/compiler/latexmk.vim b/autoload/vimtex/compiler/latexmk.vim new file mode 100644 index 00000000..d7a36708 --- /dev/null +++ b/autoload/vimtex/compiler/latexmk.vim @@ -0,0 +1,700 @@ +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#compiler#latexmk#init(options) abort " {{{1 +  let l:compiler = deepcopy(s:compiler) + +  call l:compiler.init(extend(a:options, +        \ get(g:, 'vimtex_compiler_latexmk', {}), 'keep')) + +  return l:compiler +endfunction + +" }}}1 +function! vimtex#compiler#latexmk#wrap_option(name, value) abort " {{{1 +  return has('win32') +        \ ? ' -e "$' . a:name . ' = ''' . a:value . '''"' +        \ : ' -e ''$' . a:name . ' = "' . a:value . '"''' +endfunction + +"}}}1 + +function! vimtex#compiler#latexmk#get_rc_opt(root, opt, type, default) abort " {{{1 +  " +  " Parse option from .latexmkrc. +  " +  " Arguments: +  "   root         Root of LaTeX project +  "   opt          Name of options +  "   type         0 if string, 1 if integer, 2 if list +  "   default      Value to return if option not found in latexmkrc file +  " +  " Output: +  "   [value, location] +  " +  "   value        Option value (integer or string) +  "   location     An integer that indicates where option was found +  "                 -1: not found (default value returned) +  "                  0: global latexmkrc file +  "                  1: local latexmkrc file +  " + +  if a:type == 0 +    let l:pattern = '^\s*\$' . a:opt . '\s*=\s*[''"]\(.\+\)[''"]' +  elseif a:type == 1 +    let l:pattern = '^\s*\$' . a:opt . '\s*=\s*\(\d\+\)' +  elseif a:type == 2 +    let l:pattern = '^\s*@' . a:opt . '\s*=\s*(\(.*\))' +  else +    throw 'vimtex: argument error' +  endif + +  " Candidate files +  " - each element is a pair [path_to_file, is_local_rc_file]. +  let l:files = [ +        \ [a:root . '/latexmkrc', 1], +        \ [a:root . '/.latexmkrc', 1], +        \ [fnamemodify('~/.latexmkrc', ':p'), 0], +        \] +  if !empty($XDG_CONFIG_HOME) +    call add(l:files, [$XDG_CONFIG_HOME . '/latexmk/latexmkrc', 0]) +  endif + +  let l:result = [a:default, -1] + +  for [l:file, l:is_local] in l:files +    if filereadable(l:file) +      let l:match = matchlist(readfile(l:file), l:pattern) +      if len(l:match) > 1 +        let l:result = [l:match[1], l:is_local] +        break +      end +    endif +  endfor + +  " Parse the list +  if a:type == 2 && l:result[1] > -1 +    let l:array = split(l:result[0], ',') +    let l:result[0] = [] +    for l:x in l:array +      let l:x = substitute(l:x, "^'", '', '') +      let l:x = substitute(l:x, "'$", '', '') +      let l:result[0] += [l:x] +    endfor +  endif + +  return l:result +endfunction + +" }}}1 + +let s:compiler = { +      \ 'name' : 'latexmk', +      \ 'executable' : 'latexmk', +      \ 'backend' : has('nvim') ? 'nvim' +      \                         : v:version >= 800 ? 'jobs' : 'process', +      \ 'root' : '', +      \ 'target' : '', +      \ 'target_path' : '', +      \ 'background' : 1, +      \ 'build_dir' : '', +      \ 'callback' : 1, +      \ 'continuous' : 1, +      \ 'output' : tempname(), +      \ 'options' : [ +      \   '-verbose', +      \   '-file-line-error', +      \   '-synctex=1', +      \   '-interaction=nonstopmode', +      \ ], +      \ 'hooks' : [], +      \ 'shell' : fnamemodify(&shell, ':t'), +      \} + +function! s:compiler.init(options) abort dict " {{{1 +  call extend(self, a:options) + +  call self.init_check_requirements() +  call self.init_build_dir_option() +  call self.init_pdf_mode_option() + +  call extend(self, deepcopy(s:compiler_{self.backend})) + +  " Continuous processes can't run in foreground, neither can processes run +  " with the new jobs api +  if self.continuous || self.backend !=# 'process' +    let self.background = 1 +  endif + +  if self.backend !=# 'process' +    let self.shell = 'sh' +  endif +endfunction + +" }}}1 +function! s:compiler.init_build_dir_option() abort dict " {{{1 +  " +  " Check if .latexmkrc sets the build_dir - if so this should be respected +  " +  let l:out_dir = +        \ vimtex#compiler#latexmk#get_rc_opt(self.root, 'out_dir', 0, '')[0] + +  if !empty(l:out_dir) +    if !empty(self.build_dir) && (self.build_dir !=# l:out_dir) +      call vimtex#log#warning( +            \ 'Setting out_dir from latexmkrc overrides build_dir!', +            \ 'Changed build_dir from: ' . self.build_dir, +            \ 'Changed build_dir to: ' . l:out_dir) +    endif +    let self.build_dir = l:out_dir +  endif +endfunction + +" }}}1 +function! s:compiler.init_pdf_mode_option() abort dict " {{{1 +  " If the TeX program directive was not set, and if the pdf_mode is set in +  " a .latexmkrc file, then deduce the compiler engine from the value of +  " pdf_mode. + +  " Parse the pdf_mode option. If not found, it is set to -1. +  let [l:pdf_mode, l:is_local] = +        \ vimtex#compiler#latexmk#get_rc_opt(self.root, 'pdf_mode', 1, -1) + +  " If pdf_mode has a supported value (1: pdflatex, 4: lualatex, 5: xelatex), +  " override the value of self.tex_program. +  if l:pdf_mode == 1 +    let l:tex_program = 'pdflatex' +  elseif l:pdf_mode == 3 +    let l:tex_program = 'pdfdvi' +  elseif l:pdf_mode == 4 +    let l:tex_program = 'lualatex' +  elseif l:pdf_mode == 5 +    let l:tex_program = 'xelatex' +  else +    return +  endif + +  if self.tex_program ==# '_' +    " The TeX program directive was not specified +    let self.tex_program = l:tex_program +  elseif l:is_local && self.tex_program !=# l:tex_program +    call vimtex#log#warning( +          \ 'Value of pdf_mode from latexmkrc is inconsistent with ' . +          \ 'TeX program directive!', +          \ 'TeX program: ' . self.tex_program, +          \ 'pdf_mode:    ' . l:tex_program, +          \ 'The value of pdf_mode will be ignored.') +  endif +endfunction + +" }}}1 +function! s:compiler.init_check_requirements() abort dict " {{{1 +  " Check option validity +  if self.callback +    if !(has('clientserver') || has('nvim') || has('job')) +      let self.callback = 0 +      call vimtex#log#warning( +            \ 'Can''t use callbacks without +job, +nvim, or +clientserver', +            \ 'Callback option has been disabled.') +    endif +  endif + +  " Check for required executables +  let l:required = [self.executable] +  if self.continuous && !(has('win32') || has('win32unix')) +    let l:required += ['pgrep'] +  endif +  let l:missing = filter(l:required, '!executable(v:val)') + +  " Disable latexmk if required programs are missing +  if len(l:missing) > 0 +    for l:cmd in l:missing +      call vimtex#log#warning(l:cmd . ' is not executable') +    endfor +    throw 'vimtex: Requirements not met' +  endif +endfunction + +" }}}1 + +function! s:compiler.build_cmd() abort dict " {{{1 +  if has('win32') +    let l:cmd = 'set max_print_line=2000 & ' . self.executable +  else +    if self.shell ==# 'fish' +      let l:cmd = 'set max_print_line 2000; and ' . self.executable +    else +      let l:cmd = 'max_print_line=2000 ' . self.executable +    endif +  endif + +  for l:opt in self.options +    let l:cmd .= ' ' . l:opt +  endfor + +  let l:cmd .= ' ' . self.get_engine() + +  if !empty(self.build_dir) +    let l:cmd .= ' -outdir=' . fnameescape(self.build_dir) +  endif + +  if self.continuous +    let l:cmd .= ' -pvc' + +    " Set viewer options +    if !get(g:, 'vimtex_view_automatic', 1) +          \ || get(get(b:vimtex, 'viewer', {}), 'xwin_id') > 0 +          \ || get(s:, 'silence_next_callback', 0) +      let l:cmd .= ' -view=none' +    elseif g:vimtex_view_enabled +          \ && has_key(b:vimtex.viewer, 'latexmk_append_argument') +      let l:cmd .= b:vimtex.viewer.latexmk_append_argument() +    endif + +    if self.callback +      if has('job') || has('nvim') +        for [l:opt, l:val] in items({ +              \ 'success_cmd' : 'vimtex_compiler_callback_success', +              \ 'failure_cmd' : 'vimtex_compiler_callback_failure', +              \}) +          let l:func = 'echo ' . l:val +          let l:cmd .= vimtex#compiler#latexmk#wrap_option(l:opt, l:func) +        endfor +      elseif empty(v:servername) +        call vimtex#log#warning('Can''t use callbacks with empty v:servername') +      else +        " Some notes: +        " - We excape the v:servername because this seems necessary on Windows +        "   for neovim, see e.g. Github Issue #877 +        for [l:opt, l:val] in items({'success_cmd' : 1, 'failure_cmd' : 0}) +          let l:callback = has('win32') +                \   ? '"vimtex#compiler#callback(' . l:val . ')"' +                \   : '\"vimtex\#compiler\#callback(' . l:val . ')\"' +          let l:func = vimtex#util#shellescape('""') +                \ . g:vimtex_compiler_progname +                \ . vimtex#util#shellescape('""') +                \ . ' --servername ' . vimtex#util#shellescape(v:servername) +                \ . ' --remote-expr ' . l:callback +          let l:cmd .= vimtex#compiler#latexmk#wrap_option(l:opt, l:func) +        endfor +      endif +    endif +  endif + +  return l:cmd . ' ' . vimtex#util#shellescape(self.target) +endfunction + +" }}}1 +function! s:compiler.get_engine() abort dict " {{{1 +  return get(extend(g:vimtex_compiler_latexmk_engines, +        \ { +        \  'pdfdvi'           : '-pdfdvi', +        \  'pdflatex'         : '-pdf', +        \  'luatex'           : '-lualatex', +        \  'lualatex'         : '-lualatex', +        \  'xelatex'          : '-xelatex', +        \  'context (pdftex)' : '-pdf -pdflatex=texexec', +        \  'context (luatex)' : '-pdf -pdflatex=context', +        \  'context (xetex)'  : '-pdf -pdflatex=''texexec --xtx''', +        \ }, 'keep'), self.tex_program, '-pdf') +endfunction + +" }}}1 +function! s:compiler.cleanup() abort dict " {{{1 +  if self.is_running() +    call self.kill() +  endif +endfunction + +" }}}1 +function! s:compiler.pprint_items() abort dict " {{{1 +  let l:configuration = [ +        \ ['continuous', self.continuous], +        \ ['callback', self.callback], +        \] + +  if self.backend ==# 'process' && !self.continuous +    call add(l:configuration, ['background', self.background]) +  endif + +  if !empty(self.build_dir) +    call add(l:configuration, ['build_dir', self.build_dir]) +  endif +  call add(l:configuration, ['latexmk options', self.options]) +  call add(l:configuration, ['latexmk engine', self.get_engine()]) + +  let l:list = [] +  call add(l:list, ['backend', self.backend]) +  if self.executable !=# s:compiler.executable +    call add(l:list, ['latexmk executable', self.executable]) +  endif +  if self.background +    call add(l:list, ['output', self.output]) +  endif + +  if self.target_path !=# b:vimtex.tex +    call add(l:list, ['root', self.root]) +    call add(l:list, ['target', self.target_path]) +  endif + +  call add(l:list, ['configuration', l:configuration]) + +  if has_key(self, 'process') +    call add(l:list, ['process', self.process]) +  endif + +  if has_key(self, 'job') +    if self.continuous +      if self.backend ==# 'jobs' +        call add(l:list, ['job', self.job]) +      else +        call add(l:list, ['pid', self.get_pid()]) +      endif +    endif +    call add(l:list, ['cmd', self.cmd]) +  endif + +  return l:list +endfunction + +" }}}1 + +function! s:compiler.clean(full) abort dict " {{{1 +  let l:restart = self.is_running() +  if l:restart +    call self.stop() +  endif + +  " Define and run the latexmk clean cmd +  let l:cmd = (has('win32') +        \   ? 'cd /D "' . self.root . '" & ' +        \   : 'cd ' . vimtex#util#shellescape(self.root) . '; ') +        \ . self.executable . ' ' . (a:full ? '-C ' : '-c ') +  if !empty(self.build_dir) +    let l:cmd .= printf(' -outdir=%s ', fnameescape(self.build_dir)) +  endif +  let l:cmd .= vimtex#util#shellescape(self.target) +  call vimtex#process#run(l:cmd) + +  call vimtex#log#info('Compiler clean finished' . (a:full ? ' (full)' : '')) + +  if l:restart +    let self.silent_next_callback = 1 +    silent call self.start() +  endif +endfunction + +" }}}1 +function! s:compiler.start(...) abort dict " {{{1 +  if self.is_running() +    call vimtex#log#warning( +          \ 'Compiler is already running for `' . self.target . "'") +    return +  endif + +  " +  " Create build dir if it does not exist +  " +  if !empty(self.build_dir) +    let l:dirs = split(glob(self.root . '/**/*.tex'), '\n') +    call map(l:dirs, 'fnamemodify(v:val, '':h'')') +    call map(l:dirs, 'strpart(v:val, strlen(self.root) + 1)') +    call vimtex#util#uniq(sort(filter(l:dirs, "v:val !=# ''"))) +    call map(l:dirs, +          \ (vimtex#paths#is_abs(self.build_dir) ? '' : "self.root . '/' . ") +          \ . "self.build_dir . '/' . v:val") +    call filter(l:dirs, '!isdirectory(v:val)') + +    " Create the non-existing directories +    for l:dir in l:dirs +      call mkdir(l:dir, 'p') +    endfor +  endif + +  call self.exec() + +  if self.continuous +    call vimtex#log#info('Compiler started in continuous mode' +          \ . (a:0 > 0 ? ' (single shot)' : '')) +    if exists('#User#VimtexEventCompileStarted') +      doautocmd <nomodeline> User VimtexEventCompileStarted +    endif +  else +    if self.background +      call vimtex#log#info('Compiler started in background!') +    else +      call vimtex#compiler#callback(!vimtex#qf#inquire(self.target)) +    endif +  endif +endfunction + +" }}}1 +function! s:compiler.stop() abort dict " {{{1 +  if self.is_running() +    call self.kill() +    call vimtex#log#info('Compiler stopped (' . self.target . ')') +    if exists('#User#VimtexEventCompileStopped') +      doautocmd <nomodeline> User VimtexEventCompileStopped +    endif +  else +    call vimtex#log#warning( +          \ 'There is no process to stop (' . self.target . ')') +  endif +endfunction + +" }}}1 + +let s:compiler_process = {} +function! s:compiler_process.exec() abort dict " {{{1 +  let l:process = vimtex#process#new() +  let l:process.name = 'latexmk' +  let l:process.continuous = self.continuous +  let l:process.background = self.background +  let l:process.workdir = self.root +  let l:process.output = self.output +  let l:process.cmd = self.build_cmd() + +  if l:process.continuous +    if (has('win32') || has('win32unix')) +      " Not implemented +    else +      for l:pid in split(system( +            \ 'pgrep -f "^[^ ]*perl.*latexmk.*' . self.target . '"'), "\n") +        let l:path = resolve('/proc/' . l:pid . '/cwd') . '/' . self.target +        if l:path ==# self.target_path +          let l:process.pid = str2nr(l:pid) +          break +        endif +      endfor +    endif +  endif + +  function! l:process.set_pid() abort dict " {{{2 +    if (has('win32') || has('win32unix')) +      let pidcmd = 'tasklist /fi "imagename eq latexmk.exe"' +      let pidinfo = vimtex#process#capture(pidcmd)[-1] +      let self.pid = str2nr(split(pidinfo,'\s\+')[1]) +    else +      let self.pid = str2nr(system('pgrep -nf "^[^ ]*perl.*latexmk"')[:-2]) +    endif + +    return self.pid +  endfunction + +  " }}}2 + +  let self.process = l:process +  call self.process.run() +endfunction + +" }}}1 +function! s:compiler_process.start_single() abort dict " {{{1 +  let l:continuous = self.continuous +  let self.continuous = self.background && self.callback && !empty(v:servername) + +  if self.continuous +    let g:vimtex_compiler_callback_hooks += ['VimtexSSCallback'] +    function! VimtexSSCallback(status) abort +      silent call vimtex#compiler#stop() +      call remove(g:vimtex_compiler_callback_hooks, 'VimtexSSCallback') +    endfunction +  endif + +  call self.start(1) +  let self.continuous = l:continuous +endfunction + +" }}}1 +function! s:compiler_process.is_running() abort dict " {{{1 +  return exists('self.process.pid') && self.process.pid > 0 +endfunction + +" }}}1 +function! s:compiler_process.kill() abort dict " {{{1 +  call self.process.stop() +endfunction + +" }}}1 +function! s:compiler_process.get_pid() abort dict " {{{1 +  return has_key(self, 'process') ? self.process.pid : 0 +endfunction + +" }}}1 + +let s:compiler_jobs = {} +function! s:compiler_jobs.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] + +  let l:options = { +        \ 'out_io' : 'file', +        \ 'err_io' : 'file', +        \ 'out_name' : self.output, +        \ 'err_name' : self.output, +        \} +  if self.continuous +    let l:options.out_io = 'pipe' +    let l:options.err_io = 'pipe' +    let l:options.out_cb = function('s:callback_continuous_output') +    let l:options.err_cb = function('s:callback_continuous_output') +    call writefile([], self.output, 'a') +  else +    let s:cb_target = self.target_path !=# b:vimtex.tex +          \ ? self.target_path : '' +    let l:options.exit_cb = function('s:callback') +  endif + +  call vimtex#paths#pushd(self.root) +  let self.job = job_start(l:cmd, l:options) +  call vimtex#paths#popd() +endfunction + +" }}}1 +function! s:compiler_jobs.start_single() abort dict " {{{1 +  let l:continuous = self.continuous +  let self.continuous = 0 +  call self.start() +  let self.continuous = l:continuous +endfunction + +" }}}1 +function! s:compiler_jobs.kill() abort dict " {{{1 +  call job_stop(self.job) +endfunction + +" }}}1 +function! s:compiler_jobs.is_running() abort dict " {{{1 +  return has_key(self, 'job') && job_status(self.job) ==# 'run' +endfunction + +" }}}1 +function! s:compiler_jobs.get_pid() abort dict " {{{1 +  return has_key(self, 'job') +        \ ? get(job_info(self.job), 'process') : 0 +endfunction + +" }}}1 +function! s:callback(ch, msg) abort " {{{1 +  call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target)) +endfunction + +" }}}1 +function! s:callback_continuous_output(channel, msg) abort " {{{1 +  if exists('b:vimtex') && filewritable(b:vimtex.compiler.output) +    call writefile([a:msg], b:vimtex.compiler.output, 'a') +  endif + +  if a:msg ==# 'vimtex_compiler_callback_success' +    call vimtex#compiler#callback(1) +  elseif a:msg ==# 'vimtex_compiler_callback_failure' +    call vimtex#compiler#callback(0) +  endif + +  try +    for l:Hook in get(get(get(b:, 'vimtex', {}), 'compiler', {}), 'hooks', []) +      call l:Hook(a:msg) +    endfor +  catch /E716/ +  endtry +endfunction + +" }}}1 + +let s:compiler_nvim = {} +function! s:compiler_nvim.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] + +  let l:shell = { +        \ 'on_stdout' : function('s:callback_nvim_output'), +        \ 'on_stderr' : function('s:callback_nvim_output'), +        \ 'cwd' : self.root, +        \ 'target' : self.target_path, +        \ 'output' : self.output, +        \} + +  if !self.continuous +    let l:shell.on_exit = function('s:callback_nvim_exit') +  endif + +  " Initialize output file +  try +    call writefile([], self.output) +  endtry + +  let self.job = jobstart(l:cmd, l:shell) +endfunction + +" }}}1 +function! s:compiler_nvim.start_single() abort dict " {{{1 +  let l:continuous = self.continuous +  let self.continuous = 0 +  call self.start() +  let self.continuous = l:continuous +endfunction + +" }}}1 +function! s:compiler_nvim.kill() abort dict " {{{1 +  call jobstop(self.job) +endfunction + +" }}}1 +function! s:compiler_nvim.is_running() abort dict " {{{1 +  try +    let pid = jobpid(self.job) +    return 1 +  catch +    return 0 +  endtry +endfunction + +" }}}1 +function! s:compiler_nvim.get_pid() abort dict " {{{1 +  try +    return jobpid(self.job) +  catch +    return 0 +  endtry +endfunction + +" }}}1 +function! s:callback_nvim_output(id, data, event) abort dict " {{{1 +  " Filter out unwanted newlines +  let l:data = split(substitute(join(a:data, 'QQ'), '^QQ\|QQ$', '', ''), 'QQ') + +  if !empty(l:data) && filewritable(self.output) +    call writefile(l:data, self.output, 'a') +  endif + +  if match(a:data, 'vimtex_compiler_callback_success') != -1 +    call vimtex#compiler#callback(!vimtex#qf#inquire(self.target)) +  elseif match(a:data, 'vimtex_compiler_callback_failure') != -1 +    call vimtex#compiler#callback(0) +  endif + +  try +    for l:Hook in get(get(get(b:, 'vimtex', {}), 'compiler', {}), 'hooks', []) +      call l:Hook(join(a:data, "\n")) +    endfor +  catch /E716/ +  endtry +endfunction + +" }}}1 +function! s:callback_nvim_exit(id, data, event) abort dict " {{{1 +  let l:target = self.target !=# b:vimtex.tex ? self.target : '' +  call vimtex#compiler#callback(!vimtex#qf#inquire(l:target)) +endfunction + +" }}}1 + +endif diff --git a/autoload/vimtex/compiler/latexrun.vim b/autoload/vimtex/compiler/latexrun.vim new file mode 100644 index 00000000..01ebc02f --- /dev/null +++ b/autoload/vimtex/compiler/latexrun.vim @@ -0,0 +1,250 @@ +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#compiler#latexrun#init(options) abort " {{{1 +  let l:compiler = deepcopy(s:compiler) + +  call l:compiler.init(extend(a:options, +        \ get(g:, 'vimtex_compiler_latexrun', {}), 'keep')) + +  return l:compiler +endfunction + +" }}}1 + +let s:compiler = { +      \ 'name' : 'latexrun', +      \ 'backend' : has('nvim') ? 'nvim' +      \                         : v:version >= 800 ? 'jobs' : 'process', +      \ 'root' : '', +      \ 'target' : '', +      \ 'target_path' : '', +      \ 'background' : 1, +      \ 'build_dir' : '', +      \ 'output' : tempname(), +      \ 'options' : [ +      \   '--verbose-cmds', +      \   '--latex-args="-synctex=1"', +      \ ], +      \} + +function! s:compiler.init(options) abort dict " {{{1 +  call extend(self, a:options) + +  if !executable('latexrun') +    call vimtex#log#warning('latexrun is not executable!') +    throw 'vimtex: Requirements not met' +  endif + +  call extend(self, deepcopy(s:compiler_{self.backend})) + +  " Processes run with the new jobs api will not run in the foreground +  if self.backend !=# 'process' +    let self.background = 1 +  endif +endfunction + +" }}}1 + +function! s:compiler.build_cmd() abort dict " {{{1 +  let l:cmd = 'latexrun' + +  for l:opt in self.options +    let l:cmd .= ' ' . l:opt +  endfor + +  let l:cmd .= ' --latex-cmd ' . self.get_engine() + +  let l:cmd .= ' -O ' +        \ . (empty(self.build_dir) ? '.' : fnameescape(self.build_dir)) + +  return l:cmd . ' ' . vimtex#util#shellescape(self.target) +endfunction + +" }}}1 +function! s:compiler.get_engine() abort dict " {{{1 +  return get(extend(g:vimtex_compiler_latexrun_engines, +        \ { +        \  '_'                : 'pdflatex', +        \  'pdflatex'         : 'pdflatex', +        \  'lualatex'         : 'lualatex', +        \  'xelatex'          : 'xelatex', +        \ }, 'keep'), self.tex_program, '_') +endfunction + +" }}}1 +function! s:compiler.cleanup() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.pprint_items() abort dict " {{{1 +  let l:configuration = [] + +  if self.backend ==# 'process' +    call add(l:configuration, ['background', self.background]) +  endif + +  if !empty(self.build_dir) +    call add(l:configuration, ['build_dir', self.build_dir]) +  endif +  call add(l:configuration, ['latexrun options', self.options]) +  call add(l:configuration, ['latexrun engine', self.get_engine()]) + +  let l:list = [] +  call add(l:list, ['backend', self.backend]) +  if self.background +    call add(l:list, ['output', self.output]) +  endif + +  if self.target_path !=# b:vimtex.tex +    call add(l:list, ['root', self.root]) +    call add(l:list, ['target', self.target_path]) +  endif + +  call add(l:list, ['configuration', l:configuration]) + +  if has_key(self, 'process') +    call add(l:list, ['process', self.process]) +  endif + +  if has_key(self, 'job') +    call add(l:list, ['cmd', self.cmd]) +  endif + +  return l:list +endfunction + +" }}}1 + +function! s:compiler.clean(...) abort dict " {{{1 +  let l:cmd = (has('win32') +        \   ? 'cd /D "' . self.root . '" & ' +        \   : 'cd ' . vimtex#util#shellescape(self.root) . '; ') +        \ . 'latexrun --clean-all' +        \ . ' -O ' +        \   . (empty(self.build_dir) ? '.' : fnameescape(self.build_dir)) +  call vimtex#process#run(l:cmd) + +  call vimtex#log#info('Compiler clean finished') +endfunction + +" }}}1 +function! s:compiler.start(...) abort dict " {{{1 +  call self.exec() + +  if self.background +    call vimtex#log#info('Compiler started in background') +  else +    call vimtex#compiler#callback(!vimtex#qf#inquire(self.target)) +  endif +endfunction + +" }}}1 +function! s:compiler.start_single() abort dict " {{{1 +  call self.start() +endfunction + +" }}}1 +function! s:compiler.stop() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.is_running() abort dict " {{{1 +  return 0 +endfunction + +" }}}1 +function! s:compiler.kill() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.get_pid() abort dict " {{{1 +  return 0 +endfunction + +" }}}1 + +let s:compiler_process = {} +function! s:compiler_process.exec() abort dict " {{{1 +  let self.process = vimtex#process#new() +  let self.process.name = 'latexrun' +  let self.process.background = self.background +  let self.process.workdir = self.root +  let self.process.output = self.output +  let self.process.cmd = self.build_cmd() +  call self.process.run() +endfunction + +" }}}1 + +let s:compiler_jobs = {} +function! s:compiler_jobs.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] +  let l:options = { +        \ 'out_io' : 'file', +        \ 'err_io' : 'file', +        \ 'out_name' : self.output, +        \ 'err_name' : self.output, +        \} + +  let s:cb_target = self.target_path !=# b:vimtex.tex ? self.target_path : '' +  let l:options.exit_cb = function('s:callback') + +  call vimtex#paths#pushd(self.root) +  let self.job = job_start(l:cmd, l:options) +  call vimtex#paths#popd() +endfunction + +" }}}1 +function! s:callback(ch, msg) abort " {{{1 +  call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target)) +endfunction + +" }}}1 + +let s:compiler_nvim = {} +function! s:compiler_nvim.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] + +  let l:shell = { +        \ 'on_stdout' : function('s:callback_nvim_output'), +        \ 'on_stderr' : function('s:callback_nvim_output'), +        \ 'on_exit' : function('s:callback_nvim_exit'), +        \ 'cwd' : self.root, +        \ 'target' : self.target_path, +        \ 'output' : self.output, +        \} + +  let self.job = jobstart(l:cmd, l:shell) +endfunction + +" }}}1 +function! s:callback_nvim_output(id, data, event) abort dict " {{{1 +  if !empty(a:data) +    call writefile(filter(a:data, '!empty(v:val)'), self.output, 'a') +  endif +endfunction + +" }}}1 +function! s:callback_nvim_exit(id, data, event) abort dict " {{{1 +  let l:target = self.target !=# b:vimtex.tex ? self.target : '' +  call vimtex#compiler#callback(!vimtex#qf#inquire(l:target)) +endfunction + +" }}}1 + +endif diff --git a/autoload/vimtex/compiler/tectonic.vim b/autoload/vimtex/compiler/tectonic.vim new file mode 100644 index 00000000..3b9e8139 --- /dev/null +++ b/autoload/vimtex/compiler/tectonic.vim @@ -0,0 +1,255 @@ +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#compiler#tectonic#init(options) abort " {{{1 +  let l:compiler = deepcopy(s:compiler) + +  call l:compiler.init(extend(a:options, +        \ get(g:, 'vimtex_compiler_tectonic', {}), 'keep')) + +  return l:compiler +endfunction + +" }}}1 + +let s:compiler = { +      \ 'name' : 'tectonic', +      \ 'backend' : has('nvim') ? 'nvim' +      \                         : v:version >= 800 ? 'jobs' : 'process', +      \ 'root' : '', +      \ 'target' : '', +      \ 'target_path' : '', +      \ 'background' : 1, +      \ 'build_dir' : '', +      \ 'output' : tempname(), +      \ 'options' : [ +      \   '--keep-logs', +      \   '--synctex' +      \ ], +      \} + +function! s:compiler.init(options) abort dict " {{{1 +  call extend(self, a:options) + +  if !executable('tectonic') +    call vimtex#log#warning('tectonic is not executable!') +    throw 'vimtex: Requirements not met' +  endif + +  call extend(self, deepcopy(s:compiler_{self.backend})) + +  " Processes run with the new jobs api will not run in the foreground +  if self.backend !=# 'process' +    let self.background = 1 +  endif +endfunction + +" }}}1 + +function! s:compiler.build_cmd() abort dict " {{{1 +  let l:cmd = 'tectonic' + +  for l:opt in self.options +    if l:opt =~# '^-\%(o\|-outdir\)' +      call vimtex#log#warning("Don't use --outdir or -o in compiler options," +            \ . ' use build_dir instead, see :help g:vimtex_compiler_tectonic' +            \ . ' for more details') +      continue +    endif + +    let l:cmd .= ' ' . l:opt +  endfor + +  if empty(self.build_dir) +    let self.build_dir = fnamemodify(self.target_path, ':p:h') +  elseif !isdirectory(self.build_dir) +    call vimtex#log#warning( +          \ "build_dir doesn't exist, it will be created: " . self.build_dir) +    call mkdir(self.build_dir, 'p') +  endif + +  return l:cmd +        \ . ' --outdir=' . self.build_dir +        \ . ' ' . vimtex#util#shellescape(self.target) +endfunction + +" }}}1 +function! s:compiler.cleanup() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.pprint_items() abort dict " {{{1 +  let l:configuration = [] + +  if self.backend ==# 'process' +    call add(l:configuration, ['background', self.background]) +  endif + +  call add(l:configuration, ['tectonic options', self.options]) + +  let l:list = [] +  call add(l:list, ['backend', self.backend]) +  if self.background +    call add(l:list, ['output', self.output]) +  endif + +  if self.target_path !=# b:vimtex.tex +    call add(l:list, ['root', self.root]) +    call add(l:list, ['target', self.target_path]) +  endif + +  call add(l:list, ['configuration', l:configuration]) + +  if has_key(self, 'process') +    call add(l:list, ['process', self.process]) +  endif + +  if has_key(self, 'job') +    call add(l:list, ['cmd', self.cmd]) +  endif + +  return l:list +endfunction + +" }}}1 + +function! s:compiler.clean(...) abort dict " {{{1 +  let l:files = ['synctex.gz', 'toc', 'out', 'aux', 'log'] + +  " If a full clean is required +  if a:0 > 0 && a:1 +    call extend(l:intermediate, ['pdf']) +  endif + +  let l:basename = self.build_dir . '/' . fnamemodify(self.target_path, ':t:r') +  call map(l:files, 'l:basename . v:val') + +  call vimtex#process#run('rm -f ' . join(l:files)) +  call vimtex#log#info('Compiler clean finished') +endfunction + +" }}}1 +function! s:compiler.start(...) abort dict " {{{1 +  call self.exec() + +  if self.background +    call vimtex#log#info('Compiler started in background') +  else +    call vimtex#compiler#callback(!vimtex#qf#inquire(self.target)) +  endif +endfunction + +" }}}1 +function! s:compiler.start_single() abort dict " {{{1 +  call self.start() +endfunction + +" }}}1 +function! s:compiler.stop() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.is_running() abort dict " {{{1 +  return 0 +endfunction + +" }}}1 +function! s:compiler.kill() abort dict " {{{1 +  " Pass +endfunction + +" }}}1 +function! s:compiler.get_pid() abort dict " {{{1 +  return 0 +endfunction + +" }}}1 + +let s:compiler_process = {} +function! s:compiler_process.exec() abort dict " {{{1 +  let self.process = vimtex#process#new() +  let self.process.name = 'tectonic' +  let self.process.background = self.background +  let self.process.workdir = self.root +  let self.process.output = self.output +  let self.process.cmd = self.build_cmd() +  call self.process.run() +endfunction + +" }}}1 + +let s:compiler_jobs = {} +function! s:compiler_jobs.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] +  let l:options = { +        \ 'out_io' : 'file', +        \ 'err_io' : 'file', +        \ 'out_name' : self.output, +        \ 'err_name' : self.output, +        \} + +  let s:cb_target = self.target_path !=# b:vimtex.tex ? self.target_path : '' +  let l:options.exit_cb = function('s:callback') + +  if !empty(self.root) +    let l:save_pwd = getcwd() +    execute 'lcd' fnameescape(self.root) +  endif +  let self.job = job_start(l:cmd, l:options) +  if !empty(self.root) +    execute 'lcd' fnameescape(l:save_pwd) +  endif +endfunction + +" }}}1 +function! s:callback(ch, msg) abort " {{{1 +  call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target)) +endfunction + +" }}}1 + +let s:compiler_nvim = {} +function! s:compiler_nvim.exec() abort dict " {{{1 +  let self.cmd = self.build_cmd() +  let l:cmd = has('win32') +        \ ? 'cmd /s /c "' . self.cmd . '"' +        \ : ['sh', '-c', self.cmd] + +  let l:shell = { +        \ 'on_stdout' : function('s:callback_nvim_output'), +        \ 'on_stderr' : function('s:callback_nvim_output'), +        \ 'on_exit' : function('s:callback_nvim_exit'), +        \ 'cwd' : self.root, +        \ 'target' : self.target_path, +        \ 'output' : self.output, +        \} + +  let self.job = jobstart(l:cmd, l:shell) +endfunction + +" }}}1 +function! s:callback_nvim_output(id, data, event) abort dict " {{{1 +  if !empty(a:data) +    call writefile(filter(a:data, '!empty(v:val)'), self.output, 'a') +  endif +endfunction + +" }}}1 +function! s:callback_nvim_exit(id, data, event) abort dict " {{{1 +  let l:target = self.target !=# b:vimtex.tex ? self.target : '' +  call vimtex#compiler#callback(!vimtex#qf#inquire(l:target)) +endfunction + +" }}}1 + +endif | 
