terminal: rename ReadonlyStream to TerminalStream

Rename stream_readonly.zig to stream_terminal.zig and its exported
types from ReadonlyStream/ReadonlyHandler to TerminalStream. The
"readonly" name is now wrong since the handler now supports
settable effects callbacks. The new name better reflects that this
is a stream handler for updating terminal state.
This commit is contained in:
Mitchell Hashimoto
2026-03-22 20:12:09 -07:00
parent 07272ae88f
commit 67d8d86efd
6 changed files with 35 additions and 31 deletions

View File

@@ -58,10 +58,9 @@ pub const SizeReportStyle = terminal.SizeReportStyle;
pub const StringMap = terminal.StringMap;
pub const Style = terminal.Style;
pub const Terminal = terminal.Terminal;
pub const TerminalStream = terminal.TerminalStream;
pub const Stream = terminal.Stream;
pub const StreamAction = terminal.StreamAction;
pub const ReadonlyStream = terminal.ReadonlyStream;
pub const ReadonlyHandler = terminal.ReadonlyHandler;
pub const Cursor = Screen.Cursor;
pub const CursorStyle = Screen.CursorStyle;
pub const CursorStyleReq = terminal.CursorStyle;

View File

@@ -24,8 +24,7 @@ const sgr = @import("sgr.zig");
const Tabstops = @import("Tabstops.zig");
const color = @import("color.zig");
const mouse = @import("mouse.zig");
const ReadonlyHandler = @import("stream_readonly.zig").Handler;
const ReadonlyStream = @import("stream_readonly.zig").Stream;
const Stream = @import("stream_terminal.zig").Stream;
const size = @import("size.zig");
const pagepkg = @import("page.zig");
@@ -237,19 +236,24 @@ pub fn deinit(self: *Terminal, alloc: Allocator) void {
/// Return a terminal.Stream that can process VT streams and update this
/// terminal state. The streams will only process read-only data that
/// modifies terminal state. Sequences that query or otherwise require
/// output will be ignored.
/// modifies terminal state.
///
/// Sequences that query or otherwise require output will be ignored.
/// If you want to handle side effects, use `vtHandler` and set the
/// effects field yourself, then initialize a stream.
///
/// This must be deinitialized by the caller.
///
/// Important: this creates a new stream each time with fresh parser state.
/// If you need to persist parser state across multiple writes (e.g.
/// for handling escape sequences split across write boundaries), you
/// must store and reuse the returned stream.
pub fn vtStream(self: *Terminal) ReadonlyStream {
pub fn vtStream(self: *Terminal) Stream {
return .initAlloc(self.gpa(), self.vtHandler());
}
/// This is the handler-side only for vtStream.
pub fn vtHandler(self: *Terminal) ReadonlyHandler {
pub fn vtHandler(self: *Terminal) Stream.Handler {
return .init(self);
}

View File

@@ -3,7 +3,7 @@ const testing = std.testing;
const lib_alloc = @import("../../lib/allocator.zig");
const CAllocator = lib_alloc.Allocator;
const ZigTerminal = @import("../Terminal.zig");
const ReadonlyStream = @import("../stream_readonly.zig").Stream;
const Stream = @import("../stream_terminal.zig").Stream;
const ScreenSet = @import("../ScreenSet.zig");
const PageList = @import("../PageList.zig");
const kitty = @import("../kitty/key.zig");
@@ -23,7 +23,7 @@ const log = std.log.scoped(.terminal_c);
/// across multiple vt_write calls.
const TerminalWrapper = struct {
terminal: *ZigTerminal,
stream: ReadonlyStream,
stream: Stream,
};
/// C: GhosttyTerminal
@@ -68,17 +68,24 @@ fn new_(
return error.OutOfMemory;
errdefer alloc.destroy(t);
const wrapper = alloc.create(TerminalWrapper) catch
return error.OutOfMemory;
errdefer alloc.destroy(wrapper);
// Setup our terminal
t.* = try .init(alloc, .{
.cols = opts.cols,
.rows = opts.rows,
.max_scrollback = opts.max_scrollback,
});
errdefer t.deinit(alloc);
// Setup our stream
const handler: Stream.Handler = t.vtHandler();
const wrapper = alloc.create(TerminalWrapper) catch
return error.OutOfMemory;
wrapper.* = .{
.terminal = t,
.stream = t.vtStream(),
.stream = .initAlloc(alloc, handler),
};
return wrapper;

View File

@@ -3,7 +3,7 @@ const stream = @import("stream.zig");
const ansi = @import("ansi.zig");
const csi = @import("csi.zig");
const render = @import("render.zig");
const stream_readonly = @import("stream_readonly.zig");
const stream_terminal = @import("stream_terminal.zig");
const style = @import("style.zig");
pub const apc = @import("apc.zig");
pub const dcs = @import("dcs.zig");
@@ -42,8 +42,6 @@ pub const PageList = @import("PageList.zig");
pub const Parser = @import("Parser.zig");
pub const Pin = PageList.Pin;
pub const Point = point.Point;
pub const ReadonlyHandler = stream_readonly.Handler;
pub const ReadonlyStream = stream_readonly.Stream;
pub const RenderState = render.RenderState;
pub const Screen = @import("Screen.zig");
pub const ScreenSet = @import("ScreenSet.zig");
@@ -53,6 +51,7 @@ pub const SizeReportStyle = csi.SizeReportStyle;
pub const StringMap = @import("StringMap.zig");
pub const Style = style.Style;
pub const Terminal = @import("Terminal.zig");
pub const TerminalStream = stream_terminal.Stream;
pub const Stream = stream.Stream;
pub const StreamAction = stream.Action;
pub const Cursor = Screen.Cursor;

View File

@@ -419,11 +419,12 @@ pub const Action = union(Key) {
/// e.g. you don't need to pay a conditional branching cost on every single
/// action because the Zig compiler codegens separate code paths for every
/// single action at comptime.
pub fn Stream(comptime Handler: type) type {
pub fn Stream(comptime H: type) type {
return struct {
const Self = @This();
pub const Action = streampkg.Action;
pub const Handler = H;
const T = switch (@typeInfo(Handler)) {
.pointer => |p| p.child,

View File

@@ -8,25 +8,19 @@ const osc_color = @import("osc/parsers/color.zig");
const kitty_color = @import("kitty/color.zig");
const Terminal = @import("Terminal.zig");
const log = std.log.scoped(.stream_readonly);
const log = std.log.scoped(.stream_terminal);
/// This is a Stream implementation that processes actions against
/// a Terminal and updates the Terminal state. It is called "readonly" because
/// it only processes actions that modify terminal state, while ignoring
/// any actions that require a response (like queries).
///
/// If you're implementing a terminal emulator that only needs to render
/// output and doesn't need to respond (since it maybe isn't running the
/// actual program), this is the stream type to use. For example, this is
/// ideal for replay tooling, CI logs, PaaS builder output, etc.
/// a Terminal and updates the Terminal state.
pub const Stream = stream.Stream(Handler);
/// See Stream, which is just the stream wrapper around this.
/// A stream handler that updates terminal state. By default, it is
/// readonly in the sense that it only updates terminal state and ignores
/// all other sequences that require a response or otherwise have side
/// effects (e.g. clipboards).
///
/// This isn't attached directly to Terminal because there is additional
/// state and options we plan to add in the future, such as APC/DCS which
/// don't make sense to me to add to the Terminal directly. Instead, you
/// can call `vtHandler` on Terminal to initialize this handler.
/// You can manually set various effects callbacks in the `effects` field
/// to implement certain effects such as bells, titles, clipboard, etc.
pub const Handler = struct {
/// The terminal state to modify.
terminal: *Terminal,