const std = @import("std");const activity_runtime = @import("../core/output/activity_runtime.zig");const builtin = @import("builtin");const debug_trace = @import("../core/shared/debug_trace.zig");const io_mod = @import("../core/shared/io.zig");const types = @import("../core/shared/types.zig");const transcript_runtime = @import("transcript/runtime.zig");const frame_layout = @import("render_engine/frame_layout.zig");const cursor_probe = @import("terminal/cursor_probe.zig");const resize_runtime = @import("resize_runtime.zig");const ui_terminal = @import("terminal/terminal.zig");const wasm_terminal = if (builtin.os.tag == .wasi) @import("terminal/wasm_terminal.zig") else struct {}; const Allocator = std.mem.Allocator;const Layout = types.Layout;const Metrics = types.Metrics;const TranscriptRuntime = transcript_runtime.TranscriptRuntime; const TmuxHistoryClearRunner = *const fn (Allocator, []const u8) anyerror!void;var tmux_history_clear_test_runner: if (builtin.is_test) ?TmuxHistoryClearRunner else void = if (builtin.is_test) null else {}; const supports_test_pty = switch (builtin.os.tag) { .linux, .macos, .freebsd, .netbsd, .openbsd, => true, else => false,}; extern "c" fn posix_openpt(flags: c_int) c_int;extern "c" fn grantpt(fd: c_int) c_int;extern "c" fn unlockpt(fd: c_int) c_int;extern "c" fn ptsname(fd: c_int) ?[*:0]u8; pub const supports_resize_signal = resize_runtime.supports_resize_signal;pub const ResizeHandler = if (builtin.os.tag == .wasi) *const fn () callconv(.c) voidelse std.posix.Sigaction.handler_fn;pub const ResizeApprovalInterlock = resize_runtime.ResizeApprovalInterlock;pub const RedrawMode = resize_runtime.RedrawMode; pub const PollResult = struct { readable: bool = false, hung_up: bool = false, has_error: bool = false, pub fn closed(self: PollResult) bool { return self.hung_up or self.has_error; }}; pub const CursorPosition = cursor_probe.Position; pub const AlternateScreenOwner = enum { none, file_approval, full_transcript, catalog_menu, subagent_manager, terminal_session,}; pub const TerminalState = struct { stdin_fd: std.posix.fd_t = std.posix.STDIN_FILENO, original_termios: std.posix.termios = undefined, raw_enabled: bool = false, alternate_screen_owner: AlternateScreenOwner = .none, alternate_frame_layout: frame_layout.CommittedLayoutSnapshot = .{}, alternate_mouse_tracking_active: bool = false, signal_handler_installed: bool = false, old_winch_action: ?std.posix.Sigaction = null, pub fn fileApprovalScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .file_approval; } pub fn fullTranscriptScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .full_transcript; } pub fn catalogMenuScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .catalog_menu; } pub fn subagentManagerScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .subagent_manager; } pub fn terminalSessionScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .terminal_session; } pub fn ensureInteractive(self: TerminalState) !void { if (comptime builtin.os.tag == .wasi) return; if (std.c.isatty(self.stdin_fd) == 0 or std.c.isatty(std.posix.STDOUT_FILENO) == 0) { return error.NotATerminal; } } pub fn captureOriginalTermios(self: *TerminalState) !void { if (comptime builtin.os.tag == .wasi) return; self.original_termios = try std.posix.tcgetattr(self.stdin_fd); } pub fn enableRawMode(self: *TerminalState) !void { if (comptime builtin.os.tag == .wasi) { self.raw_enabled = true; return; } var raw = self.original_termios; raw.iflag.BRKINT = false; raw.iflag.ICRNL = false; raw.iflag.INPCK = false; raw.iflag.ISTRIP = false; raw.iflag.IXON = false; raw.iflag.IXOFF = false; raw.cflag.CSIZE = .CS8; raw.lflag.ECHO = false; raw.lflag.ICANON = false; raw.lflag.IEXTEN = false; raw.lflag.ISIG = false; const vmin_idx = vminIndex(); const vtime_idx = vtimeIndex(); if (vmin_idx < raw.cc.len and vtime_idx < raw.cc.len) { raw.cc[vmin_idx] = 1; raw.cc[vtime_idx] = 0; } try std.posix.tcsetattr(self.stdin_fd, .NOW, raw); self.raw_enabled = true; } pub fn disableRawMode(self: *TerminalState) void { if (!self.raw_enabled) return; if (comptime builtin.os.tag != .wasi) { std.posix.tcsetattr(self.stdin_fd, .FLUSH, self.original_termios) catch {}; } self.raw_enabled = false; } pub fn installResizeSignal(self: *TerminalState, handler: ResizeHandler) void { if (!supports_resize_signal) return; const act: std.posix.Sigaction = .{ .handler = .{ .handler = handler }, .mask = std.posix.sigemptyset(), .flags = std.posix.SA.RESTART, }; var old: std.posix.Sigaction = undefined; std.posix.sigaction(std.posix.SIG.WINCH, &act, &old); self.old_winch_action = old; self.signal_handler_installed = true; } pub fn uninstallResizeSignal(self: *TerminalState) void { if (!supports_resize_signal or !self.signal_handler_installed) return; if (self.old_winch_action) |old| { std.posix.sigaction(std.posix.SIG.WINCH, &old, null); } self.signal_handler_installed = false; } pub fn queryLayout(self: TerminalState, footer_rows: u16) !Layout { return if (comptime builtin.os.tag == .wasi) wasm_terminal.queryLayout(footer_rows) else ui_terminal.queryLayout(self.stdin_fd, footer_rows); } pub fn queryCursorPosition(self: TerminalState) !CursorPosition { if (comptime builtin.os.tag == .wasi) { // JavaScript hosts provide a fresh terminal surface rather than an // existing shell viewport, so there are no launch rows to preserve. return .{ .row = 1, .col = 1 }; } var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), "\x1b[6n"); var buf: [64]u8 = undefined; var len: usize = 0; const deadline_ms = io_mod.milliTimestamp() + 100; while (len < buf.len) { const now_ms = io_mod.milliTimestamp(); if (now_ms >= deadline_ms) break; const remaining_ms: i32 = @intCast(deadline_ms - now_ms); const poll = try self.pollInput(remaining_ms); if (poll.closed() or !poll.readable) break; const n = try self.read(buf[len .. len + 1]); if (n == 0) break; len += n; if (cursor_probe.findPositionResponse(buf[0..len]) != null) break; } return cursor_probe.parsePositionResponse(buf[0..len]); } pub fn clearTmuxScreenAndHistory(_: *TerminalState, alloc: Allocator) void { if (comptime builtin.is_test) return; if (io_mod.getenv("TMUX") == null) return; const pane = io_mod.getenv("TMUX_PANE") orelse return; var stdout_file = std.Io.File.stdout(); stdout_file.writeStreamingAll(io_mod.getIo(), "\x1b[0m\x1b[3J\x1b[2J\x1b[H") catch |err| { debug_trace.logf("resize", "tmux_clear_screen_failed pane={s} err={s}", .{ pane, @errorName(err) }); return; }; waitForTmuxScreenClear(alloc, pane); clearTmuxHistoryForPane(alloc, pane); } pub fn requestResizeCursorPosition( _: TerminalState, protocol: cursor_probe.Protocol, ) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), cursor_probe.queryBytes(protocol)); } pub fn enableThemeNotifications(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_notification_enable_sequence); } pub fn requestThemeColorScheme(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_color_scheme_query); } pub fn requestThemeResponseFence(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_response_fence_query); } pub fn requestThemeBackground(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_background_query_with_fence); } pub fn read(self: TerminalState, out: []u8) !usize { if (comptime builtin.os.tag == .wasi) { return std.Io.File.stdin().readStreaming(io_mod.getIo(), &.{out}); } return std.posix.read(self.stdin_fd, out); } pub fn pollInput(self: TerminalState, timeout_ms: i32) !PollResult { if (comptime builtin.os.tag == .wasi) { return switch (wasm_terminal.pollInput(timeout_ms)) { 1 => .{ .readable = true }, -1 => .{ .hung_up = true }, else => .{}, }; } var fds = [_]std.posix.pollfd{.{ .fd = self.stdin_fd, .events = std.posix.POLL.IN, .revents = 0, }}; _ = try std.posix.poll(&fds, timeout_ms); const revents = fds[0].revents; return .{ .readable = (revents & std.posix.POLL.IN) != 0, .hung_up = (revents & std.posix.POLL.HUP) != 0, .has_error = (revents & std.posix.POLL.ERR) != 0, }; }}; fn clearTmuxHistoryForPane(alloc: Allocator, pane: []const u8) void { runTmuxHistoryClear(alloc, pane) catch |err| { debug_trace.logf( "resize", "tmux_clear_history_failed pane={s} err={s}", .{ pane, @errorName(err) }, ); return; }; debug_trace.logf("resize", "tmux_clear_history_complete pane={s}", .{pane});} fn waitForTmuxScreenClear(alloc: Allocator, pane: []const u8) void { for (0..5) |attempt| { const clear = tmuxScreenIsClear(alloc, pane) catch |err| { debug_trace.logf("resize", "tmux_clear_screen_check_failed pane={s} err={s}", .{ pane, @errorName(err) }); return; }; if (clear) return; if (attempt + 1 < 5) io_mod.sleep(5 * std.time.ns_per_ms); } debug_trace.logf("resize", "tmux_clear_screen_timeout pane={s}", .{pane});} fn tmuxScreenIsClear(alloc: Allocator, pane: []const u8) !bool { const result = try std.process.run(alloc, io_mod.getIo(), .{ .argv = &.{ "tmux", "capture-pane", "-p", "-t", pane }, }); defer alloc.free(result.stdout); defer alloc.free(result.stderr); if (result.term != .exited or result.term.exited != 0) return error.TmuxCapturePaneFailed; return std.mem.trim(u8, result.stdout, " \t\r\n").len == 0;} fn runTmuxHistoryClear(alloc: Allocator, pane: []const u8) !void { if (comptime builtin.is_test) { if (tmux_history_clear_test_runner) |runner| return runner(alloc, pane); } const result = try std.process.run(alloc, io_mod.getIo(), .{ .argv = &.{ "tmux", "clear-history", "-t", pane }, }); defer alloc.free(result.stdout); defer alloc.free(result.stderr); if (result.term != .exited or result.term.exited != 0) return error.TmuxClearHistoryFailed;} var tmux_history_clear_test_calls: if (builtin.is_test) usize else void = if (builtin.is_test) 0 else {}; fn failTmuxHistoryClearForTest(_: Allocator, _: []const u8) !void { tmux_history_clear_test_calls += 1; return error.TestTmuxHistoryClearFailure;} test "tmux history clear failure does not escape the reset boundary" { tmux_history_clear_test_calls = 0; tmux_history_clear_test_runner = failTmuxHistoryClearForTest; defer tmux_history_clear_test_runner = null; clearTmuxHistoryForPane(std.testing.allocator, "%1"); try std.testing.expectEqual(@as(usize, 1), tmux_history_clear_test_calls);} pub fn detectSyncUpdatesEnabled(_: Allocator) bool { const override = io_mod.getenv("FX_SYNC_UPDATES"); const fallback_override = if (override == null) io_mod.getenv("FLASH_SYNC_UPDATES") else null; const term = io_mod.getenv("TERM"); return syncUpdatesEnabledForValues(override orelse fallback_override, term);} pub fn detectHistoryResetUsesRis(_: Allocator) bool { return historyResetUsesRisForValues( io_mod.getenv("TERM_PROGRAM"), io_mod.getenv("TMUX"), );} pub fn applyToolLifecycle( alloc: Allocator, shell: anytype, event: types.ToolLifecycleEvent,) !?types.ToolActivityKind { return shell.applyToolLifecycle(alloc, event);} pub fn applyToolLifecyclePreservingNormalBufferAnchor( alloc: Allocator, shell: anytype, event: types.ToolLifecycleEvent,) !?types.ToolActivityKind { const Shell = @TypeOf(shell.*); if (comptime @hasDecl(Shell, "applyToolLifecyclePreservingNormalBufferAnchor")) { return shell.applyToolLifecyclePreservingNormalBufferAnchor(alloc, event); } return shell.applyToolLifecycle(alloc, event);} pub fn finishLifecycleBatch( alloc: Allocator, shell: anytype,) !void { return shell.finishLifecycleBatch(alloc);} pub fn activityProjection( shell: anytype,) activity_runtime.ActivityProjection { return shell.activityProjection();} pub fn focusedToolEntryId(shell: anytype) ?u32 { return shell.focusedToolEntryId();} pub fn focusedToolActivityKind( shell: anytype,) ?types.ToolActivityKind { return shell.focusedToolActivityKind();} pub fn activeToolActivityCount(shell: anytype) usize { return shell.activeToolActivityCount();} pub fn requestRedraw( shell: *TranscriptRuntime, metrics: *Metrics, mode: RedrawMode,) !void { return resize_runtime.requestRedraw(shell, metrics, mode);} pub fn collectResizeFacts( terminal: TerminalState, shell: *TranscriptRuntime, metrics: *Metrics, probe: *cursor_probe.Parser, resize_interlock: *ResizeApprovalInterlock, footer_rows: u16, debounce_ms: i64, cursor_probe_allowed: bool,) !void { return resize_runtime.collectResizeFacts( terminal, shell, metrics, probe, resize_interlock, footer_rows, debounce_ms, cursor_probe_allowed, );} pub fn admitResizeSignal( shell: *TranscriptRuntime, resize_interlock: *ResizeApprovalInterlock, now_ms: i64, debounce_ms: i64, source: []const u8,) bool { return resize_runtime.admitResizeSignal( shell, resize_interlock, now_ms, debounce_ms, source, );} pub fn resizeLifecycleIdle(shell: anytype) bool { return resize_runtime.lifecycleIdle(shell);} pub fn resizeBlocksFrameCommit(shell: anytype) bool { return resize_runtime.blocksFrameCommit(shell);} pub const ResizeFrameCommit = resize_runtime.FrameCommit; pub fn pendingResizeFrameCommit(shell: anytype, resize_reason: bool) ResizeFrameCommit { return resize_runtime.pendingFrameCommit(shell, resize_reason);} pub fn acknowledgeResizeFrameCommit(shell: anytype, commit: ResizeFrameCommit) void { resize_runtime.acknowledgeFrameCommit(shell, commit);} pub fn completeResizeCursorProbe( shell: *TranscriptRuntime, position: CursorPosition,) void { resize_runtime.completeResizeCursorProbe(shell, position);} pub fn suspendResizeCursorProbeForPaste(probe: *cursor_probe.Parser) void { return resize_runtime.suspendResizeCursorProbeForPaste(probe);} pub fn resumeResizeCursorProbeAfterPaste(probe: *cursor_probe.Parser, now_ms: i64) void { return resize_runtime.resumeResizeCursorProbeAfterPaste(probe, now_ms);} pub fn applyResizeWithLayout( shell: *TranscriptRuntime, metrics: *Metrics, new_layout: Layout, settled: bool,) !void { return resize_runtime.applyResizeWithLayout(shell, metrics, new_layout, settled);} fn syncUpdatesEnabledForValues(override: ?[]const u8, term: ?[]const u8) bool { if (override) |value| {Lines 1–500 of 651. Load more
const std = @import("std"); const activity_runtime = @import("../core/output/activity_runtime.zig"); const builtin = @import("builtin"); const debug_trace = @import("../core/shared/debug_trace.zig"); const io_mod = @import("../core/shared/io.zig"); const types = @import("../core/shared/types.zig"); const transcript_runtime = @import("transcript/runtime.zig"); const frame_layout = @import("render_engine/frame_layout.zig"); const cursor_probe = @import("terminal/cursor_probe.zig"); const resize_runtime = @import("resize_runtime.zig"); const ui_terminal = @import("terminal/terminal.zig"); const wasm_terminal = if (builtin.os.tag == .wasi) @import("terminal/wasm_terminal.zig") else struct {}; const Allocator = std.mem.Allocator; const Layout = types.Layout; const Metrics = types.Metrics; const TranscriptRuntime = transcript_runtime.TranscriptRuntime; const TmuxHistoryClearRunner = *const fn (Allocator, []const u8) anyerror!void; var tmux_history_clear_test_runner: if (builtin.is_test) ?TmuxHistoryClearRunner else void = if (builtin.is_test) null else {}; const supports_test_pty = switch (builtin.os.tag) { .linux, .macos, .freebsd, .netbsd, .openbsd, => true, else => false, }; extern "c" fn posix_openpt(flags: c_int) c_int; extern "c" fn grantpt(fd: c_int) c_int; extern "c" fn unlockpt(fd: c_int) c_int; extern "c" fn ptsname(fd: c_int) ?[*:0]u8; pub const supports_resize_signal = resize_runtime.supports_resize_signal; pub const ResizeHandler = if (builtin.os.tag == .wasi) *const fn () callconv(.c) void else std.posix.Sigaction.handler_fn; pub const ResizeApprovalInterlock = resize_runtime.ResizeApprovalInterlock; pub const RedrawMode = resize_runtime.RedrawMode; pub const PollResult = struct { readable: bool = false, hung_up: bool = false, has_error: bool = false, pub fn closed(self: PollResult) bool { return self.hung_up or self.has_error; } }; pub const CursorPosition = cursor_probe.Position; pub const AlternateScreenOwner = enum { none, file_approval, full_transcript, catalog_menu, subagent_manager, terminal_session, }; pub const TerminalState = struct { stdin_fd: std.posix.fd_t = std.posix.STDIN_FILENO, original_termios: std.posix.termios = undefined, raw_enabled: bool = false, alternate_screen_owner: AlternateScreenOwner = .none, alternate_frame_layout: frame_layout.CommittedLayoutSnapshot = .{}, alternate_mouse_tracking_active: bool = false, signal_handler_installed: bool = false, old_winch_action: ?std.posix.Sigaction = null, pub fn fileApprovalScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .file_approval; } pub fn fullTranscriptScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .full_transcript; } pub fn catalogMenuScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .catalog_menu; } pub fn subagentManagerScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .subagent_manager; } pub fn terminalSessionScreenActive(self: TerminalState) bool { return self.alternate_screen_owner == .terminal_session; } pub fn ensureInteractive(self: TerminalState) !void { if (comptime builtin.os.tag == .wasi) return; if (std.c.isatty(self.stdin_fd) == 0 or std.c.isatty(std.posix.STDOUT_FILENO) == 0) { return error.NotATerminal; } } pub fn captureOriginalTermios(self: *TerminalState) !void { if (comptime builtin.os.tag == .wasi) return; self.original_termios = try std.posix.tcgetattr(self.stdin_fd); } pub fn enableRawMode(self: *TerminalState) !void { if (comptime builtin.os.tag == .wasi) { self.raw_enabled = true; return; } var raw = self.original_termios; raw.iflag.BRKINT = false; raw.iflag.ICRNL = false; raw.iflag.INPCK = false; raw.iflag.ISTRIP = false; raw.iflag.IXON = false; raw.iflag.IXOFF = false; raw.cflag.CSIZE = .CS8; raw.lflag.ECHO = false; raw.lflag.ICANON = false; raw.lflag.IEXTEN = false; raw.lflag.ISIG = false; const vmin_idx = vminIndex(); const vtime_idx = vtimeIndex(); if (vmin_idx < raw.cc.len and vtime_idx < raw.cc.len) { raw.cc[vmin_idx] = 1; raw.cc[vtime_idx] = 0; } try std.posix.tcsetattr(self.stdin_fd, .NOW, raw); self.raw_enabled = true; } pub fn disableRawMode(self: *TerminalState) void { if (!self.raw_enabled) return; if (comptime builtin.os.tag != .wasi) { std.posix.tcsetattr(self.stdin_fd, .FLUSH, self.original_termios) catch {}; } self.raw_enabled = false; } pub fn installResizeSignal(self: *TerminalState, handler: ResizeHandler) void { if (!supports_resize_signal) return; const act: std.posix.Sigaction = .{ .handler = .{ .handler = handler }, .mask = std.posix.sigemptyset(), .flags = std.posix.SA.RESTART, }; var old: std.posix.Sigaction = undefined; std.posix.sigaction(std.posix.SIG.WINCH, &act, &old); self.old_winch_action = old; self.signal_handler_installed = true; } pub fn uninstallResizeSignal(self: *TerminalState) void { if (!supports_resize_signal or !self.signal_handler_installed) return; if (self.old_winch_action) |old| { std.posix.sigaction(std.posix.SIG.WINCH, &old, null); } self.signal_handler_installed = false; } pub fn queryLayout(self: TerminalState, footer_rows: u16) !Layout { return if (comptime builtin.os.tag == .wasi) wasm_terminal.queryLayout(footer_rows) else ui_terminal.queryLayout(self.stdin_fd, footer_rows); } pub fn queryCursorPosition(self: TerminalState) !CursorPosition { if (comptime builtin.os.tag == .wasi) { // JavaScript hosts provide a fresh terminal surface rather than an // existing shell viewport, so there are no launch rows to preserve. return .{ .row = 1, .col = 1 }; } var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), "\x1b[6n"); var buf: [64]u8 = undefined; var len: usize = 0; const deadline_ms = io_mod.milliTimestamp() + 100; while (len < buf.len) { const now_ms = io_mod.milliTimestamp(); if (now_ms >= deadline_ms) break; const remaining_ms: i32 = @intCast(deadline_ms - now_ms); const poll = try self.pollInput(remaining_ms); if (poll.closed() or !poll.readable) break; const n = try self.read(buf[len .. len + 1]); if (n == 0) break; len += n; if (cursor_probe.findPositionResponse(buf[0..len]) != null) break; } return cursor_probe.parsePositionResponse(buf[0..len]); } pub fn clearTmuxScreenAndHistory(_: *TerminalState, alloc: Allocator) void { if (comptime builtin.is_test) return; if (io_mod.getenv("TMUX") == null) return; const pane = io_mod.getenv("TMUX_PANE") orelse return; var stdout_file = std.Io.File.stdout(); stdout_file.writeStreamingAll(io_mod.getIo(), "\x1b[0m\x1b[3J\x1b[2J\x1b[H") catch |err| { debug_trace.logf("resize", "tmux_clear_screen_failed pane={s} err={s}", .{ pane, @errorName(err) }); return; }; waitForTmuxScreenClear(alloc, pane); clearTmuxHistoryForPane(alloc, pane); } pub fn requestResizeCursorPosition( _: TerminalState, protocol: cursor_probe.Protocol, ) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), cursor_probe.queryBytes(protocol)); } pub fn enableThemeNotifications(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_notification_enable_sequence); } pub fn requestThemeColorScheme(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_color_scheme_query); } pub fn requestThemeResponseFence(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_response_fence_query); } pub fn requestThemeBackground(_: TerminalState) !void { var stdout_file = std.Io.File.stdout(); try stdout_file.writeStreamingAll(io_mod.getIo(), ui_terminal.theme_background_query_with_fence); } pub fn read(self: TerminalState, out: []u8) !usize { if (comptime builtin.os.tag == .wasi) { return std.Io.File.stdin().readStreaming(io_mod.getIo(), &.{out}); } return std.posix.read(self.stdin_fd, out); } pub fn pollInput(self: TerminalState, timeout_ms: i32) !PollResult { if (comptime builtin.os.tag == .wasi) { return switch (wasm_terminal.pollInput(timeout_ms)) { 1 => .{ .readable = true }, -1 => .{ .hung_up = true }, else => .{}, }; } var fds = [_]std.posix.pollfd{.{ .fd = self.stdin_fd, .events = std.posix.POLL.IN, .revents = 0, }}; _ = try std.posix.poll(&fds, timeout_ms); const revents = fds[0].revents; return .{ .readable = (revents & std.posix.POLL.IN) != 0, .hung_up = (revents & std.posix.POLL.HUP) != 0, .has_error = (revents & std.posix.POLL.ERR) != 0, }; } }; fn clearTmuxHistoryForPane(alloc: Allocator, pane: []const u8) void { runTmuxHistoryClear(alloc, pane) catch |err| { debug_trace.logf( "resize", "tmux_clear_history_failed pane={s} err={s}", .{ pane, @errorName(err) }, ); return; }; debug_trace.logf("resize", "tmux_clear_history_complete pane={s}", .{pane}); } fn waitForTmuxScreenClear(alloc: Allocator, pane: []const u8) void { for (0..5) |attempt| { const clear = tmuxScreenIsClear(alloc, pane) catch |err| { debug_trace.logf("resize", "tmux_clear_screen_check_failed pane={s} err={s}", .{ pane, @errorName(err) }); return; }; if (clear) return; if (attempt + 1 < 5) io_mod.sleep(5 * std.time.ns_per_ms); } debug_trace.logf("resize", "tmux_clear_screen_timeout pane={s}", .{pane}); } fn tmuxScreenIsClear(alloc: Allocator, pane: []const u8) !bool { const result = try std.process.run(alloc, io_mod.getIo(), .{ .argv = &.{ "tmux", "capture-pane", "-p", "-t", pane }, }); defer alloc.free(result.stdout); defer alloc.free(result.stderr); if (result.term != .exited or result.term.exited != 0) return error.TmuxCapturePaneFailed; return std.mem.trim(u8, result.stdout, " \t\r\n").len == 0; } fn runTmuxHistoryClear(alloc: Allocator, pane: []const u8) !void { if (comptime builtin.is_test) { if (tmux_history_clear_test_runner) |runner| return runner(alloc, pane); } const result = try std.process.run(alloc, io_mod.getIo(), .{ .argv = &.{ "tmux", "clear-history", "-t", pane }, }); defer alloc.free(result.stdout); defer alloc.free(result.stderr); if (result.term != .exited or result.term.exited != 0) return error.TmuxClearHistoryFailed; } var tmux_history_clear_test_calls: if (builtin.is_test) usize else void = if (builtin.is_test) 0 else {}; fn failTmuxHistoryClearForTest(_: Allocator, _: []const u8) !void { tmux_history_clear_test_calls += 1; return error.TestTmuxHistoryClearFailure; } test "tmux history clear failure does not escape the reset boundary" { tmux_history_clear_test_calls = 0; tmux_history_clear_test_runner = failTmuxHistoryClearForTest; defer tmux_history_clear_test_runner = null; clearTmuxHistoryForPane(std.testing.allocator, "%1"); try std.testing.expectEqual(@as(usize, 1), tmux_history_clear_test_calls); } pub fn detectSyncUpdatesEnabled(_: Allocator) bool { const override = io_mod.getenv("FX_SYNC_UPDATES"); const fallback_override = if (override == null) io_mod.getenv("FLASH_SYNC_UPDATES") else null; const term = io_mod.getenv("TERM"); return syncUpdatesEnabledForValues(override orelse fallback_override, term); } pub fn detectHistoryResetUsesRis(_: Allocator) bool { return historyResetUsesRisForValues( io_mod.getenv("TERM_PROGRAM"), io_mod.getenv("TMUX"), ); } pub fn applyToolLifecycle( alloc: Allocator, shell: anytype, event: types.ToolLifecycleEvent, ) !?types.ToolActivityKind { return shell.applyToolLifecycle(alloc, event); } pub fn applyToolLifecyclePreservingNormalBufferAnchor( alloc: Allocator, shell: anytype, event: types.ToolLifecycleEvent, ) !?types.ToolActivityKind { const Shell = @TypeOf(shell.*); if (comptime @hasDecl(Shell, "applyToolLifecyclePreservingNormalBufferAnchor")) { return shell.applyToolLifecyclePreservingNormalBufferAnchor(alloc, event); } return shell.applyToolLifecycle(alloc, event); } pub fn finishLifecycleBatch( alloc: Allocator, shell: anytype, ) !void { return shell.finishLifecycleBatch(alloc); } pub fn activityProjection( shell: anytype, ) activity_runtime.ActivityProjection { return shell.activityProjection(); } pub fn focusedToolEntryId(shell: anytype) ?u32 { return shell.focusedToolEntryId(); } pub fn focusedToolActivityKind( shell: anytype, ) ?types.ToolActivityKind { return shell.focusedToolActivityKind(); } pub fn activeToolActivityCount(shell: anytype) usize { return shell.activeToolActivityCount(); } pub fn requestRedraw( shell: *TranscriptRuntime, metrics: *Metrics, mode: RedrawMode, ) !void { return resize_runtime.requestRedraw(shell, metrics, mode); } pub fn collectResizeFacts( terminal: TerminalState, shell: *TranscriptRuntime, metrics: *Metrics, probe: *cursor_probe.Parser, resize_interlock: *ResizeApprovalInterlock, footer_rows: u16, debounce_ms: i64, cursor_probe_allowed: bool, ) !void { return resize_runtime.collectResizeFacts( terminal, shell, metrics, probe, resize_interlock, footer_rows, debounce_ms, cursor_probe_allowed, ); } pub fn admitResizeSignal( shell: *TranscriptRuntime, resize_interlock: *ResizeApprovalInterlock, now_ms: i64, debounce_ms: i64, source: []const u8, ) bool { return resize_runtime.admitResizeSignal( shell, resize_interlock, now_ms, debounce_ms, source, ); } pub fn resizeLifecycleIdle(shell: anytype) bool { return resize_runtime.lifecycleIdle(shell); } pub fn resizeBlocksFrameCommit(shell: anytype) bool { return resize_runtime.blocksFrameCommit(shell); } pub const ResizeFrameCommit = resize_runtime.FrameCommit; pub fn pendingResizeFrameCommit(shell: anytype, resize_reason: bool) ResizeFrameCommit { return resize_runtime.pendingFrameCommit(shell, resize_reason); } pub fn acknowledgeResizeFrameCommit(shell: anytype, commit: ResizeFrameCommit) void { resize_runtime.acknowledgeFrameCommit(shell, commit); } pub fn completeResizeCursorProbe( shell: *TranscriptRuntime, position: CursorPosition, ) void { resize_runtime.completeResizeCursorProbe(shell, position); } pub fn suspendResizeCursorProbeForPaste(probe: *cursor_probe.Parser) void { return resize_runtime.suspendResizeCursorProbeForPaste(probe); } pub fn resumeResizeCursorProbeAfterPaste(probe: *cursor_probe.Parser, now_ms: i64) void { return resize_runtime.resumeResizeCursorProbeAfterPaste(probe, now_ms); } pub fn applyResizeWithLayout( shell: *TranscriptRuntime, metrics: *Metrics, new_layout: Layout, settled: bool, ) !void { return resize_runtime.applyResizeWithLayout(shell, metrics, new_layout, settled); } fn syncUpdatesEnabledForValues(override: ?[]const u8, term: ?[]const u8) bool { if (override) |value| {