Add define-native compiler form and native grid for sigil-tui
Introduces define-native as a compiler special form for declaring bindings provided by native C code at runtime. This lets Scheme modules export native functions without needing fallback implementations.
Compiler changes: - OPDEFINEIFUNBOUND (0x17): only defines a binding if one doesn't already exist, so bytecode won't overwrite native bindings - compiledefine_native() handles function/variable forms, creates placeholder bindings for export resolution, collects docstrings, and emits %set-spec! calls for type annotations - %set-spec! guards against non-procedure values for graceful degradation when native code isn't linked
Native grid (sigil-tui): - native/grid.c with C implementations of all hot-path grid operations using flat int32_t cell arrays instead of Scheme vectors - grid.sgl replaced with define-native declarations using grid? specs - Grid tests skip cleanly when native code isn't linked
native/grid.c | 677 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package.sgl | 31 +++++++-
src/sigil/tui/components.sgl | 2 +-
src/sigil/tui/grid.sgl | 287 ++++++++++-----------------------------------------------------------
test/test-grid.sgl | 5 ++
5 files changed, 751 insertions(+), 251 deletions(-)native/grid.cadded
/* * Native grid implementation for (sigil tui grid) * * Provides a high-performance character grid for terminal UI rendering. * Each cell stores a character, foreground color, background color, and * text attributes as flat C arrays, avoiding per-cell heap allocation. */#include "sigil-internal.h"#include <stdio.h>#include <stdlib.h>#include <string.h>/* ============================================================ * Cell layout: 4 int32_t values per cell * [0] = character (Unicode codepoint) * [1] = foreground color * [2] = background color * [3] = text attributes (bitmask) * ============================================================ */#define CELL_FIELDS 4#define CELL_CHAR 0#define CELL_FG 1#define CELL_BG 2#define CELL_ATTRS 3/* Attribute flags */#define ATTR_NONE 0#define ATTR_BOLD 1#define ATTR_DIM 2#define ATTR_ITALIC 4#define ATTR_UNDERLINE 8#define ATTR_INVERSE 16#define ATTR_STRIKETHROUGH 32/* Default cell values */#define DEFAULT_CHAR ' '#define DEFAULT_COLOR (-1)#define DEFAULT_ATTRS 0typedef struct { int width; int height; int32_t *cells; /* Flat array: width * height * CELL_FIELDS */} Grid;static Value grid_type_tag = SIGIL_UNDEFINED;static void grid_finalizer(void *data){ Grid *g = (Grid *)data; if (g) { free(g->cells); free(g); }}static int is_grid(Value v){ if (!sigil_is_foreign(v)) return 0; return sigil_foreign_type(v) == grid_type_tag;}static Grid *as_grid(Value v){ return (Grid *)sigil_foreign_data(v);}/* Get cell pointer for (col, row) */static inline int32_t *cell_at(Grid *g, int col, int row){ return &g->cells[(row * g->width + col) * CELL_FIELDS];}/* Initialize a cell to defaults */static inline void cell_clear(int32_t *cell){ cell[CELL_CHAR] = DEFAULT_CHAR; cell[CELL_FG] = DEFAULT_COLOR; cell[CELL_BG] = DEFAULT_COLOR; cell[CELL_ATTRS] = DEFAULT_ATTRS;}/* ============================================================ * Grid operations * ============================================================ *//* * %make-grid width height -> grid */static Value native_make_grid(SigilVM *vm, int argc, Value *args){ (void)argc; int w = (int)sigil_as_fixnum(args[0]); int h = (int)sigil_as_fixnum(args[1]); if (w <= 0 || h <= 0) { sigil__vm_error(vm, SIGIL_ERR_TYPE, "make-grid: dimensions must be positive"); return SIGIL_UNDEFINED; } Grid *g = malloc(sizeof(Grid)); if (!g) return SIGIL_FALSE; size_t size = (size_t)w * h * CELL_FIELDS; g->cells = malloc(size * sizeof(int32_t)); if (!g->cells) { free(g); return SIGIL_FALSE; } g->width = w; g->height = h; /* Initialize all cells to defaults */ for (size_t i = 0; i < (size_t)w * h; i++) { int32_t *cell = &g->cells[i * CELL_FIELDS]; cell_clear(cell); } return sigil_make_foreign(vm, grid_type_tag, g, grid_finalizer, sizeof(Grid) + size * sizeof(int32_t));}/* * %grid-width grid -> integer */static Value native_grid_width(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; return sigil_fixnum(as_grid(args[0])->width);}/* * %grid-height grid -> integer */static Value native_grid_height(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; return sigil_fixnum(as_grid(args[0])->height);}/* Helper: allocate a 4-element Scheme vector for cell data */static Value make_cell_vector(SigilVM *vm, int32_t *cell){ SigilVector *vec = sigil__gc_alloc(vm, SIGIL_OBJ_VECTOR, sizeof(SigilVector) + 4 * sizeof(Value)); vec->length = 4; vec->elements[0] = sigil_char((uint32_t)cell[CELL_CHAR]); vec->elements[1] = sigil_fixnum(cell[CELL_FG]); vec->elements[2] = sigil_fixnum(cell[CELL_BG]); vec->elements[3] = sigil_fixnum(cell[CELL_ATTRS]); return sigil_ptr(vec);}/* * %grid-ref grid col row -> vector #(char fg bg attrs) * * Returns a fresh vector for compatibility with existing code. */static Value native_grid_ref(SigilVM *vm, int argc, Value *args){ (void)argc; Grid *g = as_grid(args[0]); int col = (int)sigil_as_fixnum(args[1]); int row = (int)sigil_as_fixnum(args[2]); if (col < 0 || col >= g->width || row < 0 || row >= g->height) { sigil__vm_error(vm, SIGIL_ERR_TYPE, "grid-ref: index out of bounds"); return SIGIL_UNDEFINED; } return make_cell_vector(vm, cell_at(g, col, row));}/* * %grid-set! grid col row ch fg bg attrs -> void */static Value native_grid_set(SigilVM *vm, int argc, Value *args){ (void)argc; Grid *g = as_grid(args[0]); int col = (int)sigil_as_fixnum(args[1]); int row = (int)sigil_as_fixnum(args[2]); if (col < 0 || col >= g->width || row < 0 || row >= g->height) { /* Silently ignore out-of-bounds writes (truncation) */ (void)vm; return SIGIL_UNDEFINED; } int32_t *cell = cell_at(g, col, row); cell[CELL_CHAR] = (int32_t)sigil_as_char(args[3]); cell[CELL_FG] = (int32_t)sigil_as_fixnum(args[4]); cell[CELL_BG] = (int32_t)sigil_as_fixnum(args[5]); cell[CELL_ATTRS] = (int32_t)sigil_as_fixnum(args[6]); return SIGIL_UNDEFINED;}/* * %grid-clear! grid -> void */static Value native_grid_clear(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; Grid *g = as_grid(args[0]); size_t count = (size_t)g->width * g->height; for (size_t i = 0; i < count; i++) { int32_t *cell = &g->cells[i * CELL_FIELDS]; cell_clear(cell); } return SIGIL_UNDEFINED;}/* * %grid-write-string! grid col row str fg bg attrs -> void * * Write a string to the grid at (col, row), truncating at the grid edge. */static Value native_grid_write_string(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; Grid *g = as_grid(args[0]); int col = (int)sigil_as_fixnum(args[1]); int row = (int)sigil_as_fixnum(args[2]); /* args[3] = string */ int32_t fg = (int32_t)sigil_as_fixnum(args[4]); int32_t bg = (int32_t)sigil_as_fixnum(args[5]); int32_t attrs = (int32_t)sigil_as_fixnum(args[6]); if (row < 0 || row >= g->height || col >= g->width) { return SIGIL_UNDEFINED; } SigilString *str = (SigilString *)sigil_as_ptr(args[3]); const char *data = str->data; size_t byte_len = str->byte_length; int w = g->width; int c = col; size_t pos = 0; while (pos < byte_len && c < w) { /* Decode UTF-8 codepoint */ uint32_t cp; uint8_t b = (uint8_t)data[pos]; if (b < 0x80) { cp = b; pos += 1; } else if ((b & 0xE0) == 0xC0) { cp = (b & 0x1F) << 6; if (pos + 1 < byte_len) cp |= ((uint8_t)data[pos + 1] & 0x3F); pos += 2; } else if ((b & 0xF0) == 0xE0) { cp = (b & 0x0F) << 12; if (pos + 1 < byte_len) cp |= ((uint8_t)data[pos + 1] & 0x3F) << 6; if (pos + 2 < byte_len) cp |= ((uint8_t)data[pos + 2] & 0x3F); pos += 3; } else if ((b & 0xF8) == 0xF0) { cp = (b & 0x07) << 18; if (pos + 1 < byte_len) cp |= ((uint8_t)data[pos + 1] & 0x3F) << 12; if (pos + 2 < byte_len) cp |= ((uint8_t)data[pos + 2] & 0x3F) << 6; if (pos + 3 < byte_len) cp |= ((uint8_t)data[pos + 3] & 0x3F); pos += 4; } else { cp = '?'; pos += 1; } if (c >= 0) { int32_t *cell = cell_at(g, c, row); cell[CELL_CHAR] = (int32_t)cp; cell[CELL_FG] = fg; cell[CELL_BG] = bg; cell[CELL_ATTRS] = attrs; } c++; } return SIGIL_UNDEFINED;}/* * %grid-fill-rect! grid x y w h ch fg bg attrs -> void */static Value native_grid_fill_rect(SigilVM *vm, int argc, Value *args){ (void)vm; (void)argc; Grid *g = as_grid(args[0]); int x = (int)sigil_as_fixnum(args[1]); int y = (int)sigil_as_fixnum(args[2]); int w = (int)sigil_as_fixnum(args[3]); int h = (int)sigil_as_fixnum(args[4]); int32_t ch = (int32_t)sigil_as_char(args[5]); int32_t fg = (int32_t)sigil_as_fixnum(args[6]); int32_t bg = (int32_t)sigil_as_fixnum(args[7]); int32_t attrs = (int32_t)sigil_as_fixnum(args[8]); int gw = g->width; int gh = g->height; for (int r = y; r < y + h && r < gh; r++) { if (r < 0) continue; for (int c = x; c < x + w && c < gw; c++) { if (c < 0) continue; int32_t *cell = cell_at(g, c, r); cell[CELL_CHAR] = ch; cell[CELL_FG] = fg; cell[CELL_BG] = bg; cell[CELL_ATTRS] = attrs; } } return SIGIL_UNDEFINED;}/* * %grid-copy grid -> grid * * Create a deep copy of a grid. */static Value native_grid_copy(SigilVM *vm, int argc, Value *args){ (void)argc; Grid *src = as_grid(args[0]); Grid *dst = malloc(sizeof(Grid)); if (!dst) return SIGIL_FALSE; size_t size = (size_t)src->width * src->height * CELL_FIELDS; dst->cells = malloc(size * sizeof(int32_t)); if (!dst->cells) { free(dst); return SIGIL_FALSE; } dst->width = src->width; dst->height = src->height; memcpy(dst->cells, src->cells, size * sizeof(int32_t)); return sigil_make_foreign(vm, grid_type_tag, dst, grid_finalizer, sizeof(Grid) + size * sizeof(int32_t));}/* ============================================================ * Grid diffing — the performance-critical hot path * * Scans cell-by-cell, emitting minimal ANSI escape sequences to * update only changed cells. Returns a string to write atomically * via terminal-write-raw. * ============================================================ *//* Dynamic buffer for building diff output */typedef struct { char *data; size_t len; size_t cap;} DiffBuf;static void buf_init(DiffBuf *buf){ buf->cap = 4096; buf->data = malloc(buf->cap); buf->len = 0;}static void buf_ensure(DiffBuf *buf, size_t need){ if (buf->len + need > buf->cap) { while (buf->len + need > buf->cap) { buf->cap *= 2; } buf->data = realloc(buf->data, buf->cap); }}static void buf_append(DiffBuf *buf, const char *str, size_t len){ buf_ensure(buf, len); memcpy(buf->data + buf->len, str, len); buf->len += len;}static void buf_append_str(DiffBuf *buf, const char *str){ buf_append(buf, str, strlen(str));}static void buf_append_int(DiffBuf *buf, int n){ char tmp[16]; int len = snprintf(tmp, sizeof(tmp), "%d", n); buf_append(buf, tmp, len);}static void buf_append_char_utf8(DiffBuf *buf, uint32_t cp){ char tmp[4]; int len; if (cp < 0x80) { tmp[0] = (char)cp; len = 1; } else if (cp < 0x800) { tmp[0] = (char)(0xC0 | (cp >> 6)); tmp[1] = (char)(0x80 | (cp & 0x3F)); len = 2; } else if (cp < 0x10000) { tmp[0] = (char)(0xE0 | (cp >> 12)); tmp[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); tmp[2] = (char)(0x80 | (cp & 0x3F)); len = 3; } else { tmp[0] = (char)(0xF0 | (cp >> 18)); tmp[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); tmp[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); tmp[3] = (char)(0x80 | (cp & 0x3F)); len = 4; } buf_append(buf, tmp, len);}/* Emit foreground color SGR to buffer */static void emit_fg(DiffBuf *buf, int32_t color){ if (color == -1) { buf_append_str(buf, "39"); } else if (color >= 0 && color <= 7) { buf_append_int(buf, color + 30); } else if (color >= 8 && color <= 15) { buf_append_int(buf, color + 82); /* 90-97 */ } else if (color >= 16 && color <= 255) { buf_append_str(buf, "38;5;"); buf_append_int(buf, color); } else { /* Truecolor: decode packed RGB */ int v = color - 65536; /* undo +1 offset from color-rgb */ int r = v / 65536; int rem = v % 65536; int g = rem / 256; int b = rem % 256; buf_append_str(buf, "38;2;"); buf_append_int(buf, r); buf_append(buf, ";", 1); buf_append_int(buf, g); buf_append(buf, ";", 1); buf_append_int(buf, b); }}/* Emit background color SGR to buffer */static void emit_bg(DiffBuf *buf, int32_t color){ if (color == -1) { buf_append_str(buf, "49"); } else if (color >= 0 && color <= 7) { buf_append_int(buf, color + 40); } else if (color >= 8 && color <= 15) { buf_append_int(buf, color + 92); /* 100-107 */ } else if (color >= 16 && color <= 255) { buf_append_str(buf, "48;5;"); buf_append_int(buf, color); } else { int v = color - 65536; int r = v / 65536; int rem = v % 65536; int g = rem / 256; int b = rem % 256; buf_append_str(buf, "48;2;"); buf_append_int(buf, r); buf_append(buf, ";", 1); buf_append_int(buf, g); buf_append(buf, ";", 1); buf_append_int(buf, b); }}/* Emit attribute codes to buffer */static void emit_attrs(DiffBuf *buf, int32_t attrs){ if (attrs & ATTR_BOLD) buf_append_str(buf, ";1"); if (attrs & ATTR_DIM) buf_append_str(buf, ";2"); if (attrs & ATTR_ITALIC) buf_append_str(buf, ";3"); if (attrs & ATTR_UNDERLINE) buf_append_str(buf, ";4"); if (attrs & ATTR_INVERSE) buf_append_str(buf, ";7"); if (attrs & ATTR_STRIKETHROUGH) buf_append_str(buf, ";9");}/* Emit full SGR for a cell */Showing the first 500 of 678 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.
package.sglmodified
dependencies: (list (from-workspace name: "sigil-stdlib") (from-workspace name: "sigil-ansi") (from-workspace name: "sigil-socket"))) (from-workspace name: "sigil-socket")) ;; Native library for high-performance grid operations libraries: (list (library name: 'sigil-tui c-sources: '("native/grid.c") native-init: "sigil__init_sigil_tui_grid_module")) tasks: (list (task name: 'build description: "Build the sigil-tui native library" steps: (list (compile-c-sources sources: '("native/grid.c") include-dirs: '("../sigil-lib/include" "../sigil-lib/src") flags: '("-std=c99" "-Wall" "-Wextra" "-Wno-unused-parameter" "-D_GNU_SOURCE")) (create-static-library name: "sigil-tui") (compile-sigil-modules sources: "src/**/*.sgl") (copy-package-docs)))))src/sigil/tui/components.sglmodified
;;; Render a list of layout commands into a grid. (define (render-commands grid commands) (: vector? list? -> void?) (: grid? list? -> void?) (for-each (lambda (cmd) (let* ((type (car cmd))src/sigil/tui/grid.sglmodified
;;; text attributes. The diff algorithm emits minimal ANSI escape sequences;;; to update only changed cells.;;;;;; Grid operations are implemented in native C code (native/grid.c) for;;; performance. The native module is linked when building packages that;;; depend on sigil-tui.;;;;;; Color encoding:;;; - -1 = default terminal color;;; - 0-7 = standard colors (black, red, green, yellow, blue, magenta, cyan, white);;; - >= 256 = truecolor RGB (pack with color-rgb)(define-library (sigil tui grid) (import (sigil core) (sigil io) (sigil string) (sigil math) (sigil terminal)) (import (sigil core)) (export make-grid grid? grid-width grid-height grid-ref b)) ;; ============================================================ ;; Cell: 4-element vector #(char fg bg attrs) ;; Cell accessors (Scheme — lightweight wrappers) ;; ============================================================ ;;; Create a cell with the given character, colors, and attributes. (define (make-cell ch fg bg attrs) (: char? integer? integer? integer? -> vector?) (vector ch fg bg attrs)) ;; Default cell: space with default colors, no attributes (define (default-cell) (: -> vector?) (eqv? (vector-ref a 3) (vector-ref b 3)))) ;; ============================================================ ;; Grid: vector of cells + dimensions ;; Native grid operations (provided by native/grid.c) ;; ============================================================ ;;; Create a cell with the given character, colors, and attributes. (define-native (make-cell ch fg bg attrs) (: char? integer? integer? integer? -> vector?)) ;;; Create a grid of width x height cells, filled with default cells. (define (make-grid width height) (: integer? integer? -> vector?) (let* ((size (* width height)) (cells (make-vector size #f))) (let loop ((i 0)) (when (< i size) (vector-set! cells i (default-cell)) (loop (+ i 1)))) (vector width height cells))) (define-native (make-grid width height) (: integer? integer? -> grid?)) ;;; Check if a value is a native grid. (define-native (grid? obj) (: any? -> boolean?)) ;;; Get the grid width. (define (grid-width grid) (: vector? -> integer?) (vector-ref grid 0)) (define-native (grid-width grid) (: grid? -> integer?)) ;;; Get the grid height. (define (grid-height grid) (: vector? -> integer?) (vector-ref grid 1)) (define (grid-cells grid) (vector-ref grid 2)) (define (grid-index grid col row) (+ (* row (grid-width grid)) col)) (define-native (grid-height grid) (: grid? -> integer?)) ;;; Get the cell at (col, row). (define (grid-ref grid col row) (: vector? integer? integer? -> vector?) (vector-ref (grid-cells grid) (grid-index grid col row))) (define-native (grid-ref grid col row) (: grid? integer? integer? -> vector?)) ;;; Set a cell at (col, row) by mutating it in place. ;;; ;;; Avoids allocating a new cell vector on every write. (define (grid-set! grid col row ch fg bg attrs) (: vector? integer? integer? char? integer? integer? integer? -> void?) (let ((cell (vector-ref (grid-cells grid) (grid-index grid col row)))) (vector-set! cell 0 ch) (vector-set! cell 1 fg) (vector-set! cell 2 bg) (vector-set! cell 3 attrs))) (define-native (grid-set! grid col row ch fg bg attrs) (: grid? integer? integer? char? integer? integer? integer? -> void?)) ;;; Clear the grid to default cells (space, default colors, no attrs). (define (grid-clear! grid) (: vector? -> void?) (let ((cells (grid-cells grid)) (size (* (grid-width grid) (grid-height grid)))) (let loop ((i 0)) (when (< i size) (let ((cell (vector-ref cells i))) (vector-set! cell 0 #\space) (vector-set! cell 1 -1) (vector-set! cell 2 -1) (vector-set! cell 3 0)) (loop (+ i 1)))))) (define-native (grid-clear! grid) (: grid? -> void?)) ;;; Write a string to the grid at (col, row) with the given style. ;;; ;;; Characters are written left-to-right, truncated at the grid edge. (define (grid-write-string! grid col row str fg bg attrs) (: vector? integer? integer? string? integer? integer? integer? -> void?) (let ((w (grid-width grid)) (cells (grid-cells grid)) (gw (grid-width grid)) (len (string-length str))) (let loop ((i 0) (c col)) (when (and (< i len) (< c w)) (let ((cell (vector-ref cells (+ (* row gw) c)))) (vector-set! cell 0 (string-ref str i)) (vector-set! cell 1 fg) (vector-set! cell 2 bg) (vector-set! cell 3 attrs)) (loop (+ i 1) (+ c 1)))))) (define-native (grid-write-string! grid col row str fg bg attrs) (: grid? integer? integer? string? integer? integer? integer? -> void?)) ;;; Fill a rectangle with a character and style. (define (grid-fill-rect! grid x y w h ch fg bg attrs) (: vector? integer? integer? integer? integer? char? integer? integer? integer? -> void?) (let ((cells (grid-cells grid)) (gw (grid-width grid)) (gh (grid-height grid))) (let row-loop ((r y)) (when (and (< r (+ y h)) (< r gh)) (let col-loop ((c x)) (when (and (< c (+ x w)) (< c gw)) (let ((cell (vector-ref cells (+ (* r gw) c)))) (vector-set! cell 0 ch) (vector-set! cell 1 fg) (vector-set! cell 2 bg) (vector-set! cell 3 attrs)) (col-loop (+ c 1)))) (row-loop (+ r 1)))))) ;;; Create a deep copy of a grid (copies cell contents, not references). (define (grid-copy grid) (: vector? -> vector?) (let* ((w (grid-width grid)) (h (grid-height grid)) (size (* w h)) (src (grid-cells grid)) (dst (make-vector size #f))) (let loop ((i 0)) (when (< i size) (let ((cell (vector-ref src i))) (vector-set! dst i (vector (vector-ref cell 0) (vector-ref cell 1) (vector-ref cell 2) (vector-ref cell 3)))) (loop (+ i 1)))) (vector w h dst))) ;; ============================================================ ;; SGR escape code emission ;; ============================================================ (define esc "\x1b;") ;; Emit a foreground color SGR sequence to port (define (emit-fg port color) (cond ((= color -1) (display "39" port)) ((and (>= color 0) (<= color 7)) (display (number->string (+ color 30)) port)) ((and (>= color 8) (<= color 15)) (display (number->string (+ color 82)) port)) ;; 90-97 ((and (>= color 16) (<= color 255)) (display "38;5;" port) (display (number->string color) port)) (else ;; Truecolor: decode packed RGB (let* ((v (- color 65536)) ;; undo the +1 offset from color-rgb (r (quotient v 65536)) (rem (remainder v 65536)) (g (quotient rem 256)) (b (remainder rem 256))) (display "38;2;" port) (display (number->string r) port) (display ";" port) (display (number->string g) port) (display ";" port) (display (number->string b) port))))) ;; Emit a background color SGR sequence to port (define (emit-bg port color) (cond ((= color -1) (display "49" port)) ((and (>= color 0) (<= color 7)) (display (number->string (+ color 40)) port)) ((and (>= color 8) (<= color 15)) (display (number->string (+ color 92)) port)) ;; 100-107 ((and (>= color 16) (<= color 255)) (display "48;5;" port) (display (number->string color) port)) (else (let* ((v (- color 65536)) (r (quotient v 65536)) (rem (remainder v 65536)) (g (quotient rem 256)) (b (remainder rem 256))) (display "48;2;" port) (display (number->string r) port) (display ";" port) (display (number->string g) port) (display ";" port) (display (number->string b) port))))) ;; Emit attribute SGR codes to port (define (emit-attrs port attrs) (when (not (= (bitwise-and attrs attr-bold) 0)) (display ";1" port)) (when (not (= (bitwise-and attrs attr-dim) 0)) (display ";2" port)) (when (not (= (bitwise-and attrs attr-italic) 0)) (display ";3" port)) (when (not (= (bitwise-and attrs attr-underline) 0)) (display ";4" port)) (when (not (= (bitwise-and attrs attr-inverse) 0)) (display ";7" port)) (when (not (= (bitwise-and attrs attr-strikethrough) 0)) (display ";9" port))) ;; Emit full SGR for a cell (define (emit-sgr port fg bg attrs) (display esc port) (display "[0;" port) ;; reset first (emit-fg port fg) (display ";" port) (emit-bg port bg) (emit-attrs port attrs) (display "m" port)) ;; ============================================================ ;; Grid diffing ;; ============================================================ (define-native (grid-fill-rect! grid x y w h ch fg bg attrs) (: grid? integer? integer? integer? integer? char? integer? integer? integer? -> void?)) ;;; Compute the diff between prev and curr grids, return an ANSI string. ;;; ;;; Scans cell-by-cell, emitting cursor moves and SGR sequences only ;;; where cells differ. Returns the string to write atomically via ;;; terminal-write-raw. (define (grid-diff prev curr) (: vector? vector? -> string?) (let ((w (grid-width curr)) (h (grid-height curr)) (prev-cells (grid-cells prev)) (curr-cells (grid-cells curr)) (out (open-output-string)) (last-fg -2) ;; -2 = no style emitted yet (different from -1=default) (last-bg -2) (last-attrs -1) (cursor-col -1) (cursor-row -1)) (let row-loop ((row 0)) (when (< row h) (let col-loop ((col 0)) (when (< col w) (let* ((idx (+ (* row w) col)) (pc (vector-ref prev-cells idx)) (cc (vector-ref curr-cells idx))) (when (not (and (eqv? (vector-ref pc 0) (vector-ref cc 0)) (eqv? (vector-ref pc 1) (vector-ref cc 1)) (eqv? (vector-ref pc 2) (vector-ref cc 2)) (eqv? (vector-ref pc 3) (vector-ref cc 3)))) (let ((fg (vector-ref cc 1)) (bg (vector-ref cc 2)) (attrs (vector-ref cc 3)) (ch (vector-ref cc 0))) ;; Move cursor if not at expected position (when (or (not (= cursor-row row)) (not (= cursor-col col))) (display esc out) (display "[" out) (display (number->string (+ row 1)) out) (display ";" out) (display (number->string (+ col 1)) out) (display "H" out)) ;; Emit style if changed (when (or (not (= fg last-fg)) (not (= bg last-bg)) (not (= attrs last-attrs))) (emit-sgr out fg bg attrs) (set! last-fg fg) (set! last-bg bg) (set! last-attrs attrs)) ;; Emit character (display ch out) (set! cursor-col (+ col 1)) (set! cursor-row row)))) (col-loop (+ col 1)))) (row-loop (+ row 1)))) ;; Reset style at end (let ((result (get-output-string out))) (if (= (string-length result) 0) result (begin (display esc out) (display "[0m" out) (get-output-string out)))))))) (define-native (grid-diff prev curr) (: grid? grid? -> string?)) ;;; Create a deep copy of a grid (copies cell contents, not references). (define-native (grid-copy grid) (: grid? -> grid?))))test/test-grid.sglmodified
(import (sigil test) (sigil tui grid));; Skip grid tests when native code isn't linked (make-grid will be #f)(when (not (procedure? make-grid)) (display "Skipping grid tests (native code not linked)\n") (exit 0))(test-group "grid" (test-group "creation"