PranitInitial commit439f83c3 months agoHistory
850 lines · 31.6 KB · Zig
RawDownload
const std = @import("std");const types = @import("../../core/shared/types.zig");const activity_overlay = @import("activity_overlay.zig");const footer_layout = @import("footer_layout.zig");const viewport_selection = @import("viewport_selection.zig"); const Layout = types.Layout; pub const CellOwner = enum {    preserved_shell,    transcript,    gap,    activity,    footer,    diagnostic_clear,}; pub const FrameBand = struct {    top: u16,    bottom: u16,    owner: CellOwner,     pub fn empty(owner: CellOwner) FrameBand {        return .{ .top = 0, .bottom = 0, .owner = owner };    }     pub fn isEmpty(self: FrameBand) bool {        return self.top == 0 and self.bottom == 0;    }     pub fn containsRow(self: FrameBand, row: u16) bool {        return !self.isEmpty() and row >= self.top and row <= self.bottom;    }}; pub fn activityBand(activity: activity_overlay.ActivityPlacement) FrameBand {    return switch (activity) {        .transient_row => |transient| .{            .top = transient.tool_row orelse transient.row,            .bottom = transient.row +| transient.row_count -| 1,            .owner = .activity,        },        .none, .overlay_entry => FrameBand.empty(.activity),    };} pub fn paintedContentBand(plan: PaintPlan) FrameBand {    var top: u16 = 0;    var bottom: u16 = 0;    includeBand(&top, &bottom, plan.transcript_band);    includeBand(&top, &bottom, plan.activity_band);    includeBand(&top, &bottom, plan.footer_band);    if (top == 0) return FrameBand.empty(.transcript);    return .{ .top = top, .bottom = bottom, .owner = .transcript };} pub fn paintedBandChange(plan: PaintPlan, previous: FrameBand) ?FrameBand {    const current = paintedContentBand(plan);    const clamped_previous = clampBandToRows(previous, plan.layout.rows);    if (bandsSameRange(clamped_previous, current)) return null;    return unionBands(clamped_previous, current, .transcript);} pub const FrameInvalidationReason = enum {    resize,    owned_band_change,    viewport_reanchor,    terminal_scroll,    external_clear,    toggle_command_output,    partial_write,    shadow_feed_failure,    diagnostic_wipe,    reserved_gap_clear,}; pub fn frameInvalidationSeverity(reason: FrameInvalidationReason) u8 {    return switch (reason) {        .reserved_gap_clear => 0,        .external_clear, .toggle_command_output => 1,        .owned_band_change => 2,        .viewport_reanchor => 2,        .terminal_scroll => 3,        .resize => 4,        .partial_write => 5,        .shadow_feed_failure => 6,        .diagnostic_wipe => 7,    };} pub fn moreSevereFrameInvalidation(    a: FrameInvalidationReason,    b: FrameInvalidationReason,) FrameInvalidationReason {    return if (frameInvalidationSeverity(a) >= frameInvalidationSeverity(b)) a else b;} pub const FrameInvalidationRange = struct {    reason: FrameInvalidationReason,    top: u16,    bottom: u16,}; pub const FrameInvalidationError = error{    TooManyFrameInvalidations,}; pub const max_frame_invalidation_ranges = 8; pub const FrameInvalidationSet = struct {    items: [max_frame_invalidation_ranges]FrameInvalidationRange = undefined,    len: u8 = 0,     pub fn empty() FrameInvalidationSet {        return .{ .items = undefined, .len = 0 };    }     pub fn append(self: *FrameInvalidationSet, range: FrameInvalidationRange) FrameInvalidationError!void {        if (self.len >= max_frame_invalidation_ranges) return error.TooManyFrameInvalidations;        self.items[self.len] = range;        self.len += 1;    }     pub fn replaceWith(self: *FrameInvalidationSet, range: FrameInvalidationRange) void {        self.items[0] = range;        self.len = 1;    }     pub fn collapsedWith(self: *const FrameInvalidationSet, incoming: FrameInvalidationRange) FrameInvalidationRange {        var collapsed = incoming;        for (self.ranges()) |range| {            collapsed.reason = moreSevereFrameInvalidation(collapsed.reason, range.reason);            collapsed.top = @min(collapsed.top, range.top);            collapsed.bottom = @max(collapsed.bottom, range.bottom);        }        return collapsed;    }     pub fn appendOrCollapse(self: *FrameInvalidationSet, range: FrameInvalidationRange) ?FrameInvalidationRange {        self.append(range) catch |err| switch (err) {            error.TooManyFrameInvalidations => {                const collapsed = self.collapsedWith(range);                self.replaceWith(collapsed);                return collapsed;            },        };        return null;    }     pub fn ranges(self: *const FrameInvalidationSet) []const FrameInvalidationRange {        return self.items[0..@as(usize, self.len)];    }     pub fn isEmpty(self: FrameInvalidationSet) bool {        return self.len == 0;    }     pub fn requiresFullBand(self: FrameInvalidationSet, band: FrameBand) bool {        if (band.isEmpty()) return false;        for (self.ranges()) |range| {            if (range.bottom >= band.top and range.top <= band.bottom) return true;        }        return false;    }     pub fn dominantReason(self: FrameInvalidationSet) ?FrameInvalidationReason {        if (self.len == 0) return null;        var reason = self.items[0].reason;        for (self.ranges()[1..]) |range| {            reason = moreSevereFrameInvalidation(reason, range.reason);        }        return reason;    }}; pub const FrameRepaintWindowOptions = struct {    include_transcript: bool = true,    retained_transcript_band: FrameBand = FrameBand.empty(.transcript),}; pub const FrameRepaintWindow = struct {    set: FrameInvalidationSet = FrameInvalidationSet.empty(),     pub fn fromPlan(plan: PaintPlan, options: FrameRepaintWindowOptions) FrameRepaintWindow {        var window = FrameRepaintWindow{};        const transcript_exclusion = transcriptExclusion(options, plan);        if (transcript_exclusion.isEmpty()) {            if (options.include_transcript) window.appendBand(plan.transcript_band);        } else {            window.appendBandExcluding(plan.transcript_band, transcript_exclusion);        }        window.appendBand(plan.blank_band);        window.appendBand(plan.activity_band);        window.appendBand(plan.footer_gap_band);        window.appendBand(plan.footer_band);        for (plan.invalidation.ranges()) |range| {            window.appendInvalidationRange(plan, options, range);        }        return window;    }     pub fn validate(self: FrameRepaintWindow, plan: PaintPlan) PaintPlanError!void {        for (self.ranges()) |range| {            if (range.top == 0 or range.bottom == 0 or range.bottom < range.top) return error.InvalidInvalidationRange;            if (range.bottom > plan.layout.rows) return error.InvalidInvalidationRange;            if (rangeIntersectsBand(range, plan.preserved_band)) return error.InvalidBand;        }    }     pub fn ranges(self: *const FrameRepaintWindow) []const FrameInvalidationRange {        return self.set.ranges();    }     pub fn isEmpty(self: FrameRepaintWindow) bool {        return self.set.isEmpty();    }     pub fn retryInvalidations(self: *const FrameRepaintWindow, reason: FrameInvalidationReason) FrameInvalidationSet {        var invalidations = FrameInvalidationSet.empty();        const repaint_ranges = self.ranges();        if (repaint_ranges.len == 0) return invalidations;         var retry = FrameInvalidationRange{            .reason = reason,            .top = repaint_ranges[0].top,            .bottom = repaint_ranges[0].bottom,        };        for (repaint_ranges[1..]) |range| {            retry.top = @min(retry.top, range.top);            retry.bottom = @max(retry.bottom, range.bottom);        }        invalidations.replaceWith(retry);        return invalidations;    }     fn appendBand(self: *FrameRepaintWindow, band: FrameBand) void {        if (band.isEmpty()) return;        self.appendRange(.{            .reason = .owned_band_change,            .top = band.top,            .bottom = band.bottom,        });    }     fn appendBandExcluding(self: *FrameRepaintWindow, band: FrameBand, excluded: FrameBand) void {        if (band.isEmpty()) return;        var segments: [4]FrameInvalidationRange = undefined;        segments[0] = .{            .reason = .owned_band_change,            .top = band.top,            .bottom = band.bottom,        };        const len = excludeBandFromRanges(&segments, 1, excluded);        for (segments[0..len]) |segment| {            self.appendRange(segment);        }    }     fn appendRange(self: *FrameRepaintWindow, range: FrameInvalidationRange) void {        if (range.top == 0 or range.bottom == 0 or range.bottom < range.top) return;        var merged = range;        var i: usize = 0;        while (i < self.set.len) {            const existing = self.set.items[i];            if (!rangesTouchOrOverlap(existing, merged)) {                i += 1;                continue;            }            merged.reason = moreSevereFrameInvalidation(existing.reason, merged.reason);            merged.top = @min(existing.top, merged.top);            merged.bottom = @max(existing.bottom, merged.bottom);            removeRangeAt(&self.set, i);        }        _ = self.set.appendOrCollapse(merged);    }     fn appendInvalidationRange(        self: *FrameRepaintWindow,        plan: PaintPlan,        options: FrameRepaintWindowOptions,        range: FrameInvalidationRange,    ) void {        var segments: [4]FrameInvalidationRange = undefined;        segments[0] = range;        var len: usize = 1;        len = excludeBandFromRanges(&segments, len, plan.preserved_band);        len = excludeBandFromRanges(&segments, len, transcriptExclusion(options, plan));        for (segments[0..len]) |segment| {            self.appendRange(segment);        }    }}; fn transcriptExclusion(options: FrameRepaintWindowOptions, plan: PaintPlan) FrameBand {    if (!options.retained_transcript_band.isEmpty()) return options.retained_transcript_band;    if (!options.include_transcript) return plan.transcript_band;    return FrameBand.empty(.transcript);} pub const FrameCursorTarget = struct {    row: u16,    col: u16,    visible: bool,}; pub const TranscriptPaintResult = struct {    painted_rows: u16,    first_row: u16,    last_row: u16,    replaceable_start_row: u16,}; pub const ActivityPaintResult = struct {    painted: bool,    row: u16,    overlay: bool,}; pub const FooterReservationSource = enum {    none,    footer_layout,    transient_activity,    idle_footer_gap,    resize_reflow,}; pub const FooterPaintResult = struct {    painted_rows: u16,    top_row: u16,    cursor_row: u16,    cursor_col: u16,}; pub const PaintPlanError = error{    InvalidPaintPlan,    InvalidTerminalDimensions,    TerminalTooSmall,    InvalidBand,    BandOverlap,    InvalidActivityPlacement,    InvalidInvalidationRange,    CursorOutOfBounds,}; pub const PaintPlan = struct {    layout: Layout,    viewport: viewport_selection.ViewportSelection,    footer: footer_layout.FooterRows,    activity: activity_overlay.ActivityPlacement,    preserved_band: FrameBand,    transcript_band: FrameBand,    blank_band: FrameBand = .{ .top = 0, .bottom = 0, .owner = .gap },    activity_band: FrameBand,    footer_gap_band: FrameBand = .{ .top = 0, .bottom = 0, .owner = .gap },    footer_band: FrameBand,    invalidation: FrameInvalidationSet,    footer_clean_allowed: bool,    synchronized_update: bool,    cursor_target: ?FrameCursorTarget,    footer_reservation_source: FooterReservationSource,    bottom_reserved_rows: u16,    preserve_scrollback: bool,    reset_terminal: bool = false,     pub fn validate(self: PaintPlan) PaintPlanError!void {        if (self.layout.rows == 0 or self.layout.cols == 0) return error.InvalidTerminalDimensions;        if (self.viewport.top_row == 0) return error.InvalidPaintPlan;        if (self.viewport.bottom_row < self.viewport.top_row) return error.InvalidPaintPlan;        if (self.viewport.bottom_row > self.layout.rows) return error.InvalidPaintPlan;        if (self.viewport.last_visible_row > self.layout.rows) return error.InvalidPaintPlan;        if (self.footer.top == 0 or self.footer.top > self.layout.rows) return error.InvalidPaintPlan;        if (self.footer.hint < self.footer.top) return error.InvalidPaintPlan;         try validateBandInLayout(self.layout, self.preserved_band, .preserved_shell, true);        try validateBandInLayout(self.layout, self.transcript_band, .transcript, true);        try validateBandInLayout(self.layout, self.blank_band, .gap, true);        try validateBandInLayout(self.layout, self.activity_band, .activity, true);        try validateBandInLayout(self.layout, self.footer_gap_band, .gap, true);        try validateFooterBand(self.layout, self.footer, self.footer_band);         try rejectBandOverlap(self.preserved_band, self.transcript_band);        try rejectBandOverlap(self.preserved_band, self.blank_band);        try rejectBandOverlap(self.preserved_band, self.activity_band);        try rejectBandOverlap(self.preserved_band, self.footer_gap_band);        try rejectBandOverlap(self.preserved_band, self.footer_band);        try rejectBandOverlap(self.transcript_band, self.blank_band);        try rejectBandOverlap(self.transcript_band, self.activity_band);        try rejectBandOverlap(self.transcript_band, self.footer_gap_band);        try rejectBandOverlap(self.transcript_band, self.footer_band);        try rejectBandOverlap(self.blank_band, self.activity_band);        try rejectBandOverlap(self.blank_band, self.footer_gap_band);        try rejectBandOverlap(self.blank_band, self.footer_band);        try rejectBandOverlap(self.activity_band, self.footer_gap_band);        try rejectBandOverlap(self.footer_band, self.activity_band);        try rejectBandOverlap(self.footer_gap_band, self.footer_band);         try self.validateActivityPlacement();        try validateInvalidationRanges(self);        if (self.cursor_target) |cursor| try validateCursor(self.layout, cursor);    }     fn validateActivityPlacement(self: PaintPlan) PaintPlanError!void {        switch (self.activity) {            .none => {                if (!self.activity_band.isEmpty()) return error.InvalidActivityPlacement;            },            .transient_row => |activity| {                if (activity.row == 0 or activity.row > self.layout.rows) return error.InvalidActivityPlacement;                if (!self.activity_band.containsRow(activity.row)) return error.InvalidActivityPlacement;                if (activity.tool_row) |tool_row| {                    if (tool_row == 0 or tool_row +| 2 != activity.row) return error.InvalidActivityPlacement;                    if (!self.activity_band.containsRow(tool_row)) return error.InvalidActivityPlacement;                }                if (self.activity.transcriptReservationRows() == 0) return error.InvalidActivityPlacement;            },            .overlay_entry => |row| {                if (row == 0 or row > self.layout.rows) return error.InvalidActivityPlacement;                if (!self.transcript_band.containsRow(row)) return error.InvalidActivityPlacement;                if (!self.activity_band.isEmpty()) return error.InvalidActivityPlacement;            },        }    }}; pub fn validateBandInLayout(layout: Layout, band: FrameBand, expected_owner: CellOwner, allow_empty: bool) PaintPlanError!void {    if (band.owner != expected_owner) return error.InvalidBand;    if (band.isEmpty()) {        if (allow_empty) return;        return error.InvalidBand;    }    if (band.top == 0 or band.bottom == 0 or band.bottom < band.top) return error.InvalidBand;    if (band.bottom > layout.rows) return error.InvalidBand;} pub fn bandsOverlap(a: FrameBand, b: FrameBand) bool {    if (a.isEmpty() or b.isEmpty()) return false;    return a.bottom >= b.top and b.bottom >= a.top;} fn rejectBandOverlap(a: FrameBand, b: FrameBand) PaintPlanError!void {    if (bandsOverlap(a, b)) return error.BandOverlap;} pub fn validateFooterBand(layout: Layout, footer: footer_layout.FooterRows, band: FrameBand) PaintPlanError!void {    if (band.isEmpty()) {        if (footer.total_rows != 0) return error.InvalidBand;        return;    }    try validateBandInLayout(layout, band, .footer, false);    if (band.top != footer.top or band.bottom != @min(footer.hint, layout.rows)) return error.InvalidBand;} pub fn validateInvalidationRange(plan: PaintPlan, range: FrameInvalidationRange) PaintPlanError!void {    if (range.top == 0 or range.bottom == 0 or range.bottom < range.top) return error.InvalidInvalidationRange;    if (range.bottom > plan.layout.rows) return error.InvalidInvalidationRange;    switch (range.reason) {        .diagnostic_wipe, .reserved_gap_clear, .terminal_scroll, .viewport_reanchor => return,        .resize, .owned_band_change, .external_clear, .toggle_command_output, .partial_write, .shadow_feed_failure => {},    }    if (invalidationIntersectsOwnedBand(plan, range)) return;    return error.InvalidInvalidationRange;} pub fn invalidationIntersectsOwnedBand(plan: PaintPlan, range: FrameInvalidationRange) bool {    return rangeIntersectsBand(range, plan.transcript_band) or        rangeIntersectsBand(range, plan.blank_band) or        rangeIntersectsBand(range, plan.footer_band) or        rangeIntersectsBand(range, plan.activity_band) or        rangeIntersectsBand(range, plan.footer_gap_band);} fn validateInvalidationRanges(plan: PaintPlan) PaintPlanError!void {    for (plan.invalidation.ranges()) |range| {        try validateInvalidationRange(plan, range);    }} fn rangeIntersectsBand(range: FrameInvalidationRange, band: FrameBand) bool {    if (band.isEmpty()) return false;    return range.bottom >= band.top and range.top <= band.bottom;} fn rangesTouchOrOverlap(a: FrameInvalidationRange, b: FrameInvalidationRange) bool {    return a.bottom +| 1 >= b.top and b.bottom +| 1 >= a.top;} fn removeRangeAt(set: *FrameInvalidationSet, index: usize) void {    var i = index;    while (i + 1 < set.len) : (i += 1) {        set.items[i] = set.items[i + 1];    }    set.len -= 1;} fn excludeBandFromRanges(    ranges: *[4]FrameInvalidationRange,    len: usize,    band: FrameBand,) usize {    if (band.isEmpty() or len == 0) return len;

Lines 1–500 of 850.