PranitInitial commit439f83c3 months agoHistory
1376 lines · 45.5 KB · Zig
RawDownload
const std = @import("std");const builtin = @import("builtin");const debug_trace = @import("../core/shared/debug_trace.zig");const io_mod = @import("../core/shared/io.zig");const record_tape = @import("../core/workspace/record_tape.zig");const types = @import("../core/shared/types.zig");const transcript_runtime = @import("transcript/runtime.zig");const cursor_probe = @import("terminal/cursor_probe.zig");const ui_terminal = @import("terminal/terminal.zig");const paint_plan = @import("render_engine/paint_plan.zig"); const Layout = types.Layout;const Metrics = types.Metrics;const TranscriptRuntime = transcript_runtime.TranscriptRuntime;const ResizeObservation = transcript_runtime.ResizeObservation;const ResizeObservationPhase = transcript_runtime.ResizeObservationPhase;const ResizeObservationResult = transcript_runtime.ResizeObservationResult; pub const CursorPosition = cursor_probe.Position; pub const FrameCommit = enum {    none,    light,    reset,}; pub const supports_resize_signal = switch (builtin.os.tag) {    .linux,    .plan9,    .illumos,    .netbsd,    .openbsd,    .haiku,    .macos,    .ios,    .watchos,    .tvos,    .visionos,    .dragonfly,    .freebsd,    .serenity,    => true,    else => false,}; pub const ResizeApprovalInterlock = struct {    const resize_pending_bit: u8 = 1 << 0;    const affirmative_claimed_bit: u8 = 1 << 1;    const clear_resize_pending_mask: u8 = ~resize_pending_bit;    const clear_affirmative_claimed_mask: u8 = ~affirmative_claimed_bit;     // The signal handler only sets resize_pending_bit; event-loop code owns all clears.    state: std.atomic.Value(u8) = .init(0),     pub fn noteResizeSignal(self: *ResizeApprovalInterlock) void {        _ = self.state.fetchOr(resize_pending_bit, .monotonic);    }     pub fn takeResizePending(self: *ResizeApprovalInterlock) bool {        const previous = self.state.fetchAnd(            clear_resize_pending_mask,            .acq_rel,        );        return previous & resize_pending_bit != 0;    }     pub fn claimAffirmative(self: *ResizeApprovalInterlock) bool {        return self.state.cmpxchgStrong(            0,            affirmative_claimed_bit,            .acq_rel,            .acquire,        ) == null;    }     pub fn releaseAffirmative(self: *ResizeApprovalInterlock) void {        const previous = self.state.fetchAnd(            clear_affirmative_claimed_mask,            .release,        );        std.debug.assert(previous & affirmative_claimed_bit != 0);    }     pub fn resizePending(self: *const ResizeApprovalInterlock) bool {        return self.state.load(.acquire) & resize_pending_bit != 0;    }     fn affirmativeClaimed(self: *const ResizeApprovalInterlock) bool {        return self.state.load(.acquire) & affirmative_claimed_bit != 0;    }}; pub const RedrawMode = enum {    resize_light,    replay_viewport,    reanchor_top,}; test "resize pending wins before an affirmative claim" {    var interlock = ResizeApprovalInterlock{};     interlock.noteResizeSignal();     try std.testing.expect(!interlock.claimAffirmative());    try std.testing.expect(interlock.resizePending());    try std.testing.expect(interlock.takeResizePending());    try std.testing.expect(!interlock.resizePending());} test "affirmative claim preserves a later resize through release" {    var interlock = ResizeApprovalInterlock{};     try std.testing.expect(interlock.claimAffirmative());    try std.testing.expect(interlock.affirmativeClaimed());    interlock.noteResizeSignal();    interlock.releaseAffirmative();     try std.testing.expect(!interlock.affirmativeClaimed());    try std.testing.expect(interlock.resizePending());    try std.testing.expect(interlock.takeResizePending());} fn measuredCursorRowDelta(expected_row: u32, actual_row: u16) i32 {    const expected: i64 = @intCast(expected_row);    const actual: i64 = actual_row;    return @intCast(std.math.clamp(        expected - actual,        @as(i64, std.math.minInt(i32)),        @as(i64, std.math.maxInt(i32)),    ));} fn expectedResizeCursorRow(shell: *const TranscriptRuntime, new_layout: Layout) ?u32 {    if (shell.owned_top_row != 1 or        (new_layout.cols == shell.layout.cols and new_layout.rows == shell.layout.rows))    {        return null;    }    return shell.estimatedReflowedCursorRow(new_layout.cols);} pub fn requestRedraw(    shell: *TranscriptRuntime,    metrics: *Metrics,    mode: RedrawMode,) !void {    debug_trace.logf("resize", "request_redraw mode={s} layout={d}x{d} viewport_top={d} content_bottom={d} cursor={d},{d}", .{ @tagName(mode), shell.layout.cols, shell.layout.rows, shell.viewport_top_row, shell.layout.content_bottom, shell.cursor_row, shell.cursor_col });    switch (mode) {        .resize_light, .replay_viewport => {            if (shell.cursor_row > shell.layout.content_bottom) {                shell.cursor_row = shell.layout.content_bottom;            }            shell.viewport_clear_pending = true;            shell.markTranscriptDirty();            if (mode == .replay_viewport) {                shell.reserveVisibleViewportRows(shell.min_visible_viewport_rows);            }            shell.footer_viewport.invalidateAfterExternalClear();            try recordOwnedBandInvalidation(shell, .resize);        },        .reanchor_top => try reanchorTopForFrame(shell, metrics),    }    shell.render_requests.request(.footer);} fn reanchorTopForFrame(shell: *TranscriptRuntime, metrics: *Metrics) !void {    metrics.full_redraws += 1;    shell.cursor_row = shell.owned_top_row;    shell.cursor_col = 1;    shell.viewport_top_row = shell.owned_top_row;    shell.pending_scroll_compact = false;    shell.invalidateTranscriptAnchor("reanchor_top_frame");    shell.markTranscriptDirty();    shell.footer_viewport.invalidateAfterExternalClear();    try recordOwnedBandInvalidation(shell, .viewport_reanchor);    debug_trace.logf(        "frame_plan",        "reanchor_top_frame_deferred owned_top={d} rows={d}",        .{ shell.owned_top_row, shell.layout.rows },    );} pub fn collectResizeFacts(    terminal: anytype,    shell: *TranscriptRuntime,    metrics: *Metrics,    probe: *cursor_probe.Parser,    resize_interlock: *ResizeApprovalInterlock,    footer_rows: u16,    debounce_ms: i64,    cursor_probe_allowed: bool,) !void {    const now = io_mod.milliTimestamp();     if (supports_resize_signal) {        if (cursor_probe_allowed) resumeResizeCursorProbeAfterPaste(probe, now);        switch (probe.poll(now)) {            .none => {},            .probe_timed_out => {                debug_trace.logf("resize", "cursor_measure_failed err=Timeout", .{});                finishPendingResizeProbe(shell, null);            },            .late_window_expired => {                debug_trace.logf("resize", "cursor_measure_late_window_expired", .{});            },        }         _ = admitResizeSignal(            shell,            resize_interlock,            now,            debounce_ms,            "facts",        );         if (resizeObservationAwaiting(shell.pending_resize_observation)) return;         if (shell.render_requests.resize_dirty) {            if (queuedObservationMatchesDeadline(shell)) {                if (now < shell.render_requests.resize_apply_after_ms) return;                try settlePendingResize(                    terminal,                    shell,                    metrics,                    probe,                    footer_rows,                    debounce_ms,                    cursor_probe_allowed,                    now,                );                return;            }            // Position replies are indistinguishable from pasted bytes, so            // leave live geometry pending until bracketed paste releases input            // ownership and the shared parser can begin.            if (!cursor_probe_allowed or !probe.canBegin()) return;            try observeDirtyResize(                terminal,                shell,                metrics,                probe,                footer_rows,                now,            );            return;        }        if (!shell.render_requests.pending_settled_width_reflow or            now < shell.render_requests.resize_apply_after_ms)        {            return;        }        try settlePendingResize(            terminal,            shell,            metrics,            probe,            footer_rows,            debounce_ms,            cursor_probe_allowed,            now,        );        return;    }     const next_layout = terminal.queryLayout(footer_rows) catch |err| {        try handleInvalidResize(shell, err);        return;    };    if (next_layout.rows == shell.layout.rows and next_layout.cols == shell.layout.cols and !shell.terminal_dimensions_invalid) {        return;    }     shell.layout = next_layout;    shell.terminal_dimensions_invalid = false;    try recordOwnedBandInvalidation(shell, .resize);    metrics.debounced_resizes += 1;    try requestRedraw(shell, metrics, .replay_viewport);} pub fn admitResizeSignal(    shell: *TranscriptRuntime,    resize_interlock: *ResizeApprovalInterlock,    now_ms: i64,    debounce_ms: i64,    source: []const u8,) bool {    if (!supports_resize_signal or !resize_interlock.takeResizePending()) return false;    shell.render_requests.observeResizeSignal(now_ms, debounce_ms);    debug_trace.logf(        "frame_schedule",        "resize_block begin source={s} apply_after_ms={d} pending_reason_count={d}",        .{            source,            shell.render_requests.resize_apply_after_ms,            shell.render_requests.pendingReasonCount(),        },    );    return true;} pub fn lifecycleIdle(shell: anytype) bool {    return !shell.render_requests.resizeLifecyclePending() and        shell.pending_resize_observation == null and        !shell.terminal_dimensions_invalid;} pub fn blocksFrameCommit(shell: anytype) bool {    return shell.render_requests.blocksFrameCommit() or        resizeObservationAwaiting(shell.pending_resize_observation) or        resizeLightCommitted(shell.pending_resize_observation);} fn resizeLightReady(shell: anytype) bool {    const observation = shell.pending_resize_observation orelse return false;    return observation.phase == .live and        !resizeObservationAwaiting(observation) and        shell.render_requests.pending_settled_width_reflow and        !shell.terminal_reset_pending;} fn resizeResetQueued(shell: anytype) bool {    const observation = shell.pending_resize_observation orelse return false;    return observation.phase == .reset_queued and shell.terminal_reset_pending;} pub fn pendingFrameCommit(shell: anytype, resize_reason: bool) FrameCommit {    if (resizeResetQueued(shell)) return .reset;    if (resize_reason and resizeLightReady(shell)) return .light;    return .none;} pub fn acknowledgeFrameCommit(shell: anytype, commit: FrameCommit) void {    switch (commit) {        .none => {},        .light => {            const observation = shell.pending_resize_observation orelse return;            if (observation.phase != .live) return;            shell.pending_resize_observation.?.phase = .settle_pending;        },        .reset => {            const observation = shell.pending_resize_observation orelse return;            if (observation.phase != .reset_queued) return;            shell.pending_resize_observation = null;            shell.resize_history_row_delta = null;            shell.render_requests.acknowledgeSettledResizeCommit();            debug_trace.logf("resize", "settled_reset_committed", .{});        },    }} fn resizeObservationAwaiting(observation: ?ResizeObservation) bool {    const pending = observation orelse return false;    return switch (pending.result) {        .awaiting => true,        .measured, .unavailable => false,    };} fn resizeLightCommitted(observation: ?ResizeObservation) bool {    const pending = observation orelse return false;    return pending.phase == .settle_pending;} fn queuedObservationMatchesDeadline(shell: *const TranscriptRuntime) bool {    const observation = shell.pending_resize_observation orelse return false;    return observation.phase == .reset_queued and        observation.apply_after_ms == shell.render_requests.resize_apply_after_ms;} fn observeDirtyResize(    terminal: anytype,    shell: *TranscriptRuntime,    metrics: *Metrics,    probe: *cursor_probe.Parser,    footer_rows: u16,    now_ms: i64,) !void {    const next_layout = terminal.queryLayout(footer_rows) catch |err| {        try handleInvalidResize(shell, err);        if (!shell.render_requests.pending_settled_width_reflow) {            shell.pending_resize_observation = null;        } else if (shell.pending_resize_observation) |*observation| {            observation.result = .unavailable;            observation.apply_after_ms = shell.render_requests.resize_apply_after_ms;        }        shell.render_requests.completeResizeGeometry(false);        logResizeBlockEnd(shell);        return;    };     const recovering_invalid_dimensions = shell.terminal_dimensions_invalid;    const size_changed = next_layout.rows != shell.layout.rows or        next_layout.cols != shell.layout.cols;    const queued_reset = if (shell.pending_resize_observation) |observation|        observation.phase == .reset_queued    else        shell.terminal_reset_pending and            shell.render_requests.pending_settled_width_reflow;     if (!size_changed) {        if (recovering_invalid_dimensions) {            try applyResizeWithLayoutResolved(shell, metrics, next_layout, true, null);        }        if (shell.render_requests.pending_settled_width_reflow) {            const phase: ResizeObservationPhase = if (queued_reset)                .reset_queued            else if (resizeLightCommitted(shell.pending_resize_observation))                .settle_pending            else                .live;            shell.pending_resize_observation = .{                .layout = next_layout,                .apply_after_ms = shell.render_requests.resize_apply_after_ms,                .expected_row = null,                .result = .unavailable,                .phase = phase,            };            if (queued_reset) return;        } else {            shell.pending_resize_observation = null;        }        shell.render_requests.completeResizeGeometry(false);        logResizeBlockEnd(shell);        return;    }     const phase: ResizeObservationPhase = if (queued_reset) .reset_queued else .live;    try prepareResizeObservation(        terminal,        shell,        probe,        next_layout,        phase,        now_ms,    );    if (queued_reset) return;     try applyResizeWithLayoutResolved(shell, metrics, next_layout, false, null);    logResizeBlockEnd(shell);} fn prepareResizeObservation(    terminal: anytype,    shell: *TranscriptRuntime,    probe: *cursor_probe.Parser,    next_layout: Layout,    phase: ResizeObservationPhase,    now_ms: i64,) !void {    if (phase == .live) shell.resize_history_row_delta = null;    const expected_row = expectedResizeCursorRow(shell, next_layout);    var result: ResizeObservationResult = .unavailable;    if (expected_row != null and next_layout.cols >= cursor_probe.confirmation_tag_column and probe.canBegin()) {        const protocol = resizeCursorProbeProtocol();        const deadline_ms = addMillis(now_ms, cursor_probe.response_idle_timeout_ms);        try probe.begin(protocol, deadline_ms);        terminal.requestResizeCursorPosition(protocol) catch |err| {            probe.cancel();            debug_trace.logf("resize", "cursor_measure_failed err={s}", .{@errorName(err)});            shell.pending_resize_observation = .{                .layout = next_layout,                .apply_after_ms = shell.render_requests.resize_apply_after_ms,                .expected_row = expected_row,                .result = .unavailable,                .phase = phase,            };            return;        };        result = .awaiting;        debug_trace.logf(            "resize",            "cursor_measure_requested protocol={s} expected_reflow_row={d} apply_after_ms={d} deadline_ms={d} phase={s}",            .{                @tagName(protocol),                expected_row.?,                shell.render_requests.resize_apply_after_ms,                deadline_ms,                @tagName(phase),            },        );    } else if (expected_row != null and next_layout.cols < cursor_probe.confirmation_tag_column) {        debug_trace.logf(            "resize",            "cursor_measure_skipped reason=terminal_too_narrow cols={d}",            .{next_layout.cols},        );    }    shell.pending_resize_observation = .{        .layout = next_layout,        .apply_after_ms = shell.render_requests.resize_apply_after_ms,        .expected_row = expected_row,        .result = result,        .phase = phase,    };} fn settlePendingResize(    terminal: anytype,    shell: *TranscriptRuntime,    metrics: *Metrics,

Lines 1–500 of 1376.