FABRIC.md item 4.4x: split the REPL prompt into its own bottom strip
Scope expanded from pure CANVAS-rectangle arithmetic (as originally scoped) to also splitting the REPL prompt/input line out of the scrollback box into an independent single-line strip, per Captain Bob's explicit fold-in after the gap was reported (§25.0 rule 3) rather than silently expanded. vt100.c: VT100_BOX_ORIGIN_X/Y are no longer hardcoded per-arch literals -- both are now derived from fb_width()/fb_height() at vt100_enable_ttf() time. New vt100_strip_draw() renders the bottom strip (gray border lines, bright-white text) directly via the existing ttf_draw_glyph_cell() rasterizer, independent of the box's own grid/cursor state. Border lines are drawn after the glyph loop so an oversized cell can only be clipped by them, never erase them. console.c/console.h: console_fb_strip_draw() thin wrapper, matching the existing console_fb_enable_ttf()/console_fb_scroll_*() pattern. repl.c: builds a plain-text "[VMName] ok> <input>" mirror in g_strip_prompt/strip_refresh(), refreshed on every keystroke (including backspace) from sk_readline() -- already wired for item 4.4v, since keyboard-typed characters will flow through the same console_getc() path once that lands. Also widened sk_repl_step()/sk_repl_run()'s local input buffer from a second, smaller 256-byte buffer to INPUT_BUFFER_SIZE (1025), per 4.4w's decision. Verified: three-arch clean QEMU boot + logs, amd64 screendump showing the box and strip as two visually distinct regions with no visible glyph/border clipping. Punch list §25 item 4.4x complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
763c6f2cd5
commit
91742e02f4
@@ -317,6 +317,13 @@ void console_fb_scroll_fwd(uint32_t n)
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_strip_draw(const char *text)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_strip_draw(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single character from serial console (non-blocking)
|
||||
* Returns -1 if no character available
|
||||
|
||||
+97
-13
@@ -45,6 +45,7 @@
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "log.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Maximum CSI parameters */
|
||||
#define VT100_MAX_PARAMS 16
|
||||
@@ -82,23 +83,40 @@ static ttf_raster_cache_t g_ttf_cache;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* FABRIC.md item 4.4t: confine REPL text (TTF mode only) to the 640x480
|
||||
* CANVAS box computed in 4.4o and pixel-verified in 4.4p. Per-arch raster
|
||||
* top-left origin, from those items' measurements. Boot/POST (bitmap mode)
|
||||
* is untouched -- g_origin_x/g_origin_y default to (0,0) and only change
|
||||
* once vt100_enable_ttf() runs.
|
||||
* CANVAS box computed in 4.4o and pixel-verified in 4.4p. Boot/POST
|
||||
* (bitmap mode) is untouched -- g_origin_x/g_origin_y default to (0,0)
|
||||
* and only change once vt100_enable_ttf() runs.
|
||||
*
|
||||
* FABRIC.md item 4.4x: origin is now DERIVED from fb_width()/fb_height()
|
||||
* at vt100_enable_ttf() time rather than hardcoded per-arch literals --
|
||||
* both the old per-arch numbers (320/104 amd64, 80/4 aarch64+riscv64) and
|
||||
* the new ones below check out against the same formula, so this is a
|
||||
* strict generalization, not a behavior change for X. Y changes because
|
||||
* 4.4u/4.4w replaced 4.4m's 96px/4-line REPL strip with a single-line
|
||||
* strip pinned to the bottom of the screen (see VT100_STRIP_* below) --
|
||||
* the box now sits directly above that strip instead of assuming 96px.
|
||||
* origin_x = (fb_width() - VT100_BOX_W) / 2 -- horizontal center
|
||||
* origin_y = fb_height() - VT100_BOX_H -- box bottom edge
|
||||
* - VT100_STRIP_BOX_GAP_PX -- gap above strip
|
||||
* - (2*VT100_STRIP_GAP_PX + 1) -- strip height
|
||||
* --------------------------------------------------------------------- */
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#define VT100_BOX_ORIGIN_X 320u
|
||||
#define VT100_BOX_ORIGIN_Y 104u
|
||||
#else
|
||||
#define VT100_BOX_ORIGIN_X 80u
|
||||
#define VT100_BOX_ORIGIN_Y 4u
|
||||
#endif
|
||||
#define VT100_BOX_W 640u
|
||||
#define VT100_BOX_H 480u
|
||||
#define VT100_BOX_COLS 53u /* 640 / VT100_TTF_CELL_W_PX, exact */
|
||||
#define VT100_BOX_ROWS 20u /* 480 / VT100_TTF_CELL_H_PX, exact */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* FABRIC.md items 4.4u/4.4w/4.4x: the REPL prompt/input strip -- a
|
||||
* single-line region pinned to the bottom of the screen, independent of
|
||||
* the scrollback box's own grid (see vt100_strip_draw() below). Numbers
|
||||
* pinned by Captain Bob directly, 2026-08-12 (4.4w).
|
||||
* --------------------------------------------------------------------- */
|
||||
#define VT100_STRIP_GAP_PX 15u /* baseline-to-border-line gap, both sides, mirrored */
|
||||
#define VT100_STRIP_BOX_GAP_PX 8u /* gap between the strip's top border and the box's bottom edge */
|
||||
#define VT100_STRIP_BORDER_GRAY FB_RGB(0xAA, 0xAA, 0xAA) /* FB_ANSI_PALETTE[7] */
|
||||
#define VT100_STRIP_TEXT_WHITE FB_RGB(0xFF, 0xFF, 0xFF)
|
||||
#define VT100_STRIP_LEFT_MARGIN_PX(fbw) (((fbw) - VT100_BOX_W) / 2u) /* aligned under the box */
|
||||
|
||||
static uint32_t g_origin_x = 0; /* box top-left, TTF mode only; 0 in bitmap mode */
|
||||
static uint32_t g_origin_y = 0;
|
||||
|
||||
@@ -268,6 +286,68 @@ static void ttf_draw_glyph_cell(uint32_t cell_px, uint32_t cell_py, uint8_t ch,
|
||||
}
|
||||
}
|
||||
|
||||
/* FABRIC.md items 4.4u/4.4w/4.4x: draw the REPL prompt/input strip -- a
|
||||
* single-line region pinned to the bottom of the screen, independent of
|
||||
* the scrollback box's own grid and cursor state (this function never
|
||||
* touches g_vt, and nothing in the box's own put_char()/scroll_up() path
|
||||
* touches the strip). Reuses ttf_draw_glyph_cell() for the actual glyph
|
||||
* blit -- same rasterizer, same cache, as the box's own grid -- rather
|
||||
* than a second text-drawing path.
|
||||
*
|
||||
* Horizontal scroll (4.4u step 4): @p text is drawn tail-anchored once it
|
||||
* exceeds the strip's width. That is equivalent to cursor-anchored
|
||||
* scrolling because the caller (repl.c) only ever appends at, or
|
||||
* backspaces from, the end of the line -- there is no mid-line cursor
|
||||
* movement in this REPL, so "keep the cursor visible" and "keep the tail
|
||||
* visible" are the same window.
|
||||
*
|
||||
* Border lines are drawn LAST, after the glyph loop, not first: a glyph
|
||||
* cell's own opaque background fill (cell_h()=24px) can extend a few
|
||||
* pixels past the strip's 15px baseline-to-line gap for full-ascender
|
||||
* characters -- 4.4u's own record already flags these gap numbers as
|
||||
* "informal... need to be re-checked once actually built." Drawing the
|
||||
* borders last means an oversized cell can never erase them; it can only
|
||||
* be visually clipped by them, which is the correct failure direction.
|
||||
*
|
||||
* No-op before TTF mode is active. */
|
||||
void vt100_strip_draw(const char *text)
|
||||
{
|
||||
uint32_t fbw, fbh, bottom_line_y, top_line_y, baseline_y, cell_py;
|
||||
uint32_t margin_x, avail_px, avail_cols, len, start, i;
|
||||
|
||||
if (!g_ttf_ready || g_glyph_mode != VT_GLYPH_TTF) return;
|
||||
if (!text) text = "";
|
||||
|
||||
fbw = fb_width();
|
||||
fbh = fb_height();
|
||||
bottom_line_y = fbh - 1u;
|
||||
top_line_y = bottom_line_y - (2u * VT100_STRIP_GAP_PX);
|
||||
baseline_y = bottom_line_y - VT100_STRIP_GAP_PX;
|
||||
cell_py = baseline_y - VT100_TTF_SIZE_PX;
|
||||
margin_x = VT100_STRIP_LEFT_MARGIN_PX(fbw);
|
||||
|
||||
/* Clear the whole strip first -- both border lines get redrawn every
|
||||
* call regardless, so clearing border-to-border is simpler than
|
||||
* clearing just the interior and no less correct. */
|
||||
fb_fill_rect(0, top_line_y, fbw, bottom_line_y - top_line_y + 1u, VT100_DEFAULT_BG);
|
||||
|
||||
avail_px = (fbw > 2u * margin_x) ? (fbw - 2u * margin_x) : 0u;
|
||||
avail_cols = cell_w() ? (avail_px / cell_w()) : 0u;
|
||||
|
||||
len = (uint32_t)strlen(text);
|
||||
start = (len > avail_cols) ? (len - avail_cols) : 0u;
|
||||
|
||||
for (i = start; i < len; i++) {
|
||||
uint32_t col = i - start;
|
||||
ttf_draw_glyph_cell(margin_x + col * cell_w(), cell_py,
|
||||
(uint8_t)text[i],
|
||||
VT100_STRIP_TEXT_WHITE, VT100_DEFAULT_BG);
|
||||
}
|
||||
|
||||
fb_fill_rect(0, top_line_y, fbw, 1u, VT100_STRIP_BORDER_GRAY);
|
||||
fb_fill_rect(0, bottom_line_y, fbw, 1u, VT100_STRIP_BORDER_GRAY);
|
||||
}
|
||||
|
||||
static void draw_cursor_glyph(uint8_t ch)
|
||||
{
|
||||
uint32_t f = eff_fg();
|
||||
@@ -391,8 +471,12 @@ void vt100_enable_ttf(void)
|
||||
* scrollback allocation block just below depends on g_vt.cols via
|
||||
* g_line_stride, so origin/cols/rows MUST be set first or every
|
||||
* shadow/ring index silently corrupts instead of crashing. */
|
||||
g_origin_x = VT100_BOX_ORIGIN_X;
|
||||
g_origin_y = VT100_BOX_ORIGIN_Y;
|
||||
/* FABRIC.md item 4.4x: derived from the real framebuffer size, not a
|
||||
* hardcoded per-arch literal -- see the formula in this file's header
|
||||
* comment above VT100_BOX_W. */
|
||||
g_origin_x = VT100_STRIP_LEFT_MARGIN_PX(fb_width());
|
||||
g_origin_y = fb_height() - VT100_BOX_H - VT100_STRIP_BOX_GAP_PX
|
||||
- (2u * VT100_STRIP_GAP_PX + 1u);
|
||||
g_vt.cols = VT100_BOX_COLS;
|
||||
g_vt.rows = VT100_BOX_ROWS;
|
||||
g_vt.cx = g_vt.cy = 0;
|
||||
|
||||
+78
-2
@@ -35,6 +35,7 @@
|
||||
#include "starkernel/timer.h"
|
||||
#include "starkernel/arch.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* FABRIC.md 4.4: "ok>" (including its trailing space) renders in bright
|
||||
* cyan, 0x55FFFF -- reuses FB_ANSI_PALETTE[14]. Sent as a real SGR escape
|
||||
@@ -44,6 +45,72 @@
|
||||
|
||||
const char lithos_version[64] = LITHOS_VERSION_STR;
|
||||
|
||||
/*===========================================================================
|
||||
* FABRIC.md items 4.4u/4.4w/4.4x: the bottom REPL strip -- a plain-text
|
||||
* (no ANSI/SGR) mirror of "[VMName] ok> <input so far>", refreshed into
|
||||
* console_fb_strip_draw() on every keystroke. Built separately from
|
||||
* SK_PROMPT_TEXT rather than derived from it, since the strip renders in
|
||||
* one fixed color (4.4u) and has no SGR parser of its own -- the ANSI
|
||||
* version keeps going to console_puts() exactly as before, unaffected,
|
||||
* for serial and for the scrollback box's own history.
|
||||
*===========================================================================*/
|
||||
|
||||
#define SK_STRIP_PROMPT_MAX 80u
|
||||
|
||||
static char g_strip_prompt[SK_STRIP_PROMPT_MAX] = "ok> ";
|
||||
|
||||
/* Builds "[VMName] ok> " or "[VMName] zuse)ok> " (no VM name -> just the
|
||||
* suffix) into g_strip_prompt, bounded, no strcat -- mirrors emit_prefix()'s
|
||||
* bracket format (console.c) so the strip and the box agree on what a
|
||||
* prompt looks like, minus the ANSI color codes the strip doesn't use. */
|
||||
static void set_strip_prompt(VM *vm)
|
||||
{
|
||||
const char *vn = console_get_vm_name();
|
||||
char *p = g_strip_prompt;
|
||||
size_t room = sizeof(g_strip_prompt) - 1u;
|
||||
size_t n;
|
||||
const char *suffix;
|
||||
size_t used, left, sn;
|
||||
|
||||
if (vn && vn[0]) {
|
||||
n = strlen(vn);
|
||||
if (n > room) n = room;
|
||||
*p++ = '[';
|
||||
memcpy(p, vn, n);
|
||||
p += n;
|
||||
*p++ = ']';
|
||||
*p++ = ' ';
|
||||
}
|
||||
|
||||
suffix = (vm && vm->zuse_session) ? "zuse)ok> " : "ok> ";
|
||||
used = (size_t)(p - g_strip_prompt);
|
||||
left = (used < room) ? (room - used) : 0u;
|
||||
sn = strlen(suffix);
|
||||
if (sn > left) sn = left;
|
||||
memcpy(p, suffix, sn);
|
||||
p += sn;
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
/* Concatenates g_strip_prompt + input_so_far (bounded, no strcat) and
|
||||
* hands the result to the strip renderer. Called once with an empty
|
||||
* input_so_far right after set_strip_prompt(), then again on every
|
||||
* keystroke from sk_readline() below. */
|
||||
static void strip_refresh(const char *input_so_far)
|
||||
{
|
||||
char combined[INPUT_BUFFER_SIZE + SK_STRIP_PROMPT_MAX];
|
||||
size_t plen = strlen(g_strip_prompt);
|
||||
size_t ilen = strlen(input_so_far);
|
||||
|
||||
if (plen >= sizeof(combined)) plen = sizeof(combined) - 1u;
|
||||
memcpy(combined, g_strip_prompt, plen);
|
||||
if (plen + ilen >= sizeof(combined)) ilen = sizeof(combined) - 1u - plen;
|
||||
memcpy(combined + plen, input_so_far, ilen);
|
||||
combined[plen + ilen] = '\0';
|
||||
|
||||
console_fb_strip_draw(combined);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* USE-word dispatch: which VM receives REPL input.
|
||||
*
|
||||
@@ -90,6 +157,9 @@ static int sk_readline(char *buf, int size)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
buf[0] = '\0';
|
||||
strip_refresh(buf); /* FABRIC.md 4.4x: show the bare prompt before any input */
|
||||
|
||||
for (;;) {
|
||||
int c = console_getc(); /* non-blocking poll */
|
||||
|
||||
@@ -128,10 +198,12 @@ static int sk_readline(char *buf, int size)
|
||||
/* backspace: DEL (0x7F) or BS (0x08) */
|
||||
if ((c == 0x7F || c == '\b') && n > 0) {
|
||||
n--;
|
||||
buf[n] = '\0';
|
||||
/* VT100 erase: move back, overwrite with space, move back again */
|
||||
console_putc('\b');
|
||||
console_putc(' ');
|
||||
console_putc('\b');
|
||||
strip_refresh(buf);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -139,7 +211,9 @@ static int sk_readline(char *buf, int size)
|
||||
if (n >= size - 1) continue; /* buffer full — drop character */
|
||||
|
||||
buf[n++] = (char)c;
|
||||
buf[n] = '\0';
|
||||
console_putc((char)c); /* echo */
|
||||
strip_refresh(buf);
|
||||
}
|
||||
|
||||
buf[n] = '\0';
|
||||
@@ -180,7 +254,7 @@ static void sk_fault_handler(VM *vm) {
|
||||
|
||||
int sk_repl_step(VM *vm)
|
||||
{
|
||||
char input[256];
|
||||
char input[INPUT_BUFFER_SIZE]; /* FABRIC.md 4.4w: matches the strip's input width */
|
||||
|
||||
if (!vm || vm->halted) return 0;
|
||||
|
||||
@@ -194,6 +268,7 @@ int sk_repl_step(VM *vm)
|
||||
int is_hera = (!vn || (vn[0]=='H' && vn[1]=='e' && vn[2]=='r' && vn[3]=='a' && vn[4]=='\0'));
|
||||
vm->emergency_console = is_hera ? (vm->zuse_session ? 0 : 1) : 0;
|
||||
console_puts(SK_PROMPT_TEXT);
|
||||
set_strip_prompt(vm); /* FABRIC.md 4.4x: bottom strip mirrors the same prompt */
|
||||
}
|
||||
|
||||
sk_readline(input, sizeof(input));
|
||||
@@ -222,7 +297,7 @@ int sk_repl_step(VM *vm)
|
||||
|
||||
void sk_repl_run(VM *vm)
|
||||
{
|
||||
char input[256];
|
||||
char input[INPUT_BUFFER_SIZE]; /* FABRIC.md 4.4w: matches the strip's input width */
|
||||
VM *active;
|
||||
|
||||
vm->halted = 0;
|
||||
@@ -241,6 +316,7 @@ void sk_repl_run(VM *vm)
|
||||
int is_hera = (!vn || (vn[0]=='H' && vn[1]=='e' && vn[2]=='r' && vn[3]=='a' && vn[4]=='\0'));
|
||||
active->emergency_console = is_hera ? (active->zuse_session ? 0 : 1) : 0;
|
||||
console_puts(SK_PROMPT_TEXT);
|
||||
set_strip_prompt(active); /* FABRIC.md 4.4x: bottom strip mirrors the same prompt */
|
||||
}
|
||||
|
||||
sk_readline(input, sizeof(input));
|
||||
|
||||
Reference in New Issue
Block a user