Update examples
|
@ -142,9 +142,17 @@ val meta = Map(
|
|||
val presentation = Presentation(
|
||||
Vector(
|
||||
titleSlide,
|
||||
/* for simple slides, the `markdown` template and `IncludeMarkdown` Slide allow including plain markdown files.
|
||||
* (currently requires mdcat) */
|
||||
markdown("Wozu braucht man das?", pwd / "slides" / "01wozu.md"),
|
||||
slide("Wozu braucht man das?")(
|
||||
ulist(
|
||||
"Versionierungssysteme speichern _Änderungsverlauf_ von Dateien",
|
||||
"Lässt einen Änderungen rückgängig machen",
|
||||
"Unterstützt einen bei *Zusammenführung* von mehreren Versionen",
|
||||
"(z.B. stabile und Entwicklungsversion)",
|
||||
"Synchronisierung zwischen mehreren Rechnern",
|
||||
"Fehlersuche",
|
||||
"...",
|
||||
)
|
||||
),
|
||||
chapter("Basics", "Grundlegende Befehle"),
|
||||
/* `slide` is a template for a `Group` with an optional title, that clears the screen */
|
||||
slide("Basics")(
|
||||
|
@ -331,9 +339,38 @@ val presentation = Presentation(
|
|||
sh("git add hello.txt; git commit -m \"Extended greeting\""),
|
||||
gitStatus,
|
||||
),
|
||||
markdown("Was bisher geschah", pwd / "slides" / "02summary.md"),
|
||||
slide("Was bisher geschah")(
|
||||
ulist(
|
||||
s"${"git init [dir]".yellow}: neues Repo anlegen",
|
||||
s"${"git status".yellow}: Aktuellen Zustand zeigen (geänderte, ungetrackte Dateien, Stagingbereich)",
|
||||
s"${"git add <path>".yellow}: Datei zu Repo hinzufügen / Änderungen in Datei für Commit markieren / \"stagen\"",
|
||||
s"${"git restore --staged <path>".yellow}: Änderungen aus Staging wieder herausnehmen",
|
||||
s"${"git restore <path>".yellow}: Änderungen in Datei verwerfen (zurück zu Dateizustand aus letzten Commit)",
|
||||
s"${"git commit [-m message]".yellow}: Neuen Commit mit Änderung im Stagingbereich erstellen",
|
||||
s"${"git log".yellow}: Bisherige Commits anzeigen",
|
||||
s"(${"git cat-file".yellow}: Git-Objekte im internen Format anzeigen. Braucht man im Alltag nicht)",
|
||||
)
|
||||
),
|
||||
chapter("Branches", "Grundlegende Befehle"),
|
||||
slide("\n".par, IncludeMarkdown(pwd / "slides" / "02branches.md")),
|
||||
slide("Branches")(
|
||||
"Branches sind mehrere parallele Entwicklungszweige. Wozu Branches?".par,
|
||||
ulist(
|
||||
"Hauptbranch (master, main, etc.) für stabile Version nutzen",
|
||||
"neue Branches pro zusätzlichem Feature erstellen",
|
||||
"Arbeit an mehreren Branches unabhängig voneinander möglich",
|
||||
"Anzeigen aller Änderungen zwischen Branches möglich",
|
||||
"Springen zwischen Zustand verschiedener Branches",
|
||||
),
|
||||
"Verschiedene Workflows möglich, je nach Projekt unterschiedlich.".par,
|
||||
"Branches sind in Git \"billig\":".par,
|
||||
ulist(
|
||||
"Ein Branch ist ein Pointer auf einen Commit",
|
||||
"Normalerweise ist ein Branch als aktueller Branch ausgewählt",
|
||||
"Neuer Commit => aktueller Branch zeigt nun auf diesen",
|
||||
"Neue Branches erstellen = neue Datei mit Hash anlegen",
|
||||
"Branches können auch komplett lokal bleiben (im Gegensatz zu SVN)",
|
||||
),
|
||||
),
|
||||
slide("Branches")(
|
||||
sh("git branch"),
|
||||
---,
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
36
examples/termescape/figlet-pagga-block
Executable file
|
@ -0,0 +1,36 @@
|
|||
#!/bin/zsh
|
||||
zparseopts -D -E p:=padding -padding:=padding
|
||||
lines=("$@")
|
||||
if [[ -z $lines ]]; then
|
||||
lines=()
|
||||
while read line; do
|
||||
lines+=($line)
|
||||
done
|
||||
fi
|
||||
|
||||
width=0
|
||||
for line in $lines; do
|
||||
if [[ ${#line} -gt $width ]]; then
|
||||
width=${#line}
|
||||
fi
|
||||
done
|
||||
|
||||
lpad_width=$((width + ${padding[2]:-0}))
|
||||
rpad_width=$((lpad_width * 2))
|
||||
|
||||
pagga=( $(
|
||||
for i in {1..$#lines}; do
|
||||
line=$lines[$i]
|
||||
padded="${(r:$(( rpad_width - ${#line})):: :)${(l:${lpad_width}:: :)line}}"
|
||||
spaces=$(echo $line | tr -cd ' ' | wc -c)
|
||||
if [[ $spaces -gt 0 ]]; then
|
||||
padded="$padded${(l:spaces:: :)}"
|
||||
fi
|
||||
echo $padded
|
||||
done | figlet -fpagga -t
|
||||
) )
|
||||
|
||||
left_space=$(( (COLUMNS - ${#pagga[1]}) / 2 ))
|
||||
for line in ${pagga[*]}; do
|
||||
echo "${(l:left_space:: :)}$line"
|
||||
done
|
BIN
examples/termescape/img/csi.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
105
examples/termescape/img/csi.svg
Normal file
After Width: | Height: | Size: 562 KiB |
BIN
examples/termescape/img/earth.gif
Normal file
After Width: | Height: | Size: 978 KiB |
529
examples/termescape/terminal-presentation.sc
Executable file
|
@ -0,0 +1,529 @@
|
|||
#!/usr/bin/env -S scala-cli shebang
|
||||
//>using scala 3.3
|
||||
//>using dep de.qwertyuiop::copret:0.0.2
|
||||
|
||||
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/terminal/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 lolcatCustom(str: String, opts: String*): String = String(
|
||||
os.proc("lolcat", "-f", opts).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.interactiveUntyped("./title.zsh").disable("", noInteractiveCommands),
|
||||
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 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 link(id: Int, uri: String) = s"\u001b]8;id=$id;$uri\u001b\\" + s + "\u001b]8;;\u001b\\"
|
||||
def emph = sgr(3) + lolcat(s) + reset
|
||||
def strong = sgr(1) + lolcat(s) + reset
|
||||
|
||||
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*)
|
||||
|
||||
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 ${"inband".emph}".par,
|
||||
),
|
||||
slide("Steuersignale")(
|
||||
s"""|Wie unterscheiden Terminal und Programm normalen IO von Steuersignalen?
|
||||
|Durch ${"Steuerzeichen".emph}, aufgeteilt in zwei Bereiche:""".par,
|
||||
s"""|${"C0".strong}: 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"""|${"C1".strong}: 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)} ${"<ext>".style(96)} ${"arg1 : arg2 : ... : argn".style(93)} ${"<cmd>".style(92)}".block.text,
|
||||
s"""|
|
||||
|${"✦ ESC [".style(94)} ist der Teil, der CSI genannt wird
|
||||
|${"✦ <cmd>".style(92)} gibt an, was gemacht werden soll. Immer ein Zeichen zwischen 0x40 (@) und 0x7F (~)
|
||||
|${"✦ <ext>".style(92)} darf aus dem ASCII-Bereich 0x20 - 0x3F außer Ziffern, ; und : sein
|
||||
| Gibt es, weil man mehr Befehle wollte, als für <cmd> Zeichen erlaubt sind.
|
||||
|${"✦ arg1 : arg2 : ... : argn".style(93)} sind numerische Parameter.
|
||||
| Statt : wird teilweise ; benutzt (ISO-Standard vs. Realität).
|
||||
|\n""".stripMargin.block.text,
|
||||
s"""\nBeispiel: ${"<ESC>[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)} ${"<Zahl>".style(92)} ; ${"args...".style(93)} ${"ST".style(96)}".block.text,
|
||||
s"""|
|
||||
|${"✦ ESC ]".style(94)} startet ein OSC
|
||||
|${"✦ <Zahl>".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, oft Base64.
|
||||
|${"✦ ST".style(96)} ist das String Terminator Zeichen. Entweder BEL (\\a) oder ESC \\
|
||||
|""".stripMargin.block.text,
|
||||
s"""|
|
||||
|Beispiel: ${"<ESC>]52;c;SGVsbG9Xb3JsZA==<ESC>\\".style(38, 5, 145)}
|
||||
| OSC52 = Clipboard, "c" gibt an welches Clipboard
|
||||
| base64-codierter Text entspricht "HelloWorld"
|
||||
""".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("Farben und ", "Formatierung", "Escapes für dargestellte Zellen"),
|
||||
slide("Terminaloutput")(
|
||||
"""|"Bevor wir uns Formatierung ansehen, erst ein kurzer Überblick darüber,
|
||||
|wie die Ausgabe eines Terminals funktioniert:""".par,
|
||||
ulist(
|
||||
"State Machine, die Cursorposition und aktuelle Formatierung hält",
|
||||
"Ausgabe aufgeteilt in Zellen, normalerweise ein Zeichen pro Zelle",
|
||||
"Escapes in Ausgabe können Formatierung und Cursorposition ändern",
|
||||
"druckbare Zeichen wird an Cursorposition geschrieben\nCursor wandert weiter, Formatierung bleibt",
|
||||
),
|
||||
"""|=> Formatierung gilt für die Zelle, in der der Cursor steht
|
||||
| und alle folgenden Zellen, bis sie geändert wird""".par,
|
||||
),
|
||||
slide("SGR Codes")(
|
||||
"""|Die meisten Formatierungen sind SGR-Sequenzen (Select Graphic Rendition).
|
||||
|Das sind CSI-Sequenzen mit "m" als Endzeichen.
|
||||
|Die vermutlich wichtigste ist die zum Zurücksetzen auf Default-Formatierung:""".par,
|
||||
s"${"ESC [".style(94)} ${"0".style(92)} ${"m".style(96)}".block.text,
|
||||
s"""|
|
||||
|Alle SGR-Codes beginnen mit dem ${"CSI".style(94)} und enden mit einem "${"m".style(96)}".
|
||||
|Die ${"0".style(92)} in der Mitte gibt den eigentlichen Befehl.
|
||||
|Hier können je nach Code weitere Parameter folgen""".par,
|
||||
),
|
||||
slide("Farben")(
|
||||
"""|Farben sind die wohl am häufigsten genutzte Formatierung.
|
||||
|Es gibt verschiedene Arten von Farbcodes in Terminals:""".par,
|
||||
ulist(
|
||||
"Palettenfarben mit 8 oder 16 Farben",
|
||||
"256-Farbenpalette, inzwischen auch fast überall unterstützt",
|
||||
"Truecolor, 24-Bit-Farben, die in modernen Terminals unterstützt werden",
|
||||
),
|
||||
s"""|Beispiel: Farbverlauf von #55ff22 ${"██".style("38:2::85:255:34")} nach #0000ff ${"██".style(
|
||||
"38:2::0:0:255",
|
||||
)}:""".par,
|
||||
("256 Farbpalette: " + lolcatCustom("█".repeat(60), "-h", "0.18", "-g", "55ff22:0000ff")).par,
|
||||
("24-Bit-Farben: " + lolcatCustom("█".repeat(60), "-h", "0.40", "-g", "55ff22:0000ff", "-b")).par,
|
||||
),
|
||||
slide("Die Grundpalette")(
|
||||
"""|Wir fangen an mit den 8/16 Grundfarben. Die Codes zum Setzen bestehen aus mehreren Ziffern.
|
||||
|Der Anfang gibt an, welche Farbe gesetzt werden soll:""".par,
|
||||
ulist(
|
||||
"Beginn mit 3: Vordergrundfarbe",
|
||||
"Beginn mit 4: Hintergrundfarbe",
|
||||
"Beginn mit 9: Helle Vordergrundfarbe",
|
||||
"Beginn mit 10: Helle Hintergrundfarbe",
|
||||
),
|
||||
"Anschließend folgt eine Ziffer für die Farbe (tatsächliche Farbe einstellungsabhängig):".par,
|
||||
s"""|0 Schwarz ${"abc".style("30")} ${"abc".style("40")} hell: ${"abc".style("90")} ${"abc".style("100")}
|
||||
|1 Rot ${"abc".style("31")} ${"abc".style("41")} hell: ${"abc".style("91")} ${"abc".style("101")}
|
||||
|2 Grün ${"abc".style("32")} ${"abc".style("42")} hell: ${"abc".style("92")} ${"abc".style("102")}
|
||||
|3 Gelb ${"abc".style("33")} ${"abc".style("43")} hell: ${"abc".style("93")} ${"abc".style("103")}
|
||||
|4 Blau ${"abc".style("34")} ${"abc".style("44")} hell: ${"abc".style("94")} ${"abc".style("104")}
|
||||
|5 Magenta ${"abc".style("35")} ${"abc".style("45")} hell: ${"abc".style("95")} ${"abc".style("105")}
|
||||
|6 Cyan ${"abc".style("36")} ${"abc".style("46")} hell: ${"abc".style("96")} ${"abc".style("106")}
|
||||
|7 Weiß ${"abc".style("37")} ${"abc".style("47")} hell: ${"abc".style("97")} ${"abc".style("107")}
|
||||
""".stripMargin.block.text,
|
||||
s"""\nBeispiel von vorhin: ${"<ESC>[31m".style(31)} schaltet auf rote Schrift um""".par,
|
||||
),
|
||||
slide("256 Farben")(
|
||||
s"""|Die 256-Farbenpalette ist eine Erweiterung der 8/16-Farbenpalette.
|
||||
|0-7 entsprechen denen der Terminalpalette, 8-15 den hellen Varianten.
|
||||
|Dann folgen 216 Farben, die in einem 6x6x6-Würfel angeordnet sind:
|
||||
|
|
||||
| ${"✦ ".style(31)} Mit jedem Index steigt der Blauwert eine Stufe, nach 6 Farben wiederholt er sich
|
||||
| ${"✦ ".style(32)} Mit jedem 6. Index steigt der Grünwert eine Stufe, analog.
|
||||
| ${"✦ ".style(34)} Mit jedem 36. Index steigt der Rotwert eine Stufe
|
||||
|
|
||||
|Die restlichen 24 Farben sind Grautöne von schwarz nach weiß.
|
||||
|
|
||||
|Die Codes für die 256-Farbenpalette sind folgendermaßen aufgebaut:""".par,
|
||||
s"Vordergrund: CSI ${"38".style(92)} : ${"5".style(93)} : ${"<Farbe>".style(95)} m".block.text,
|
||||
s"Hintergrund: CSI ${"48".style(92)} : ${"5".style(93)} : ${"<Farbe>".style(95)} m".block.text,
|
||||
s"""|
|
||||
|${"✦ 5".style(93)} gibt an, dass die 256-Farben-Palette benutzt werden soll.
|
||||
|${"✦ <Farbe>".style(95)} ist der Index der Farbe in der Palette.
|
||||
|""".stripMargin.block.text,
|
||||
PauseKey,
|
||||
TypedCommand.interactiveUntyped("sh", "-c", "colortest -w | less -R").disable("", noInteractiveCommands),
|
||||
),
|
||||
slide("24 Bit \"Truecolor\"")(
|
||||
s"""|Mit Truecolor gibt es keine Indices mehr.
|
||||
|Es werden direkt die Werte für die Kanäle angegeben
|
||||
|Der Aufbau ist ähnlich wie bei den 256 Farben (Vordergrund 38, Hintergrund 48):""".par,
|
||||
s"CSI ${"38".style(92)} : ${"<Modell>".style(93)} : ${"<Farbraum>".style(95)} : ${"<Kanäle>".style(94)} m".block.text,
|
||||
s"""|
|
||||
|${"✦ <Modell>".style(93)} ist entweder 2 für RGB, 3 für CMY, 4 für CMYK (RGB am besten supported)
|
||||
|${"✦ <Farbraum>".style(95)} gibt den Farbraum an. Schwierig, Doku dazu zu finden, meistens leer.
|
||||
|${"✦ <Kanäle>".style(94)} sind die Werte für die einzelnen Kanäle in dezimal, getrennt durch :
|
||||
|✦ Nicht-standardisiertes, aber verbreitetes xterm-Encoding:
|
||||
| Semikolon statt Doppelpunkt, Farbraumfeld fehlt.
|
||||
|""".stripMargin.block.text,
|
||||
s"""\nBeispiel: ${"<ESC>[38:2::255:166:86m".style(
|
||||
"38:2::255:166:86",
|
||||
)} ergibt den Orangeton RGB(255, 166, 86) bzw. #FFA656""".par,
|
||||
),
|
||||
slide("weitere Textformatierungen")(
|
||||
"Weitere SGR-Codes zum Formatieren von Text (in Klammern: Code zum Abschalten):".par,
|
||||
ulist(
|
||||
s"1 (22): ${"Fett".style(1)}",
|
||||
s"2 (22): ${"Faint".style(2)} (meistens nicht unterstützt oder wechselt nur zu dunklerer Farbe)",
|
||||
s"3 (23): ${"Kursiv".style(3)}",
|
||||
s"5 und 6 (25): ${"Blinkend".style(5)} und ${"schnell Blinkend".style(6)}, durchwachsener Support",
|
||||
s"7 (27): ${"Invertiert".style(7)} (Vorder- und Hintergrundfarbe tauschen)",
|
||||
s"8 (28): Versteckt (Vordergrundfarbe = Hintergrundfarbe)",
|
||||
s"9 (29): ${"Durchgestrichen".style(9)}",
|
||||
),
|
||||
),
|
||||
slide("weitere Textformatierungen")(
|
||||
"Weitere SGR-Codes zum Formatieren von Text:".par,
|
||||
ulist(
|
||||
s"""|4 (24): ${"Unterstrichen".style(4)}. Moderne Terminals können zusätzlich noch:
|
||||
| 4:1 ${"Normal unterstrichen".style(4, 1)}
|
||||
| 4:2 ${"Doppelt unterstrichen".style(4, 2)}
|
||||
| 4:3 ${"Unterringelt".style(4, 3)}
|
||||
| 4:4 ${"gepunktet".style(4, 4)}
|
||||
| 4:5 ${"gestrichelt".style(4, 5)}
|
||||
|
|
||||
| Außerdem kann in manchen Terminals die ${"Farbe".style(4).style("58:2::255:0:0")} gesetzt werden.
|
||||
| 256er-Paletten- oder Truecolor-Code, einfach 38/48 durch 58 ersetzen.
|
||||
|""".stripMargin,
|
||||
),
|
||||
),
|
||||
slide("Bereiche formatieren")(
|
||||
"""Mit der folgenden Escapesequenz kann man direkt einen größeren Bereich formatieren:""".par,
|
||||
s"CSI ${"Pt; Pl; Pb; Pr".style(93)} ; ${"arg1 ; ... ; argn".style(95)} ${"$"}r".block.text,
|
||||
s"""|
|
||||
|${"✦ Pt; Pl; Pb; Pr".style(93)} geben den Bereich an, der formatiert werden soll
|
||||
| Pt,Pl geben die erste Zelle an (top/left), Pb, Pr die letzte (bottom/right).
|
||||
| Die oberste linke Zelle des Terminals ist hierbei 1,1.
|
||||
| Weggelassene Werte entsprechen dem jeweiligen Extremwert.
|
||||
|${"✦ arg1 ; ... ; argn".style(95)} sind ein SGR-Code (ohne das "m" am Ende)
|
||||
|✦ Formatiert standardmäßig zeilenweise alles von der ersten bis zur letzten Zelle.
|
||||
| Mit ${"CSI 2 * x".style(3)} wechselt man stattdessen zu blockweiser Formatierung,
|
||||
| mit ${"CSI * x".style(3)} wieder zu zeilenweise.
|
||||
|\n""".stripMargin.block.text,
|
||||
s"""|Standardisiert sind nur fett, unterstrichen, blinken und invertiert.
|
||||
|Manche Terminals, z.B. kitty unterstützen alle SGRs, andere wiederum keine.""".par,
|
||||
),
|
||||
chapter("Cursor", "Bewegen und verstecken"),
|
||||
slide("Einfache Cursorbewegung")(
|
||||
s"""|Wenn wir komplexere Anwendungen bauen wollen, insbesondere interaktive,
|
||||
|wollen wir nicht immer ein Zeichen nach dem anderen schreiben.
|
||||
|Daher gibt es Escape-Codes, die den Cursor bewegen.
|
||||
|
|
||||
|Die folgenden Befehle sind alles CSI-Sequenzen mit 1-2 numerischen Argumenten.
|
||||
|Fehlende Argumente entsprechen in den meisten Fällen einer 1.""".par,
|
||||
ulist(
|
||||
"A, B, C, D: Cursor um arg1 Zeichen nach oben, unten, rechts, links bewegen",
|
||||
"E, F: Cursor auf Anfang der Zeile arg1 Zeilen unter/über aktueller Position setzen",
|
||||
"G, d: Cursor auf Spalte bzw. Zeile arg1 setzen",
|
||||
"H: Cursor auf Zeile arg1, Spalte arg2 setzen",
|
||||
"s: Cursorposition speichern, u: zurück zur gespeicherten Position",
|
||||
),
|
||||
s"""|
|
||||
|Für absolute Positionen ist die oberste linke Zelle (1, 1).
|
||||
|Beispiel:
|
||||
|${"<ESC>[HHello<ESC>[3;6HWorld".style(3)} gibt "Hello" oben links aus,
|
||||
|"World" in der dritten Zeile um 6 Zellen nach rechts verschoben.""".par,
|
||||
PauseKey,
|
||||
TypedCommand.interactiveUntyped("./cursorpos.zsh").disable("", noInteractiveCommands),
|
||||
),
|
||||
slide("Cursordarstellung")(
|
||||
"""Der Cursor kann auch versteckt werden (z.B. tut das diese Präsentation):""".par,
|
||||
ulist(
|
||||
s"${"CSI ? 25 l".style(3)} versteckt den Cursor",
|
||||
s"${"CSI ? 25 h".style(3)} zeigt den Cursor wieder an",
|
||||
),
|
||||
s"""|Mit ${"CSI <n> ␠ q".style(3)} kann die Form des Cursors angepasst werden.
|
||||
|Hierbei ist ␠ ein Leerzeichen und <n> eine der folgenden Zahlen:""".par,
|
||||
ulist(
|
||||
"0: Default-Cursor",
|
||||
"1: Blinkender Block",
|
||||
"2: Block",
|
||||
"3: Blinkender Unterstrich",
|
||||
"4: Unterstrich",
|
||||
"5: Blinkender vertikaler Strich",
|
||||
"6: vertikaler Strich",
|
||||
),
|
||||
),
|
||||
slide("Dinge entfernen")(
|
||||
"""|Oft ist es praktisch, Text auch wieder löschen zu können, z.B. um den Inhalt
|
||||
|einer Zeile zu aktualisieren. Einfach überschreiben führt zu Artefakten, wenn
|
||||
|der neue Text kürzer ist als der alte.""".par,
|
||||
ulist(
|
||||
s"${"CSI K".style(3)} oder ${"CSI 0 K".style(3)} löscht vom Cursor bis zum Ende der Zeile",
|
||||
s"${"CSI 1 K".style(3)} löscht vom Anfang der Zeile bis zum Cursor",
|
||||
s"${"CSI 2 K".style(3)} löscht die ganze Zeile",
|
||||
s"${"CSI J".style(3)} oder ${"CSI 0 J".style(3)} löscht vom Cursor bis zum Ende des Bildschirms",
|
||||
s"${"CSI 1 J".style(3)} löscht vom Anfang des Bildschirms bis zum Cursor",
|
||||
s"${"CSI 2 J".style(3)} löscht den ganzen Bildschirm (was \"clear\" tut)",
|
||||
s"${"CSI 3 J".style(3)} löscht den Bildschirm und Scrollback",
|
||||
),
|
||||
),
|
||||
chapter("Weitere", "Features", "vor allem neuere"),
|
||||
slide("Hyperlinks")(
|
||||
"""|Hyperlinks sind ein relativ neues Feature in Terminals. Sie funktionieren ähnlich wie
|
||||
|Links in Browsern.
|
||||
|""".par,
|
||||
s"Start: ${"OSC 8".style(92)} ; ${"<params>".style(93)} ; ${"<URI>".style(95)} ST".block.text,
|
||||
s"Ende: ${"OSC 8".style(92)} ; ; ST".block.text,
|
||||
s"""|
|
||||
|${"✦ OSC 8".style(92)} gibt an, dass wir einen Hyperlink wollen (zur Erinnerung: ${"OSC = <ESC> ]".style(3)})
|
||||
|${"✦ <params>".style(93)} sind key=value Paare, getrennt durch Doppelpunkt
|
||||
| Aktuell ist nur "id" spezifiziert.
|
||||
|${"✦ <URI>".style(95)} ist das Linkziel, URI-encoded. Erlaubte Schemas terminalabhängig.
|
||||
| Wegen Remoteverbindungen wie SSH müssen file:// URIs den Hostnamen enthalten.
|
||||
|✦ ${"ST".style(3)} ist der String Terminator (ESC \\)""".stripMargin.block.text,
|
||||
),
|
||||
slide("Hyperlinks")(
|
||||
s"""|Wozu die ID? Beispiel von egmontkob [1]:
|
||||
|
|
||||
| ╔═ file1 ════╗
|
||||
| ║ ╔═ file2 ═══╗
|
||||
| ║${"http://exa".link(42, "http://example.com")}║Lorem ipsum║
|
||||
| ║${"le.com".link(42, "http://example.com")} ║ dolor sit ║
|
||||
| ║ ║amet, conse║
|
||||
| ╚══════════║ctetur adip║
|
||||
| ╚═══════════╝
|
||||
|
|
||||
|Umgebrochener Link in der linken Box soll ein Link bleiben. Das Terminal interpretiert aber
|
||||
|Attribute pro Zelle. ID ist nötig, um nicht aufeinanderfolgende Zellen zu gruppieren.
|
||||
|
|
||||
|
|
||||
|[1]: ${"https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda".style(38, 5, 39)}""".par,
|
||||
),
|
||||
slide("Clipboard-Zugriff")(
|
||||
s"""|Moderne Terminals erlauben es Programmen, auf das Clipboard zuzugreifen.
|
||||
|Wozu, wenn das auch über "xclip" u.ä. geht?
|
||||
| => Mit Escapecode funktioniert es auch über SSH!""".par,
|
||||
s"${"OSC 52".style(92)} ; ${"<target>".style(93)} ; ${"<data>".style(95)} ST".block.text,
|
||||
s"""|
|
||||
|${"✦ OSC 52".style(92)} gibt an, dass wir auf das clipboard zugreifen wollen
|
||||
|${"✦ <target>".style(93)} wählt das Clipboard, das wir nutzen möchten.
|
||||
| "c" steht hierbei für das normale Clipboard, "p" für die "Primary selection".
|
||||
|${"✦ <data>".style(95)} ist entweder ein "?", um aus dem Clipboard zu lesen,
|
||||
| oder die base64-kodierten Daten, die reingeschrieben werden sollen.
|
||||
|""".stripMargin.block.text,
|
||||
s"""|
|
||||
|Beispiel (von vorhin): ${"<ESC>]52;c;SGVsbG9Xb3JsZA==<ESC>\\".style(38, 5, 145)}
|
||||
| kopiert "HelloWorld" in die Zwischenablage""".par,
|
||||
),
|
||||
slide("""Moment, da muss das Terminal antworten""")(
|
||||
s"""|Bis eben hatten wir nur Escape-Codes, bei denen das Terminal nichts zurückgeben muss.
|
||||
|Wie erhalten wir die Antworten vom Terminal? => via stdin, auch als Escapecodes.
|
||||
|Allerdings arbeitet stdin standardmäßig im ${"canonical mode".emph}
|
||||
| => Eingabe kommt erst nach Newline an
|
||||
|
|
||||
|Hier müssen wir tatsächlich via Syscalls das ${"Terminal Device".emph} umkonfigurieren.
|
||||
|Unter unixoiden Betriebssystemen können wir dafür das stty-Programm benutzen:""".par,
|
||||
s"${"stty".style(92)} ${"-icanon".style(93)} ${"min 1".style(95)}".block.text,
|
||||
s"""|
|
||||
|${"✦ stty".style(92)} ist ein Programm, das diverse Terminalsyscalls kapselt
|
||||
|${"✦ -icanon".style(93)} deaktiviert den canonical mode => ungepuffertes stdin
|
||||
|${"✦ min 1".style(95)} setzt die minimale Größe für reads auf ein Byte
|
||||
| => einzelne Zeichen lesbar, z.B. zum lesen von Tastendrücken""".stripMargin.block.text,
|
||||
s"""|
|
||||
|Wer in einer kompilierten Sprache arbeitet und die Syscalls direkt benutzen will,
|
||||
|Die relevanten Suchbegriffe sind tcgetattr, tcsetattr und termios.h
|
||||
|Bzw. unter Windows: GetConsoleMode, SetConsoleMode und windows.h""".par,
|
||||
),
|
||||
slide("Clipboard auslesen")(
|
||||
s"""|Zum Auslesen des Clipboards können wir nun die OSC52-Sequenz
|
||||
|mit einem "?" als Datenparameter senden.
|
||||
|
|
||||
|Die Antwort des Terminals hat das selbe Format wie der Escapecode zum kopieren.
|
||||
|Wenn wir also mit ${"<ESC>]52;c;SGVsbG9Xb3JsZA==<ESC>\\".style(38, 5, 145)} "HelloWorld"
|
||||
|in die Zwischenablage schreiben können, würde auch genau dieser String zurück kommen
|
||||
|wenn wir das Clipboard auslesen, während "HelloWorld" darin steht.""".par,
|
||||
),
|
||||
slide("Cursorposition auslesen")(
|
||||
s"""|Nachdem wir jetzt wissen, wie uns das Terminal Dinge antworten kann, können wir
|
||||
|auch noch einen CSI-Code anschauen, den wir vorhin ausgelassen haben:""".par,
|
||||
s"CSI ${"6 n".style(92)} ".block.text,
|
||||
s"""|
|
||||
|Auf diese Sequenz antwortet das Terminal mit der aktuellen Cursorposition, im Format:""".par,
|
||||
s"CSI ${"<ROW>".style(92)} ; ${"<COL>".style(92)} ${"".style(95)} ".block.text,
|
||||
s"""|
|
||||
|In Kombination mit anderen Escapes, die wir schon gesehen haben, kann man auf etwas
|
||||
|hackige Art die Größe des Terminals bekommen:""".par,
|
||||
s"${"CSI s".style(92)} ${"CSI 999;999 H".style(93)} ${"CSI 6 n".style(94)} ${"CSI u".style(96)}".block.text,
|
||||
),
|
||||
slide("Bilder im Terminal")(
|
||||
s"""|Man kann mittlerweile in vielen Terminals Grafiken ausgeben. Allerdings gibt es dafür
|
||||
|verschiedene Protokolle und die meisten Emulatoren können nur eines.""".par,
|
||||
ulist(
|
||||
s"""|${"sixel".emph}: Ein altes Protokoll, das schon von Hardwareterminals unterstützt wurde
|
||||
|Eingeschränkt auf 1024 Farbwerte pro Bild""".stripMargin,
|
||||
s"""|${"iTerm2 Protocol".emph}: proprietäres, relativ einfaches Protokoll von iTerm2.
|
||||
|Benutzt Escapes mit unbegrenzter Länge
|
||||
| => Problemen wenn nicht supported""".stripMargin,
|
||||
s"""|${"Kitty Graphics Protocol".emph}: proprietäres, aber ausführlich spezifiziertes Protokoll
|
||||
|von kitty. Hat sehr viele Konfigurationsmöglichkeiten, benutzt eine APC-Sequenz.
|
||||
|Auch von wenigen anderen implementiert (u.a. KDE Konsole).
|
||||
|Support über Query prüfbar, wird ignoriert wenn nicht implementiert""".stripMargin,
|
||||
),
|
||||
),
|
||||
slide("Bilder im Terminal")(
|
||||
"Mit dem Kitty-Protokoll geht das auch in bewegt:".par,
|
||||
TypedCommand.interactiveUntyped("kitten", "icat", "img/earth.gif"),
|
||||
),
|
||||
chapter("Ende", "... des vorbereiteten Teils ;)"),
|
||||
slide("Sonstige Escapes")(
|
||||
"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)
|
||||
|
||||
presentation.start(using
|
||||
Keymap.default ++ Map(
|
||||
Key('i') -> SlideAction.runForeground("tmux"),
|
||||
),
|
||||
)
|
8
examples/termescape/title.zsh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/zsh
|
||||
printf '\e[?25l\e[2J'
|
||||
while true; do
|
||||
printf "\e[$((LINES / 2 - 14));0H"
|
||||
${ZSH_SCRIPT:a:h}/figlet-pagga-block -p 8 "" Bunte Terminals "" "und sonstiger" "Spass mit" ESCAPECODES "" | lolcat --24bit -r
|
||||
read -s -k -t 0.5 && break
|
||||
done
|
||||
printf '\e[?25h'
|