Commit137af6fdRecorded1 Mar 2026Repositorysigil-tui

Add define-native compiler form and native grid for sigil-tui

Message

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

Changed
 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(-)
Diff
native/grid.cadded
@@ -0,0 +1,677 @@
+1
/*
+2
* Native grid implementation for (sigil tui grid)
+3
*
+4
* Provides a high-performance character grid for terminal UI rendering.
+5
* Each cell stores a character, foreground color, background color, and
+6
* text attributes as flat C arrays, avoiding per-cell heap allocation.
+7
*/
+8
+9
#include "sigil-internal.h"
+10
#include <stdio.h>
+11
#include <stdlib.h>
+12
#include <string.h>
+13
+14
/* ============================================================
+15
* Cell layout: 4 int32_t values per cell
+16
* [0] = character (Unicode codepoint)
+17
* [1] = foreground color
+18
* [2] = background color
+19
* [3] = text attributes (bitmask)
+20
* ============================================================ */
+21
+22
#define CELL_FIELDS 4
+23
#define CELL_CHAR 0
+24
#define CELL_FG 1
+25
#define CELL_BG 2
+26
#define CELL_ATTRS 3
+27
+28
/* Attribute flags */
+29
#define ATTR_NONE 0
+30
#define ATTR_BOLD 1
+31
#define ATTR_DIM 2
+32
#define ATTR_ITALIC 4
+33
#define ATTR_UNDERLINE 8
+34
#define ATTR_INVERSE 16
+35
#define ATTR_STRIKETHROUGH 32
+36
+37
/* Default cell values */
+38
#define DEFAULT_CHAR ' '
+39
#define DEFAULT_COLOR (-1)
+40
#define DEFAULT_ATTRS 0
+41
+42
typedef struct {
+43
int width;
+44
int height;
+45
int32_t *cells; /* Flat array: width * height * CELL_FIELDS */
+46
} Grid;
+47
+48
static Value grid_type_tag = SIGIL_UNDEFINED;
+49
+50
static void grid_finalizer(void *data)
+51
{
+52
Grid *g = (Grid *)data;
+53
if (g) {
+54
free(g->cells);
+55
free(g);
+56
}
+57
}
+58
+59
static int is_grid(Value v)
+60
{
+61
if (!sigil_is_foreign(v)) return 0;
+62
return sigil_foreign_type(v) == grid_type_tag;
+63
}
+64
+65
static Grid *as_grid(Value v)
+66
{
+67
return (Grid *)sigil_foreign_data(v);
+68
}
+69
+70
/* Get cell pointer for (col, row) */
+71
static inline int32_t *cell_at(Grid *g, int col, int row)
+72
{
+73
return &g->cells[(row * g->width + col) * CELL_FIELDS];
+74
}
+75
+76
/* Initialize a cell to defaults */
+77
static inline void cell_clear(int32_t *cell)
+78
{
+79
cell[CELL_CHAR] = DEFAULT_CHAR;
+80
cell[CELL_FG] = DEFAULT_COLOR;
+81
cell[CELL_BG] = DEFAULT_COLOR;
+82
cell[CELL_ATTRS] = DEFAULT_ATTRS;
+83
}
+84
+85
/* ============================================================
+86
* Grid operations
+87
* ============================================================ */
+88
+89
/*
+90
* %make-grid width height -> grid
+91
*/
+92
static Value native_make_grid(SigilVM *vm, int argc, Value *args)
+93
{
+94
(void)argc;
+95
+96
int w = (int)sigil_as_fixnum(args[0]);
+97
int h = (int)sigil_as_fixnum(args[1]);
+98
+99
if (w <= 0 || h <= 0) {
+100
sigil__vm_error(vm, SIGIL_ERR_TYPE,
+101
"make-grid: dimensions must be positive");
+102
return SIGIL_UNDEFINED;
+103
}
+104
+105
Grid *g = malloc(sizeof(Grid));
+106
if (!g) return SIGIL_FALSE;
+107
+108
size_t size = (size_t)w * h * CELL_FIELDS;
+109
g->cells = malloc(size * sizeof(int32_t));
+110
if (!g->cells) {
+111
free(g);
+112
return SIGIL_FALSE;
+113
}
+114
+115
g->width = w;
+116
g->height = h;
+117
+118
/* Initialize all cells to defaults */
+119
for (size_t i = 0; i < (size_t)w * h; i++) {
+120
int32_t *cell = &g->cells[i * CELL_FIELDS];
+121
cell_clear(cell);
+122
}
+123
+124
return sigil_make_foreign(vm, grid_type_tag, g, grid_finalizer,
+125
sizeof(Grid) + size * sizeof(int32_t));
+126
}
+127
+128
/*
+129
* %grid-width grid -> integer
+130
*/
+131
static Value native_grid_width(SigilVM *vm, int argc, Value *args)
+132
{
+133
(void)vm; (void)argc;
+134
return sigil_fixnum(as_grid(args[0])->width);
+135
}
+136
+137
/*
+138
* %grid-height grid -> integer
+139
*/
+140
static Value native_grid_height(SigilVM *vm, int argc, Value *args)
+141
{
+142
(void)vm; (void)argc;
+143
return sigil_fixnum(as_grid(args[0])->height);
+144
}
+145
+146
/* Helper: allocate a 4-element Scheme vector for cell data */
+147
static Value make_cell_vector(SigilVM *vm, int32_t *cell)
+148
{
+149
SigilVector *vec = sigil__gc_alloc(vm, SIGIL_OBJ_VECTOR,
+150
sizeof(SigilVector) + 4 * sizeof(Value));
+151
vec->length = 4;
+152
vec->elements[0] = sigil_char((uint32_t)cell[CELL_CHAR]);
+153
vec->elements[1] = sigil_fixnum(cell[CELL_FG]);
+154
vec->elements[2] = sigil_fixnum(cell[CELL_BG]);
+155
vec->elements[3] = sigil_fixnum(cell[CELL_ATTRS]);
+156
return sigil_ptr(vec);
+157
}
+158
+159
/*
+160
* %grid-ref grid col row -> vector #(char fg bg attrs)
+161
*
+162
* Returns a fresh vector for compatibility with existing code.
+163
*/
+164
static Value native_grid_ref(SigilVM *vm, int argc, Value *args)
+165
{
+166
(void)argc;
+167
+168
Grid *g = as_grid(args[0]);
+169
int col = (int)sigil_as_fixnum(args[1]);
+170
int row = (int)sigil_as_fixnum(args[2]);
+171
+172
if (col < 0 || col >= g->width || row < 0 || row >= g->height) {
+173
sigil__vm_error(vm, SIGIL_ERR_TYPE,
+174
"grid-ref: index out of bounds");
+175
return SIGIL_UNDEFINED;
+176
}
+177
+178
return make_cell_vector(vm, cell_at(g, col, row));
+179
}
+180
+181
/*
+182
* %grid-set! grid col row ch fg bg attrs -> void
+183
*/
+184
static Value native_grid_set(SigilVM *vm, int argc, Value *args)
+185
{
+186
(void)argc;
+187
+188
Grid *g = as_grid(args[0]);
+189
int col = (int)sigil_as_fixnum(args[1]);
+190
int row = (int)sigil_as_fixnum(args[2]);
+191
+192
if (col < 0 || col >= g->width || row < 0 || row >= g->height) {
+193
/* Silently ignore out-of-bounds writes (truncation) */
+194
(void)vm;
+195
return SIGIL_UNDEFINED;
+196
}
+197
+198
int32_t *cell = cell_at(g, col, row);
+199
cell[CELL_CHAR] = (int32_t)sigil_as_char(args[3]);
+200
cell[CELL_FG] = (int32_t)sigil_as_fixnum(args[4]);
+201
cell[CELL_BG] = (int32_t)sigil_as_fixnum(args[5]);
+202
cell[CELL_ATTRS] = (int32_t)sigil_as_fixnum(args[6]);
+203
+204
return SIGIL_UNDEFINED;
+205
}
+206
+207
/*
+208
* %grid-clear! grid -> void
+209
*/
+210
static Value native_grid_clear(SigilVM *vm, int argc, Value *args)
+211
{
+212
(void)vm; (void)argc;
+213
+214
Grid *g = as_grid(args[0]);
+215
size_t count = (size_t)g->width * g->height;
+216
+217
for (size_t i = 0; i < count; i++) {
+218
int32_t *cell = &g->cells[i * CELL_FIELDS];
+219
cell_clear(cell);
+220
}
+221
+222
return SIGIL_UNDEFINED;
+223
}
+224
+225
/*
+226
* %grid-write-string! grid col row str fg bg attrs -> void
+227
*
+228
* Write a string to the grid at (col, row), truncating at the grid edge.
+229
*/
+230
static Value native_grid_write_string(SigilVM *vm, int argc, Value *args)
+231
{
+232
(void)vm; (void)argc;
+233
+234
Grid *g = as_grid(args[0]);
+235
int col = (int)sigil_as_fixnum(args[1]);
+236
int row = (int)sigil_as_fixnum(args[2]);
+237
/* args[3] = string */
+238
int32_t fg = (int32_t)sigil_as_fixnum(args[4]);
+239
int32_t bg = (int32_t)sigil_as_fixnum(args[5]);
+240
int32_t attrs = (int32_t)sigil_as_fixnum(args[6]);
+241
+242
if (row < 0 || row >= g->height || col >= g->width) {
+243
return SIGIL_UNDEFINED;
+244
}
+245
+246
SigilString *str = (SigilString *)sigil_as_ptr(args[3]);
+247
const char *data = str->data;
+248
size_t byte_len = str->byte_length;
+249
int w = g->width;
+250
int c = col;
+251
size_t pos = 0;
+252
+253
while (pos < byte_len && c < w) {
+254
/* Decode UTF-8 codepoint */
+255
uint32_t cp;
+256
uint8_t b = (uint8_t)data[pos];
+257
if (b < 0x80) {
+258
cp = b;
+259
pos += 1;
+260
} else if ((b & 0xE0) == 0xC0) {
+261
cp = (b & 0x1F) << 6;
+262
if (pos + 1 < byte_len) cp |= ((uint8_t)data[pos + 1] & 0x3F);
+263
pos += 2;
+264
} else if ((b & 0xF0) == 0xE0) {
+265
cp = (b & 0x0F) << 12;
+266
if (pos + 1 < byte_len) cp |= ((uint8_t)data[pos + 1] & 0x3F) << 6;
+267
if (pos + 2 < byte_len) cp |= ((uint8_t)data[pos + 2] & 0x3F);
+268
pos += 3;
+269
} else if ((b & 0xF8) == 0xF0) {
+270
cp = (b & 0x07) << 18;
+271
if (pos + 1 < byte_len) cp |= ((uint8_t)data[pos + 1] & 0x3F) << 12;
+272
if (pos + 2 < byte_len) cp |= ((uint8_t)data[pos + 2] & 0x3F) << 6;
+273
if (pos + 3 < byte_len) cp |= ((uint8_t)data[pos + 3] & 0x3F);
+274
pos += 4;
+275
} else {
+276
cp = '?';
+277
pos += 1;
+278
}
+279
+280
if (c >= 0) {
+281
int32_t *cell = cell_at(g, c, row);
+282
cell[CELL_CHAR] = (int32_t)cp;
+283
cell[CELL_FG] = fg;
+284
cell[CELL_BG] = bg;
+285
cell[CELL_ATTRS] = attrs;
+286
}
+287
c++;
+288
}
+289
+290
return SIGIL_UNDEFINED;
+291
}
+292
+293
/*
+294
* %grid-fill-rect! grid x y w h ch fg bg attrs -> void
+295
*/
+296
static Value native_grid_fill_rect(SigilVM *vm, int argc, Value *args)
+297
{
+298
(void)vm; (void)argc;
+299
+300
Grid *g = as_grid(args[0]);
+301
int x = (int)sigil_as_fixnum(args[1]);
+302
int y = (int)sigil_as_fixnum(args[2]);
+303
int w = (int)sigil_as_fixnum(args[3]);
+304
int h = (int)sigil_as_fixnum(args[4]);
+305
int32_t ch = (int32_t)sigil_as_char(args[5]);
+306
int32_t fg = (int32_t)sigil_as_fixnum(args[6]);
+307
int32_t bg = (int32_t)sigil_as_fixnum(args[7]);
+308
int32_t attrs = (int32_t)sigil_as_fixnum(args[8]);
+309
+310
int gw = g->width;
+311
int gh = g->height;
+312
+313
for (int r = y; r < y + h && r < gh; r++) {
+314
if (r < 0) continue;
+315
for (int c = x; c < x + w && c < gw; c++) {
+316
if (c < 0) continue;
+317
int32_t *cell = cell_at(g, c, r);
+318
cell[CELL_CHAR] = ch;
+319
cell[CELL_FG] = fg;
+320
cell[CELL_BG] = bg;
+321
cell[CELL_ATTRS] = attrs;
+322
}
+323
}
+324
+325
return SIGIL_UNDEFINED;
+326
}
+327
+328
/*
+329
* %grid-copy grid -> grid
+330
*
+331
* Create a deep copy of a grid.
+332
*/
+333
static Value native_grid_copy(SigilVM *vm, int argc, Value *args)
+334
{
+335
(void)argc;
+336
+337
Grid *src = as_grid(args[0]);
+338
Grid *dst = malloc(sizeof(Grid));
+339
if (!dst) return SIGIL_FALSE;
+340
+341
size_t size = (size_t)src->width * src->height * CELL_FIELDS;
+342
dst->cells = malloc(size * sizeof(int32_t));
+343
if (!dst->cells) {
+344
free(dst);
+345
return SIGIL_FALSE;
+346
}
+347
+348
dst->width = src->width;
+349
dst->height = src->height;
+350
memcpy(dst->cells, src->cells, size * sizeof(int32_t));
+351
+352
return sigil_make_foreign(vm, grid_type_tag, dst, grid_finalizer,
+353
sizeof(Grid) + size * sizeof(int32_t));
+354
}
+355
+356
/* ============================================================
+357
* Grid diffing — the performance-critical hot path
+358
*
+359
* Scans cell-by-cell, emitting minimal ANSI escape sequences to
+360
* update only changed cells. Returns a string to write atomically
+361
* via terminal-write-raw.
+362
* ============================================================ */
+363
+364
/* Dynamic buffer for building diff output */
+365
typedef struct {
+366
char *data;
+367
size_t len;
+368
size_t cap;
+369
} DiffBuf;
+370
+371
static void buf_init(DiffBuf *buf)
+372
{
+373
buf->cap = 4096;
+374
buf->data = malloc(buf->cap);
+375
buf->len = 0;
+376
}
+377
+378
static void buf_ensure(DiffBuf *buf, size_t need)
+379
{
+380
if (buf->len + need > buf->cap) {
+381
while (buf->len + need > buf->cap) {
+382
buf->cap *= 2;
+383
}
+384
buf->data = realloc(buf->data, buf->cap);
+385
}
+386
}
+387
+388
static void buf_append(DiffBuf *buf, const char *str, size_t len)
+389
{
+390
buf_ensure(buf, len);
+391
memcpy(buf->data + buf->len, str, len);
+392
buf->len += len;
+393
}
+394
+395
static void buf_append_str(DiffBuf *buf, const char *str)
+396
{
+397
buf_append(buf, str, strlen(str));
+398
}
+399
+400
static void buf_append_int(DiffBuf *buf, int n)
+401
{
+402
char tmp[16];
+403
int len = snprintf(tmp, sizeof(tmp), "%d", n);
+404
buf_append(buf, tmp, len);
+405
}
+406
+407
static void buf_append_char_utf8(DiffBuf *buf, uint32_t cp)
+408
{
+409
char tmp[4];
+410
int len;
+411
if (cp < 0x80) {
+412
tmp[0] = (char)cp;
+413
len = 1;
+414
} else if (cp < 0x800) {
+415
tmp[0] = (char)(0xC0 | (cp >> 6));
+416
tmp[1] = (char)(0x80 | (cp & 0x3F));
+417
len = 2;
+418
} else if (cp < 0x10000) {
+419
tmp[0] = (char)(0xE0 | (cp >> 12));
+420
tmp[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
+421
tmp[2] = (char)(0x80 | (cp & 0x3F));
+422
len = 3;
+423
} else {
+424
tmp[0] = (char)(0xF0 | (cp >> 18));
+425
tmp[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
+426
tmp[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
+427
tmp[3] = (char)(0x80 | (cp & 0x3F));
+428
len = 4;
+429
}
+430
buf_append(buf, tmp, len);
+431
}
+432
+433
/* Emit foreground color SGR to buffer */
+434
static void emit_fg(DiffBuf *buf, int32_t color)
+435
{
+436
if (color == -1) {
+437
buf_append_str(buf, "39");
+438
} else if (color >= 0 && color <= 7) {
+439
buf_append_int(buf, color + 30);
+440
} else if (color >= 8 && color <= 15) {
+441
buf_append_int(buf, color + 82); /* 90-97 */
+442
} else if (color >= 16 && color <= 255) {
+443
buf_append_str(buf, "38;5;");
+444
buf_append_int(buf, color);
+445
} else {
+446
/* Truecolor: decode packed RGB */
+447
int v = color - 65536; /* undo +1 offset from color-rgb */
+448
int r = v / 65536;
+449
int rem = v % 65536;
+450
int g = rem / 256;
+451
int b = rem % 256;
+452
buf_append_str(buf, "38;2;");
+453
buf_append_int(buf, r);
+454
buf_append(buf, ";", 1);
+455
buf_append_int(buf, g);
+456
buf_append(buf, ";", 1);
+457
buf_append_int(buf, b);
+458
}
+459
}
+460
+461
/* Emit background color SGR to buffer */
+462
static void emit_bg(DiffBuf *buf, int32_t color)
+463
{
+464
if (color == -1) {
+465
buf_append_str(buf, "49");
+466
} else if (color >= 0 && color <= 7) {
+467
buf_append_int(buf, color + 40);
+468
} else if (color >= 8 && color <= 15) {
+469
buf_append_int(buf, color + 92); /* 100-107 */
+470
} else if (color >= 16 && color <= 255) {
+471
buf_append_str(buf, "48;5;");
+472
buf_append_int(buf, color);
+473
} else {
+474
int v = color - 65536;
+475
int r = v / 65536;
+476
int rem = v % 65536;
+477
int g = rem / 256;
+478
int b = rem % 256;
+479
buf_append_str(buf, "48;2;");
+480
buf_append_int(buf, r);
+481
buf_append(buf, ";", 1);
+482
buf_append_int(buf, g);
+483
buf_append(buf, ";", 1);
+484
buf_append_int(buf, b);
+485
}
+486
}
+487
+488
/* Emit attribute codes to buffer */
+489
static void emit_attrs(DiffBuf *buf, int32_t attrs)
+490
{
+491
if (attrs & ATTR_BOLD) buf_append_str(buf, ";1");
+492
if (attrs & ATTR_DIM) buf_append_str(buf, ";2");
+493
if (attrs & ATTR_ITALIC) buf_append_str(buf, ";3");
+494
if (attrs & ATTR_UNDERLINE) buf_append_str(buf, ";4");
+495
if (attrs & ATTR_INVERSE) buf_append_str(buf, ";7");
+496
if (attrs & ATTR_STRIKETHROUGH) buf_append_str(buf, ";9");
+497
}
+498
+499
/* 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
@@ -14,4 +14,33 @@
14
dependencies: (list
15
(from-workspace name: "sigil-stdlib")
16
(from-workspace name: "sigil-ansi")
17
(from-workspace name: "sigil-socket")))
+17
(from-workspace name: "sigil-socket"))
+18
+19
;; Native library for high-performance grid operations
+20
libraries: (list
+21
(library
+22
name: 'sigil-tui
+23
c-sources: '("native/grid.c")
+24
native-init: "sigil__init_sigil_tui_grid_module"))
+25
+26
tasks: (list
+27
(task
+28
name: 'build
+29
description: "Build the sigil-tui native library"
+30
steps: (list
+31
(compile-c-sources
+32
sources: '("native/grid.c")
+33
include-dirs: '("../sigil-lib/include"
+34
"../sigil-lib/src")
+35
flags: '("-std=c99"
+36
"-Wall" "-Wextra"
+37
"-Wno-unused-parameter"
+38
"-D_GNU_SOURCE"))
+39
+40
(create-static-library
+41
name: "sigil-tui")
+42
+43
(compile-sigil-modules
+44
sources: "src/**/*.sgl")
+45
+46
(copy-package-docs)))))
src/sigil/tui/components.sglmodified
@@ -189,7 +189,7 @@
189
190
;;; Render a list of layout commands into a grid.
191
(define (render-commands grid commands)
192
(: vector? list? -> void?)
+192
(: grid? list? -> void?)
193
(for-each
194
(lambda (cmd)
195
(let* ((type (car cmd))
src/sigil/tui/grid.sglmodified
@@ -5,6 +5,10 @@
5
;;; text attributes. The diff algorithm emits minimal ANSI escape sequences
6
;;; to update only changed cells.
7
;;;
+8
;;; Grid operations are implemented in native C code (native/grid.c) for
+9
;;; performance. The native module is linked when building packages that
+10
;;; depend on sigil-tui.
+11
;;;
12
;;; Color encoding:
13
;;; - -1 = default terminal color
14
;;; - 0-7 = standard colors (black, red, green, yellow, blue, magenta, cyan, white)
@@ -13,13 +17,10 @@
17
;;; - >= 256 = truecolor RGB (pack with color-rgb)
18
19
(define-library (sigil tui grid)
16
(import (sigil core)
17
(sigil io)
18
(sigil string)
19
(sigil math)
20
(sigil terminal))
+20
(import (sigil core))
21
22
(export make-grid
+23
grid?
24
grid-width
25
grid-height
26
grid-ref
@@ -105,14 +106,9 @@
106
b))
107
108
;; ============================================================
108
;; Cell: 4-element vector #(char fg bg attrs)
+109
;; Cell accessors (Scheme — lightweight wrappers)
110
;; ============================================================
111
111
;;; Create a cell with the given character, colors, and attributes.
112
(define (make-cell ch fg bg attrs)
113
(: char? integer? integer? integer? -> vector?)
114
(vector ch fg bg attrs))
115
112
;; Default cell: space with default colors, no attributes
113
(define (default-cell)
114
(: -> vector?)
@@ -147,266 +143,59 @@
143
(eqv? (vector-ref a 3) (vector-ref b 3))))
144
145
;; ============================================================
150
;; Grid: vector of cells + dimensions
+146
;; Native grid operations (provided by native/grid.c)
147
;; ============================================================
148
+149
;;; Create a cell with the given character, colors, and attributes.
+150
(define-native (make-cell ch fg bg attrs)
+151
(: char? integer? integer? integer? -> vector?))
+152
153
;;; Create a grid of width x height cells, filled with default cells.
154
(define (make-grid width height)
155
(: integer? integer? -> vector?)
156
(let* ((size (* width height))
157
(cells (make-vector size #f)))
158
(let loop ((i 0))
159
(when (< i size)
160
(vector-set! cells i (default-cell))
161
(loop (+ i 1))))
162
(vector width height cells)))
+154
(define-native (make-grid width height)
+155
(: integer? integer? -> grid?))
+156
+157
;;; Check if a value is a native grid.
+158
(define-native (grid? obj)
+159
(: any? -> boolean?))
160
161
;;; Get the grid width.
165
(define (grid-width grid)
166
(: vector? -> integer?)
167
(vector-ref grid 0))
+162
(define-native (grid-width grid)
+163
(: grid? -> integer?))
164
165
;;; Get the grid height.
170
(define (grid-height grid)
171
(: vector? -> integer?)
172
(vector-ref grid 1))
173
174
(define (grid-cells grid)
175
(vector-ref grid 2))
176
177
(define (grid-index grid col row)
178
(+ (* row (grid-width grid)) col))
+166
(define-native (grid-height grid)
+167
(: grid? -> integer?))
168
169
;;; Get the cell at (col, row).
181
(define (grid-ref grid col row)
182
(: vector? integer? integer? -> vector?)
183
(vector-ref (grid-cells grid) (grid-index grid col row)))
+170
(define-native (grid-ref grid col row)
+171
(: grid? integer? integer? -> vector?))
172
173
;;; Set a cell at (col, row) by mutating it in place.
186
;;;
187
;;; Avoids allocating a new cell vector on every write.
188
(define (grid-set! grid col row ch fg bg attrs)
189
(: vector? integer? integer? char? integer? integer? integer? -> void?)
190
(let ((cell (vector-ref (grid-cells grid) (grid-index grid col row))))
191
(vector-set! cell 0 ch)
192
(vector-set! cell 1 fg)
193
(vector-set! cell 2 bg)
194
(vector-set! cell 3 attrs)))
+174
(define-native (grid-set! grid col row ch fg bg attrs)
+175
(: grid? integer? integer? char? integer? integer? integer? -> void?))
176
177
;;; Clear the grid to default cells (space, default colors, no attrs).
197
(define (grid-clear! grid)
198
(: vector? -> void?)
199
(let ((cells (grid-cells grid))
200
(size (* (grid-width grid) (grid-height grid))))
201
(let loop ((i 0))
202
(when (< i size)
203
(let ((cell (vector-ref cells i)))
204
(vector-set! cell 0 #\space)
205
(vector-set! cell 1 -1)
206
(vector-set! cell 2 -1)
207
(vector-set! cell 3 0))
208
(loop (+ i 1))))))
+178
(define-native (grid-clear! grid)
+179
(: grid? -> void?))
180
181
;;; Write a string to the grid at (col, row) with the given style.
182
;;;
183
;;; Characters are written left-to-right, truncated at the grid edge.
213
(define (grid-write-string! grid col row str fg bg attrs)
214
(: vector? integer? integer? string? integer? integer? integer? -> void?)
215
(let ((w (grid-width grid))
216
(cells (grid-cells grid))
217
(gw (grid-width grid))
218
(len (string-length str)))
219
(let loop ((i 0) (c col))
220
(when (and (< i len) (< c w))
221
(let ((cell (vector-ref cells (+ (* row gw) c))))
222
(vector-set! cell 0 (string-ref str i))
223
(vector-set! cell 1 fg)
224
(vector-set! cell 2 bg)
225
(vector-set! cell 3 attrs))
226
(loop (+ i 1) (+ c 1))))))
+184
(define-native (grid-write-string! grid col row str fg bg attrs)
+185
(: grid? integer? integer? string? integer? integer? integer? -> void?))
186
187
;;; Fill a rectangle with a character and style.
229
(define (grid-fill-rect! grid x y w h ch fg bg attrs)
230
(: vector? integer? integer? integer? integer? char? integer? integer? integer? -> void?)
231
(let ((cells (grid-cells grid))
232
(gw (grid-width grid))
233
(gh (grid-height grid)))
234
(let row-loop ((r y))
235
(when (and (< r (+ y h)) (< r gh))
236
(let col-loop ((c x))
237
(when (and (< c (+ x w)) (< c gw))
238
(let ((cell (vector-ref cells (+ (* r gw) c))))
239
(vector-set! cell 0 ch)
240
(vector-set! cell 1 fg)
241
(vector-set! cell 2 bg)
242
(vector-set! cell 3 attrs))
243
(col-loop (+ c 1))))
244
(row-loop (+ r 1))))))
245
246
;;; Create a deep copy of a grid (copies cell contents, not references).
247
(define (grid-copy grid)
248
(: vector? -> vector?)
249
(let* ((w (grid-width grid))
250
(h (grid-height grid))
251
(size (* w h))
252
(src (grid-cells grid))
253
(dst (make-vector size #f)))
254
(let loop ((i 0))
255
(when (< i size)
256
(let ((cell (vector-ref src i)))
257
(vector-set! dst i (vector (vector-ref cell 0)
258
(vector-ref cell 1)
259
(vector-ref cell 2)
260
(vector-ref cell 3))))
261
(loop (+ i 1))))
262
(vector w h dst)))
263
264
;; ============================================================
265
;; SGR escape code emission
266
;; ============================================================
267
268
(define esc "\x1b;")
269
270
;; Emit a foreground color SGR sequence to port
271
(define (emit-fg port color)
272
(cond
273
((= color -1)
274
(display "39" port))
275
((and (>= color 0) (<= color 7))
276
(display (number->string (+ color 30)) port))
277
((and (>= color 8) (<= color 15))
278
(display (number->string (+ color 82)) port)) ;; 90-97
279
((and (>= color 16) (<= color 255))
280
(display "38;5;" port)
281
(display (number->string color) port))
282
(else
283
;; Truecolor: decode packed RGB
284
(let* ((v (- color 65536)) ;; undo the +1 offset from color-rgb
285
(r (quotient v 65536))
286
(rem (remainder v 65536))
287
(g (quotient rem 256))
288
(b (remainder rem 256)))
289
(display "38;2;" port)
290
(display (number->string r) port)
291
(display ";" port)
292
(display (number->string g) port)
293
(display ";" port)
294
(display (number->string b) port)))))
295
296
;; Emit a background color SGR sequence to port
297
(define (emit-bg port color)
298
(cond
299
((= color -1)
300
(display "49" port))
301
((and (>= color 0) (<= color 7))
302
(display (number->string (+ color 40)) port))
303
((and (>= color 8) (<= color 15))
304
(display (number->string (+ color 92)) port)) ;; 100-107
305
((and (>= color 16) (<= color 255))
306
(display "48;5;" port)
307
(display (number->string color) port))
308
(else
309
(let* ((v (- color 65536))
310
(r (quotient v 65536))
311
(rem (remainder v 65536))
312
(g (quotient rem 256))
313
(b (remainder rem 256)))
314
(display "48;2;" port)
315
(display (number->string r) port)
316
(display ";" port)
317
(display (number->string g) port)
318
(display ";" port)
319
(display (number->string b) port)))))
320
321
;; Emit attribute SGR codes to port
322
(define (emit-attrs port attrs)
323
(when (not (= (bitwise-and attrs attr-bold) 0))
324
(display ";1" port))
325
(when (not (= (bitwise-and attrs attr-dim) 0))
326
(display ";2" port))
327
(when (not (= (bitwise-and attrs attr-italic) 0))
328
(display ";3" port))
329
(when (not (= (bitwise-and attrs attr-underline) 0))
330
(display ";4" port))
331
(when (not (= (bitwise-and attrs attr-inverse) 0))
332
(display ";7" port))
333
(when (not (= (bitwise-and attrs attr-strikethrough) 0))
334
(display ";9" port)))
335
336
;; Emit full SGR for a cell
337
(define (emit-sgr port fg bg attrs)
338
(display esc port)
339
(display "[0;" port) ;; reset first
340
(emit-fg port fg)
341
(display ";" port)
342
(emit-bg port bg)
343
(emit-attrs port attrs)
344
(display "m" port))
345
346
;; ============================================================
347
;; Grid diffing
348
;; ============================================================
+188
(define-native (grid-fill-rect! grid x y w h ch fg bg attrs)
+189
(: grid? integer? integer? integer? integer? char? integer? integer? integer? -> void?))
190
191
;;; Compute the diff between prev and curr grids, return an ANSI string.
192
;;;
193
;;; Scans cell-by-cell, emitting cursor moves and SGR sequences only
194
;;; where cells differ. Returns the string to write atomically via
195
;;; terminal-write-raw.
355
(define (grid-diff prev curr)
356
(: vector? vector? -> string?)
357
(let ((w (grid-width curr))
358
(h (grid-height curr))
359
(prev-cells (grid-cells prev))
360
(curr-cells (grid-cells curr))
361
(out (open-output-string))
362
(last-fg -2) ;; -2 = no style emitted yet (different from -1=default)
363
(last-bg -2)
364
(last-attrs -1)
365
(cursor-col -1)
366
(cursor-row -1))
367
(let row-loop ((row 0))
368
(when (< row h)
369
(let col-loop ((col 0))
370
(when (< col w)
371
(let* ((idx (+ (* row w) col))
372
(pc (vector-ref prev-cells idx))
373
(cc (vector-ref curr-cells idx)))
374
(when (not (and (eqv? (vector-ref pc 0) (vector-ref cc 0))
375
(eqv? (vector-ref pc 1) (vector-ref cc 1))
376
(eqv? (vector-ref pc 2) (vector-ref cc 2))
377
(eqv? (vector-ref pc 3) (vector-ref cc 3))))
378
(let ((fg (vector-ref cc 1))
379
(bg (vector-ref cc 2))
380
(attrs (vector-ref cc 3))
381
(ch (vector-ref cc 0)))
382
;; Move cursor if not at expected position
383
(when (or (not (= cursor-row row))
384
(not (= cursor-col col)))
385
(display esc out)
386
(display "[" out)
387
(display (number->string (+ row 1)) out)
388
(display ";" out)
389
(display (number->string (+ col 1)) out)
390
(display "H" out))
391
;; Emit style if changed
392
(when (or (not (= fg last-fg))
393
(not (= bg last-bg))
394
(not (= attrs last-attrs)))
395
(emit-sgr out fg bg attrs)
396
(set! last-fg fg)
397
(set! last-bg bg)
398
(set! last-attrs attrs))
399
;; Emit character
400
(display ch out)
401
(set! cursor-col (+ col 1))
402
(set! cursor-row row))))
403
(col-loop (+ col 1))))
404
(row-loop (+ row 1))))
405
;; Reset style at end
406
(let ((result (get-output-string out)))
407
(if (= (string-length result) 0)
408
result
409
(begin
410
(display esc out)
411
(display "[0m" out)
412
(get-output-string out))))))))
+196
(define-native (grid-diff prev curr)
+197
(: grid? grid? -> string?))
+198
+199
;;; Create a deep copy of a grid (copies cell contents, not references).
+200
(define-native (grid-copy grid)
+201
(: grid? -> grid?))))
test/test-grid.sglmodified
@@ -1,6 +1,11 @@
1
(import (sigil test)
2
(sigil tui grid))
3
+4
;; Skip grid tests when native code isn't linked (make-grid will be #f)
+5
(when (not (procedure? make-grid))
+6
(display "Skipping grid tests (native code not linked)\n")
+7
(exit 0))
+8
9
(test-group "grid"
10
11
(test-group "creation"