1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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" specifying 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
|