diff --git a/copret/src/Presentation.scala b/copret/src/Presentation.scala new file mode 100644 index 0000000..a978cf5 --- /dev/null +++ b/copret/src/Presentation.scala @@ -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 diff --git a/copret/src/logger.scala b/copret/src/logger.scala new file mode 100644 index 0000000..2c8d6fd --- /dev/null +++ b/copret/src/logger.scala @@ -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.* diff --git a/copret/src/slides.scala b/copret/src/slides.scala index f47ff00..6cac196 100644 --- a/copret/src/slides.scala +++ b/copret/src/slides.scala @@ -4,119 +4,6 @@ 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) = - 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) sealed trait Slide @@ -209,14 +96,14 @@ object TypedCommand: def runShell(using Path): Vector[String] => String = c => runProcess(Vector(shell, "-c", c.mkString(" "))) - def runInteractive(using Path): Vector[String] => String = - c => { os.proc(c).call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit); "" } + def runInteractive(using cwd: Path): Vector[String] => String = + 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]] = TypedCommand(run, cmd.mkString(" "), cmd.toVector) - def apply[T](exec: T => String, display: String, cmd: T) = - new TypedCommand(exec, display, cmd, false, false) + def apply[T](exec: T => String, display: String, cmd: T): TypedCommand[T] = + TypedCommand(exec, display, cmd, false, false) def shell(cmd: String*)(using Path): TypedCommand[Vector[String]] = TypedCommand(runShell, cmd.mkString(" "), cmd.toVector)