AtlatestRepositorysigil-tui
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, and6
* text attributes as flat C arrays, avoiding per-cell heap allocation.7
*/9
#include "sigil-internal.h"10
#include <stdio.h>11
#include <stdlib.h>12
#include <string.h>14
/* ============================================================15
* Cell layout: 4 int32_t values per cell16
* [0] = character (Unicode codepoint)17
* [1] = foreground color18
* [2] = background color19
* [3] = text attributes (bitmask)20
* ============================================================ */22
#define CELL_FIELDS 423
#define CELL_CHAR 024
#define CELL_FG 125
#define CELL_BG 226
#define CELL_ATTRS 328
/* Attribute flags */29
#define ATTR_NONE 030
#define ATTR_BOLD 131
#define ATTR_DIM 232
#define ATTR_ITALIC 433
#define ATTR_UNDERLINE 834
#define ATTR_INVERSE 1635
#define ATTR_STRIKETHROUGH 3237
/* Default cell values */38
#define DEFAULT_CHAR ' '39
#define DEFAULT_COLOR (-1)40
#define DEFAULT_ATTRS 042
typedef struct {43
int width;44
int height;45
int32_t *cells; /* Flat array: width * height * CELL_FIELDS */46
} Grid;48
static Value grid_type_tag = SIGIL_UNDEFINED;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
}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
}65
static Grid *as_grid(Value v)66
{67
return (Grid *)sigil_foreign_data(v);68
}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
}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
}85
/* ============================================================86
* Grid operations87
* ============================================================ */89
/*90
* %make-grid width height -> grid91
*/92
static Value native_make_grid(SigilVM *vm, int argc, Value *args)93
{94
(void)argc;96
int w = (int)sigil_as_fixnum(args[0]);97
int h = (int)sigil_as_fixnum(args[1]);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
}105
Grid *g = malloc(sizeof(Grid));106
if (!g) return SIGIL_FALSE;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
}115
g->width = w;116
g->height = h;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
}124
return sigil_make_foreign(vm, grid_type_tag, g, grid_finalizer,125
sizeof(Grid) + size * sizeof(int32_t));126
}128
/*129
* %grid-width grid -> integer130
*/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
}137
/*138
* %grid-height grid -> integer139
*/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
}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
}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;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]);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
}178
return make_cell_vector(vm, cell_at(g, col, row));179
}181
/*182
* %grid-set! grid col row ch fg bg attrs -> void183
*/184
static Value native_grid_set(SigilVM *vm, int argc, Value *args)185
{186
(void)argc;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]);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
}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]);204
return SIGIL_UNDEFINED;205
}207
/*208
* %grid-clear! grid -> void209
*/210
static Value native_grid_clear(SigilVM *vm, int argc, Value *args)211
{212
(void)vm; (void)argc;214
Grid *g = as_grid(args[0]);215
size_t count = (size_t)g->width * g->height;217
for (size_t i = 0; i < count; i++) {218
int32_t *cell = &g->cells[i * CELL_FIELDS];219
cell_clear(cell);220
}222
return SIGIL_UNDEFINED;223
}225
/*226
* %grid-write-string! grid col row str fg bg attrs -> void227
*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;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]);242
if (row < 0 || row >= g->height || col >= g->width) {243
return SIGIL_UNDEFINED;244
}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;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
}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
}290
return SIGIL_UNDEFINED;291
}293
/*294
* %grid-fill-rect! grid x y w h ch fg bg attrs -> void295
*/296
static Value native_grid_fill_rect(SigilVM *vm, int argc, Value *args)297
{298
(void)vm; (void)argc;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]);310
int gw = g->width;311
int gh = g->height;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
}325
return SIGIL_UNDEFINED;326
}328
/*329
* %grid-copy grid -> grid330
*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;337
Grid *src = as_grid(args[0]);338
Grid *dst = malloc(sizeof(Grid));339
if (!dst) return SIGIL_FALSE;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
}348
dst->width = src->width;349
dst->height = src->height;350
memcpy(dst->cells, src->cells, size * sizeof(int32_t));352
return sigil_make_foreign(vm, grid_type_tag, dst, grid_finalizer,353
sizeof(Grid) + size * sizeof(int32_t));354
}356
/* ============================================================357
* Grid diffing — the performance-critical hot path358
*359
* Scans cell-by-cell, emitting minimal ANSI escape sequences to360
* update only changed cells. Returns a string to write atomically361
* via terminal-write-raw.362
* ============================================================ */364
/* Dynamic buffer for building diff output */365
typedef struct {366
char *data;367
size_t len;368
size_t cap;369
} DiffBuf;371
static void buf_init(DiffBuf *buf)372
{373
buf->cap = 4096;374
buf->data = malloc(buf->cap);375
buf->len = 0;376
}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
}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
}395
static void buf_append_str(DiffBuf *buf, const char *str)396
{397
buf_append(buf, str, strlen(str));398
}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
}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
}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
}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
}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
}499
/* Emit full SGR for a cell */500
static void emit_sgr(DiffBuf *buf, int32_t fg, int32_t bg, int32_t attrs)501
{502
buf_append_str(buf, "\x1b[0;");503
emit_fg(buf, fg);504
buf_append(buf, ";", 1);505
emit_bg(buf, bg);506
emit_attrs(buf, attrs);507
buf_append(buf, "m", 1);508
}510
/*511
* %grid-diff prev curr -> string512
*513
* Compute minimal ANSI diff between two grids.514
*/515
static Value native_grid_diff(SigilVM *vm, int argc, Value *args)516
{517
(void)argc;519
Grid *prev = as_grid(args[0]);520
Grid *curr = as_grid(args[1]);522
int w = curr->width;523
int h = curr->height;524
int32_t *pc = prev->cells;525
int32_t *cc = curr->cells;527
DiffBuf buf;528
buf_init(&buf);530
int32_t last_fg = -2; /* -2 = no style emitted yet */531
int32_t last_bg = -2;532
int32_t last_attrs = -1;533
int cursor_col = -1;534
int cursor_row = -1;536
for (int row = 0; row < h; row++) {537
for (int col = 0; col < w; col++) {538
int idx = (row * w + col) * CELL_FIELDS;539
int32_t *p = &pc[idx];540
int32_t *c = &cc[idx];542
/* Skip unchanged cells */543
if (p[CELL_CHAR] == c[CELL_CHAR] &&544
p[CELL_FG] == c[CELL_FG] &&545
p[CELL_BG] == c[CELL_BG] &&546
p[CELL_ATTRS] == c[CELL_ATTRS]) {547
continue;548
}550
int32_t fg = c[CELL_FG];551
int32_t bg = c[CELL_BG];552
int32_t attrs = c[CELL_ATTRS];553
int32_t ch = c[CELL_CHAR];555
/* Move cursor if not at expected position */556
if (cursor_row != row || cursor_col != col) {557
buf_append_str(&buf, "\x1b[");558
buf_append_int(&buf, row + 1);559
buf_append(&buf, ";", 1);560
buf_append_int(&buf, col + 1);561
buf_append(&buf, "H", 1);562
}564
/* Emit style if changed */565
if (fg != last_fg || bg != last_bg || attrs != last_attrs) {566
emit_sgr(&buf, fg, bg, attrs);567
last_fg = fg;568
last_bg = bg;569
last_attrs = attrs;570
}572
/* Emit character */573
buf_append_char_utf8(&buf, (uint32_t)ch);574
cursor_col = col + 1;575
cursor_row = row;576
}577
}579
Value result;580
if (buf.len == 0) {581
result = sigil_make_string(vm, "", 0);582
} else {583
/* Append reset sequence */584
buf_append_str(&buf, "\x1b[0m");585
result = sigil_make_string(vm, buf.data, buf.len);586
}588
free(buf.data);589
return result;590
}592
/*593
* %grid? value -> boolean594
*/595
static Value native_grid_p(SigilVM *vm, int argc, Value *args)596
{597
(void)vm; (void)argc;598
return sigil_bool(is_grid(args[0]));599
}601
/*602
* Cell constructors and accessors — expose for Scheme compatibility603
*/605
/*606
* %make-cell ch fg bg attrs -> vector607
*/608
static Value native_make_cell(SigilVM *vm, int argc, Value *args)609
{610
(void)argc;611
SigilVector *vec = sigil__gc_alloc(vm, SIGIL_OBJ_VECTOR,612
sizeof(SigilVector) + 4 * sizeof(Value));613
vec->length = 4;614
vec->elements[0] = args[0]; /* char */615
vec->elements[1] = args[1]; /* fg */616
vec->elements[2] = args[2]; /* bg */617
vec->elements[3] = args[3]; /* attrs */618
return sigil_ptr(vec);619
}621
/* ============================================================622
* Module initialization623
* ============================================================ */625
#define REGISTER_AND_EXPORT(name, func, arity, doc) \626
sigil_module_register_native(vm, name, func, arity, doc); \627
sigil_module_export(vm, name)629
void sigil__init_sigil_tui_grid_module(SigilVM *vm)630
{631
grid_type_tag = sigil_intern_symbol(vm, "tui-grid", 8);633
SigilModule *module = sigil_begin_module(vm, "(sigil tui grid)");634
if (!module) return;636
/* Grid operations — same names as Scheme definitions to override them */637
REGISTER_AND_EXPORT("make-grid", native_make_grid,638
SIGIL_ARITY_EXACT(2),639
"Create a grid of width x height cells");640
REGISTER_AND_EXPORT("grid?", native_grid_p,641
SIGIL_ARITY_EXACT(1),642
"Check if value is a grid");643
REGISTER_AND_EXPORT("grid-width", native_grid_width,644
SIGIL_ARITY_EXACT(1),645
"Get grid width");646
REGISTER_AND_EXPORT("grid-height", native_grid_height,647
SIGIL_ARITY_EXACT(1),648
"Get grid height");649
REGISTER_AND_EXPORT("grid-ref", native_grid_ref,650
SIGIL_ARITY_EXACT(3),651
"Get cell at (col, row) as vector");652
REGISTER_AND_EXPORT("grid-set!", native_grid_set,653
SIGIL_ARITY_EXACT(7),654
"Set cell at (col, row)");655
REGISTER_AND_EXPORT("grid-clear!", native_grid_clear,656
SIGIL_ARITY_EXACT(1),657
"Clear grid to default cells");658
REGISTER_AND_EXPORT("grid-write-string!", native_grid_write_string,659
SIGIL_ARITY_EXACT(7),660
"Write string to grid at position");661
REGISTER_AND_EXPORT("grid-fill-rect!", native_grid_fill_rect,662
SIGIL_ARITY_EXACT(9),663
"Fill rectangle with character and style");664
REGISTER_AND_EXPORT("grid-diff", native_grid_diff,665
SIGIL_ARITY_EXACT(2),666
"Compute ANSI diff between two grids");667
REGISTER_AND_EXPORT("grid-copy", native_grid_copy,668
SIGIL_ARITY_EXACT(1),669
"Create deep copy of grid");670
REGISTER_AND_EXPORT("make-cell", native_make_cell,671
SIGIL_ARITY_EXACT(4),672
"Create a cell vector");674
sigil_end_module(vm);675
}677
#undef REGISTER_AND_EXPORT