// console_ui — FlashOS shared terminal look. // // One module, compiled into every binary that draws to the console: the kernel // boot log and the userspace tools (fsh, login, dmesg, …). Editing this module // restyles the whole system on the next build — there is no second copy of a // bracket tag or an ANSI code anywhere else in the tree. // // Layout: the look is split by concern so it scales as the UI grows, but it // stays a single import — consumers only ever `@import("console_ui")`. // * palette.flash — the `color` knob + the ANSI palette // * tags.flash — the `Level` severity taxonomy + each level's `Tag` // * this file — the `Sink`, the renderers, the `Logger`, and the // homescreen; it re-exports the two above so a consumer // reaches the whole surface through one name. // // Freestanding by construction: no allocator, no std, no dependency on kernel // internals or flibc. Output is routed through a caller-supplied `Sink`, so the // same renderers serve the kernel (main_output) and userspace (write(2)) with // neither side leaking in. Because it is pure and target-agnostic, each // consumer recompiles it with its own settings. pub use "palette" as palette pub use "tags" as tags pub use "screen" as screen // ---- public surface (flat re-exports) -------------------------------------- // The hot names a consumer reaches for, lifted to the top level so call sites // read `console_ui.ok` / `console_ui.color` rather than digging through a // sub-namespace. The full palette + taxonomy stay reachable as `console_ui. // palette.*` / `console_ui.tags.*`. pub const color = palette.color pub const Level = tags.Level pub const Tag = tags.Tag pub const ok = tags.ok pub const info = tags.info pub const load = tags.load pub const warn = tags.warn pub const fail = tags.fail pub const skip = tags.skip /// A byte sink. Each consumer binds it to its own console writer: /// kernel -> a byte loop over main_output_char(MU, b) /// user -> write(1, bytes.ptr, bytes.len) pub const Sink = *fn([]u8) void /// Box-drawing charset for the screen-layer panels — single-sourced in /// palette.flash and re-exported here so call sites keep reading /// `console_ui.unicode`. false = ASCII (+-|), true = Unicode. pub const unicode bool = palette.unicode /// Boot-success marker — the homescreen tail. Frozen: scripts/run_qemu_test.sh /// greps this literal (x3 per boot) as the boot pass signal. Single source of /// truth — do not reword without updating the contract header in /// scripts/run_qemu_test.sh. pub const marker_ready = " - type 'help' for commands" // ---- renderers ------------------------------------------------------------- /// Write a tag as `
` with the brackets + padding in the
/// default color and only `word` tinted by `t.ansi`. Color off => both ANSI
/// strings are empty and the bytes are the plain six-wide `[ OK ]` form.
fn writeTag(sink Sink, t Tag) void {
    sink(t.pre)
    sink(t.ansi)
    sink(t.word)
    sink(palette.reset)
    sink(t.post)
}

/// Write a tag followed by a single space, with no message and no newline — the
/// seam for a line whose tail is assembled by the caller (e.g. a boot line that
/// interleaves dynamic digits).
pub fn tagged(sink Sink, t Tag) void {
    writeTag(sink, t)
    sink(" ")
}

/// Write one finished tagged line: ` \n`, the tag colored when
/// enabled.
pub fn line(sink Sink, t Tag, msg []u8) void {
    tagged(sink, t)
    sink(msg)
    sink("\n")
}

/// A pending stage that resolves in place. `stage()` prints `[LOAD] ` with
/// no newline; a later `.done()` / `.failed()` carriage-returns to column 0 and
/// overwrites the tag, then ends the line. Same width + same message text means
/// the overwrite is exact even with color on (the escapes are zero-width).
pub const Stage = struct {
    sink Sink,
    msg []u8,

    /// Flip the pending tag to green [ OK ] and finish the line.
    pub fn done(self Stage) void {
        self.resolve(ok)
    }

    /// Flip the pending tag to red [FAIL] and finish the line.
    pub fn failed(self Stage) void {
        self.resolve(fail)
    }

    fn resolve(self Stage, t Tag) void {
        self.sink("\r")
        line(self.sink, t, self.msg)
    }
}

/// Begin a pending stage: prints `[LOAD] ` (no newline yet). Resolve it
/// with `.done()` or `.failed()`.
pub fn stage(sink Sink, msg []u8) Stage {
    tagged(sink, load)
    sink(msg)
    return .{ .sink = sink, .msg = msg }
}

/// A plain banner / homescreen line (text + newline). Placeholder seam for the
/// richer panel + key/value renderers, which land when a screen needs them.
pub fn banner(sink Sink, text []u8) void {
    sink(text)
    sink("\n")
}

/// A `Sink` bound once, so a consumer logs `log.ok("…")` instead of repeating
/// the sink at every call. Pure sugar over the free `line` renderer — the look
/// is unchanged. The free renderers stay available for one-off and assembled
/// lines.
pub const Logger = struct {
    sink Sink,

    pub fn ok(self Logger, msg []u8) void {
        line(self.sink, tags.ok, msg)
    }
    pub fn info(self Logger, msg []u8) void {
        line(self.sink, tags.info, msg)
    }
    pub fn warn(self Logger, msg []u8) void {
        line(self.sink, tags.warn, msg)
    }
    pub fn fail(self Logger, msg []u8) void {
        line(self.sink, tags.fail, msg)
    }
    pub fn skip(self Logger, msg []u8) void {
        line(self.sink, tags.skip, msg)
    }
    /// Log at a runtime-chosen level.
    pub fn status(self Logger, level Level, msg []u8) void {
        line(self.sink, tags.of(level), msg)
    }
}

/// Bind a `Sink` into a `Logger`.
pub fn logger(sink Sink) Logger {
    return .{ .sink = sink }
}

/// FlashOS shell homescreen: `FlashOS [v] by  - type 'help'
/// for commands`, followed by a blank line. `version` and `author` are passed
/// in (this module is freestanding) — fsh feeds `build_options.version`, itself
/// sourced from build.zig.zon, so the release version lives in exactly one
/// place. The `type 'help' for commands` tail is the frozen boot-success marker
/// (run_qemu_test.sh greps it x3) — keep it byte-for-byte.
pub fn homescreen(sink Sink, version []u8, author []u8) void {
    sink("FlashOS [v")
    sink(version)
    sink("] by ")
    sink(author)
    sink(marker_ready)
    sink("\n\n")
}