Improve terminal escape handling
This commit is contained in:
parent
2b7a4e8d26
commit
0acbca2e62
|
@ -6,6 +6,7 @@ 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()
|
||||
run()
|
||||
|
||||
import Presentation._
|
||||
|
@ -34,7 +35,12 @@ case class Presentation(slides: Vector[Slide], meta: Map[String, String] = Map.e
|
|||
else
|
||||
rec(p, pos, waitkey)
|
||||
case Interactive(cmd, path) =>
|
||||
%(cmd)(path)
|
||||
Terminal.showCursor()
|
||||
try
|
||||
%(cmd)(path)
|
||||
catch case _ => ()
|
||||
Terminal.hideCursor()
|
||||
Terminal.clear()
|
||||
rec(p, pos - 1, QuickNext)
|
||||
case Goto(target) =>
|
||||
for i <- 0 until target
|
||||
|
@ -57,11 +63,12 @@ case class Presentation(slides: Vector[Slide], meta: Map[String, String] = Map.e
|
|||
object Presentation:
|
||||
def executeSlide(p: Presentation, pos: Int)(slide: Slide = p.slides(pos)): Unit = slide match
|
||||
case Paragraph(contents) => println(contents)
|
||||
case Clear => print("\u001b[2J\u001b[;H")
|
||||
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, width, height, keepAspect) => print(Terminal.showImage(file, width, height, keepAspect))
|
||||
case Image(file, None) => Terminal.showImage(file)
|
||||
case Image(file, Some(ImageSize(w, h, aspect))) => Terminal.showImageScaled(file, w, h, aspect)
|
||||
case cmd: TypedCommand[_] => cmd.show()
|
||||
case Silent(actions) => actions()
|
||||
case Group(slides) => slides.foreach(executeSlide(p, pos))
|
||||
|
@ -80,16 +87,32 @@ object Presentation:
|
|||
case cmd: TypedCommand[_] => cmd.force()
|
||||
case Group(slides) => slides.foreach(executeSilent(p, pos))
|
||||
case lios @ LazyIOSlide(_, display) => executeSilent(p, pos)(lios.genSlide())
|
||||
case Paragraph(_) | Image(_,_,_,_) | Clear | IncludeMarkdown(_) | Meta(_) => ()
|
||||
case Paragraph(_) | Image(_,_) | Clear | IncludeMarkdown(_) | Meta(_) => ()
|
||||
case _ => executeQuick(p, pos)(slide)
|
||||
|
||||
|
||||
case class ImageSize(width: Double, height: Double, keepAspect: Boolean)
|
||||
|
||||
sealed trait Slide
|
||||
case class Paragraph(contents: fansi.Str) extends Slide
|
||||
case class Paragraph(contents: String) extends Slide:
|
||||
def centerVertical(height: Int): Paragraph =
|
||||
val lines = contents.toString.count(_ == '\n') + 1
|
||||
val pad = "\n" * ((height - lines) / 2)
|
||||
Paragraph(pad + contents + pad)
|
||||
|
||||
object Paragraph:
|
||||
def apply(str: fansi.Str): Paragraph = Paragraph(str.toString)
|
||||
|
||||
case class IncludeMarkdown(path: Path) extends Slide:
|
||||
def markdownBlock() =
|
||||
%%%("/usr/bin/mdcat", "--columns", (columns * 0.8).toInt.toString, path.toString)(using ImplicitWd.implicitCwd).block
|
||||
case class Image(path: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) extends Slide
|
||||
|
||||
case class Image(path: Path, sizing: Option[ImageSize]) extends Slide
|
||||
object Image:
|
||||
def apply(path: Path) = new Image(path, None)
|
||||
def scaled(path: Path, width: Double, height: Double, keepAspect: Boolean) =
|
||||
Image(path, Some(ImageSize(width, height, keepAspect)))
|
||||
|
||||
case object Clear extends Slide
|
||||
case class Pause(millisec: Long) extends Slide
|
||||
case object PauseKey extends Slide
|
||||
|
@ -103,8 +126,10 @@ case class TypedCommand[T](exec: T => String, display: String, cmd: T) extends S
|
|||
|
||||
def show() =
|
||||
prompt()
|
||||
Terminal.showCursor()
|
||||
typeCmd()
|
||||
print(output)
|
||||
Terminal.hideCursor()
|
||||
|
||||
def quickShow() =
|
||||
prompt()
|
||||
|
|
|
@ -14,9 +14,15 @@ object Terminal:
|
|||
%%("sh", "-c", "stty -icanon min 1 < /dev/tty")(pwd)
|
||||
%%("sh", "-c", "stty -echo < /dev/tty")(pwd)
|
||||
|
||||
extension (percent: Double)
|
||||
def toPercent: String = s"${(percent * 100).toInt}%"
|
||||
|
||||
private[copret] lazy val jterm = org.jline.terminal.TerminalBuilder.terminal()
|
||||
private[copret] lazy val lineReader = LineReaderBuilder.builder().terminal(jterm).build()
|
||||
|
||||
def height = jterm.getSize.getRows
|
||||
def width = jterm.getSize.getColumns
|
||||
|
||||
def waitkey(using keymap: Keymap): SlideAction =
|
||||
// ignore keypresses done during slide animations
|
||||
while Console.in.ready() do Console.in.read
|
||||
|
@ -42,22 +48,40 @@ object Terminal:
|
|||
|
||||
def isTmux = sys.env.contains("TMUX") || term.startsWith("screen")
|
||||
|
||||
def osc = if isTmux then "\u001bPtmux\u001b\u001b]" else "\u001b]"
|
||||
def osc(code: String) = (if isTmux then "\u001bPtmux\u001b\u001b]" else "\u001b]") + code + st
|
||||
def st = if isTmux then "\u0007\u001b\\" else "\u0007"
|
||||
def csi(code: String) = "\u001b[" + code
|
||||
|
||||
def showImage(img: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) =
|
||||
if term == "xterm-kitty" then showImageKitty(img)
|
||||
else showImageIterm(img, width, height, keepAspect)
|
||||
def hideCursor() = print(csi("?25l"))
|
||||
def showCursor() = print(csi("?25h"))
|
||||
def cursorTo(row: Int, col: Int) = print(csi(s"${row};${col}H"))
|
||||
def clear() = print(csi("2J") + csi(";H"))
|
||||
|
||||
def showImageIterm(img: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) =
|
||||
def showImage(img: Path): Unit =
|
||||
print(
|
||||
if term == "xterm-kitty" then showImageKitty(img)
|
||||
else showImageIterm(img, "100%", "100%", true)
|
||||
)
|
||||
|
||||
def showImageScaled(img: Path, width: Double, height: Double, keepAspect: Boolean): Unit =
|
||||
print(
|
||||
if term == "xterm-kitty" then
|
||||
val cols = (jterm.getSize.getColumns * width).toInt
|
||||
val rows = (jterm.getSize.getRows * height).toInt
|
||||
showImageKitty(img) // TODO
|
||||
else
|
||||
showImageIterm(img, width.toPercent, height.toPercent, keepAspect)
|
||||
)
|
||||
|
||||
def showImageIterm(img: Path, width: String, height: String, keepAspect: Boolean = true): String =
|
||||
import java.util.Base64
|
||||
val image = Base64.getEncoder.encodeToString(read.bytes(img))
|
||||
val aspect = if keepAspect then 1 else 0
|
||||
s"${osc}1337;File=inline=1;width=$width;height=$height;preserveAspectRatio=$aspect:$image$st"
|
||||
osc(s"1337;File=inline=1;width=$width;height=$height;preserveAspectRatio=$aspect:$image")
|
||||
|
||||
def showImageKitty(img: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) =
|
||||
def showImageKitty(img: Path): String =
|
||||
import java.util.Base64
|
||||
s"\u001b_Gf=100,t=f,a=T;${Base64.getEncoder.encodeToString(img.toString.toCharArray.map(_.toByte))}\u001b\\"
|
||||
s"\u001b_Gf=100,t=f,a=T,C=1;${Base64.getEncoder.encodeToString(img.toString.toCharArray.map(_.toByte))}\u001b\\"
|
||||
|
||||
private[copret] trait TerminalSyntax:
|
||||
import Terminal._
|
||||
|
|
Loading…
Reference in a new issue