TTY
Terminal-Eingabeereignisse, formatierte Ausgabe, Präsentations-Surfaces und lokale virtuelle Viewports.
process.lua auf einem regulären process.host besitzt ein virtuelles Terminal, wenn er mit einem Viewport-Grant gespawnt wird. Ohne eine dieser Anbindungen gibt das Modul "no terminal context" zurück.
Laden
local tty = require("tty")
Modell
Eine Surface ist die exklusive Präsentations-Lease eines Prozesses auf seinem Terminal-Port. Sie veröffentlicht vollständige Zeilen-Snapshots; das Backend übernimmt Diffing und Terminal-Wiederherstellung. Auf einem Port darf jeweils nur eine Surface offen sein.
Ein Canvas ist ein prozessinterner Kompositionspuffer aus gestalteten Zellen. Er clippt an Zellgrenzen und gibt nie eigene Terminal-Steuerbefehle aus.
Ein Viewport ist eine lokale, strukturierte Terminal-Grenze, die es einem Prozess erlaubt, die Surface eines anderen Prozesses zu hosten, ohne Byte-Streams zu teilen. Die Shell entscheidet, wo Viewport-Inhalte erscheinen, und übersetzt Eingaben in die Koordinaten des Kindprozesses; der Kindprozess sieht einen gewöhnlichen Terminal-Port und weiß nicht, ob er Vollbild, gekachelt, in einem Tab oder verborgen ist.
Viewports sind lokal zu einem Runtime-Knoten. Grants und Handles sind opake lokale Capabilities, keine serialisierbaren Netzwerkreferenzen.
Eingabe-Schleife
Starte die Eingabezustellung, abonniere Ereignisse und verarbeite sie in einer Schleife:
local tty = require("tty")
local io = require("io")
local function handler()
local events = tty.events()
tty.start()
while true do
local ev, open = events:receive()
if not open then break end
if ev.type == "key" then
if ev.key == "q" or (ev.ctrl and ev.key == "c") then
break
end
local _, print_err = io.print("Key: " .. ev.key)
if print_err then loop_err = print_err; break end
elseif ev.type == "resize" then
local _, print_err = io.print("Size: " .. ev.width .. "x" .. ev.height)
if print_err then loop_err = print_err; break end
end
end
local _, stop_err = tty.stop()
if loop_err then return nil, loop_err end
if stop_err then return nil, stop_err end
return started
end
Rufe events() vor start() auf, damit ein Konsument bereitsteht, wenn die ersten Ereignisse eintreffen. Auf einem virtuellen Port öffnet start() die Ereigniszustellung vom Betrachter zum Produzenten und stop() schließt sie: Ein Viewport:send() außerhalb dieses Intervalls schlägt fehl, statt Eingaben stillschweigend zu verwerfen. Die Zustellung von Resize-Ereignissen ist vom Eingabezustand unabhängig.
Eingabesteuerung
tty.start()
Startet die Eingabezustellung für den aktuellen Port. Ein physisches Terminal wechselt in den Raw-Modus.
local ok, err = tty.start()
Rückgabe: boolean, error
tty.stop()
Stoppt die Eingabezustellung und stellt das Terminal in den Normalmodus zurück.
local ok, err = tty.stop()
Rückgabe: boolean, error
tty.events()
Abonniert die Terminal-Ereignisse des Ports und gibt einen Channel zurück. Ereignisse werden als Tabellen mit einem type-Feld geliefert. Einmal abonnieren und den Channel wiederverwenden.
local events, err = tty.events()
Rückgabe: EventChannel, error
EventChannel besitzt receive() und case_receive() und lässt sich damit mit channel.select kombinieren.
tty.screen_size()
Fragt die aktuellen Terminal-Dimensionen ab.
local width, height, err = tty.screen_size()
Rückgabe: number, number, error
tty.mouse(enable)
Aktiviert oder deaktiviert das Maus-Ereignis-Tracking.
local ok, err = tty.mouse(true)
| Parameter | Typ | Beschreibung |
|---|---|---|
enable |
boolean | true zum Aktivieren, false zum Deaktivieren |
Rückgabe: boolean, error
Surface
Eine Surface ist die Präsentations-Lease des Ports. Eine Lease erwerben, vollständige Frames veröffentlichen und sie am Ende schließen.
tty.surface(options?)
local surface, err = tty.surface({
alternate_screen = true,
hide_cursor = true,
synchronized_output = true,
})
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
alternate_screen |
boolean | false | Auf dem Alternate-Screen-Puffer des Terminals präsentieren |
hide_cursor |
boolean | false | Den Terminal-Cursor verbergen, solange die Surface offen ist |
synchronized_output |
boolean | false | Jeden Frame in Synchronized-Output-Marker einfassen |
Rückgabe: Surface, error
Das Öffnen einer zweiten Surface auf einem Port, der bereits eine hat, schlägt fehl. Ein virtueller Port behält die Optionen als Surface-Metadaten; ein physischer Port übersetzt sie in Terminal-Modi und stellt sie beim Schließen wieder her.
surface:present(rows, options?)
Veröffentlicht ein vollständiges Array von Zeilen-Strings. Zeile 1 ist die oberste Zeile.
local stats, err = surface:present(rows, {
cursor = {x = 12, y = 3, visible = true},
images = {
{placement_id = "logo", image = logo, x = 2, y = 2, cols = 20, rows = 8, alt = "Logo"},
},
})
| Parameter | Typ | Beschreibung |
|---|---|---|
rows |
string[] | Vollständiger Frame, höchstens 16384 Zeilen |
options.cursor |
table | {x, y, visible} in einsbasierten Surface-Koordinaten |
options.images |
table[] | Vollständiger Satz beibehaltener Bildplatzierungen für den Frame |
Wird cursor weggelassen, bleibt der letzte explizite Cursor-Zustand erhalten. Ist cursor vorhanden, sind alle drei Cursor-Felder erforderlich.
Rückgabe: stats, error — ein unveränderlicher Datensatz mit rows, changed_rows und bytes_written. Ein physischer Frame, der mit dem vorherigen identisch ist, schreibt nichts.
surface:invalidate()
Verwirft den Präsentationszustand des Backends, ohne den logischen Frame zu löschen. Das nächste present wird auch dann committet, wenn seine Zeilen unverändert sind. Nach einer Größenänderung des äußeren Terminals verwenden oder wenn ein anderer Eigentümer den physischen Zustand gestört haben könnte.
Rückgabe: boolean
surface:close()
Gibt die Lease frei. Idempotent: Spätere Aufrufe geben das Ergebnis des ersten Schließens zurück. Ein physisches Backend stellt die Terminal-Modi wieder her.
Rückgabe: boolean, error
surface:capabilities()
Gibt {images = "native" | "kitty" | "pending" | "none"} zurück. Starten Sie
die Terminal-Eingabe vor der Abfrage. Ein physisches Backend kann kurzzeitig
pending zurückgeben, während es das Terminal abfragt; virtuelle Surfaces
behalten Bilder ohne Abfrage.
Rückgabe: table, error
surface:clipboard(text)
Schreibt eine OSC-52-Zwischenablageanforderung auf eine physische Surface. Der Text muss gültiges UTF-8 und höchstens 65.536 Byte lang sein. Erfolg bedeutet, dass die Terminalausgabe die Anforderung angenommen hat; die Terminalrichtlinie kann sie dennoch ignorieren. Virtuelle Surfaces geben einen Nichtunterstützt- Fehler zurück; die API bietet weder Zwischenablage-Lesen noch Bestätigung.
Rückgabe: boolean, error
Beibehaltene Bilder
Importieren Sie ein PNG in den begrenzten Laufzeitspeicher und platzieren Sie sein Handle in einem vollständigen Surface-Frame:
local image = assert(tty.image(png_bytes))
local info = image:info() -- id, format, width, height, bytes
assert(surface:present(rows, {images = {{
placement_id = "preview", image = image,
x = 1, y = 1, cols = 40, rows = 12,
src = {x = 0, y = 0, width = info.width, height = info.height},
z = 1, alt = "Preview",
}}}))
tty.image() prüft PNG-Bytes asynchron. image:read() exportiert die
codierten Bytes ausdrücklich und image:close() gibt die Referenz frei.
Quellpixelkoordinaten beginnen bei null, Zielzellkoordinaten bei eins. Wird
images bei einem späteren present weggelassen, werden frühere Platzierungen
gelöscht. Nicht unterstützte physische Terminals zeigen den alt-Text; virtuelle
Surfaces behalten die Bildressource für Betrachter.
Canvas
Ein Canvas ist ein begrenzter Puffer aus gestalteten Zellen, mit dem ein Frame vor dem Präsentieren komponiert wird.
tty.canvas(width, height)
local canvas = tty.canvas(width, height)
Die Breite ist auf 16384 Spalten begrenzt, die Höhe auf 16384 Zeilen und die Fläche auf 262.144 Zellen. Argumente außerhalb des gültigen Bereichs lösen einen Argumentfehler aus.
Rückgabe: Canvas
Das Zeichnen akzeptiert gestalteten Text, keine Terminal-Befehle. SGR-Farben und OSC-8-Links bleiben erhalten; Löschen, Cursor-Bewegungen und andere reine Steuerausgaben werden nicht ausgegeben. Jede Platzierung wird unabhängig an Zellgrenzen mit Beachtung der Graphem-Breite geclippt, sodass eine abgeschnittene Escape-Sequenz nicht in benachbarte Inhalte durchsickern kann.
canvas:clear(fill?)
Löscht jede Zelle. Ein optionaler gestalteter fill-String wird über jede Zeile wiederholt.
canvas:clear()
canvas:clear(tty.style():background("#1a1a1a"):render(" "))
Rückgabe: boolean
canvas:put(x, y, text, width?)
Platziert eine gestaltete Zeile an den einsbasierten Koordinaten x, y und clippt sie auf width Zellen (Standard: die Canvas-Breite). Koordinaten dürfen negativ oder jenseits des Randes liegen; die Platzierung wird geclippt statt abgelehnt. Ein Zeilenumbruch beendet die Zeile, verwende daher put_rows für mehrzeilige Inhalte.
canvas:put(3, 1, tty.style():bold():render("Title"), 40)
Rückgabe: boolean
canvas:put_rows(x, y, rows, width?)
Platziert ein Array gestalteter Zeilen ab x, y, eine Zeile pro Zeile nach unten. Jeder Eintrag wird validiert, bevor etwas gezeichnet wird.
canvas:put_rows(2, 2, child_rows, inner_width)
Rückgabe: boolean
canvas:rows()
Rendert das vollständige Zeilen-Array, bereit für surface:present.
Rückgabe: string[]
Viewport
Ein Viewport ist ein virtueller Terminal-Port. Der erstellende Prozess ist sein erster Betrachter; der mit seinem Grant zugelassene Prozess ist sein Produzent.
tty.viewport(options?)
local view, err = tty.viewport({width = 80, height = 24})
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
width |
number | 80 | Spalten, 1 bis 65535 |
height |
number | 24 | Zeilen, 1 bis 65535 |
page |
table | keine | Undurchsichtige Standardwerte für Vorder- und Hintergrund in #RRGGBB |
Die Fläche ist auf 262.144 Zellen begrenzt.
Rückgabe: Viewport, error
tty.attach(handle)
Fügt einem bestehenden Viewport einen weiteren lokalen Betrachter hinzu. Ein Handle gewährt das Betrachten, nie die Präsentations-Eigentümerschaft, und ist auf einem anderen Knoten nicht gültig.
local view, err = tty.attach(handle)
Rückgabe: Viewport, error
viewport:grant()
Gibt die einmalige Produzenten-Capability zurück. Sie wird als Spawn-Option terminal übergeben:
local grant = assert(view:grant())
local child = assert(process.with_options({terminal = grant})
:spawn_monitored("app:child", "app:workers"))
Die Zulassung verbraucht den Grant transaktional: Ein abgelehnter Start stellt einen unaufgelösten Grant wieder her, während ein Prozess, der den Port aufgelöst hat, ihn dauerhaft verbraucht. Ein Host, der keine Terminal-Anbindungen unterstützt, lehnt den Spawn ab, statt die Option zu verwerfen. Siehe Prozesse.
Rückgabe: string, error
viewport:handle()
Gibt das lokale Betrachter-Handle für tty.attach zurück.
Rückgabe: string
viewport:snapshot(after_revision?)
Liest die aktuellen Dimensionen, Zeilen, den Cursor und die Revision. Mit after_revision wird nil zurückgegeben, wenn die Revision unverändert ist.
local frame = view:snapshot(revision)
if frame then
revision = frame.revision
canvas:put_rows(2, 2, frame.rows, inner_width)
end
Rückgabe: snapshot oder nil
| Feld | Typ | Beschreibung |
|---|---|---|
revision |
number | Monotone Revision dieses Frames |
width |
number | Viewport-Spalten |
height |
number | Viewport-Zeilen |
rows |
string[] | Zuletzt vom Produzenten veröffentlichte Zeilen |
cursor |
table | {x, y, visible} in einsbasierten Koordinaten, fehlt, bis der Produzent einen expliziten Cursor-Zustand veröffentlicht |
images |
table[] | Metadaten der beibehaltenen Bildplatzierungen |
layers |
table[] | Geordnete Präsentations-Layer |
images_omitted |
boolean | Bildressourcen existieren, werden aber in diesem einfachen Snapshot nicht beibehalten |
Eine Seite löst Terminal-Standardzellen und ausgelassene Zeilen in explizite
Farben auf. Der Ersteller kann sie mit viewport:set_page(page) ändern; nil
stellt die ursprünglichen Zeilen des Produzenten wieder her. Seitenänderungen
erhöhen die Revision, ohne dass der Produzent neu zeichnen muss.
viewport:updates()
Gibt einen Channel zusammengefasster Revisions-Wasserzeichen zurück. receive() liefert die Revisionsnummer; case_receive() lässt sich mit channel.select kombinieren.
local updates = assert(view:updates())
Updates sind begrenzte Hinweise, kein Ereignisprotokoll. Ein langsamer Betrachter erhält nur das neueste Wasserzeichen und muss snapshot() für den Zustand aufrufen. Präsentation und Größenänderung blockieren nie wegen eines langsamen Betrachters.
Rückgabe: ViewportUpdateChannel, error
viewport:send(event)
Leitet einen validierten Ereignis-Datensatz an den Produzenten weiter. Der Produzent muss tty.start() aufgerufen haben; andernfalls schlägt der Aufruf fehl, statt das Ereignis zu verwerfen.
assert(view:send(event))
assert(view:send({type = "close"}))
Rückgabe: boolean, error
viewport:resize(width, height)
Aktualisiert die Viewport-Geometrie. Ändert sich die Größe, erhalten Betrachter eine neue Revision und der Produzent ein resize-Ereignis.
Rückgabe: boolean, error
viewport:close()
Löst nur diesen Betrachter ab. Das Schließen des letzten Betrachters beendet keinen lebenden Produzenten, und das Schließen des Produzenten-Ports zerstört den Zustand nicht, solange Betrachter verbleiben.
Rückgabe: boolean, error
viewport:mount(recipient_pid, rights)
Stellt eine an einen Prozess gebundene Referenz für einen lokalen oder entfernten Betrachter aus. Rechte sind unabhängig und standardmäßig deaktiviert:
local observation = assert(view:mount(agent_pid, {observe = true}))
local control = assert(view:mount(agent_pid, {input = true, resize = true}))
-- Im genauen Empfängerprozess auf diesem Knoten oder einem authentifizierten Mesh-Peer:
local observer = assert(tty.attach(observation))
local controller = assert(tty.attach(control))
Die Referenz ist an die vollständige PID des Empfängers gebunden und kann nur
einmal eingelöst werden. Montierte Betrachter können keine Producer-Grants oder
weiteren Mounts erzeugen. Remote-Mounts verwenden eine erneuerbare Lease; nach
einer Wiederverbindung ist ein neuer Mount erforderlich und Terminaleingaben
können nicht wiedergegeben werden. Mit viewport:revoke(reference) widerrufen
Sie eine ausgestellte Referenz; das Schließen des Besitzer-Viewports oder das
Ende seines Prozesses widerruft seine Mounts.
viewport:capture()
Fixiert atomar eine Viewport-Revision und ihre beibehaltenen Bildressourcen:
local capture = assert(view:capture())
local snapshot = capture:snapshot()
local image = assert(capture:image(snapshot.images[1].image_id))
assert(capture:close())
Ein gewöhnlicher snapshot() hält keine Bildbytes. Ein Capture hält sie bis zum
Schließen; daraus erworbene Bild-Handles bleiben unabhängig im Besitz.
Rückgabe: Capture, error
Ereignistypen
Ereignisse sind Tabellen mit einem type-Feld, das bestimmt, welche anderen Felder vorhanden sind. Koordinaten sind einsbasiert. Dieselben Datensätze werden von viewport:send() akzeptiert.
Key-Ereignis
{
type = "key",
key = "a", -- printable character or key name
key_type = "runes", -- "runes" for printable, or special key name
action = "press", -- "press" or "release"
alt = false,
ctrl = false,
shift = false
}
Maus-Ereignis
Erfordert tty.mouse(true).
{
type = "mouse",
action = "press", -- "press", "release", "motion", "wheel"
button = "left", -- button name
x = 10,
y = 5,
alt = false,
ctrl = false,
shift = false
}
Resize-Ereignis
{type = "resize", width = 120, height = 40}
Start-Ereignis
Wird einmal nach tty.start() mit den initialen Dimensionen ausgegeben.
{type = "start", width = 120, height = 40}
Focus-Ereignis
Meldet die Tastatur-Eigentümerschaft.
{type = "focus", focused = true}
Visibility-Ereignis
Meldet, ob ein Neuzeichnen sinnvoll ist. Es schreibt weder den Anwendungslebenszyklus noch Hintergrundberechnungen vor.
{type = "visibility", visible = true}
Paste-Ereignis
{type = "paste", text = "pasted content"}
Close-Ereignis
Fordert den Produzenten zum Herunterfahren auf. Eine Shell sendet es über viewport:send, um ein geordnetes Beenden des Kindprozesses anzufordern.
{type = "close"}
Tastenbindungen
Erstelle wiederverwendbare Tastenbindungen, die mit Tastenereignissen abgeglichen werden:
local quit = tty.bind({
keys = {"q", "ctrl+c"},
help = {key = "q/ctrl+c", desc = "quit"}
})
-- In event loop
if quit:matches(ev) then
break
end
tty.bind(config)
| Feld | Typ | Beschreibung |
|---|---|---|
keys |
string[] | Erforderlich. Zu vergleichende Tastenmuster, zum Beispiel "a", "ctrl+c", "enter" |
help |
table | Optional. {key = "...", desc = "..."} für Hilfetext |
Rückgabe: KeyBinding
Das Typschema verlangt keys. Zur Laufzeit erzeugt eine fehlende oder leere keys-Tabelle eine Bindung, die nie zutrifft.
KeyBinding-Methoden
| Methode | Rückgabe | Beschreibung |
|---|---|---|
matches(event) |
boolean | Prüft, ob ein Tastenereignis zu dieser Bindung passt |
set_enabled(bool) |
self | Aktiviert oder deaktiviert die Bindung |
is_enabled() |
boolean | Prüft, ob die Bindung aktiviert ist |
help() |
table | Gibt {key, desc}-Hilfeinformationen zurück |
Stile
Erstelle formatierte Textausgabe mit lipgloss-basiertem Styling. Alle Stilmethoden geben einen neuen Stil zurück (unveränderlich).
local tty = require("tty")
local io = require("io")
local title = tty.style()
:bold()
:foreground("#FF0000")
:padding(0, 1)
local box = tty.style()
:border(tty.borders.ROUNDED)
:border_foreground("#00FF00")
:width(40)
:padding(1, 2)
local _, print_err = io.print(box:render(title:render("Hello"), "World"))
if print_err then return nil, print_err end
tty.style()
Erstellt einen neuen leeren Stil.
Rückgabe: Style
Style-Methoden
Alle Methoden geben einen neuen Style zurück und können verkettet werden.
Textdekoration
| Methode | Parameter | Beschreibung |
|---|---|---|
foreground(color) |
string | Textfarbe (Hex "#FF0000", ANSI "9" oder Name) |
background(color) |
string | Hintergrundfarbe |
bold(enable?) |
boolean | Fetter Text (Standard: true) |
italic(enable?) |
boolean | Kursiver Text |
underline(enable?) |
boolean | Unterstrichener Text |
strikethrough(enable?) |
boolean | Durchgestrichener Text |
faint(enable?) |
boolean | Gedimmter Text |
blink(enable?) |
boolean | Blinkender Text |
reverse(enable?) |
boolean | Vorder- und Hintergrund tauschen |
Layout
| Methode | Parameter | Beschreibung |
|---|---|---|
width(n) |
number | Feste Breite |
height(n) |
number | Feste Höhe |
max_width(n) |
number | Maximale Breite |
max_height(n) |
number | Maximale Höhe |
padding(...) |
numbers | Padding (CSS-Stil: oben, rechts, unten, links) |
margin(...) |
numbers | Margin (CSS-Stil) |
align(pos) |
number | Horizontale Ausrichtung |
align_vertical(pos) |
number | Vertikale Ausrichtung |
inline(enable?) |
boolean | Inline-Rendering-Modus |
Rahmen
| Methode | Parameter | Beschreibung |
|---|---|---|
border(name, ...) |
string, booleans | Rahmenstil, optionale Pro-Seiten-Toggles |
border_foreground(...) |
strings | Rahmenfarbe(n) |
border_background(...) |
strings | Rahmen-Hintergrundfarbe(n) |
Sonstiges
| Methode | Beschreibung |
|---|---|
render(...) |
Rendert Strings mit angewendetem Stil |
copy() |
Erstellt eine Kopie dieses Stils |
Rahmenkonstanten
tty.borders.NORMAL
tty.borders.ROUNDED
tty.borders.THICK
tty.borders.DOUBLE
tty.borders.HIDDEN
Ausrichtungskonstanten
tty.align.LEFT -- 0
tty.align.CENTER -- 0.5
tty.align.RIGHT -- 1
Text-Hilfsfunktionen
Layout- und Messfunktionen für formatierten Text. Verfügbar unter tty.text.
Messung
local w = tty.text.width("hello") -- printable width (ANSI-aware)
local h = tty.text.height("a\nb\nc") -- line count
local w, h = tty.text.size("hello\nworld") -- both
Clipping
-- Auf eine druckbare Breite kürzen, mit optionalem Anhang
local head = tty.text.truncate(line, 40)
local head = tty.text.truncate(line, 40, "…")
-- Den druckbaren Zellbereich [left, right) nehmen
local middle = tty.text.cut(line, 10, 30)
Beide erhalten den ANSI-Zustand und Graphem-Grenzen, sodass gestalteter Text geclippt und zusammengefügt werden kann, ohne Escape-Sequenzen zu zerstören. truncate gibt bei einer Breite von null oder weniger einen leeren String zurück; cut gibt einen leeren String zurück, wenn right nicht größer als left ist.
Verbinden
-- Join side by side, aligned at top
local row = tty.text.join_horizontal(tty.text.position.TOP, left, right)
-- Stack vertically, centered
local col = tty.text.join_vertical(tty.text.position.CENTER, top, bottom)
Maximale Dimensionen
local w = tty.text.max_width({"short", "a longer string"}) -- widest
local h = tty.text.max_height({"one\ntwo", "single"}) -- tallest
Platzierung
Platziert einen String in einer Box mit gegebenen Dimensionen:
-- Center in a 80x24 box
local out = tty.text.place(80, 24, tty.text.position.CENTER, tty.text.position.CENTER, content)
-- Horizontal only
local out = tty.text.place_horizontal(80, tty.text.position.RIGHT, content)
-- Vertical only
local out = tty.text.place_vertical(24, tty.text.position.BOTTOM, content)
Positionskonstanten
tty.text.position.TOP -- 0
tty.text.position.LEFT -- 0
tty.text.position.CENTER -- 0.5
tty.text.position.BOTTOM -- 1
tty.text.position.RIGHT -- 1
Berechtigungen
Ein physisches Terminal kommt aus dem Prozess-Frame. Das Anhängen eines
Producers mit process.with_options({terminal = grant}) erfordert beim Spawn
process.context. Delegierte Viewports prüfen außerdem tty.mount,
tty.observe, tty.input und tty.resize gegen das Owner-Viewport-Handle.
Siehe auch
- Terminal-UI — eine Shell bauen, die einen Kindprozess in einem Viewport hostet
- Terminal-I/O — stdin/stdout/stderr-Operationen
- Terminal-Host — Terminal-Host-Konfiguration
- Kommandoausführung — PTY-Prozesse und Terminal-Sessions
- Prozesse — Spawn-Optionen, Monitoring, Lebenszyklus-Ereignisse