sprawling-console-ffi 0.0.12

The Zig leaf that draws the console's frames, offered as one safe function.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Copyright (c) 2026 2youg1 and the sprawling contributors

// One frame on the main screen, as one synchronized update (mode 2026):
// the live region drawn last time erased from its first row down, the
// new transcript lines written where it stood, and the live region drawn
// again under them with the cursor put back in the composer. A terminal
// that does not know mode 2026 ignores it and draws the same bytes.

const std = @import("std");
const part = @import("part.zig");
const scene = @import("scene.zig");
const paint = @import("paint.zig");
const transcript = @import("transcript.zig");
const live = @import("live.zig");
const Grid = @import("grid.zig").Grid;

pub const Error = transcript.Error;

/// What a frame left on the screen that the next one needs: how many
/// rows the cursor stands below the live region's first row.
pub const Left = struct { cursor_row: usize };

pub fn inline_frame(out: *std.Io.Writer, r: *scene.Reader) Error!Left {
    const columns = try r.short();
    const erase = try r.short();
    const before = try r.byte();
    const entries = try r.word();
    try out.writeAll("\x1b[?2026h\x1b[?25l");
    if (erase > 0) try out.print("\x1b[{d}A", .{erase});
    try out.writeAll("\r\x1b[J");
    var p = paint.Painter.init(out, columns);
    const grid = Grid.of(&p);
    var previous: ?part.Part = std.enums.fromInt(part.Part, before);
    for (0..entries) |_| {
        const tag = try r.tag();
        if (transcript.spaced(previous, tag)) try p.end();
        try transcript.entry(&p, r, tag, grid);
        previous = tag;
    }
    var left: Left = .{ .cursor_row = 0 };
    if (!r.done()) {
        const top = p.row;
        const cursor = try live.region(&p, r, grid);
        const up = p.row - cursor.row;
        if (up > 0) try out.print("\x1b[{d}A", .{up});
        try out.print("\x1b[{d}G", .{cursor.column + 1});
        left.cursor_row = cursor.row - top;
    }
    // The cursor was hidden while the frame was drawn; it is shown again
    // whether or not a live region put it back in the composer.
    try out.writeAll("\x1b[?25h\x1b[?2026l");
    return left;
}