diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 7a75bb92a..ea140c76c 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -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; diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index a0ebe8a07..323c82a2c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -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); } diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 2dd56a519..bebcf4ea1 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -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; diff --git a/src/terminal/main.zig b/src/terminal/main.zig index 17cc3a81d..e58c828b8 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -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; diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index f78bcc6f5..c582635b1 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -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, diff --git a/src/terminal/stream_readonly.zig b/src/terminal/stream_terminal.zig similarity index 97% rename from src/terminal/stream_readonly.zig rename to src/terminal/stream_terminal.zig index d072418d1..23a244fc9 100644 --- a/src/terminal/stream_readonly.zig +++ b/src/terminal/stream_terminal.zig @@ -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,