PranitInitial commit439f83c3 months agoHistory
197 lines · 7.4 KB · Zig
RawDownload
// Host contract:// - fields: transcript, entries, max_transcript_bytes, replaceable_last_line,//   replaceable_start, cursor_row, cursor_col, layout, viewport_top_row,//   owned_top_row, pending_scroll_compact,//   has_painted_transcript, transcript_band_dirty, last_rendered_cols,//   transcript_cache_origin_untrimmed// - methods: forgetShimmer, assertCanMutateTranscript, advanceCursor,//   ensurePaintReservation, rebuildTranscriptCacheFromEntries,//   recomputeCursorFromTranscript// This module owns transcript writes plus scroll coordination during writes. const std = @import("std");const debug_trace = @import("../../core/shared/debug_trace.zig");const types = @import("../../core/shared/types.zig");const render_engine = @import("../render_engine.zig");const transcript_store = @import("store.zig"); const Allocator = std.mem.Allocator;const Metrics = types.Metrics;const transcript_blocks = render_engine.transcript_blocks;const RawEntryClass = transcript_blocks.RawEntryClass; fn requestTranscriptPaint(    self: anytype,    content_changed: bool,    dirty_entry_id: ?u32,) void {    const Runtime = @TypeOf(self.*);    if (content_changed) {        if (dirty_entry_id) |entry_id| {            if (comptime @hasDecl(Runtime, "markTranscriptContentDirtyFrom")) {                self.markTranscriptContentDirtyFrom(entry_id);                return;            }        }        if (comptime @hasDecl(Runtime, "markTranscriptContentDirty")) {            self.markTranscriptContentDirty();            return;        }    }    if (comptime @hasDecl(Runtime, "markTranscriptDirty")) {        self.markTranscriptDirty();    } else {        self.transcript_band_dirty = true;    }} pub fn writeTranscriptBytes(self: anytype, alloc: Allocator, metrics: *Metrics, text: []const u8, record: bool) !void {    return writeTranscriptBytesInternal(self, alloc, metrics, text, record, null);} pub fn writeTranscriptBytesFrom(    self: anytype,    alloc: Allocator,    metrics: *Metrics,    text: []const u8,    record: bool,    entry_id: u32,) !void {    return writeTranscriptBytesInternal(self, alloc, metrics, text, record, entry_id);} fn writeTranscriptBytesInternal(    self: anytype,    alloc: Allocator,    metrics: *Metrics,    text: []const u8,    record: bool,    dirty_entry_id: ?u32,) !void {    if (text.len == 0) return;     _ = metrics;    self.forgetShimmer();     // `record` gates transcript-buffer and replaceable-state    // bookkeeping only. Cursor geometry still advances so frame    // preparation sees the same pending layout as recorded writes.    if (record) {        try self.assertCanMutateTranscript();        self.transcript_cache_origin_untrimmed = false;        _ = try transcript_store.appendCappedWithinCapacity(            &self.transcript,            alloc,            text,            self.max_transcript_bytes,            &self.replaceable_last_line,            &self.replaceable_start,        );        if (self.replaceable_last_line) {            debug_trace.logf("transcript.replaceable_flip", "writeTranscriptBytes appended non-replaceable, text_bytes={d}", .{text.len});        }        self.replaceable_last_line = false;        self.replaceable_start = 0;    }     // Cursor tracking stays so the pre-paint heuristics in main.zig    // (e.g. the `cursor_col != 1` leading-newline test) still see    // sensible values between ticks. The paint pass will    // authoritatively overwrite these when it runs.    const cursor_row_before: u32 = @as(u32, self.cursor_row);    const walk = self.advanceCursor(text);    std.debug.assert(walk.end_row >= cursor_row_before);     // Viewport selection converts overflow into frame-owned scroll.    const content_bottom_u32: u32 = @as(u32, self.layout.content_bottom);    if (walk.end_row > content_bottom_u32) {        const total_scroll_u32 = walk.end_row - content_bottom_u32;        const total_scroll: u16 = @intCast(@min(total_scroll_u32, @as(u32, std.math.maxInt(u16))));        self.cursor_row = self.layout.content_bottom;        debug_trace.logf(            "scroll",            "cursor_overflow_deferred_to_selection total_scroll={d} viewport_top={d} owned_top={d} cursor_row_after={d} walk_end_row={d} text_bytes={d} has_painted={s}",            .{ total_scroll, self.viewport_top_row, self.owned_top_row, self.cursor_row, walk.end_row, text.len, if (self.has_painted_transcript) "true" else "false" },        );    }     requestTranscriptPaint(self, record, dirty_entry_id);} fn recordTranscriptFrameInvalidation(self: anytype, reason: render_engine.paint_plan.FrameInvalidationReason) void {    if (self.layout.rows == 0) return;    const Shell = @TypeOf(self.*);    if (comptime !@hasDecl(Shell, "recordFrameInvalidation")) return;    const top = if (self.owned_top_row == 0 or self.owned_top_row > self.layout.rows) @as(u16, 1) else self.owned_top_row;    self.recordFrameInvalidation(.{        .reason = reason,        .top = top,        .bottom = self.layout.rows,    }) catch |err| {        debug_trace.logf(            "frame_plan",            "transcript_invalidation_drop reason={s} top={d} bottom={d} err={s}",            .{ @tagName(reason), top, self.layout.rows, @errorName(err) },        );    };} pub fn writeTranscript(self: anytype, alloc: Allocator, metrics: *Metrics, text: []const u8, record: bool) !void {    _ = try writeTranscriptReturningEntryIdClassified(self, alloc, metrics, text, record, .unknown_raw);} pub fn writeTranscriptClassified(    self: anytype,    alloc: Allocator,    metrics: *Metrics,    text: []const u8,    record: bool,    class: RawEntryClass,) !void {    _ = try writeTranscriptReturningEntryIdClassified(self, alloc, metrics, text, record, class);} pub fn writeTranscriptReturningEntryIdClassified(    self: anytype,    alloc: Allocator,    metrics: *Metrics,    text: []const u8,    record: bool,    class: RawEntryClass,) !u32 {    if (text.len == 0) return 0;     if (!record) {        try self.writeTranscriptBytes(alloc, metrics, text, false);        return 0;    }     const entry_id = try transcript_store.writeRecordedTranscriptClassifiedAtomic(        self,        alloc,        metrics,        text,        class,    );    self.forgetShimmer();    return entry_id;} pub fn reconstructivePaint(self: anytype, alloc: Allocator, metrics: *Metrics) !void {    _ = metrics;    debug_trace.logf("reconstructive", "enter cols={d} rows={d} content_bottom={d} entries={d} cap={d}", .{ self.layout.cols, self.layout.rows, self.layout.content_bottom, self.entries.items.len, self.max_transcript_bytes });    try self.ensurePaintReservation(alloc);    try self.rebuildTranscriptCacheFromEntries(alloc, "reconstructive paint");    self.recomputeCursorFromTranscript();    self.pending_scroll_compact = false;    self.invalidateTranscriptAnchor("reconstructive_paint");    self.viewport_clear_pending = true;    requestTranscriptPaint(self, false, null);    recordTranscriptFrameInvalidation(self, .diagnostic_wipe);    debug_trace.logf(        "frame_plan",        "reconstructive_paint_frame_deferred rows={d} entries={d}",        .{ self.layout.rows, self.entries.items.len },    );    debug_trace.logf("reconstructive", "exit cursor={d},{d} viewport_top={d} transcript_bytes={d} replaceable={s}", .{ self.cursor_row, self.cursor_col, self.viewport_top_row, self.transcript.items.len, if (self.replaceable_last_line) "true" else "false" });}