1if not modules then modules = { } end modules ['node-syn'] = {
2 version = 1.001,
3 comment = "companion to node-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18local type, rawset = type, rawset
19local concat = table.concat
20local formatters = string.formatters
21local replacesuffix, suffixonly, nameonly, collapsepath = file.replacesuffix, file.suffix, file.nameonly, file.collapsepath
22local openfile, renamefile, removefile = io.open, os.rename, os.remove
23
24local report_system = logs.reporter("system")
25
26local tex = tex
27local texget = tex.get
28
29local nuts = nodes.nuts
30
31local getid = nuts.getid
32local getlist = nuts.getlist
33local setlist = nuts.setlist
34local getnext = nuts.getnext
35local getwhd = nuts.getwhd
36local getsubtype = nuts.getsubtype
37
38local nodecodes = nodes.nodecodes
39local kerncodes = nodes.kerncodes
40
41local glyph_code = nodecodes.glyph
42local disc_code = nodecodes.disc
43local glue_code = nodecodes.glue
44local penalty_code = nodecodes.penalty
45local kern_code = nodecodes.kern
46local hlist_code = nodecodes.hlist
47local vlist_code = nodecodes.vlist
48local fontkern_code = kerncodes.fontkern
49
50local insertbefore = nuts.insertbefore
51local insertafter = nuts.insertafter
52
53local nodepool = nuts.pool
54local new_latelua = nodepool.latelua
55local new_rule = nodepool.rule
56local new_kern = nodepool.kern
57
58local getdimensions = nuts.dimensions
59local getrangedimensions = nuts.rangedimensions
60
61local getinputfields = nuts.getsynctexfields
62local forceinputstatefile = tex.forcesynctextag or tex.force_synctex_tag
63local forceinputstateline = tex.forcesynctexline or tex.force_synctex_line
64local getinputstateline = tex.getsynctexline or tex.get_synctex_line
65local setinputstatemode = tex.setsynctexmode or tex.set_synctex_mode
66
67local foundintree = resolvers.foundintree
68
69local getpagedimensions = nil
70
71local eol = "\010"
72local z_hlist = "[0,0:0,0:0,0,0\010"
73local s_hlist = "]\010"
74local f_hvoid = formatters["h%i,%i:%i,%i:%i,%i,%i\010"]
75local f_hlist = formatters["(%i,%i:%i,%i:%i,%i,%i\010"]
76local s_hlist = ")\010"
77local f_rule = formatters["r%i,%i:%i,%s:%i,%i,%i\010"]
78local f_plist = formatters["[%i,%i:%i,%i:%i,%i,%i\010"]
79local s_plist = "]\010"
80
81local synctex = luatex.synctex or { }
82luatex.synctex = synctex
83
84local getpos ; getpos = function() getpos = job.positions.getpos return getpos() end
85
86
87
88local enabled = false
89local userule = false
90local paused = 0
91local used = false
92local never = false
93
94
95
96tex.set_synctex_no_files(1)
97
98local noftags = 0
99local stnums = { }
100local nofblocked = 0
101local blockedfilenames = { }
102local blockedsuffixes = {
103 mkii = true,
104 mkiv = true,
105 mkvi = true,
106 mkxl = true,
107 mklx = true,
108 mkix = true,
109 mkxi = true,
110
111}
112
113local sttags = table.setmetatableindex(function(t,fullname)
114 local name = collapsepath(fullname)
115 if blockedsuffixes[suffixonly(name)] then
116
117 nofblocked = nofblocked + 1
118 return 0
119 elseif blockedfilenames[nameonly(name)] then
120
121 nofblocked = nofblocked + 1
122 return 0
123 elseif foundintree(name) then
124
125 nofblocked = nofblocked + 1
126 return 0
127 else
128 noftags = noftags + 1
129 t[name] = noftags
130 if name ~= fullname then
131 t[fullname] = noftags
132 end
133 stnums[noftags] = name
134 return noftags
135 end
136end)
137
138function synctex.blockfilename(name)
139 blockedfilenames[nameonly(name)] = name
140end
141
142function synctex.setfilename(name,line)
143 if paused == 0 and name then
144 forceinputstatefile(sttags[name])
145 if line then
146 forceinputstateline(line)
147 end
148 end
149end
150
151function synctex.resetfilename()
152 if paused == 0 then
153 forceinputstatefile(0)
154 forceinputstateline(0)
155 end
156end
157
158do
159
160 local nesting = 0
161 local ignored = false
162
163 function synctex.pushline()
164 nesting = nesting + 1
165 if nesting == 1 then
166 local l = getinputstateline()
167 ignored = l and l > 0
168 if not ignored then
169 forceinputstateline(texget("inputlineno"))
170 end
171 end
172 end
173
174 function synctex.popline()
175 if nesting == 1 then
176 if not ignored then
177 forceinputstateline()
178 ignored = false
179 end
180 end
181 nesting = nesting - 1
182 end
183
184end
185
186
187
188local filehandle = nil
189local nofsheets = 0
190local nofobjects = 0
191local last = 0
192local filesdone = 0
193local sncfile = false
194
195local function writeanchor()
196 local size = filehandle:seek("end")
197 filehandle:write("!",size-last,eol)
198 last = size
199end
200
201local function writefiles()
202 local total = #stnums
203 if filesdone < total then
204 for i=filesdone+1,total do
205 filehandle:write("Input:",i,":",stnums[i],eol)
206 end
207 filesdone = total
208 end
209end
210
211local function makenames()
212 sncfile = replacesuffix(tex.jobname,"synctex")
213end
214
215local function flushpreamble()
216 makenames()
217 filehandle = openfile(sncfile,"wb")
218 if filehandle then
219 filehandle:write("SyncTeX Version:1",eol)
220 writefiles()
221 filehandle:write("Output:pdf",eol)
222 filehandle:write("Magnification:1000",eol)
223 filehandle:write("Unit:1",eol)
224 filehandle:write("X Offset:0",eol)
225 filehandle:write("Y Offset:0",eol)
226 filehandle:write("Content:",eol)
227 flushpreamble = function()
228 writefiles()
229 return filehandle
230 end
231 else
232 enabled = false
233 end
234 return filehandle
235end
236
237function synctex.wrapup()
238 sncfile = nil
239end
240
241local function flushpostamble()
242 if not filehandle then
243 return
244 end
245 writeanchor()
246 filehandle:write("Postamble:",eol)
247 filehandle:write("Count:",nofobjects,eol)
248 writeanchor()
249 filehandle:write("Post scriptum:",eol)
250 filehandle:close()
251 enabled = false
252end
253
254getpagedimensions = function()
255 getpagedimensions = backends.codeinjections.getpagedimensions
256 return getpagedimensions()
257end
258
259local x_hlist do
260
261 local function doaction(t,l,w,h,d)
262 local pagewidth, pageheight = getpagedimensions()
263 local x, y = getpos()
264 y = pageheight - y
265 if userule then
266
267 filehandle:write(f_hlist(t,l,x,y,0,0,0))
268 filehandle:write(f_rule(t,l,x,y,w,h,d))
269 filehandle:write(s_hlist)
270 else
271
272 filehandle:write(f_hvoid(t,l,x,y,w,h,d))
273 end
274 nofobjects = nofobjects + 1
275 end
276
277 x_hlist = function(head,current,t,l,w,h,d)
278 if filehandle then
279 return insertbefore(head,current,new_latelua(function() doaction(t,l,w,h,d) end))
280 else
281 return head
282 end
283 end
284
285end
286
287
288
289local collect = nil
290local fulltrace = false
291local trace = false
292local height = 10 * 65536
293local depth = 5 * 65536
294local traceheight = 32768
295local tracedepth = 32768
296
297trackers.register("system.synctex.visualize", function(v)
298 trace = v
299 fulltrace = v == "real"
300end)
301
302local collect_min do
303
304 local function inject(head,first,last,tag,line)
305 local w, h, d = getdimensions(first,getnext(last))
306 if h < height then
307 h = height
308 end
309 if d < depth then
310 d = depth
311 end
312 if trace then
313 head = insertbefore(head,first,new_rule(w,fulltrace and h or traceheight,fulltrace and d or tracedepth))
314 head = insertbefore(head,first,new_kern(-w))
315 end
316 head = x_hlist(head,first,tag,line,w,h,d)
317 return head
318 end
319
320 collect_min = function(head)
321 local current = head
322 while current do
323 local id = getid(current)
324 if id == glyph_code then
325 local first = current
326 local last = current
327 local tag = 0
328 local line = 0
329 while true do
330 if id == glyph_code then
331 local tc, lc = getinputfields(current)
332 if tc and tc > 0 then
333 tag = tc
334 line = lc
335 end
336 last = current
337 elseif id == disc_code or (id == kern_code and getsubtype(current) == fontkern_code) then
338 last = current
339 else
340 if tag > 0 then
341 head = inject(head,first,last,tag,line)
342 end
343 break
344 end
345 current = getnext(current)
346 if current then
347 id = getid(current)
348 else
349 if tag > 0 then
350 head = inject(head,first,last,tag,line)
351 end
352 return head
353 end
354 end
355 end
356
357 if id == hlist_code or id == vlist_code then
358 local list = getlist(current)
359 if list then
360 local l = collect(list)
361 if l ~= list then
362 setlist(current,l)
363 end
364 end
365 end
366 current = getnext(current)
367 end
368 return head
369 end
370
371end
372
373local collect_max do
374
375 local function inject(parent,head,first,last,tag,line)
376 local w, h, d = getrangedimensions(parent,first,getnext(last))
377 if h < height then
378 h = height
379 end
380 if d < depth then
381 d = depth
382 end
383 if trace then
384 head = insertbefore(head,first,new_rule(w,fulltrace and h or traceheight,fulltrace and d or tracedepth))
385 head = insertbefore(head,first,new_kern(-w))
386 end
387 head = x_hlist(head,first,tag,line,w,h,d)
388 return head
389 end
390
391 collect_max = function(head,parent)
392 local current = head
393 while current do
394 local id = getid(current)
395 if id == glyph_code then
396 local first = current
397 local last = current
398 local tag = 0
399 local line = 0
400 while true do
401 if id == glyph_code then
402 local tc, lc = getinputfields(current)
403 if tc and tc > 0 then
404 if tag > 0 and (tag ~= tc or line ~= lc) then
405 head = inject(parent,head,first,last,tag,line)
406 first = current
407 end
408 tag = tc
409 line = lc
410 last = current
411 else
412 if tag > 0 then
413 head = inject(parent,head,first,last,tag,line)
414 tag = 0
415 end
416 first = nil
417 last = nil
418 end
419 elseif id == disc_code then
420 if not first then
421 first = current
422 end
423 last = current
424 elseif id == kern_code and getsubtype(current) == fontkern_code then
425 if first then
426 last = current
427 end
428 elseif id == glue_code then
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447 elseif id == penalty_code then
448
449 else
450 if tag > 0 then
451 head = inject(parent,head,first,last,tag,line)
452 tag = 0
453 end
454 break
455 end
456 current = getnext(current)
457 if current then
458 id = getid(current)
459 else
460 if tag > 0 then
461 head = inject(parent,head,first,last,tag,line)
462 end
463 return head
464 end
465 end
466 end
467
468 if id == hlist_code or id == vlist_code then
469 local list = getlist(current)
470 if list then
471 local l = collect(list,current)
472 if l and l ~= list then
473 setlist(current,l)
474 end
475 end
476 end
477 current = getnext(current)
478 end
479 return head
480 end
481
482end
483
484collect = collect_max
485
486function synctex.collect(head,where)
487 if enabled and where ~= "object" then
488 return collect(head,head)
489 else
490 return head
491 end
492end
493
494
495
496function synctex.start()
497 if enabled then
498 nofsheets = nofsheets + 1
499 if flushpreamble() then
500 writeanchor()
501 filehandle:write("{",nofsheets,eol)
502
503
504 filehandle:write(f_plist(1,0,0,0,0,0,0))
505 end
506 end
507end
508
509function synctex.stop()
510 if enabled then
511 filehandle:write(s_plist)
512 writeanchor()
513 filehandle:write("}",nofsheets,eol)
514 nofobjects = nofobjects + 2
515 end
516end
517
518local enablers = { }
519local disablers = { }
520
521function synctex.registerenabler(f)
522 enablers[#enablers+1] = f
523end
524
525function synctex.registerdisabler(f)
526 disablers[#disablers+1] = f
527end
528
529function synctex.enable(use_rule)
530 if not never and not enabled then
531 enabled = true
532 userule = use_rule
533 setinputstatemode(3)
534 if not used then
535 nodes.tasks.enableaction("shipouts","luatex.synctex.collect")
536 report_system("synctex functionality is enabled, expect 5-10 pct runtime overhead!")
537 used = true
538 end
539 for i=1,#enablers do
540 enablers[i](true)
541 end
542 end
543end
544
545function synctex.disable()
546 if enabled then
547 setinputstatemode(0)
548 report_system("synctex functionality is disabled!")
549 enabled = false
550 for i=1,#disablers do
551 disablers[i](false)
552 end
553 end
554end
555
556function synctex.finish()
557 if enabled then
558 flushpostamble()
559 else
560 makenames()
561 removefile(sncfile)
562 end
563end
564
565local filename = nil
566
567function synctex.pause()
568 paused = paused + 1
569 if enabled and paused == 1 then
570 setinputstatemode(0)
571 end
572end
573
574function synctex.resume()
575 if enabled and paused == 1 then
576 setinputstatemode(3)
577 end
578 paused = paused - 1
579end
580
581
582
583luatex.registerstopactions(synctex.finish)
584
585statistics.register("synctex tracing",function()
586 if used then
587 return string.format("%i referenced files, %i files ignored, %i objects flushed, logfile: %s",
588 noftags,nofblocked,nofobjects,sncfile)
589 end
590end)
591
592local implement = interfaces.implement
593local variables = interfaces.variables
594
595function synctex.setup(t)
596 if t.state == variables.never then
597 synctex.disable()
598 never = true
599 return
600 end
601 if t.method == variables.max then
602 collect = collect_max
603 else
604 collect = collect_min
605 end
606 if t.state == variables.start then
607 synctex.enable(false)
608 elseif t.state == variables["repeat"] then
609 synctex.enable(true)
610 else
611 synctex.disable()
612 end
613end
614
615implement {
616 name = "synctexblockfilename",
617 arguments = "string",
618 actions = synctex.blockfilename,
619}
620
621implement {
622 name = "synctexsetfilename",
623 arguments = "string",
624 actions = synctex.setfilename,
625}
626
627implement {
628 name = "synctexresetfilename",
629 actions = synctex.resetfilename,
630}
631
632implement {
633 name = "setupsynctex",
634 actions = synctex.setup,
635 arguments = {
636 {
637 { "state" },
638 { "method" },
639 },
640 },
641}
642
643implement {
644 name = "synctexpause",
645 actions = synctex.pause,
646}
647
648implement {
649 name = "synctexresume",
650 actions = synctex.resume,
651}
652
653implement {
654 name = "synctexpushline",
655 actions = synctex.pushline,
656}
657
658implement {
659 name = "synctexpopline",
660 actions = synctex.popline,
661}
662
663implement {
664 name = "synctexdisable",
665 actions = synctex.disable,
666}
667 |