const std = @import("std");const io_mod = @import("../../core/shared/io.zig");const types = @import("../../core/shared/types.zig");const command_output_content = @import("../../core/tooling/command_output_content.zig");const full_transcript_screen = @import("../full_transcript_screen.zig");const input_visual_layout = @import("../input/visual_layout.zig");const render_engine = @import("../render_engine.zig");const command_output_runtime = @import("command_output_runtime.zig");const transcript_painter = @import("painter.zig");const transcript_runtime = @import("runtime.zig");const transcript_store = @import("store.zig");const transcript_writer = @import("writer.zig");const ui_render = @import("../render.zig");const user_message_card = @import("../assistant/user_message_card.zig");const vt_emulator = @import("../../core/terminal/engine.zig"); const Allocator = std.mem.Allocator;const Layout = types.Layout;const Metrics = types.Metrics;const transcript_blocks = render_engine.transcript_blocks;const viewport_selection = render_engine.viewport_selection; const RawEntryClass = transcript_blocks.RawEntryClass;const Styles = transcript_blocks.Styles;const ToolDetailRecord = transcript_blocks.ToolDetailRecord;const TranscriptPreparationSource = transcript_runtime.TranscriptPreparationSource;const TranscriptRuntime = transcript_runtime.TranscriptRuntime;const HardLineStarts = viewport_selection.HardLineStarts;const TranscriptBuffer = viewport_selection.TranscriptBuffer;const VisibleTranscriptLine = viewport_selection.VisibleTranscriptLine;fn renderEntriesToBytes(alloc: Allocator, entries: []const transcript_blocks.TranscriptEntry, cols: u16) ![]u8 { return transcript_runtime.renderEntriesToBytes(alloc, entries, cols, .{});}const visualRowsForLine = transcript_blocks.visualRowsForLine; fn resolveAndSealTranscriptTransitionForTest( runtime: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, plan: *render_engine.paint_plan.PaintPlan, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan,) !transcript_runtime.TranscriptTransition { const scroll_facts = try runtime.prepareTranscriptScrollFactsForFrame( alloc, source, prepared, false, false, ); const destructive_invalidation = render_engine.frame_retention.transcriptAreaHasDestructiveInvalidation( runtime.committed_frame_layout.transcript_area, plan.invalidation, ); const resolved = try runtime.resolveTranscriptTransitionTargetForFrame( alloc, source, prepared, target_layout, scroll_plan, scroll_facts, destructive_invalidation, plan.activity == .overlay_entry, ); resolved.applyToPaintPlan(plan); return runtime.sealTranscriptTransition( alloc, source, prepared, plan, resolved, );} fn resolveVisibleLineForTest(line: VisibleTranscriptLine, buf: TranscriptBuffer) []const u8 { return switch (line) { .transcript => |t| t.ref.resolve(buf), .folded_line => |f| f.text, };} fn commandOutputTestRuntime(sink: std.Io.File) TranscriptRuntime { return .{ .stdout_file = sink, .maxxing_mode = .legacy, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, }, };} fn testSelection(start_line: usize) viewport_selection.ViewportSelection { return .{ .top_row = 1, .bottom_row = 4, .start_line = start_line, .partial_skip_rows = 0, .line_count = 16, };} fn transcriptTestLayout(cols: u16, rows: u16, content_bottom: u16) Layout { return .{ .rows = rows, .cols = cols, .content_bottom = content_bottom, .divider_top_row = content_bottom + 1, .input_row = content_bottom + 2, .divider_bottom_row = content_bottom + 3, .hint_row = rows, };} fn makeLongPastePrompt(alloc: Allocator, line_count: usize) ![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try out.writer.writeAll("LONG_PASTE_FIRST_MARKER\n"); var index: usize = 0; while (index < line_count) : (index += 1) { try out.writer.print("long-paste-line-{d}\n", .{index}); } try out.writer.writeAll("LONG_PASTE_LAST_MARKER\n"); return out.toOwnedSlice();} fn installStableAnchorWithOriginForTest( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, selection: viewport_selection.ViewportSelection, visual_offset: u32, cursor_row: u16, cursor_col: u16, layout_id: u64, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin,) !void { var target_cache: std.ArrayList(u8) = .empty; errdefer target_cache.deinit(alloc); try target_cache.appendSlice(alloc, flow); var transition = transcript_runtime.TranscriptTransition{ .scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none( runtime.layout.rows, runtime.owned_top_row, ), .document_append = .{}, .materialized_without_append = .{}, .target_flow = try alloc.dupe(u8, flow), .target_cache = target_cache, .target_cache_origin_untrimmed = runtime.transcript_cache_origin_untrimmed, .folded_summary_indices = &.{}, .target_layout = .{ .layout_id = layout_id, .owned_top = runtime.owned_top_row, .owned_band = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.rows }, .body_area = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, .transcript_area = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, .footer_area = .{ .top = runtime.layout.content_bottom + 1, .bottom = runtime.layout.rows }, .solved_frame_height = runtime.layout.rows - runtime.owned_top_row + 1, .terminal_rows = runtime.layout.rows, .terminal_cols = runtime.layout.cols, }, .selection = selection, .visual_offset = visual_offset, .history_visual_offset = visual_offset, .total_visual_rows = visual_offset + 64, .cursor_row = cursor_row, .cursor_col = cursor_col, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = cursor_row, .trace_transcript_frame = false, .trace_visible_rows = selection.bottom_row - selection.top_row + 1, .measured_history_origin = measured_history_origin, }; defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 0, .changed_cells = 0, .full_repaint = false, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = cursor_row, .committed_cursor_col = cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), });} fn installStableAnchorForTest( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, selection: viewport_selection.ViewportSelection, visual_offset: u32, cursor_row: u16, cursor_col: u16, layout_id: u64,) !void { return installStableAnchorWithOriginForTest( runtime, alloc, flow, selection, visual_offset, cursor_row, cursor_col, layout_id, null, );} fn testPaintPlan( runtime: *const TranscriptRuntime, selection: viewport_selection.ViewportSelection,) render_engine.paint_plan.PaintPlan { const footer_top = runtime.layout.content_bottom + 1; return .{ .layout = runtime.layout, .viewport = selection, .footer = .{ .top = footer_top, .top_divider = footer_top, .banner = footer_top, .banner_active = false, .input_base = @min(footer_top + 1, runtime.layout.rows), .picker_divider = @min(footer_top + 2, runtime.layout.rows), .picker_start = runtime.layout.rows, .bottom_divider = @min(footer_top + 2, runtime.layout.rows), .hint = runtime.layout.rows, .total_rows = runtime.layout.rows - footer_top + 1, }, .activity = .none, .preserved_band = if (runtime.owned_top_row > 1) .{ .top = 1, .bottom = runtime.owned_top_row - 1, .owner = .preserved_shell } else render_engine.paint_plan.FrameBand.empty(.preserved_shell), .transcript_band = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom, .owner = .transcript, }, .activity_band = render_engine.paint_plan.FrameBand.empty(.activity), .footer_band = .{ .top = footer_top, .bottom = runtime.layout.rows, .owner = .footer, }, .invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), .footer_clean_allowed = true, .synchronized_update = true, .cursor_target = .{ .row = selection.bottom_row, .col = 1, .visible = true }, .footer_reservation_source = .none, .bottom_reserved_rows = 0, .preserve_scrollback = true, };} const TestTranscriptFixedPointFrame = struct { layout: render_engine.frame_layout.FrameLayout, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan,}; fn solveTestTranscriptFixedPointFrame( runtime: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, source: *const TranscriptPreparationSource, footer_rows: u16, prepared: *?transcript_painter.PreparedTranscriptSurfacePaint,) !TestTranscriptFixedPointFrame { var release_floor_rows: u16 = 0; var previous_candidate_owned_top: ?u16 = null; while (true) { const release = render_engine.frame_layout.solveWithPreservedRowRelease(.{ .terminal = runtime.layout, .owned_top = runtime.owned_top_row, .footer = .{ .natural_rows = footer_rows, .min_rows = footer_rows, .max_rows = footer_rows, }, .transcript = source.preview, .prior = runtime.committed_frame_layout, }, release_floor_rows); const candidate = release.layout; if (previous_candidate_owned_top) |previous| { if (candidate.owned_top >= previous) return error.InvalidFrameScrollPlan; } if (prepared.*) |*paint| { paint.deinit(alloc); prepared.* = null; } var inline_advance_rows: u16 = 0; if (!candidate.transcript_area.isEmpty()) { prepared.* = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, metrics, source, candidate.transcript_area, ); inline_advance_rows = runtime.planTranscriptScroll(&prepared.*.?).planned_rows; } const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, release.released_rows, inline_advance_rows, ); if (candidate.owned_top == scroll_plan.post_scroll_owned_top) { return .{ .layout = candidate, .scroll_plan = scroll_plan, }; } previous_candidate_owned_top = candidate.owned_top; release_floor_rows = scroll_plan.preserved_release_rows; }} fn testPaintPlanForFrame( layout: render_engine.frame_layout.FrameLayout, selection: viewport_selection.ViewportSelection, cursor_row: u16, cursor_col: u16,) render_engine.paint_plan.PaintPlan { const footer_rows = render_engine.footer_layout.resolve(.{ .footer_top_for_extra = layout.footer_area.top, .terminal_rows = layout.terminal.rows, .activity_offset = 0, .extra_input_rows = 0, .input_extra = 0, .picker_rows = 0, .banner_active = false, .allocated_rows = layout.footer_area.height(), }); return layout.toPaintPlan(.{ .footer_rows = footer_rows, .viewport = selection, .cursor_target = .{ .row = cursor_row, .col = cursor_col, .visible = true, }, });} fn testSource( alloc: Allocator, bytes: []const u8, cols: u16,) !TranscriptPreparationSource { return .{ .bytes = try alloc.dupe(u8, bytes), .folded_summary_indices = &.{}, .preview = .{ .natural_visual_rows = 4 }, .tail_kind = null, .tracked_entry_id = null, .tracked_entry_start_line = null, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 1, .welcome_cut_line = null, .welcome_boundary = null, .cols = cols, };} fn testFoldedSource( alloc: Allocator, bytes: []const u8, cols: u16, summary_transcript_index: usize,) !TranscriptPreparationSource { var source = try testSource(alloc, bytes, cols); errdefer source.deinit(alloc); source.folded_summary_indices = try alloc.dupe( usize, &.{summary_transcript_index}, ); return source;} fn testSourceWithReplaceableTail( alloc: Allocator, bytes: []const u8, cols: u16, replaceable_start: usize,) !TranscriptPreparationSource { var source = try testSource(alloc, bytes, cols); source.replaceable_last_line = true; source.replaceable_start = replaceable_start; return source;} fn appendFoldedLineForTest( runtime: *TranscriptRuntime, alloc: Allocator, block_index: usize, text: []const u8,) !void { const owned = try alloc.dupe(u8, text); errdefer alloc.free(owned); try runtime.folded_command_blocks.items[block_index].lines.append(alloc, .{ .stream = .stdout, .text = owned, });} fn prepareTestSourceForCurrentArea( runtime: *TranscriptRuntime, alloc: Allocator, source: *const TranscriptPreparationSource,) !transcript_painter.PreparedTranscriptSurfacePaint { var metrics: Metrics = .{}; return runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, source, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom, }, );} fn preparedVisualOffsetForTest( prepared: *const transcript_painter.PreparedTranscriptSurfacePaint,) u32 { var offset: u32 = 0; const end = @min( prepared.selection.start_line, prepared.sourceVisualRows().len, ); for (prepared.sourceVisualRows()[0..end]) |rows| offset += rows; return offset + prepared.selection.partial_skip_rows;} test "unchanged transcript keeps stable projection during footer reflow" { const alloc = std.testing.allocator; const flow = "line 00\n" ++ "line 01\n" ++ "line 02\n" ++ "line 03\n" ++ "line 04\n" ++ "line 05\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(60, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var metrics = Metrics{}; var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var compact = try runtime.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, &metrics, &source, .{ .top = 1, .bottom = 4 }, false, ); defer compact.deinit(alloc); try std.testing.expectEqual(@as(usize, 2), compact.selection.start_line); try installStableAnchorWithOriginForTest( &runtime, alloc, flow, compact.selection, preparedVisualOffsetForTest(&compact), compact.cursor.cursor_row, compact.cursor.cursor_col, 800, .{ .source_cols = runtime.layout.cols, .source_visual_offset = preparedVisualOffsetForTest(&compact), }, ); var expanded = try runtime.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, &metrics, &source, .{ .top = 1, .bottom = 6 }, true, ); defer expanded.deinit(alloc); try std.testing.expectEqual(compact.selection.start_line, expanded.selection.start_line); try std.testing.expectEqual(compact.selection.last_visible_row, expanded.selection.last_visible_row);Lines 1–500 of 15890. Load more
const std = @import("std"); const io_mod = @import("../../core/shared/io.zig"); const types = @import("../../core/shared/types.zig"); const command_output_content = @import("../../core/tooling/command_output_content.zig"); const full_transcript_screen = @import("../full_transcript_screen.zig"); const input_visual_layout = @import("../input/visual_layout.zig"); const render_engine = @import("../render_engine.zig"); const command_output_runtime = @import("command_output_runtime.zig"); const transcript_painter = @import("painter.zig"); const transcript_runtime = @import("runtime.zig"); const transcript_store = @import("store.zig"); const transcript_writer = @import("writer.zig"); const ui_render = @import("../render.zig"); const user_message_card = @import("../assistant/user_message_card.zig"); const vt_emulator = @import("../../core/terminal/engine.zig"); const Allocator = std.mem.Allocator; const Layout = types.Layout; const Metrics = types.Metrics; const transcript_blocks = render_engine.transcript_blocks; const viewport_selection = render_engine.viewport_selection; const RawEntryClass = transcript_blocks.RawEntryClass; const Styles = transcript_blocks.Styles; const ToolDetailRecord = transcript_blocks.ToolDetailRecord; const TranscriptPreparationSource = transcript_runtime.TranscriptPreparationSource; const TranscriptRuntime = transcript_runtime.TranscriptRuntime; const HardLineStarts = viewport_selection.HardLineStarts; const TranscriptBuffer = viewport_selection.TranscriptBuffer; const VisibleTranscriptLine = viewport_selection.VisibleTranscriptLine; fn renderEntriesToBytes(alloc: Allocator, entries: []const transcript_blocks.TranscriptEntry, cols: u16) ![]u8 { return transcript_runtime.renderEntriesToBytes(alloc, entries, cols, .{}); } const visualRowsForLine = transcript_blocks.visualRowsForLine; fn resolveAndSealTranscriptTransitionForTest( runtime: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, plan: *render_engine.paint_plan.PaintPlan, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, ) !transcript_runtime.TranscriptTransition { const scroll_facts = try runtime.prepareTranscriptScrollFactsForFrame( alloc, source, prepared, false, false, ); const destructive_invalidation = render_engine.frame_retention.transcriptAreaHasDestructiveInvalidation( runtime.committed_frame_layout.transcript_area, plan.invalidation, ); const resolved = try runtime.resolveTranscriptTransitionTargetForFrame( alloc, source, prepared, target_layout, scroll_plan, scroll_facts, destructive_invalidation, plan.activity == .overlay_entry, ); resolved.applyToPaintPlan(plan); return runtime.sealTranscriptTransition( alloc, source, prepared, plan, resolved, ); } fn resolveVisibleLineForTest(line: VisibleTranscriptLine, buf: TranscriptBuffer) []const u8 { return switch (line) { .transcript => |t| t.ref.resolve(buf), .folded_line => |f| f.text, }; } fn commandOutputTestRuntime(sink: std.Io.File) TranscriptRuntime { return .{ .stdout_file = sink, .maxxing_mode = .legacy, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, }, }; } fn testSelection(start_line: usize) viewport_selection.ViewportSelection { return .{ .top_row = 1, .bottom_row = 4, .start_line = start_line, .partial_skip_rows = 0, .line_count = 16, }; } fn transcriptTestLayout(cols: u16, rows: u16, content_bottom: u16) Layout { return .{ .rows = rows, .cols = cols, .content_bottom = content_bottom, .divider_top_row = content_bottom + 1, .input_row = content_bottom + 2, .divider_bottom_row = content_bottom + 3, .hint_row = rows, }; } fn makeLongPastePrompt(alloc: Allocator, line_count: usize) ![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try out.writer.writeAll("LONG_PASTE_FIRST_MARKER\n"); var index: usize = 0; while (index < line_count) : (index += 1) { try out.writer.print("long-paste-line-{d}\n", .{index}); } try out.writer.writeAll("LONG_PASTE_LAST_MARKER\n"); return out.toOwnedSlice(); } fn installStableAnchorWithOriginForTest( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, selection: viewport_selection.ViewportSelection, visual_offset: u32, cursor_row: u16, cursor_col: u16, layout_id: u64, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin, ) !void { var target_cache: std.ArrayList(u8) = .empty; errdefer target_cache.deinit(alloc); try target_cache.appendSlice(alloc, flow); var transition = transcript_runtime.TranscriptTransition{ .scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none( runtime.layout.rows, runtime.owned_top_row, ), .document_append = .{}, .materialized_without_append = .{}, .target_flow = try alloc.dupe(u8, flow), .target_cache = target_cache, .target_cache_origin_untrimmed = runtime.transcript_cache_origin_untrimmed, .folded_summary_indices = &.{}, .target_layout = .{ .layout_id = layout_id, .owned_top = runtime.owned_top_row, .owned_band = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.rows }, .body_area = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, .transcript_area = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, .footer_area = .{ .top = runtime.layout.content_bottom + 1, .bottom = runtime.layout.rows }, .solved_frame_height = runtime.layout.rows - runtime.owned_top_row + 1, .terminal_rows = runtime.layout.rows, .terminal_cols = runtime.layout.cols, }, .selection = selection, .visual_offset = visual_offset, .history_visual_offset = visual_offset, .total_visual_rows = visual_offset + 64, .cursor_row = cursor_row, .cursor_col = cursor_col, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = cursor_row, .trace_transcript_frame = false, .trace_visible_rows = selection.bottom_row - selection.top_row + 1, .measured_history_origin = measured_history_origin, }; defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 0, .changed_cells = 0, .full_repaint = false, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = cursor_row, .committed_cursor_col = cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); } fn installStableAnchorForTest( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, selection: viewport_selection.ViewportSelection, visual_offset: u32, cursor_row: u16, cursor_col: u16, layout_id: u64, ) !void { return installStableAnchorWithOriginForTest( runtime, alloc, flow, selection, visual_offset, cursor_row, cursor_col, layout_id, null, ); } fn testPaintPlan( runtime: *const TranscriptRuntime, selection: viewport_selection.ViewportSelection, ) render_engine.paint_plan.PaintPlan { const footer_top = runtime.layout.content_bottom + 1; return .{ .layout = runtime.layout, .viewport = selection, .footer = .{ .top = footer_top, .top_divider = footer_top, .banner = footer_top, .banner_active = false, .input_base = @min(footer_top + 1, runtime.layout.rows), .picker_divider = @min(footer_top + 2, runtime.layout.rows), .picker_start = runtime.layout.rows, .bottom_divider = @min(footer_top + 2, runtime.layout.rows), .hint = runtime.layout.rows, .total_rows = runtime.layout.rows - footer_top + 1, }, .activity = .none, .preserved_band = if (runtime.owned_top_row > 1) .{ .top = 1, .bottom = runtime.owned_top_row - 1, .owner = .preserved_shell } else render_engine.paint_plan.FrameBand.empty(.preserved_shell), .transcript_band = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom, .owner = .transcript, }, .activity_band = render_engine.paint_plan.FrameBand.empty(.activity), .footer_band = .{ .top = footer_top, .bottom = runtime.layout.rows, .owner = .footer, }, .invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), .footer_clean_allowed = true, .synchronized_update = true, .cursor_target = .{ .row = selection.bottom_row, .col = 1, .visible = true }, .footer_reservation_source = .none, .bottom_reserved_rows = 0, .preserve_scrollback = true, }; } const TestTranscriptFixedPointFrame = struct { layout: render_engine.frame_layout.FrameLayout, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, }; fn solveTestTranscriptFixedPointFrame( runtime: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, source: *const TranscriptPreparationSource, footer_rows: u16, prepared: *?transcript_painter.PreparedTranscriptSurfacePaint, ) !TestTranscriptFixedPointFrame { var release_floor_rows: u16 = 0; var previous_candidate_owned_top: ?u16 = null; while (true) { const release = render_engine.frame_layout.solveWithPreservedRowRelease(.{ .terminal = runtime.layout, .owned_top = runtime.owned_top_row, .footer = .{ .natural_rows = footer_rows, .min_rows = footer_rows, .max_rows = footer_rows, }, .transcript = source.preview, .prior = runtime.committed_frame_layout, }, release_floor_rows); const candidate = release.layout; if (previous_candidate_owned_top) |previous| { if (candidate.owned_top >= previous) return error.InvalidFrameScrollPlan; } if (prepared.*) |*paint| { paint.deinit(alloc); prepared.* = null; } var inline_advance_rows: u16 = 0; if (!candidate.transcript_area.isEmpty()) { prepared.* = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, metrics, source, candidate.transcript_area, ); inline_advance_rows = runtime.planTranscriptScroll(&prepared.*.?).planned_rows; } const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, release.released_rows, inline_advance_rows, ); if (candidate.owned_top == scroll_plan.post_scroll_owned_top) { return .{ .layout = candidate, .scroll_plan = scroll_plan, }; } previous_candidate_owned_top = candidate.owned_top; release_floor_rows = scroll_plan.preserved_release_rows; } } fn testPaintPlanForFrame( layout: render_engine.frame_layout.FrameLayout, selection: viewport_selection.ViewportSelection, cursor_row: u16, cursor_col: u16, ) render_engine.paint_plan.PaintPlan { const footer_rows = render_engine.footer_layout.resolve(.{ .footer_top_for_extra = layout.footer_area.top, .terminal_rows = layout.terminal.rows, .activity_offset = 0, .extra_input_rows = 0, .input_extra = 0, .picker_rows = 0, .banner_active = false, .allocated_rows = layout.footer_area.height(), }); return layout.toPaintPlan(.{ .footer_rows = footer_rows, .viewport = selection, .cursor_target = .{ .row = cursor_row, .col = cursor_col, .visible = true, }, }); } fn testSource( alloc: Allocator, bytes: []const u8, cols: u16, ) !TranscriptPreparationSource { return .{ .bytes = try alloc.dupe(u8, bytes), .folded_summary_indices = &.{}, .preview = .{ .natural_visual_rows = 4 }, .tail_kind = null, .tracked_entry_id = null, .tracked_entry_start_line = null, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 1, .welcome_cut_line = null, .welcome_boundary = null, .cols = cols, }; } fn testFoldedSource( alloc: Allocator, bytes: []const u8, cols: u16, summary_transcript_index: usize, ) !TranscriptPreparationSource { var source = try testSource(alloc, bytes, cols); errdefer source.deinit(alloc); source.folded_summary_indices = try alloc.dupe( usize, &.{summary_transcript_index}, ); return source; } fn testSourceWithReplaceableTail( alloc: Allocator, bytes: []const u8, cols: u16, replaceable_start: usize, ) !TranscriptPreparationSource { var source = try testSource(alloc, bytes, cols); source.replaceable_last_line = true; source.replaceable_start = replaceable_start; return source; } fn appendFoldedLineForTest( runtime: *TranscriptRuntime, alloc: Allocator, block_index: usize, text: []const u8, ) !void { const owned = try alloc.dupe(u8, text); errdefer alloc.free(owned); try runtime.folded_command_blocks.items[block_index].lines.append(alloc, .{ .stream = .stdout, .text = owned, }); } fn prepareTestSourceForCurrentArea( runtime: *TranscriptRuntime, alloc: Allocator, source: *const TranscriptPreparationSource, ) !transcript_painter.PreparedTranscriptSurfacePaint { var metrics: Metrics = .{}; return runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, source, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom, }, ); } fn preparedVisualOffsetForTest( prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, ) u32 { var offset: u32 = 0; const end = @min( prepared.selection.start_line, prepared.sourceVisualRows().len, ); for (prepared.sourceVisualRows()[0..end]) |rows| offset += rows; return offset + prepared.selection.partial_skip_rows; } test "unchanged transcript keeps stable projection during footer reflow" { const alloc = std.testing.allocator; const flow = "line 00\n" ++ "line 01\n" ++ "line 02\n" ++ "line 03\n" ++ "line 04\n" ++ "line 05\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(60, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var metrics = Metrics{}; var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var compact = try runtime.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, &metrics, &source, .{ .top = 1, .bottom = 4 }, false, ); defer compact.deinit(alloc); try std.testing.expectEqual(@as(usize, 2), compact.selection.start_line); try installStableAnchorWithOriginForTest( &runtime, alloc, flow, compact.selection, preparedVisualOffsetForTest(&compact), compact.cursor.cursor_row, compact.cursor.cursor_col, 800, .{ .source_cols = runtime.layout.cols, .source_visual_offset = preparedVisualOffsetForTest(&compact), }, ); var expanded = try runtime.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, &metrics, &source, .{ .top = 1, .bottom = 6 }, true, ); defer expanded.deinit(alloc); try std.testing.expectEqual(compact.selection.start_line, expanded.selection.start_line); try std.testing.expectEqual(compact.selection.last_visible_row, expanded.selection.last_visible_row);