summaryrefslogtreecommitdiffstats
path: root/autoload/vimtex/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'autoload/vimtex/compiler')
-rw-r--r--autoload/vimtex/compiler/arara.vim218
-rw-r--r--autoload/vimtex/compiler/latexmk.vim700
-rw-r--r--autoload/vimtex/compiler/latexrun.vim250
-rw-r--r--autoload/vimtex/compiler/tectonic.vim255
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