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, 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>
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 * ============================================================ */
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
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
37/* Default cell values */
38#define DEFAULT_CHAR ' '
39#define DEFAULT_COLOR (-1)
40#define DEFAULT_ATTRS 0
42typedef struct {
43 int width;
44 int height;
45 int32_t *cells; /* Flat array: width * height * CELL_FIELDS */
46} Grid;
48static Value grid_type_tag = SIGIL_UNDEFINED;
50static void grid_finalizer(void *data)
52 Grid *g = (Grid *)data;
53 if (g) {
54 free(g->cells);
55 free(g);
56 }
59static int is_grid(Value v)
61 if (!sigil_is_foreign(v)) return 0;
62 return sigil_foreign_type(v) == grid_type_tag;
65static Grid *as_grid(Value v)
67 return (Grid *)sigil_foreign_data(v);
70/* Get cell pointer for (col, row) */
71static inline int32_t *cell_at(Grid *g, int col, int row)
73 return &g->cells[(row * g->width + col) * CELL_FIELDS];
76/* Initialize a cell to defaults */
77static inline void cell_clear(int32_t *cell)
79 cell[CELL_CHAR] = DEFAULT_CHAR;
80 cell[CELL_FG] = DEFAULT_COLOR;
81 cell[CELL_BG] = DEFAULT_COLOR;
82 cell[CELL_ATTRS] = DEFAULT_ATTRS;
85/* ============================================================
86 * Grid operations
87 * ============================================================ */
89/*
90 * %make-grid width height -> grid
91 */
92static Value native_make_grid(SigilVM *vm, int argc, Value *args)
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));
128/*
129 * %grid-width grid -> integer
130 */
131static Value native_grid_width(SigilVM *vm, int argc, Value *args)
133 (void)vm; (void)argc;
134 return sigil_fixnum(as_grid(args[0])->width);
137/*
138 * %grid-height grid -> integer
139 */
140static Value native_grid_height(SigilVM *vm, int argc, Value *args)
142 (void)vm; (void)argc;
143 return sigil_fixnum(as_grid(args[0])->height);
146/* Helper: allocate a 4-element Scheme vector for cell data */
147static Value make_cell_vector(SigilVM *vm, int32_t *cell)
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);
159/*
160 * %grid-ref grid col row -> vector #(char fg bg attrs)
161 *
162 * Returns a fresh vector for compatibility with existing code.
163 */
164static Value native_grid_ref(SigilVM *vm, int argc, Value *args)
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));
181/*
182 * %grid-set! grid col row ch fg bg attrs -> void
183 */
184static Value native_grid_set(SigilVM *vm, int argc, Value *args)
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;
207/*
208 * %grid-clear! grid -> void
209 */
210static Value native_grid_clear(SigilVM *vm, int argc, Value *args)
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;
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 */
230static Value native_grid_write_string(SigilVM *vm, int argc, Value *args)
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;
293/*
294 * %grid-fill-rect! grid x y w h ch fg bg attrs -> void
295 */
296static Value native_grid_fill_rect(SigilVM *vm, int argc, Value *args)
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;
328/*
329 * %grid-copy grid -> grid
330 *
331 * Create a deep copy of a grid.
332 */
333static Value native_grid_copy(SigilVM *vm, int argc, Value *args)
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));
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 * ============================================================ */
364/* Dynamic buffer for building diff output */
365typedef struct {
366 char *data;
367 size_t len;
368 size_t cap;
369} DiffBuf;
371static void buf_init(DiffBuf *buf)
373 buf->cap = 4096;
374 buf->data = malloc(buf->cap);
375 buf->len = 0;
378static void buf_ensure(DiffBuf *buf, size_t need)
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 }
388static void buf_append(DiffBuf *buf, const char *str, size_t len)
390 buf_ensure(buf, len);
391 memcpy(buf->data + buf->len, str, len);
392 buf->len += len;
395static void buf_append_str(DiffBuf *buf, const char *str)
397 buf_append(buf, str, strlen(str));
400static void buf_append_int(DiffBuf *buf, int n)
402 char tmp[16];
403 int len = snprintf(tmp, sizeof(tmp), "%d", n);
404 buf_append(buf, tmp, len);
407static void buf_append_char_utf8(DiffBuf *buf, uint32_t cp)
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);
433/* Emit foreground color SGR to buffer */
434static void emit_fg(DiffBuf *buf, int32_t color)
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 }
461/* Emit background color SGR to buffer */
462static void emit_bg(DiffBuf *buf, int32_t color)
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 }
488/* Emit attribute codes to buffer */
489static void emit_attrs(DiffBuf *buf, int32_t attrs)
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");
499/* Emit full SGR for a cell */
500static void emit_sgr(DiffBuf *buf, int32_t fg, int32_t bg, int32_t attrs)
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);
510/*
511 * %grid-diff prev curr -> string
512 *
513 * Compute minimal ANSI diff between two grids.
514 */
515static Value native_grid_diff(SigilVM *vm, int argc, Value *args)
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;
592/*
593 * %grid? value -> boolean
594 */
595static Value native_grid_p(SigilVM *vm, int argc, Value *args)
597 (void)vm; (void)argc;
598 return sigil_bool(is_grid(args[0]));
601/*
602 * Cell constructors and accessors — expose for Scheme compatibility
603 */
605/*
606 * %make-cell ch fg bg attrs -> vector
607 */
608static Value native_make_cell(SigilVM *vm, int argc, Value *args)
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);
621/* ============================================================
622 * Module initialization
623 * ============================================================ */
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)
629void sigil__init_sigil_tui_grid_module(SigilVM *vm)
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);
677#undef REGISTER_AND_EXPORT