//! The byte source/sink a `Program` drives. Mirrors the seam bubbletea //! exposes through `WithInput`/`WithOutput`/`WithWindowSize`: the local //! terminal (`Tty`) is one implementation; an injected reader/writer pair //! (`StreamTerminal`, used to serve a model over SSH) is another. //! //! Vtable-based like `Model`: a concrete backend embeds a `Terminal` field //! and recovers itself with `@fieldParentPtr`. const std = @import("std"); const Io = std.Io; const Msg = @import("msg.zig").Msg; const Terminal = @This(); vtable: *const VTable, pub const Size = struct { width: u16, height: u16 }; pub const VTable = struct { /// Block until the next input event. Returns null when woken (see `wake`) /// or the input source reaches end-of-stream. nextMsg: *const fn (*Terminal) anyerror!?Msg, /// Sink for rendered frames. The returned writer is valid for the /// terminal's lifetime. writer: *const fn (*Terminal) *Io.Writer, /// Best-effort current window size. An error means "unknown", in which /// case the `Program` falls back to its configured initial size. windowSize: *const fn (*Terminal) anyerror!Size, /// Cancel a blocked `nextMsg`; subsequent calls also return null. wake: *const fn (*Terminal) void, /// Release backend resources (restore termios, free buffers, …). deinit: *const fn (*Terminal) void, }; pub fn nextMsg(self: *Terminal) anyerror!?Msg { return self.vtable.nextMsg(self); } pub fn writer(self: *Terminal) *Io.Writer { return self.vtable.writer(self); } pub fn windowSize(self: *Terminal) anyerror!Size { return self.vtable.windowSize(self); } pub fn wake(self: *Terminal) void { self.vtable.wake(self); } pub fn deinit(self: *Terminal) void { self.vtable.deinit(self); }