diff options
Diffstat (limited to '')
-rw-r--r-- | runtime/autoload/typeset.vim | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/runtime/autoload/typeset.vim b/runtime/autoload/typeset.vim new file mode 100644 index 0000000..35cf17b --- /dev/null +++ b/runtime/autoload/typeset.vim @@ -0,0 +1,233 @@ +vim9script + +# Language: Generic TeX typesetting engine +# Maintainer: Nicola Vitacolonna <nvitacolonna@gmail.com> +# Latest Revision: 2022 Aug 12 + +# Constants and helpers {{{ +const SLASH = !exists("+shellslash") || &shellslash ? '/' : '\' + +def Echo(msg: string, mode: string, label: string) + redraw + echo "\r" + execute 'echohl' mode + echomsg printf('[%s] %s', label, msg) + echohl None +enddef + +def EchoMsg(msg: string, label = 'Notice') + Echo(msg, 'ModeMsg', label) +enddef + +def EchoWarn(msg: string, label = 'Warning') + Echo(msg, 'WarningMsg', label) +enddef + +def EchoErr(msg: string, label = 'Error') + Echo(msg, 'ErrorMsg', label) +enddef +# }}} + +# Track jobs {{{ +var running_jobs = {} # Dictionary of job IDs of jobs currently executing + +def AddJob(label: string, j: job) + if !has_key(running_jobs, label) + running_jobs[label] = [] + endif + + add(running_jobs[label], j) +enddef + +def RemoveJob(label: string, j: job) + if has_key(running_jobs, label) && index(running_jobs[label], j) != -1 + remove(running_jobs[label], index(running_jobs[label], j)) + endif +enddef + +def GetRunningJobs(label: string): list<job> + return has_key(running_jobs, label) ? running_jobs[label] : [] +enddef +# }}} + +# Callbacks {{{ +def ProcessOutput(qfid: number, wd: string, efm: string, ch: channel, msg: string) + # Make sure the quickfix list still exists + if getqflist({'id': qfid}).id != qfid + EchoErr("Quickfix list not found, stopping the job") + call job_stop(ch_getjob(ch)) + return + endif + + # Make sure the working directory is correct + silent execute "lcd" wd + setqflist([], 'a', {'id': qfid, 'lines': [msg], 'efm': efm}) + silent lcd - +enddef + +def CloseCb(ch: channel) + job_status(ch_getjob(ch)) # Trigger exit_cb's callback +enddef + +def ExitCb(label: string, jobid: job, exitStatus: number) + RemoveJob(label, jobid) + + if exitStatus == 0 + botright cwindow + EchoMsg('Success!', label) + elseif exitStatus < 0 + EchoWarn('Job terminated', label) + else + botright copen + wincmd p + EchoWarn('There are errors.', label) + endif +enddef +# }}} + +# Create a new empty quickfix list at the end of the stack and return its id {{{ +def NewQuickfixList(path: string): number + if setqflist([], ' ', {'nr': '$', 'title': path}) == -1 + return -1 + endif + + return getqflist({'nr': '$', 'id': 0}).id +enddef +# }}} + +# Public interface {{{ +# When a TeX document is split into several source files, each source file +# may contain a "magic line" specifiying the "root" file, e.g.: +# +# % !TEX root = main.tex +# +# Using this line, Vim can know which file to typeset even if the current +# buffer is different from main.tex. +# +# This function searches for the magic line in the first ten lines of the +# given buffer, and returns the full path of the root document. +# +# NOTE: the value of "% !TEX root" *must* be a relative path. +export def FindRootDocument(bufname: string = bufname("%")): string + const bufnr = bufnr(bufname) + + if !bufexists(bufnr) + return bufname + endif + + var rootpath = fnamemodify(bufname(bufnr), ':p') + + # Search for magic line `% !TEX root = ...` in the first ten lines + const header = getbufline(bufnr, 1, 10) + const idx = match(header, '^\s*%\s\+!TEX\s\+root\s*=\s*\S') + if idx > -1 + const main = matchstr(header[idx], '!TEX\s\+root\s*=\s*\zs.*$') + rootpath = simplify(fnamemodify(rootpath, ":h") .. SLASH .. main) + endif + + return rootpath +enddef + +export def LogPath(bufname: string): string + const logfile = FindRootDocument(bufname) + return fnamemodify(logfile, ":r") .. ".log" +enddef + +# Typeset the specified path +# +# Parameters: +# label: a descriptive string used in messages to identify the kind of job +# Cmd: a function that takes the path of a document and returns the typesetting command +# path: the path of the document to be typeset. To avoid ambiguities, pass a *full* path. +# efm: the error format string to parse the output of the command. +# env: environment variables for the process (passed to job_start()) +# +# Returns: +# true if the job is started successfully; +# false otherwise. +export def Typeset( + label: string, + Cmd: func(string): list<string>, + path: string, + efm: string, + env: dict<string> = {} +): bool + var fp = fnamemodify(path, ":p") + var wd = fnamemodify(fp, ":h") + var qfid = NewQuickfixList(fp) + + if qfid == -1 + EchoErr('Could not create quickfix list', label) + return false + endif + + if !filereadable(fp) + EchoErr(printf('File not readable: %s', fp), label) + return false + endif + + var jobid = job_start(Cmd(path), { + env: env, + cwd: wd, + in_io: "null", + callback: (c, m) => ProcessOutput(qfid, wd, efm, c, m), + close_cb: CloseCb, + exit_cb: (j, e) => ExitCb(label, j, e), + }) + + if job_status(jobid) ==# "fail" + EchoErr("Failed to start job", label) + return false + endif + + AddJob(label, jobid) + + EchoMsg('Typesetting...', label) + + return true +enddef + +export def JobStatus(label: string) + EchoMsg('Jobs still running: ' .. string(len(GetRunningJobs(label))), label) +enddef + +export def StopJobs(label: string) + for job in GetRunningJobs(label) + job_stop(job) + endfor + + EchoMsg('Done.', label) +enddef + +# Typeset the specified buffer +# +# Parameters: +# name: a buffer's name. this may be empty to indicate the current buffer. +# cmd: a function that takes the path of a document and returns the typesetting command +# label: a descriptive string used in messages to identify the kind of job +# env: environment variables for the process (passed to job_start()) +# +# Returns: +# true if the job is started successfully; +# false otherwise. +export def TypesetBuffer( + name: string, + Cmd: func(string): list<string>, + env = {}, + label = 'Typeset' +): bool + const bufname = bufname(name) + + if empty(bufname) + EchoErr('Please save the buffer first.', label) + return false + endif + + const efm = getbufvar(bufnr(bufname), "&efm") + const rootpath = FindRootDocument(bufname) + + return Typeset('ConTeXt', Cmd, rootpath, efm, env) +enddef +# }}} + +# vim: sw=2 fdm=marker |