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 |