const std = @import("std");const activity_overlay = @import("activity_overlay.zig");const debug_trace = @import("../../core/shared/debug_trace.zig");const frame_layout = @import("frame_layout.zig");const io_mod = @import("../../core/shared/io.zig");const paint_plan = @import("paint_plan.zig");const vt_emulator = @import("../../core/terminal/engine.zig"); const Allocator = std.mem.Allocator; pub const FrameCell = struct { codepoint: u21, width: u8, /// If non-zero, the id is owned by this FrameSurface's combining /// suffix pool, not by a temporary source grid. combining_suffix_id: u32 = 0, /// If `style.hyperlink_id != 0`, the id is owned by this /// FrameSurface's hyperlink pool, not by a temporary source grid. style: vt_emulator.Style, owner: paint_plan.CellOwner,}; pub const SurfaceHyperlink = struct { uri: []u8, pub fn deinit(self: *SurfaceHyperlink, alloc: Allocator) void { alloc.free(self.uri); }}; pub const OwnerWritePolicy = enum { same_owner, activity_over_transcript, diagnostic_overwrite,}; pub const FrameSurfaceError = error{ OutOfMemory, InvalidGridSize, AnsiBandOverflow, OutOfBounds, OwnerConflict, PreservedShellMutation, WriteOutsideBand, UnownedFxCell, CursorOutOfBounds, WideCellInvariantViolation, MissingHyperlinkResource, MissingCombiningSuffixResource,}; pub const AnsiBandWriteResult = struct { rows_touched: u16, first_row: u16, last_row: u16,}; const AnsiBandWritePlan = struct { start_row: u16, max_rows: u16, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, autowrap: bool, fn resolve( surface: FrameSurface, start_row: u16, max_rows: u16, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, autowrap: bool, ) FrameSurfaceError!AnsiBandWritePlan { if (start_row == 0 or max_rows == 0) return error.WriteOutsideBand; const end_row = start_row +| max_rows -| 1; if (end_row > surface.rows) return error.WriteOutsideBand; var dest_row = start_row; while (dest_row <= end_row) : (dest_row += 1) { if (!surface.ownerPlannedForRow(dest_row, owner, policy)) return error.WriteOutsideBand; } return .{ .start_row = start_row, .max_rows = max_rows, .owner = owner, .policy = policy, .autowrap = autowrap, }; } fn destRow(self: AnsiBandWritePlan, source_row: u16) u16 { return self.start_row + source_row - 1; } fn overflowed(self: AnsiBandWritePlan, grid: vt_emulator.Grid, stats: vt_emulator.FeedStats) bool { return stats.scrolled or stats.max_row_touched > self.max_rows or grid.cursor_row > self.max_rows; } fn result(self: AnsiBandWritePlan, stats: vt_emulator.FeedStats) AnsiBandWriteResult { if (stats.max_row_touched == 0) { return .{ .rows_touched = 0, .first_row = 0, .last_row = 0 }; } return .{ .rows_touched = stats.max_row_touched, .first_row = self.start_row, .last_row = self.start_row + stats.max_row_touched - 1, }; }}; const AnsiBandFeed = struct { grid: vt_emulator.Grid, stats: vt_emulator.FeedStats,}; pub const FrameSurface = struct { alloc: Allocator, rows: u16, cols: u16, cells: []FrameCell, hyperlinks: std.ArrayList(SurfaceHyperlink) = .empty, combining_suffixes: std.ArrayList([]u8) = .empty, plan: paint_plan.PaintPlan, cursor_target: ?paint_plan.FrameCursorTarget, pub fn initFromShadow(alloc: Allocator, plan: paint_plan.PaintPlan, previous: vt_emulator.Grid) FrameSurfaceError!FrameSurface { if (plan.layout.rows == 0 or plan.layout.cols == 0) return error.InvalidGridSize; if (previous.rows != plan.layout.rows or previous.cols != plan.layout.cols) return error.InvalidGridSize; const cell_count = @as(usize, plan.layout.rows) * @as(usize, plan.layout.cols); const cells = try alloc.alloc(FrameCell, cell_count); var surface = FrameSurface{ .alloc = alloc, .rows = plan.layout.rows, .cols = plan.layout.cols, .cells = cells, .plan = plan, .cursor_target = plan.cursor_target, }; errdefer surface.deinit(); try surface.importHyperlinkPool(previous); try surface.importCombiningSuffixPool(previous); var row: u16 = 1; while (row <= surface.rows) : (row += 1) { const owner = surface.initialOwnerForRow(row); var col: u16 = 1; while (col <= surface.cols) : (col += 1) { const idx = surface.index(row, col) orelse return error.OutOfBounds; if (owner == .preserved_shell) { const source_cell = previous.cellAt(row, col) orelse return error.InvalidGridSize; surface.cells[idx] = try surface.frameCellFromAuthoritativeGrid(previous, source_cell, owner); } else { surface.cells[idx] = blankCell(owner); } } } return surface; } pub fn retainTranscriptBandFromGrid( self: *FrameSurface, source: vt_emulator.Grid, source_area: frame_layout.FrameRect, ) FrameSurfaceError!void { if (source.rows != self.rows or source.cols != self.cols) return error.InvalidGridSize; if (source_area.isEmpty()) return; if (!self.plan.transcript_band.containsRow(source_area.top) or !self.plan.transcript_band.containsRow(source_area.bottom)) { return error.WriteOutsideBand; } var row = source_area.top; while (row <= source_area.bottom) : (row += 1) { var col: u16 = 1; while (col <= self.cols) : (col += 1) { const idx = self.index(row, col) orelse return error.OutOfBounds; const source_cell = source.cellAt(row, col) orelse return error.InvalidGridSize; self.cells[idx] = try self.frameCellFromAuthoritativeGrid( source, source_cell, .transcript, ); } } } pub fn deinit(self: *FrameSurface) void { self.alloc.free(self.cells); for (self.hyperlinks.items) |*link| link.deinit(self.alloc); self.hyperlinks.deinit(self.alloc); for (self.combining_suffixes.items) |suffix| self.alloc.free(suffix); self.combining_suffixes.deinit(self.alloc); } pub fn cellAt(self: FrameSurface, row: u16, col: u16) ?FrameCell { const idx = self.index(row, col) orelse return null; return self.cells[idx]; } pub fn hyperlinkUrl(self: FrameSurface, id: u32) ?[]const u8 { if (id == 0) return null; if (id - 1 >= self.hyperlinks.items.len) return null; return self.hyperlinks.items[id - 1].uri; } pub fn combiningSuffix(self: FrameSurface, id: u32) ?[]const u8 { if (id == 0) return null; if (id - 1 >= self.combining_suffixes.items.len) return null; return self.combining_suffixes.items[id - 1]; } pub fn internHyperlink(self: *FrameSurface, uri: []const u8) FrameSurfaceError!u32 { for (self.hyperlinks.items, 0..) |link, idx| { if (std.mem.eql(u8, link.uri, uri)) return @intCast(idx + 1); } const dup = try self.alloc.dupe(u8, uri); errdefer self.alloc.free(dup); try self.hyperlinks.append(self.alloc, .{ .uri = dup }); return @intCast(self.hyperlinks.items.len); } fn internCombiningSuffix(self: *FrameSurface, bytes: []const u8) FrameSurfaceError!u32 { for (self.combining_suffixes.items, 0..) |suffix, idx| { if (std.mem.eql(u8, suffix, bytes)) return @intCast(idx + 1); } const dup = try self.alloc.dupe(u8, bytes); errdefer self.alloc.free(dup); try self.combining_suffixes.append(self.alloc, dup); return @intCast(self.combining_suffixes.items.len); } pub fn writeCell(self: *FrameSurface, row: u16, col: u16, cell: FrameCell, policy: OwnerWritePolicy) FrameSurfaceError!void { const idx = self.index(row, col) orelse return error.OutOfBounds; if (cell.width > 2) return error.WideCellInvariantViolation; if (cell.width == 2 and col == self.cols) return error.WideCellInvariantViolation; if (cell.width == 0 and cell.combining_suffix_id != 0) return error.WideCellInvariantViolation; try self.validateWrite(row, col, self.cells[idx].owner, cell.owner, policy); self.cells[idx] = cell; } /// Feeds ANSI bytes through a temporary VT grid, checks for overflow /// before copying cells, then remaps grid-local hyperlink IDs into /// this surface's hyperlink pool. pub fn writeAnsiBand( self: *FrameSurface, start_row: u16, max_rows: u16, bytes: []const u8, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, ) FrameSurfaceError!AnsiBandWriteResult { return self.writeAnsiBandWithAutowrap(start_row, max_rows, bytes, owner, policy, true); } /// Feed ANSI bytes with DECAWM disabled. Footer rows are painted as /// terminal lines, and fx keeps autowrap disabled session-wide. pub fn writeAnsiBandNoWrap( self: *FrameSurface, start_row: u16, max_rows: u16, bytes: []const u8, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, ) FrameSurfaceError!AnsiBandWriteResult { return self.writeAnsiBandWithAutowrap(start_row, max_rows, bytes, owner, policy, false); } fn writeAnsiBandWithAutowrap( self: *FrameSurface, start_row: u16, max_rows: u16, bytes: []const u8, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, autowrap: bool, ) FrameSurfaceError!AnsiBandWriteResult { const plan = try AnsiBandWritePlan.resolve(self.*, start_row, max_rows, owner, policy, autowrap); var feed = try self.feedAnsiBand(plan, bytes); defer feed.grid.deinit(); try self.copyAnsiBandCells(plan, feed.grid); return plan.result(feed.stats); } fn feedAnsiBand(self: *FrameSurface, plan: AnsiBandWritePlan, bytes: []const u8) FrameSurfaceError!AnsiBandFeed { var temp = vt_emulator.Grid.init(self.alloc, self.cols, plan.max_rows +| 1) catch |err| switch (err) { error.InvalidGridSize => return error.InvalidGridSize, error.OutOfMemory => return error.OutOfMemory, }; errdefer temp.deinit(); temp.autowrap = plan.autowrap; var stats: vt_emulator.FeedStats = .{}; temp.feedWithStats(bytes, &stats) catch return error.OutOfMemory; if (plan.overflowed(temp, stats)) return error.AnsiBandOverflow; return .{ .grid = temp, .stats = stats }; } fn copyAnsiBandCells(self: *FrameSurface, plan: AnsiBandWritePlan, source: vt_emulator.Grid) FrameSurfaceError!void { var source_row: u16 = 1; while (source_row <= plan.max_rows) : (source_row += 1) { const dest_row = plan.destRow(source_row); var col: u16 = 1; while (col <= self.cols) : (col += 1) { const source_cell = source.cellAt(source_row, col) orelse return error.InvalidGridSize; const frame_cell = try self.frameCellFromGrid(source, source_cell, plan.owner); try self.writeCell(dest_row, col, frame_cell, plan.policy); } } } pub fn copyToTargetGrid(self: FrameSurface, alloc: Allocator) !vt_emulator.Grid { var target = try vt_emulator.Grid.init(alloc, self.cols, self.rows); errdefer target.deinit(); try target.hyperlink_pool.ensureTotalCapacity(alloc, self.hyperlinks.items.len); for (self.hyperlinks.items) |link| { const dup = try alloc.dupe(u8, link.uri); target.hyperlink_pool.appendAssumeCapacity(dup); } try target.combining_suffix_pool.ensureTotalCapacity(alloc, self.combining_suffixes.items.len); for (self.combining_suffixes.items) |suffix| { const dup = try alloc.dupe(u8, suffix); target.combining_suffix_pool.appendAssumeCapacity(dup); } for (self.cells, 0..) |cell, idx| { target.cells[idx] = .{ .codepoint = cell.codepoint, .width = cell.width, .combining_suffix_id = cell.combining_suffix_id, .style = cell.style, }; } if (self.cursor_target) |cursor| { target.cursor_row = cursor.row; target.cursor_col = cursor.col; target.cursor_visible = cursor.visible; } return target; } /// Removes fx-owned color, emphasis, and hyperlink presentation while /// preserving glyphs, geometry, ownership, and shell-owned cells. pub fn neutralizeFxOwnedPresentation(self: *FrameSurface) void { for (self.cells) |*cell| { if (cell.owner == .preserved_shell) continue; cell.style = .{}; } } pub fn validate(self: FrameSurface) FrameSurfaceError!void { if (self.cells.len != @as(usize, self.rows) * @as(usize, self.cols)) return error.InvalidGridSize; if (self.cursor_target) |cursor| { if (cursor.row == 0 or cursor.row > self.rows or cursor.col == 0 or cursor.col > self.cols) { return error.CursorOutOfBounds; } } var row: u16 = 1; while (row <= self.rows) : (row += 1) { var col: u16 = 1; while (col <= self.cols) : (col += 1) { const cell = self.cellAt(row, col).?; if (!self.ownerLegalAt(row, cell.owner)) return error.UnownedFxCell; try self.validateWideCell(row, col, cell); } } } fn initialOwnerForRow(self: FrameSurface, row: u16) paint_plan.CellOwner { if (self.plan.preserved_band.containsRow(row)) return .preserved_shell; if (self.plan.blank_band.containsRow(row)) return .gap; if (self.plan.activity_band.containsRow(row)) return .activity; if (self.plan.footer_gap_band.containsRow(row)) return .gap; if (self.plan.footer_band.containsRow(row)) return .footer; if (self.plan.transcript_band.containsRow(row)) return .transcript; if (self.invalidationContains(row)) return .diagnostic_clear; return .preserved_shell; } fn frameCellFromGrid(self: *FrameSurface, grid: vt_emulator.Grid, cell: vt_emulator.Cell, owner: paint_plan.CellOwner) FrameSurfaceError!FrameCell { return .{ .codepoint = cell.codepoint, .width = cell.width, .combining_suffix_id = try self.remapCombiningSuffixFromGrid(grid, cell.combining_suffix_id), .style = try self.remapStyleFromGrid(grid, cell.style), .owner = owner, }; } fn importHyperlinkPool(self: *FrameSurface, grid: vt_emulator.Grid) FrameSurfaceError!void { std.debug.assert(self.hyperlinks.items.len == 0); try self.hyperlinks.ensureTotalCapacity(self.alloc, grid.hyperlink_pool.items.len); for (grid.hyperlink_pool.items) |uri| { const dup = try self.alloc.dupe(u8, uri); self.hyperlinks.appendAssumeCapacity(.{ .uri = dup }); } } fn importCombiningSuffixPool(self: *FrameSurface, grid: vt_emulator.Grid) FrameSurfaceError!void { std.debug.assert(self.combining_suffixes.items.len == 0); try self.combining_suffixes.ensureTotalCapacity(self.alloc, grid.combining_suffix_pool.items.len); for (grid.combining_suffix_pool.items) |bytes| { const dup = try self.alloc.dupe(u8, bytes); self.combining_suffixes.appendAssumeCapacity(dup); } } fn frameCellFromAuthoritativeGrid( self: *FrameSurface, grid: vt_emulator.Grid, cell: vt_emulator.Cell, owner: paint_plan.CellOwner, ) FrameSurfaceError!FrameCell { if (cell.style.hyperlink_id != 0) { const source_uri = grid.hyperlinkUrl(cell.style.hyperlink_id) orelse return error.MissingHyperlinkResource; const imported_uri = self.hyperlinkUrl(cell.style.hyperlink_id) orelse return error.MissingHyperlinkResource; if (!std.mem.eql(u8, source_uri, imported_uri)) { return error.MissingHyperlinkResource; } } if (cell.combining_suffix_id != 0) { const source_suffix = grid.combiningSuffix(cell.combining_suffix_id) orelse return error.MissingCombiningSuffixResource; const imported_suffix = self.combiningSuffix(cell.combining_suffix_id) orelse return error.MissingCombiningSuffixResource; if (!std.mem.eql(u8, source_suffix, imported_suffix)) { return error.MissingCombiningSuffixResource; } } return .{ .codepoint = cell.codepoint, .width = cell.width, .combining_suffix_id = cell.combining_suffix_id, .style = cell.style, .owner = owner, }; } fn remapCombiningSuffixFromGrid(self: *FrameSurface, grid: vt_emulator.Grid, id: u32) FrameSurfaceError!u32 { if (id == 0) return 0; const suffix = grid.combiningSuffix(id) orelse return error.MissingCombiningSuffixResource; return self.internCombiningSuffix(suffix); } fn remapStyleFromGrid(self: *FrameSurface, grid: vt_emulator.Grid, style: vt_emulator.Style) FrameSurfaceError!vt_emulator.Style { if (style.hyperlink_id == 0) return style; const uri = grid.hyperlinkUrl(style.hyperlink_id) orelse return error.MissingHyperlinkResource; var remapped = style; remapped.hyperlink_id = try self.internHyperlink(uri); return remapped; } fn validateWrite( self: FrameSurface, row: u16, col: u16, current_owner: paint_plan.CellOwner, next_owner: paint_plan.CellOwner, policy: OwnerWritePolicy, ) FrameSurfaceError!void { if (current_owner == .preserved_shell) { self.traceOwnerViolation(row, col, current_owner, next_owner, policy, "preserved_shell_mutation"); return error.PreservedShellMutation; } if (!self.ownerPlannedForRow(row, next_owner, policy)) { self.traceOwnerViolation(row, col, current_owner, next_owner, policy, "write_outside_band"); return error.WriteOutsideBand; } if (current_owner == next_owner) return; if (policy == .activity_over_transcript and next_owner == .activity and current_owner == .transcript and self.activityOverlayRow() == row) { return; } if (policy == .diagnostic_overwrite and next_owner == .diagnostic_clear) return; self.traceOwnerViolation(row, col, current_owner, next_owner, policy, "owner_conflict"); return error.OwnerConflict; } fn traceOwnerViolation( self: FrameSurface, row: u16, col: u16, current_owner: paint_plan.CellOwner, next_owner: paint_plan.CellOwner, policy: OwnerWritePolicy, reason: []const u8, ) void {Lines 1–500 of 1104. Load more
const std = @import("std"); const activity_overlay = @import("activity_overlay.zig"); const debug_trace = @import("../../core/shared/debug_trace.zig"); const frame_layout = @import("frame_layout.zig"); const io_mod = @import("../../core/shared/io.zig"); const paint_plan = @import("paint_plan.zig"); const vt_emulator = @import("../../core/terminal/engine.zig"); const Allocator = std.mem.Allocator; pub const FrameCell = struct { codepoint: u21, width: u8, /// If non-zero, the id is owned by this FrameSurface's combining /// suffix pool, not by a temporary source grid. combining_suffix_id: u32 = 0, /// If `style.hyperlink_id != 0`, the id is owned by this /// FrameSurface's hyperlink pool, not by a temporary source grid. style: vt_emulator.Style, owner: paint_plan.CellOwner, }; pub const SurfaceHyperlink = struct { uri: []u8, pub fn deinit(self: *SurfaceHyperlink, alloc: Allocator) void { alloc.free(self.uri); } }; pub const OwnerWritePolicy = enum { same_owner, activity_over_transcript, diagnostic_overwrite, }; pub const FrameSurfaceError = error{ OutOfMemory, InvalidGridSize, AnsiBandOverflow, OutOfBounds, OwnerConflict, PreservedShellMutation, WriteOutsideBand, UnownedFxCell, CursorOutOfBounds, WideCellInvariantViolation, MissingHyperlinkResource, MissingCombiningSuffixResource, }; pub const AnsiBandWriteResult = struct { rows_touched: u16, first_row: u16, last_row: u16, }; const AnsiBandWritePlan = struct { start_row: u16, max_rows: u16, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, autowrap: bool, fn resolve( surface: FrameSurface, start_row: u16, max_rows: u16, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, autowrap: bool, ) FrameSurfaceError!AnsiBandWritePlan { if (start_row == 0 or max_rows == 0) return error.WriteOutsideBand; const end_row = start_row +| max_rows -| 1; if (end_row > surface.rows) return error.WriteOutsideBand; var dest_row = start_row; while (dest_row <= end_row) : (dest_row += 1) { if (!surface.ownerPlannedForRow(dest_row, owner, policy)) return error.WriteOutsideBand; } return .{ .start_row = start_row, .max_rows = max_rows, .owner = owner, .policy = policy, .autowrap = autowrap, }; } fn destRow(self: AnsiBandWritePlan, source_row: u16) u16 { return self.start_row + source_row - 1; } fn overflowed(self: AnsiBandWritePlan, grid: vt_emulator.Grid, stats: vt_emulator.FeedStats) bool { return stats.scrolled or stats.max_row_touched > self.max_rows or grid.cursor_row > self.max_rows; } fn result(self: AnsiBandWritePlan, stats: vt_emulator.FeedStats) AnsiBandWriteResult { if (stats.max_row_touched == 0) { return .{ .rows_touched = 0, .first_row = 0, .last_row = 0 }; } return .{ .rows_touched = stats.max_row_touched, .first_row = self.start_row, .last_row = self.start_row + stats.max_row_touched - 1, }; } }; const AnsiBandFeed = struct { grid: vt_emulator.Grid, stats: vt_emulator.FeedStats, }; pub const FrameSurface = struct { alloc: Allocator, rows: u16, cols: u16, cells: []FrameCell, hyperlinks: std.ArrayList(SurfaceHyperlink) = .empty, combining_suffixes: std.ArrayList([]u8) = .empty, plan: paint_plan.PaintPlan, cursor_target: ?paint_plan.FrameCursorTarget, pub fn initFromShadow(alloc: Allocator, plan: paint_plan.PaintPlan, previous: vt_emulator.Grid) FrameSurfaceError!FrameSurface { if (plan.layout.rows == 0 or plan.layout.cols == 0) return error.InvalidGridSize; if (previous.rows != plan.layout.rows or previous.cols != plan.layout.cols) return error.InvalidGridSize; const cell_count = @as(usize, plan.layout.rows) * @as(usize, plan.layout.cols); const cells = try alloc.alloc(FrameCell, cell_count); var surface = FrameSurface{ .alloc = alloc, .rows = plan.layout.rows, .cols = plan.layout.cols, .cells = cells, .plan = plan, .cursor_target = plan.cursor_target, }; errdefer surface.deinit(); try surface.importHyperlinkPool(previous); try surface.importCombiningSuffixPool(previous); var row: u16 = 1; while (row <= surface.rows) : (row += 1) { const owner = surface.initialOwnerForRow(row); var col: u16 = 1; while (col <= surface.cols) : (col += 1) { const idx = surface.index(row, col) orelse return error.OutOfBounds; if (owner == .preserved_shell) { const source_cell = previous.cellAt(row, col) orelse return error.InvalidGridSize; surface.cells[idx] = try surface.frameCellFromAuthoritativeGrid(previous, source_cell, owner); } else { surface.cells[idx] = blankCell(owner); } } } return surface; } pub fn retainTranscriptBandFromGrid( self: *FrameSurface, source: vt_emulator.Grid, source_area: frame_layout.FrameRect, ) FrameSurfaceError!void { if (source.rows != self.rows or source.cols != self.cols) return error.InvalidGridSize; if (source_area.isEmpty()) return; if (!self.plan.transcript_band.containsRow(source_area.top) or !self.plan.transcript_band.containsRow(source_area.bottom)) { return error.WriteOutsideBand; } var row = source_area.top; while (row <= source_area.bottom) : (row += 1) { var col: u16 = 1; while (col <= self.cols) : (col += 1) { const idx = self.index(row, col) orelse return error.OutOfBounds; const source_cell = source.cellAt(row, col) orelse return error.InvalidGridSize; self.cells[idx] = try self.frameCellFromAuthoritativeGrid( source, source_cell, .transcript, ); } } } pub fn deinit(self: *FrameSurface) void { self.alloc.free(self.cells); for (self.hyperlinks.items) |*link| link.deinit(self.alloc); self.hyperlinks.deinit(self.alloc); for (self.combining_suffixes.items) |suffix| self.alloc.free(suffix); self.combining_suffixes.deinit(self.alloc); } pub fn cellAt(self: FrameSurface, row: u16, col: u16) ?FrameCell { const idx = self.index(row, col) orelse return null; return self.cells[idx]; } pub fn hyperlinkUrl(self: FrameSurface, id: u32) ?[]const u8 { if (id == 0) return null; if (id - 1 >= self.hyperlinks.items.len) return null; return self.hyperlinks.items[id - 1].uri; } pub fn combiningSuffix(self: FrameSurface, id: u32) ?[]const u8 { if (id == 0) return null; if (id - 1 >= self.combining_suffixes.items.len) return null; return self.combining_suffixes.items[id - 1]; } pub fn internHyperlink(self: *FrameSurface, uri: []const u8) FrameSurfaceError!u32 { for (self.hyperlinks.items, 0..) |link, idx| { if (std.mem.eql(u8, link.uri, uri)) return @intCast(idx + 1); } const dup = try self.alloc.dupe(u8, uri); errdefer self.alloc.free(dup); try self.hyperlinks.append(self.alloc, .{ .uri = dup }); return @intCast(self.hyperlinks.items.len); } fn internCombiningSuffix(self: *FrameSurface, bytes: []const u8) FrameSurfaceError!u32 { for (self.combining_suffixes.items, 0..) |suffix, idx| { if (std.mem.eql(u8, suffix, bytes)) return @intCast(idx + 1); } const dup = try self.alloc.dupe(u8, bytes); errdefer self.alloc.free(dup); try self.combining_suffixes.append(self.alloc, dup); return @intCast(self.combining_suffixes.items.len); } pub fn writeCell(self: *FrameSurface, row: u16, col: u16, cell: FrameCell, policy: OwnerWritePolicy) FrameSurfaceError!void { const idx = self.index(row, col) orelse return error.OutOfBounds; if (cell.width > 2) return error.WideCellInvariantViolation; if (cell.width == 2 and col == self.cols) return error.WideCellInvariantViolation; if (cell.width == 0 and cell.combining_suffix_id != 0) return error.WideCellInvariantViolation; try self.validateWrite(row, col, self.cells[idx].owner, cell.owner, policy); self.cells[idx] = cell; } /// Feeds ANSI bytes through a temporary VT grid, checks for overflow /// before copying cells, then remaps grid-local hyperlink IDs into /// this surface's hyperlink pool. pub fn writeAnsiBand( self: *FrameSurface, start_row: u16, max_rows: u16, bytes: []const u8, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, ) FrameSurfaceError!AnsiBandWriteResult { return self.writeAnsiBandWithAutowrap(start_row, max_rows, bytes, owner, policy, true); } /// Feed ANSI bytes with DECAWM disabled. Footer rows are painted as /// terminal lines, and fx keeps autowrap disabled session-wide. pub fn writeAnsiBandNoWrap( self: *FrameSurface, start_row: u16, max_rows: u16, bytes: []const u8, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, ) FrameSurfaceError!AnsiBandWriteResult { return self.writeAnsiBandWithAutowrap(start_row, max_rows, bytes, owner, policy, false); } fn writeAnsiBandWithAutowrap( self: *FrameSurface, start_row: u16, max_rows: u16, bytes: []const u8, owner: paint_plan.CellOwner, policy: OwnerWritePolicy, autowrap: bool, ) FrameSurfaceError!AnsiBandWriteResult { const plan = try AnsiBandWritePlan.resolve(self.*, start_row, max_rows, owner, policy, autowrap); var feed = try self.feedAnsiBand(plan, bytes); defer feed.grid.deinit(); try self.copyAnsiBandCells(plan, feed.grid); return plan.result(feed.stats); } fn feedAnsiBand(self: *FrameSurface, plan: AnsiBandWritePlan, bytes: []const u8) FrameSurfaceError!AnsiBandFeed { var temp = vt_emulator.Grid.init(self.alloc, self.cols, plan.max_rows +| 1) catch |err| switch (err) { error.InvalidGridSize => return error.InvalidGridSize, error.OutOfMemory => return error.OutOfMemory, }; errdefer temp.deinit(); temp.autowrap = plan.autowrap; var stats: vt_emulator.FeedStats = .{}; temp.feedWithStats(bytes, &stats) catch return error.OutOfMemory; if (plan.overflowed(temp, stats)) return error.AnsiBandOverflow; return .{ .grid = temp, .stats = stats }; } fn copyAnsiBandCells(self: *FrameSurface, plan: AnsiBandWritePlan, source: vt_emulator.Grid) FrameSurfaceError!void { var source_row: u16 = 1; while (source_row <= plan.max_rows) : (source_row += 1) { const dest_row = plan.destRow(source_row); var col: u16 = 1; while (col <= self.cols) : (col += 1) { const source_cell = source.cellAt(source_row, col) orelse return error.InvalidGridSize; const frame_cell = try self.frameCellFromGrid(source, source_cell, plan.owner); try self.writeCell(dest_row, col, frame_cell, plan.policy); } } } pub fn copyToTargetGrid(self: FrameSurface, alloc: Allocator) !vt_emulator.Grid { var target = try vt_emulator.Grid.init(alloc, self.cols, self.rows); errdefer target.deinit(); try target.hyperlink_pool.ensureTotalCapacity(alloc, self.hyperlinks.items.len); for (self.hyperlinks.items) |link| { const dup = try alloc.dupe(u8, link.uri); target.hyperlink_pool.appendAssumeCapacity(dup); } try target.combining_suffix_pool.ensureTotalCapacity(alloc, self.combining_suffixes.items.len); for (self.combining_suffixes.items) |suffix| { const dup = try alloc.dupe(u8, suffix); target.combining_suffix_pool.appendAssumeCapacity(dup); } for (self.cells, 0..) |cell, idx| { target.cells[idx] = .{ .codepoint = cell.codepoint, .width = cell.width, .combining_suffix_id = cell.combining_suffix_id, .style = cell.style, }; } if (self.cursor_target) |cursor| { target.cursor_row = cursor.row; target.cursor_col = cursor.col; target.cursor_visible = cursor.visible; } return target; } /// Removes fx-owned color, emphasis, and hyperlink presentation while /// preserving glyphs, geometry, ownership, and shell-owned cells. pub fn neutralizeFxOwnedPresentation(self: *FrameSurface) void { for (self.cells) |*cell| { if (cell.owner == .preserved_shell) continue; cell.style = .{}; } } pub fn validate(self: FrameSurface) FrameSurfaceError!void { if (self.cells.len != @as(usize, self.rows) * @as(usize, self.cols)) return error.InvalidGridSize; if (self.cursor_target) |cursor| { if (cursor.row == 0 or cursor.row > self.rows or cursor.col == 0 or cursor.col > self.cols) { return error.CursorOutOfBounds; } } var row: u16 = 1; while (row <= self.rows) : (row += 1) { var col: u16 = 1; while (col <= self.cols) : (col += 1) { const cell = self.cellAt(row, col).?; if (!self.ownerLegalAt(row, cell.owner)) return error.UnownedFxCell; try self.validateWideCell(row, col, cell); } } } fn initialOwnerForRow(self: FrameSurface, row: u16) paint_plan.CellOwner { if (self.plan.preserved_band.containsRow(row)) return .preserved_shell; if (self.plan.blank_band.containsRow(row)) return .gap; if (self.plan.activity_band.containsRow(row)) return .activity; if (self.plan.footer_gap_band.containsRow(row)) return .gap; if (self.plan.footer_band.containsRow(row)) return .footer; if (self.plan.transcript_band.containsRow(row)) return .transcript; if (self.invalidationContains(row)) return .diagnostic_clear; return .preserved_shell; } fn frameCellFromGrid(self: *FrameSurface, grid: vt_emulator.Grid, cell: vt_emulator.Cell, owner: paint_plan.CellOwner) FrameSurfaceError!FrameCell { return .{ .codepoint = cell.codepoint, .width = cell.width, .combining_suffix_id = try self.remapCombiningSuffixFromGrid(grid, cell.combining_suffix_id), .style = try self.remapStyleFromGrid(grid, cell.style), .owner = owner, }; } fn importHyperlinkPool(self: *FrameSurface, grid: vt_emulator.Grid) FrameSurfaceError!void { std.debug.assert(self.hyperlinks.items.len == 0); try self.hyperlinks.ensureTotalCapacity(self.alloc, grid.hyperlink_pool.items.len); for (grid.hyperlink_pool.items) |uri| { const dup = try self.alloc.dupe(u8, uri); self.hyperlinks.appendAssumeCapacity(.{ .uri = dup }); } } fn importCombiningSuffixPool(self: *FrameSurface, grid: vt_emulator.Grid) FrameSurfaceError!void { std.debug.assert(self.combining_suffixes.items.len == 0); try self.combining_suffixes.ensureTotalCapacity(self.alloc, grid.combining_suffix_pool.items.len); for (grid.combining_suffix_pool.items) |bytes| { const dup = try self.alloc.dupe(u8, bytes); self.combining_suffixes.appendAssumeCapacity(dup); } } fn frameCellFromAuthoritativeGrid( self: *FrameSurface, grid: vt_emulator.Grid, cell: vt_emulator.Cell, owner: paint_plan.CellOwner, ) FrameSurfaceError!FrameCell { if (cell.style.hyperlink_id != 0) { const source_uri = grid.hyperlinkUrl(cell.style.hyperlink_id) orelse return error.MissingHyperlinkResource; const imported_uri = self.hyperlinkUrl(cell.style.hyperlink_id) orelse return error.MissingHyperlinkResource; if (!std.mem.eql(u8, source_uri, imported_uri)) { return error.MissingHyperlinkResource; } } if (cell.combining_suffix_id != 0) { const source_suffix = grid.combiningSuffix(cell.combining_suffix_id) orelse return error.MissingCombiningSuffixResource; const imported_suffix = self.combiningSuffix(cell.combining_suffix_id) orelse return error.MissingCombiningSuffixResource; if (!std.mem.eql(u8, source_suffix, imported_suffix)) { return error.MissingCombiningSuffixResource; } } return .{ .codepoint = cell.codepoint, .width = cell.width, .combining_suffix_id = cell.combining_suffix_id, .style = cell.style, .owner = owner, }; } fn remapCombiningSuffixFromGrid(self: *FrameSurface, grid: vt_emulator.Grid, id: u32) FrameSurfaceError!u32 { if (id == 0) return 0; const suffix = grid.combiningSuffix(id) orelse return error.MissingCombiningSuffixResource; return self.internCombiningSuffix(suffix); } fn remapStyleFromGrid(self: *FrameSurface, grid: vt_emulator.Grid, style: vt_emulator.Style) FrameSurfaceError!vt_emulator.Style { if (style.hyperlink_id == 0) return style; const uri = grid.hyperlinkUrl(style.hyperlink_id) orelse return error.MissingHyperlinkResource; var remapped = style; remapped.hyperlink_id = try self.internHyperlink(uri); return remapped; } fn validateWrite( self: FrameSurface, row: u16, col: u16, current_owner: paint_plan.CellOwner, next_owner: paint_plan.CellOwner, policy: OwnerWritePolicy, ) FrameSurfaceError!void { if (current_owner == .preserved_shell) { self.traceOwnerViolation(row, col, current_owner, next_owner, policy, "preserved_shell_mutation"); return error.PreservedShellMutation; } if (!self.ownerPlannedForRow(row, next_owner, policy)) { self.traceOwnerViolation(row, col, current_owner, next_owner, policy, "write_outside_band"); return error.WriteOutsideBand; } if (current_owner == next_owner) return; if (policy == .activity_over_transcript and next_owner == .activity and current_owner == .transcript and self.activityOverlayRow() == row) { return; } if (policy == .diagnostic_overwrite and next_owner == .diagnostic_clear) return; self.traceOwnerViolation(row, col, current_owner, next_owner, policy, "owner_conflict"); return error.OwnerConflict; } fn traceOwnerViolation( self: FrameSurface, row: u16, col: u16, current_owner: paint_plan.CellOwner, next_owner: paint_plan.CellOwner, policy: OwnerWritePolicy, reason: []const u8, ) void {