From d6e24a2499289e73366f8755df13633544c57f57 Mon Sep 17 00:00:00 2001 From: Alexander Gehrke Date: Mon, 20 May 2024 23:51:34 +0200 Subject: [PATCH] development version of terminal escape code talk --- copret/src/main.scala | 230 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 219 insertions(+), 11 deletions(-) diff --git a/copret/src/main.scala b/copret/src/main.scala index 16f7649..47ae888 100644 --- a/copret/src/main.scala +++ b/copret/src/main.scala @@ -2,15 +2,223 @@ package de.qwertyuiop.copret import de.qwertyuiop.copret.Terminal.* +import de.qwertyuiop.copret.* +import de.qwertyuiop.copret.syntax.* +import TypedCommand.{interactive, shell => sh} +import os.Path + +given presentationRoot: Path = os.Path("/home/crater2150/org/opencolloq/termescape") +val imgs = presentationRoot / "img" +given theme: Theme = Theme.default + +def figlet(str: String, font: String): String = String( + os.proc("figlet", "-t", "-f", font, str).call().out.bytes, +) + +def lolcat(str: String): String = String( + os.proc("lolcat", "-b", "-f", "-h", "0.7", "-r").call(stdin = str).out.bytes, +) + +def lollet(str: String, font: String): String = lolcat(figlet(str, font)) + +// debug mode +val noInteractiveCommands = false + +val titleSlide = + Group( + Clear, + TypedCommand(TypedCommand.runInteractive(using presentationRoot), "", Vector("./title.zsh"), true, false), + Clear, + header, + Paragraph( + lollet("Terminal", "mono12").block ++ lollet("basics", "mono12").block + ++ "\n" ++ "Was tut so eine Terminalanwendung eigentlich?".right(10), + ), + ) + +/* copret templates are simply normal methods that return slides */ +def chapter(title: String, subtitle: String): Group = + chapter(title, "", subtitle) +def chapter(title1: String, title2: String, subtitle: String): Group = { + val font = + if ((title1.length max title2.length) < columns / 10) "mono12" else "mono9" + Group( + Clear, + header, // a built in template + Paragraph( + lollet(title1, font).block ++ lollet(title2, font).block ++ "\n" ++ subtitle.right(10), + ), + ) +} + +val parentSHApattern = raw"parent ([a-f0-9]{40})".r +val treeSHApattern = raw"tree ([a-f0-9]{40})".r +val blobSHApattern = raw"blob ([a-f0-9]{40})".r + +val meta = Map( + "author" -> "Alexander Gehrke", + "title" -> "Terminals", +) + +def sgr(code: String): String = s"\u001b[${code}m" + +def sgr(code: Int*): String = sgr(code.mkString(";")) + +val reset = sgr(0) + +extension (s: String) + def style(code: String): String = sgr(code) + s + reset + def style(code: Int*): String = sgr(code*) + s + reset + def italic = s.style(3) + def bold = s.style(1) + + def underline(style: Int = 1, color: Int = 7) = + sgr("4:" + style) + sgr(58, 5, color) + s + reset + +val rainbow = Vector( + "\u001b[38;5;196m", + "\u001b[38;5;202m", + "\u001b[38;5;208m", + "\u001b[38;5;214m", + "\u001b[38;5;220m", + "\u001b[38;5;226m", + "\u001b[38;5;190m", + "\u001b[38;5;154m", + "\u001b[38;5;118m", + "\u001b[38;5;82m", + "\u001b[38;5;46m", + "\u001b[38;5;47m", + "\u001b[38;5;48m", + "\u001b[38;5;49m", + "\u001b[38;5;50m", + "\u001b[38;5;51m", + "\u001b[38;5;45m", + "\u001b[38;5;39m", + "\u001b[38;5;33m", +) + +def list(bullet: (Int, Int) => String, continued: (Int, Int) => String)(items: String*)(using Theme): Paragraph = + val randbow = util.Random.shuffle(rainbow) + items.zipWithIndex + .map((str, index) => { + def formatFirst(first: String) = + s" ${randbow(index % randbow.length)}${bullet(index, items.size)}\u001b[0m $first" + + str.split("\n").toList match + case first :: Nil => formatFirst(first) + case first :: cont => + formatFirst(first) + + cont.map(line => s" ${continued(index, items.size)}${line}").mkString("\n", "\n", "") + case Nil => "" + }) + .mkString("\n") + .par + +def ulist(items: String*)(using Theme) = list(bullet = (_, _) => "✦ ", continued = (_, _) => " ")(items*) + +def digits(i: Int): Int = math.ceil(math.log10(i)).toInt + +def olist(items: String*)(using Theme) = + list( + bullet = (i, max) => (" " * (digits(max + 1) - digits(i + 1) - 1)) + s"${i + 1}.", + continued = (i, max) => " " * digits(max + 1), + )(items*) + +def emph(str: String) = sgr(3) + lolcat(str) + reset +def strong(str: String) = sgr(1) + lolcat(str) + reset + +val slides = Vector( + titleSlide, + slide("Terminal IO")( + "Eine Anwendung in einem Terminal kommuniziert (fast) nur via Standard-IO mit diesem:".par, + ulist( + "stdin, für Benutzereingaben und Nachrichten vom Terminal", + "stdout, für Textausgabe und Nachrichten an das Terminal", + "stderr, wird wie stdout behandelt", + ), + s"=> Alle Steuersignale vom Programm ans Terminal oder umgekehrt sind ${emph("inband")}".par, + ), + slide("Steuersignale")( + s"""|Wie unterscheiden Terminal und Programm normalen IO von Steuersignalen? + |Durch ${emph("Steuerzeichen")}, aufgeteilt in zwei Bereiche:""".par, + s"""|${strong("C0")}: alle Zeichen im Bereich von 0x00 bis 0x1F + |Größtenteils veraltet. Noch relevant sind u.a.:""".par, + ulist( + "Whitespaces (\\n, \\r, \\t, ...) und Backspace (0x08)", + "Bell (\\a), führt bei den meisten Desktops zu blinkendem Icon o.ä. (Urgent Flag)", + "XON/XOFF (0x11/0x13), schaltet Ausgabe an/aus (Flow Control)", + "Escape (\\e, 0x1B), das wichtigste für diesen Talk, startet Escapesequenzen", + ), + s"""|${strong("C1")}: Zeichen im Bereich von 0x80 bis 0x9F + |Wegen Kollisionen mit Unicode inkompatibel. + |Heute üblicherweise durch Escapesequenzen ersetzt""".par, + ), + chapter("Escape-", "sequenzen", "die verschiedenen Typen"), + slide("CSI")( + PauseKey, + Image(imgs / "csi.png"), + ), + slide("CSI")( + """|Control Sequence Introducer (CSI) ist der vermutlich häufigste Typ. + |Wird benutzt für:""".par, + ulist( + "Cursor-Steuerung", + "Löschen von Zeichen oder Zeilen", + s"${lolcat("Farben")} ${"und".italic} ${"andere".style(42)} ${"Formatierung".underline(style = 3, color = 1)}", + ), + "|CSI-Sequenzen haben folgendes Format:\n".par, + s"${"ESC [".style(94)} ${"arg1 ; arg2 ; ... ; argn".style(93)} ${"c".style(92)}".block.text, + s"""| + |${"✦ ESC [".style(94)} ist der Teil, der CSI genannt wird + |${"✦ c".style(92)} gibt an, was gemacht werden soll. Immer ein Zeichen zwischen 0x40 (@) und 0x7F (~) + |${"✦ arg1 ; arg2 ; ... ; argn".style(93)} sind Parameter, meistens Zahlen. + | Zeichen, die für ${"c".style(92)} erlaubt sind, sind nicht als Parameter erlaubt. + |\n""".stripMargin.block.text, + s"""\nBeispiel: ${"\\u001b[31m".style(31)} schaltet auf rote Schrift um""".par, + ), + slide("OSC")( + """|Operating System Commands (OSC) haben verschiedenste Anwendungszwecke, + |z.B. Setzen des Fenstertitels, Zugriff aufs Clipboard, etc. + |Im Gegensatz zu CSI Escapes können sie die meisten Zeichen enthalten + | => komplexere Befehle möglich.""".par, + s"${"ESC ]".style(94)} ${"".style(92)} ; ${"args...".style(93)} ${"ST".style(96)}".block.text, + s"""| + |${"✦ ESC ]".style(94)} startet ein OSC + |${"✦ ".style(92)} gibt an, was gemacht werden soll, ein- bis vierstellige Zahl + |${"✦ args...".style(93)} sind je nach Befehl die Argumente. Hier ist alles außer ST erlaubt + |${"✦ ST".style(96)} ist das String Terminator Zeichen. Entweder BEL (\\a) oder ESC \\ + |""".stripMargin.block.text, + s"""\nBeispiel: TODO irgendwas mit clipboard""".par, + ), + slide("APC")( + "Application Program Commands (APC) sind ähnlich zu OSC, aber größtenteils\nspezifisch für einzelne Terminals".par, + ulist( + s"fangen mit ${"ESC _".style(3, 94)} an statt mit ${"ESC ]".style(3, 94)}", + s"Format bis zum ${"ST".style(3, 96)} ist uneinheitlich", + "Benutzt z.B. bei screen und tmux zum Setzen von Statusleisten", + "Beispiel für mittlerweile verbreitetere Anwendung: Kitty Graphics Protocol", + ), + ), + chapter("Ende", "Noch Fragen?", ""), + slide("Sonstige")( + "Ein paar weitere Typen von Escapesequenzen, die wir uns nicht genauer anschauen".par, + ulist( + "DCS (Device Control String): ähnlich wie OSC, kaum noch genutzt.\nU.a. für ältere Grafikprotokolle", + "SCS, DOCS: für Zeichensatzwechsel. Wir haben jetzt Unicode.", + "DEC: Für Dinge wie doppelte Zeilenhöhe. In manchen Terminals noch supported.", + "SM, RM: Setzen von Terminalmodi, war für Hardwareterminals relevant", + "DECSET, DECRST: Noch mehr Terminalmodi, vendorspezifisch.\nWerden noch genutzt, z.B. für Maus-Support", + ), + ), +) + +val presentation = Presentation(slides, meta = meta) + @main def main = - enterRawMode() - // print(queryTerm(s"${apc}Gi=31,s=10,v=2,t=s;L3NvbWUtc2hhcmVkLW1lbW9yeS1uYW1lCg==\u001b\\")) - // print(queryTerm(s"\u001b[6n")) - val canHazGraphics = queryTerm(s"${apc}Gi=${KittyGraphicsProtocol.MaxID},s=1,v=1,a=q,t=d,f=24;AAAA${st}${csi}c") - println(canHazGraphics.contains(s"${apc}Gi=${KittyGraphicsProtocol.MaxID}")) - // println( - // KittyGraphicsProtocol.showImage( - // os.Path("/home/crater2150/org/opencolloq/gittalk/img/repo-graph.png"), - // ), - // ) - // println(getSize) + // enterRawMode() + // println(Terminal.getSize()) + presentation.start(using + Keymap.default ++ Map( + Key('i') -> SlideAction.runForeground("tmux"), + ), + )