2

This question led to a new package:
rescansync

Let's say I want to replace all a (in source code, not rendered output) in a section with b...

%! TEX program = lualatex
\documentclass{article}
\usepackage{filecontentsdef}
\begin{document}
\begin{filecontentsdefmacro}{\zzz}
abc

abc

abc \end{filecontentsdefmacro} \ExplSyntaxOn \regex_replace_all:nnN {a} {b} \zzz \ExplSyntaxOff \filecontentsexec\zzz \end{document}

It works, however the synctex data is lost (it points to the whole block, not individual paragraph/line)

How can I keep the synctex data?

LuaTeX-only solutions are okay.

mbert
  • 4,171
user202729
  • 7,143
  • 1
    I guess it's possible to use one of the set_synctex_* functions in LuaTeX, but the documentation is quite sparse. Or modify the .synctex file after compilation. – user202729 Jan 29 '22 at 05:00
  • 1
    it depends how you define "all". In luatex you could use a lua pattern replace in the process_input_buffer callback, but that would change \makebox to \mbkebox is that OK? – David Carlisle Jan 29 '22 at 11:03
  • @DavidCarlisle Yes that's expected... (for now assume that the capture, or somehow getting the whole content in advance is necessary, otherwise process input buffer can do indeed) – user202729 Jan 29 '22 at 11:08
  • [[Note]] I made this into a package https://github.com/user202729/TeXlib/blob/main/rescansync.sty (unfortunately currently there's no documentation&¬ on CTAN) – user202729 Apr 05 '22 at 08:00
  • Note 2. I realize that from Lua it's possible to read the whole file at once... so as long as you don't need to change the number of lines you can just read the whole file, process, and return the correct line according to the line number. See https://tex.stackexchange.com/questions/641312/lua-approach-for-itemize-fails-if-multiple-lines-are-given-after-item?noredirect=1#comment1598433_641312 for example. – user202729 Apr 19 '22 at 15:36

1 Answers1

2

There's a method. For the documentation, see texdoc luatex and texdoc ltluatex (and the packages being imported).

Disadvantage: it's necessary to write to a real external file (not \scantokens)

Search for !! in the code for the important parts. (note that directlua instead of luacode* is used, so be careful with the catcode)

%! TEX program = lualatex
\documentclass{article}
\usepackage{currfile} % !! need this package for currfilename to be defined
\begin{document}

% write the content to a separate file \directlua{magic_offset=1 inputlineno_offset=tex.inputlineno+magic_offset} % !! \begin{filecontents*}[overwrite]{b.tex} \typeout{1} abc

abc

abc \typeout{2} \end{filecontents*}

% it's also possible to typeset something between the "write" part and the "rescan" part first line

\directlua{ --[[ !! ]] saved_synctex_tag=tex.get_synctex_tag() function handler() if token.get_macro("currfilename")=="b.tex" and tex.get_synctex_tag()>0 then if not (saved_synctex_tag==nil) then tex.set_synctex_tag(saved_synctex_tag) saved_synctex_tag=nil end tex.set_synctex_line(tex.inputlineno+inputlineno_offset) end end tex.set_synctex_mode(2) luatexbase.add_to_callback('process_input_buffer', handler, "synctex patch callback") }\input{b.tex}\directlua{luatexbase.remove_from_callback('process_input_buffer', "synctex patch callback") tex.set_synctex_mode(0) tex.set_synctex_line(0) }

last line

last line \end{document}

Note that for some weird reason, if [abspath] option is provided to currfile package the handler will be called several times with tex.get_synctex_tag()==0.

Thus the check is added so that it's only called once, but on the actual file.

(I guess that it's because the primitive expand-only the \input command or something)

Removing saved_synctex_tag=nil line; or the get_macro("currfilename")=="b.tex" check will make something worse because (I think, not tested) if b.tex includes e.g. c.tex, then the set_synctex_line should not be run for c.tex.

Alternative to callback, setting the tag within the input-ed file itself always work.

user202729
  • 7,143