fix: presentation navigation now works correctly again

This commit is contained in:
Alexander Gehrke 2024-05-20 23:44:53 +02:00
parent 3ad18addb0
commit add8e9a714
3 changed files with 145 additions and 117 deletions

View file

@ -0,0 +1,125 @@
package de.qwertyuiop.copret
import os.Path
import Terminal.*
import syntax.*
case class Presentation(
slides: Vector[Slide],
meta: Map[String, String] = Map.empty,
):
def start(using keymap: Keymap = Keymap.default) =
Terminal.enterRawMode()
Terminal.hideCursor()
Thread.sleep(1000)
run()
import Presentation._
def run()(using keymap: Keymap) =
logger.info("Starting presentation")
import SlideAction.*
@annotation.tailrec
def rec(currentPos: Int, action: SlideAction): Unit =
logger.info(s"Executing slide $currentPos with action $action")
inline def redraw() =
logger.info(s"redrawing ${currentPos}")
// rec(currentPos - 1, QuickNext)
navigate(currentPos, true, 0)(executeQuick)
inline def navigate(pos: Int, condition: Boolean, direction: Int)(
executor: Int => Slide => Unit,
) =
logger.info(s"Navigating from ${pos}, condition is $condition, direction is ${direction}")
if condition then
executor(pos + direction)(slides(pos + direction))
rec(pos + direction, waitkey)
else rec(pos, waitkey)
inline def runInteractive(cmd: Vector[String], path: Path) =
logger.info(s"Running interactive command ${cmd} with working directory ${path}")
Terminal.showCursor()
try os.proc(cmd).call(cwd = path)
catch case _ => ()
Terminal.hideCursor()
Terminal.clear()
redraw()
action match
case Start => navigate(0, true, 0)(executeSlide)
case Next => navigate(currentPos, currentPos + 1 < slides.size, 1)(executeSlide)
case QuickNext => navigate(currentPos, currentPos + 1 < slides.size, 1)(executeQuick)
case Prev => navigate(currentPos, currentPos > 0, -1)(executeQuick)
case Interactive(cmd, path) => runInteractive(cmd, path)
case Goto(target) =>
for i <- 0 until target
do executeSilent(i)()
rec(target - 1, QuickNext)
case GotoSelect =>
promptSlide() match
case Some(i) => rec(currentPos, Goto(i))
case None => redraw()
case Help =>
Terminal.clear()
println(keymap.help)
waitkey
redraw()
case Other(codes) =>
Terminal.printStatus(action.show)
rec(currentPos, waitkey)
case Quit =>
Terminal.showCursor()
end match
end rec
rec(0, Start)
end run
def promptSlide() =
val maxSlide = slides.size
prompt(s"Go to slide (1 - $maxSlide):", _.toIntOption)(
(res, input) => res.filter((1 to maxSlide).contains).isEmpty && input.nonEmpty,
in => s"No such slide: $in (empty input to abort)",
).map(_ - 1)
def executeSlide(pos: Int)(
slide: Slide = slides(pos),
): Unit = slide match
case Paragraph(contents) => println(contents)
case Clear => Terminal.clear()
case PauseKey => waitkey(using Keymap.empty)
case Pause(msec) => Thread.sleep(msec)
case incMd @ IncludeMarkdown(_) => println(incMd.markdownBlock())
case Image(file, None) => println(KittyGraphicsProtocol.showImage(file))
case Image(file, Some(ImageSize(w, h, aspect))) =>
println(KittyGraphicsProtocol.showImage(file)) // TODO
case cmd: TypedCommand[_] => cmd.show()
case Silent(actions) => actions()
case Group(slides) => slides.foreach(executeSlide(pos))
case lios @ LazyIOSlide(_, display) => executeSlide(pos)(lios.genSlide())
case Meta(genSlide) => executeSlide(pos)(genSlide(this, pos))
def executeQuick(pos: Int)(
slide: Slide = slides(pos),
): Unit = slide match
case Pause(msec) => ()
case PauseKey => ()
case cmd: TypedCommand[_] => cmd.quickShow()
case Group(slides) => slides.foreach(executeQuick(pos))
case lios @ LazyIOSlide(_, display) => executeQuick(pos)(lios.genSlide())
case _ => executeSlide(pos)(slide)
def executeSilent(pos: Int)(
slide: Slide = slides(pos),
): Unit = slide match
case cmd: TypedCommand[_] => cmd.force()
case Group(slides) => slides.foreach(executeSilent(pos))
case lios @ LazyIOSlide(_, display) =>
executeSilent(pos)(lios.genSlide())
case Paragraph(_) | Image(_, _) | Clear | IncludeMarkdown(_) | Meta(_) => ()
case _ => executeQuick(pos)(slide)
end Presentation

16
copret/src/logger.scala Normal file
View file

@ -0,0 +1,16 @@
package de.qwertyuiop.copret
object logger:
import java.util.logging.*
val log = Logger.getLogger("copret")
val fh = FileHandler("/tmp/copret.log")
log.addHandler(fh)
log.setUseParentHandlers(false)
System.setProperty(
"java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS.%1$tN %1$Tp %2$s: %4$s: %5$s%6$s%n",
)
fh.setFormatter(new SimpleFormatter())
fh.setLevel(Level.ALL)
export log.*

View file

