sile-typesetter / sile

The SILE Typesetter — Simon’s Improved Layout Engine
https://sile-typesetter.org
MIT License
1.63k stars 96 forks source link

It would be cool to have an undo for scripts #925

Open ctrlcctrlv opened 4 years ago

ctrlcctrlv commented 4 years ago

E.g.:

issue925.lua

local state = require("undoredo") -- I don't exist, but I might some day!
evalstatements = {}
local eval = function(evalme)
  if not evalme then
    evalme = table.concat(evalstatements, "\n")
  end

  local tree = SILE.inputs.TeXlike.docToTree(evalme)
  SILE.process(tree)
  -- SU.dump(tree) -- for debugging
end

local main = function(_, _)
  state.save()
  eval("\\hbox{In Soviet Russia, SILE typesets you.}")
  local cursorX = SILE.typesetter.frame.state.cursorX:tonumber()
  state.undo()
  eval(string.format("\\glue[width=%d]", cursorX))
  eval("\\hbox{In Soviet Russia, SILE typesets you.}")
end, ":-)"

SILE.registerCommand("issue925", main)

We have a function like this in the FontForge Python API, I think it is very useful.

alerque commented 4 years ago

If what you want it to save the condition (not content) of the typesetter there is already SILE.typesetter.pushState() and .popState(). If what you want is to try something on for size, cache the content of SILE.typesetter.state.nodes and restore it when you are done. The catch is that you can't get away with doing so much with the content in the mean time that you get to a page break or other point that flushes the content to PDF or otherwise casts it in stone; everything is still processed a page at a time and once you finalize a page you can't really go back and undo anything.

It would be useful in some cases to do everything in memory to enable more sweeping use cases, but in most cases when you want to do this it's for a few nodes in the current horizontal queue anyway. You'll find lots of places scattered around the SILE code that does this already.

Also, you probably don't want to be inventing an eval like this:

local eval = function (evalme)
  -- ...
  SILE.process(evalme)
end
eval(string.format("\\glue[width=%d]", cursorX))

Ty just passing Lua lambdas:

local myfunc = function (content)
  -- ...
  SILE.process(content)
end
myfunc(function() SILE.call("glue", { width = cursorX }) end)

There are several other errors in that Lua code mockup, but using fewer layers of parsing and language switching would make it a lot easier to see them.

Omikhleia commented 1 year ago

This is really really really an unclear issue, and I don't get where the discussion between @alerque and @ctrlcctrlv was heading. Can we have a better rewording of the actual need to address, or close it?

alerque commented 1 year ago

I get where this was going. And I've been headed towards this for a while.

We already have a settings API for this (push/pop state). What we need is one for ALL states including the outputter. As a partial measure we already do this in practice using an ugly hack to output some nodes, then measure, then rip them out of the node queue. What we don't have is a nice interface for it that makes it user friendly. Also it only works before the page break is finalized.

I think we should keep going in stages to achieve a full rewind capability.

  1. The typesetter needs a Lua API for push/pop queue state like we already do in hacks.
  2. The typesetter content pop API should return an error if used after state finalized since the relevant push.
  3. The outputters should be optionally buffered in memory. Instead of writing the PDF to disk on every page output we should cache up all the outputter commands.the PDF to disk on every page output we should cache up all the outputter commands.the PDF to disk on every page output we should cache up all the outputter commands.the PDF to disk on every page output we should cache up all the outputter commands.the PDF to disk on every page output we should cache up all the outputter commands.the PDF to disk on every page output we should cache up all the outputter commands.the PDF to disk on every page output we could cache up all the outputter commands and replay them as the very last step when the outputter finalizes. This would allow us to backtrack and go back to save points even after output is finalized for a page.
  4. We've been heading towards unloading packages for a while. The undo point should eventually include the list of currently loaded packages and rewind to that.
  5. Assorted other "state" should have flags to include or not include in an undo command. For example include scratch variables or not? Include the local Lua environment or not?