29756
|
1 vim9script
|
|
2
|
|
3 # Language: Generic TeX typesetting engine
|
|
4 # Maintainer: Nicola Vitacolonna <nvitacolonna@gmail.com>
|
|
5 # Latest Revision: 2022 Aug 12
|
|
6
|
|
7 # Constants and helpers {{{
|
|
8 const SLASH = !exists("+shellslash") || &shellslash ? '/' : '\'
|
|
9
|
|
10 def Echo(msg: string, mode: string, label: string)
|
|
11 redraw
|
|
12 echo "\r"
|
|
13 execute 'echohl' mode
|
|
14 echomsg printf('[%s] %s', label, msg)
|
|
15 echohl None
|
|
16 enddef
|
|
17
|
|
18 def EchoMsg(msg: string, label = 'Notice')
|
|
19 Echo(msg, 'ModeMsg', label)
|
|
20 enddef
|
|
21
|
|
22 def EchoWarn(msg: string, label = 'Warning')
|
|
23 Echo(msg, 'WarningMsg', label)
|
|
24 enddef
|
|
25
|
|
26 def EchoErr(msg: string, label = 'Error')
|
|
27 Echo(msg, 'ErrorMsg', label)
|
|
28 enddef
|
|
29 # }}}
|
|
30
|
|
31 # Track jobs {{{
|
|
32 var running_jobs = {} # Dictionary of job IDs of jobs currently executing
|
|
33
|
|
34 def AddJob(label: string, j: job)
|
|
35 if !has_key(running_jobs, label)
|
|
36 running_jobs[label] = []
|
|
37 endif
|
|
38
|
|
39 add(running_jobs[label], j)
|
|
40 enddef
|
|
41
|
|
42 def RemoveJob(label: string, j: job)
|
|
43 if has_key(running_jobs, label) && index(running_jobs[label], j) != -1
|
|
44 remove(running_jobs[label], index(running_jobs[label], j))
|
|
45 endif
|
|
46 enddef
|
|
47
|
|
48 def GetRunningJobs(label: string): list<job>
|
|
49 return has_key(running_jobs, label) ? running_jobs[label] : []
|
|
50 enddef
|
|
51 # }}}
|
|
52
|
|
53 # Callbacks {{{
|
|
54 def ProcessOutput(qfid: number, wd: string, efm: string, ch: channel, msg: string)
|
|
55 # Make sure the quickfix list still exists
|
|
56 if getqflist({'id': qfid}).id != qfid
|
|
57 EchoErr("Quickfix list not found, stopping the job")
|
|
58 call job_stop(ch_getjob(ch))
|
|
59 return
|
|
60 endif
|
|
61
|
|
62 # Make sure the working directory is correct
|
|
63 silent execute "lcd" wd
|
|
64 setqflist([], 'a', {'id': qfid, 'lines': [msg], 'efm': efm})
|
|
65 silent lcd -
|
|
66 enddef
|
|
67
|
|
68 def CloseCb(ch: channel)
|
|
69 job_status(ch_getjob(ch)) # Trigger exit_cb's callback
|
|
70 enddef
|
|
71
|
|
72 def ExitCb(label: string, jobid: job, exitStatus: number)
|
|
73 RemoveJob(label, jobid)
|
|
74
|
|
75 if exitStatus == 0
|
|
76 botright cwindow
|
|
77 EchoMsg('Success!', label)
|
|
78 elseif exitStatus < 0
|
|
79 EchoWarn('Job terminated', label)
|
|
80 else
|
|
81 botright copen
|
|
82 wincmd p
|
|
83 EchoWarn('There are errors.', label)
|
|
84 endif
|
|
85 enddef
|
|
86 # }}}
|
|
87
|
|
88 # Create a new empty quickfix list at the end of the stack and return its id {{{
|
|
89 def NewQuickfixList(path: string): number
|
|
90 if setqflist([], ' ', {'nr': '$', 'title': path}) == -1
|
|
91 return -1
|
|
92 endif
|
|
93
|
|
94 return getqflist({'nr': '$', 'id': 0}).id
|
|
95 enddef
|
|
96 # }}}
|
|
97
|
|
98 # Public interface {{{
|
|
99 # When a TeX document is split into several source files, each source file
|
|
100 # may contain a "magic line" specifiying the "root" file, e.g.:
|
|
101 #
|
|
102 # % !TEX root = main.tex
|
|
103 #
|
|
104 # Using this line, Vim can know which file to typeset even if the current
|
|
105 # buffer is different from main.tex.
|
|
106 #
|
|
107 # This function searches for the magic line in the first ten lines of the
|
|
108 # given buffer, and returns the full path of the root document.
|
|
109 #
|
|
110 # NOTE: the value of "% !TEX root" *must* be a relative path.
|
|
111 export def FindRootDocument(bufname: string = bufname("%")): string
|
|
112 const bufnr = bufnr(bufname)
|
|
113
|
|
114 if !bufexists(bufnr)
|
|
115 return bufname
|
|
116 endif
|
|
117
|
|
118 var rootpath = fnamemodify(bufname(bufnr), ':p')
|
|
119
|
|
120 # Search for magic line `% !TEX root = ...` in the first ten lines
|
|
121 const header = getbufline(bufnr, 1, 10)
|
|
122 const idx = match(header, '^\s*%\s\+!TEX\s\+root\s*=\s*\S')
|
|
123 if idx > -1
|
|
124 const main = matchstr(header[idx], '!TEX\s\+root\s*=\s*\zs.*$')
|
|
125 rootpath = simplify(fnamemodify(rootpath, ":h") .. SLASH .. main)
|
|
126 endif
|
|
127
|
|
128 return rootpath
|
|
129 enddef
|
|
130
|
|
131 export def LogPath(bufname: string): string
|
|
132 const logfile = FindRootDocument(bufname)
|
|
133 return fnamemodify(logfile, ":r") .. ".log"
|
|
134 enddef
|
|
135
|
|
136 # Typeset the specified path
|
|
137 #
|
|
138 # Parameters:
|
|
139 # label: a descriptive string used in messages to identify the kind of job
|
|
140 # Cmd: a function that takes the path of a document and returns the typesetting command
|
|
141 # path: the path of the document to be typeset. To avoid ambiguities, pass a *full* path.
|
|
142 # efm: the error format string to parse the output of the command.
|
|
143 # env: environment variables for the process (passed to job_start())
|
|
144 #
|
|
145 # Returns:
|
|
146 # true if the job is started successfully;
|
|
147 # false otherwise.
|
|
148 export def Typeset(
|
|
149 label: string,
|
|
150 Cmd: func(string): list<string>,
|
|
151 path: string,
|
|
152 efm: string,
|
|
153 env: dict<string> = {}
|
|
154 ): bool
|
|
155 var fp = fnamemodify(path, ":p")
|
|
156 var wd = fnamemodify(fp, ":h")
|
|
157 var qfid = NewQuickfixList(fp)
|
|
158
|
|
159 if qfid == -1
|
|
160 EchoErr('Could not create quickfix list', label)
|
|
161 return false
|
|
162 endif
|
|
163
|
|
164 if !filereadable(fp)
|
|
165 EchoErr(printf('File not readable: %s', fp), label)
|
|
166 return false
|
|
167 endif
|
|
168
|
|
169 var jobid = job_start(Cmd(path), {
|
|
170 env: env,
|
|
171 cwd: wd,
|
|
172 in_io: "null",
|
|
173 callback: (c, m) => ProcessOutput(qfid, wd, efm, c, m),
|
|
174 close_cb: CloseCb,
|
|
175 exit_cb: (j, e) => ExitCb(label, j, e),
|
|
176 })
|
|
177
|
|
178 if job_status(jobid) ==# "fail"
|
|
179 EchoErr("Failed to start job", label)
|
|
180 return false
|
|
181 endif
|
|
182
|
|
183 AddJob(label, jobid)
|
|
184
|
|
185 EchoMsg('Typesetting...', label)
|
|
186
|
|
187 return true
|
|
188 enddef
|
|
189
|
|
190 export def JobStatus(label: string)
|
|
191 EchoMsg('Jobs still running: ' .. string(len(GetRunningJobs(label))), label)
|
|
192 enddef
|
|
193
|
|
194 export def StopJobs(label: string)
|
|
195 for job in GetRunningJobs(label)
|
|
196 job_stop(job)
|
|
197 endfor
|
|
198
|
|
199 EchoMsg('Done.', label)
|
|
200 enddef
|
|
201
|
|
202 # Typeset the specified buffer
|
|
203 #
|
|
204 # Parameters:
|
|
205 # name: a buffer's name. this may be empty to indicate the current buffer.
|
|
206 # cmd: a function that takes the path of a document and returns the typesetting command
|
|
207 # label: a descriptive string used in messages to identify the kind of job
|
|
208 # env: environment variables for the process (passed to job_start())
|
|
209 #
|
|
210 # Returns:
|
|
211 # true if the job is started successfully;
|
|
212 # false otherwise.
|
|
213 export def TypesetBuffer(
|
|
214 name: string,
|
|
215 Cmd: func(string): list<string>,
|
|
216 env = {},
|
|
217 label = 'Typeset'
|
|
218 ): bool
|
|
219 const bufname = bufname(name)
|
|
220
|
|
221 if empty(bufname)
|
|
222 EchoErr('Please save the buffer first.', label)
|
|
223 return false
|
|
224 endif
|
|
225
|
|
226 const efm = getbufvar(bufnr(bufname), "&efm")
|
|
227 const rootpath = FindRootDocument(bufname)
|
|
228
|
|
229 return Typeset('ConTeXt', Cmd, rootpath, efm, env)
|
|
230 enddef
|
|
231 # }}}
|
|
232
|
|
233 # vim: sw=2 fdm=marker
|