@ -4,119 +4,6 @@ import os.Path
import Terminal.* import Terminal.*
import syntax.* import syntax.*
case class Presentation(
slides: Vector[Slide],
meta: Map[String, String] = Map.empty,
):
def start(using keymap: Keymap = Keymap.default) =
Terminal.enterRawMode()
Terminal.hideCursor()
Thread.sleep(1000)
run()
import Presentation._
def run()(using keymap: Keymap) =
import SlideAction.*
@annotation.tailrec
def rec(pos: Int, action: SlideAction): Unit =
inline def redraw() = rec(pos - 1, QuickNext)
inline def navigate(pos: Int, condition: Boolean, direction: Int)(
executor: Int => Slide => Unit,
) =
if condition then
executor(pos + direction)(slides(pos))
rec(pos + direction, waitkey)
else rec(pos, waitkey)
inline def runInteractive(cmd: Vector[String], path: Path) =
Terminal.showCursor()
try os.proc(cmd).call(cwd = path)
catch case _ => ()
Terminal.hideCursor()
Terminal.clear()
redraw()
action match
case Start => navigate(0, true, 0)(executeSlide)
case Next => navigate(pos, pos + 1 < slides.size, 1)(executeSlide)
case QuickNext => navigate(pos, pos + 1 < slides.size, 1)(executeQuick)
case Prev => navigate(pos, pos > 0, -1)(executeQuick)
case Interactive(cmd, path) => runInteractive(cmd, path)
case Goto(target) =>
for i <- 0 until target
do executeSilent(i)()
rec(target - 1, QuickNext)
case GotoSelect =>
promptSlide() match
case Some(i) => rec(pos, Goto(i))
case None => redraw()
case Help =>
Terminal.clear()
println(keymap.help)
waitkey
redraw()
case Other(codes) =>
Terminal.printStatus(action.show)
rec(pos, waitkey)
case Quit =>
Terminal.showCursor()
end match
end rec
rec(0, Start)
end run
def promptSlide() =
val maxSlide = slides.size - 1
prompt(s"Go to slide (0 - $maxSlide):", _.toIntOption)(
(res, input) => res.filter((0 to maxSlide).contains).isEmpty && input.nonEmpty,
in => s"No such slide: $in (empty input to abort)",
)
def executeSlide(pos: Int)(
slide: Slide = slides(pos),
): Unit = slide match
case Paragraph(contents) => println(contents)
case Clear => Terminal.clear()
case PauseKey => waitkey(using Keymap.empty)
case Pause(msec) => Thread.sleep(msec)
case incMd @ IncludeMarkdown(_) => println(incMd.markdownBlock())
case Image(file, None) => println(KittyGraphicsProtocol.showImage(file))
case Image(file, Some(ImageSize(w, h, aspect))) =>
println(KittyGraphicsProtocol.showImage(file)) // TODO
case cmd: TypedCommand[_] => cmd.show()
case Silent(actions) => actions()
case Group(slides) => slides.foreach(executeSlide(pos))
case lios @ LazyIOSlide(_, display) => executeSlide(pos)(lios.genSlide())
case Meta(genSlide) => executeSlide(pos)(genSlide(this, pos))
def executeQuick(pos: Int)(
slide: Slide = slides(pos),
): Unit = slide match
case Pause(msec) => ()
case PauseKey => ()
case cmd: TypedCommand[_] => cmd.quickShow()
case Group(slides) => slides.foreach(executeQuick(pos))
case lios @ LazyIOSlide(_, display) => executeQuick(pos)(lios.genSlide())
case _ => executeSlide(pos)(slide)
def executeSilent(pos: Int)(
slide: Slide = slides(pos),
): Unit = slide match
case cmd: TypedCommand[_] => cmd.force()
case Group(slides) => slides.foreach(executeSilent(pos))
case lios @ LazyIOSlide(_, display) =>
executeSilent(pos)(lios.genSlide())
case Paragraph(_) | Image(_, _) | Clear | IncludeMarkdown(_) | Meta(_) => ()
case _ => executeQuick(pos)(slide)
end Presentation
case class ImageSize(width: Double, height: Double, keepAspect: Boolean) case class ImageSize(width: Double, height: Double, keepAspect: Boolean)
sealed trait Slide sealed trait Slide
@ -209,14 +96,14 @@ object TypedCommand:
def runShell(using Path): Vector[String] => String = def runShell(using Path): Vector[String] => String =
c => runProcess(Vector(shell, "-c", c.mkString(" "))) c => runProcess(Vector(shell, "-c", c.mkString(" ")))
def runInteractive(using Path): Vector[String] => String = def runInteractive(using cwd: Path): Vector[String] => String =
c => { os.proc(c).call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit); "" } c => { os.proc(c).call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit, cwd = cwd); "" }
def apply(cmd: String*)(using Path): TypedCommand[Vector[String]] = def apply(cmd: String*)(using Path): TypedCommand[Vector[String]] =
TypedCommand(run, cmd.mkString(" "), cmd.toVector) TypedCommand(run, cmd.mkString(" "), cmd.toVector)
def apply[T](exec: T => String, display: String, cmd: T) = def apply[T](exec: T => String, display: String, cmd: T): TypedCommand[T] =
new TypedCommand(exec, display, cmd, false, false) TypedCommand(exec, display, cmd, false, false)
def shell(cmd: String*)(using Path): TypedCommand[Vector[String]] = def shell(cmd: String*)(using Path): TypedCommand[Vector[String]] =
TypedCommand(runShell, cmd.mkString(" "), cmd.toVector) TypedCommand(runShell, cmd.mkString(" "), cmd.toVector)