Migrate chez-scintilla to jerboa-scintilla: 10 libs under (jerboa-scintilla ...). C shim renamed jerboa_scintilla_shim.c. Source 10/10 transpile clean; shim build needs gerbil-scintilla vendor archives (Linux/CI).

ober

faad1833d938c3e2da81181a281e3c961e45e732

diff --git a/.gitignore b/.gitignore
index 6c1c914..f002dc2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 *.so
 *.wpo
+.jerbuild-hashes
diff --git a/Makefile b/Makefile
index 8746b4f..2c46a12 100644
--- a/Makefile
+++ b/Makefile
@@ -1,21 +1,37 @@
-.PHONY: all build shim clean test vendor-deps
+JERBOA_HOME ?= $(HOME)/mine/jerboa
+SCHEME      ?= $(JERBOA_HOME)/.chez/bin/scheme
+JERBUILD    ?= $(JERBOA_HOME)/jerbuild
+LIBDIRS      = lib:$(JERBOA_HOME)/lib
 
-# Gerbil-scintilla vendor directory (reuse already-built artifacts)
+# Vendor (gerbil-scintilla static archives)
 GERBIL_SCINTILLA ?= $(HOME)/mine/gerbil-scintilla
+VENDOR           = $(GERBIL_SCINTILLA)/vendor
+SCI_DIR          = $(VENDOR)/scintilla
+SCI_TB_DIR       = $(SCI_DIR)/termbox
+TERMBOX_DIR      = $(SCI_TB_DIR)/termbox_next
+LEXILLA_DIR      = $(VENDOR)/lexilla
+SCINTILLA_A      = $(SCI_DIR)/bin/scintilla.a
+TERMBOX_A        = $(TERMBOX_DIR)/bin/termbox.a
+LEXILLA_A        = $(LEXILLA_DIR)/bin/liblexilla.a
 
-# Vendor paths
-VENDOR          = $(GERBIL_SCINTILLA)/vendor
-SCI_DIR         = $(VENDOR)/scintilla
-SCI_TB_DIR      = $(SCI_DIR)/termbox
-TERMBOX_DIR     = $(SCI_TB_DIR)/termbox_next
-LEXILLA_DIR     = $(VENDOR)/lexilla
+CC ?= cc
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Darwin)
+  SHARED_FLAG  = -dynamiclib
+  SO_EXT       = .so
+  LD_VAR       = DYLD_LIBRARY_PATH
+  WHOLE_ARCHIVE_OPEN  = -Wl,-force_load,
+  WHOLE_ARCHIVE_CLOSE =
+else
+  SHARED_FLAG  = -shared
+  SO_EXT       = .so
+  LD_VAR       = LD_LIBRARY_PATH
+  WHOLE_ARCHIVE_OPEN  = -Wl,--whole-archive
+  WHOLE_ARCHIVE_CLOSE = -Wl,--no-whole-archive
+endif
 
-# Static archives
-SCINTILLA_A     = $(SCI_DIR)/bin/scintilla.a
-TERMBOX_A       = $(TERMBOX_DIR)/bin/termbox.a
-LEXILLA_A       = $(LEXILLA_DIR)/bin/liblexilla.a
+SHIM := jerboa_scintilla_shim$(SO_EXT)
 
-# Include paths
 CC_INCLUDES = \
 	-I$(SCI_DIR)/include \
 	-I$(SCI_DIR)/src \
@@ -23,43 +39,40 @@ CC_INCLUDES = \
 	-I$(TERMBOX_DIR)/src \
 	-I$(LEXILLA_DIR)/include
 
-# Chez Scheme library path
-CHEZ_LIB = src
-SHIM_SO  = chez_scintilla_shim.so
+.PHONY: all build transpile test clean shim vendor-deps vendor-check
 
 all: build
 
-# Build the C shim shared object
-shim: $(SHIM_SO)
+vendor-check:
+	@if [ ! -f $(SCINTILLA_A) ] || [ ! -f $(LEXILLA_A) ] || [ ! -f $(TERMBOX_A) ]; then \
+	  echo "WARN: vendor archives missing (expected under $(GERBIL_SCINTILLA))."; \
+	  echo "WARN: skip shim build; run 'make vendor-deps' guidance in $(GERBIL_SCINTILLA)."; exit 1; \
+	fi
 
-$(SHIM_SO): chez_scintilla_shim.c $(SCINTILLA_A) $(LEXILLA_A) $(TERMBOX_A)
-	gcc -shared -fPIC -o $@ $< \
-		$(CC_INCLUDES) \
-		-Wl,--whole-archive $(SCINTILLA_A) $(LEXILLA_A) $(TERMBOX_A) -Wl,--no-whole-archive \
+shim: vendor-check $(SHIM)
+
+ifeq ($(UNAME_S),Darwin)
+$(SHIM): jerboa_scintilla_shim.c
+	$(CC) $(SHARED_FLAG) -fPIC -O2 -o $@ $< $(CC_INCLUDES) \
+		$(WHOLE_ARCHIVE_OPEN)$(SCINTILLA_A) $(WHOLE_ARCHIVE_OPEN)$(LEXILLA_A) $(WHOLE_ARCHIVE_OPEN)$(TERMBOX_A) \
+		-lc++ -lpthread
+else
+$(SHIM): jerboa_scintilla_shim.c
+	$(CC) $(SHARED_FLAG) -fPIC -O2 -o $@ $< $(CC_INCLUDES) \
+		$(WHOLE_ARCHIVE_OPEN) $(SCINTILLA_A) $(LEXILLA_A) $(TERMBOX_A) $(WHOLE_ARCHIVE_CLOSE) \
 		-lstdc++ -lpthread
+endif
 
-# Build everything: shim + compile Scheme libraries
-build: $(SHIM_SO)
-	@echo "Compiling Scheme libraries..."
-	@CHEZ_SCINTILLA_LIB=$(CURDIR) \
-	scheme --libdirs $(CHEZ_LIB) --compile-imported-libraries --program /dev/null \
-		< <(echo '(import (chez-scintilla ffi) (chez-scintilla constants) (chez-scintilla scintilla) (chez-scintilla tui) (chez-scintilla lexer) (chez-scintilla style) (chez-scintilla search) (chez-scintilla folding) (chez-scintilla autocomplete) (chez-scintilla markers))') \
-		2>&1 || true
-	@echo "Build complete."
+transpile:
+	$(JERBUILD) transpile src lib --force
 
-# Run tests
-test: build
-	CHEZ_SCINTILLA_LIB=$(CURDIR) \
-	scheme --libdirs $(CHEZ_LIB) --script tests/run-tests.ss
+build: transpile shim
 
-# Check that vendor deps exist
-vendor-deps:
-	@test -f $(SCINTILLA_A) || (echo "ERROR: scintilla.a not found. Run 'make vendor-deps' in $(GERBIL_SCINTILLA)" && exit 1)
-	@test -f $(LEXILLA_A) || (echo "ERROR: liblexilla.a not found." && exit 1)
-	@test -f $(TERMBOX_A) || (echo "ERROR: termbox.a not found." && exit 1)
-	@echo "Vendor dependencies OK."
+test: build
+	JERBOA_SCINTILLA_LIB=$(CURDIR) \
+	$(LD_VAR)=$(CURDIR) \
+	$(SCHEME) --libdirs "$(LIBDIRS)" --script tests/run-tests.ss
 
 clean:
-	rm -f $(SHIM_SO)
-	find src -name '*.so' -delete
-	find src -name '*.wpo' -delete
+	rm -f $(SHIM) chez_scintilla_shim.so
+	rm -rf lib
diff --git a/chez_scintilla_shim.c b/chez_scintilla_shim.c
deleted file mode 100644
index 58288c1..0000000
--- a/chez_scintilla_shim.c
+++ /dev/null
@@ -1,341 +0,0 @@
-/* chez_scintilla_shim.c — C shim for Chez Scheme FFI to Scintilla + termbox
- *
- * Extracted from gerbil-scintilla/ffi.ss c-declare block.
- * Provides: instance lifecycle, message passing, notification ring buffer,
- *           input, display, clipboard, lexilla, and termbox wrappers.
- *
- * All functions use simple C types (void*, int, long, const char*)
- * compatible with Chez Scheme's foreign-procedure.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-#include "Scintilla.h"
-#include "ScintillaTermbox.h"
-#include "Lexilla.h"
-#include "termbox.h"
-
-/* ================================================================
-   Types
-   ================================================================ */
-typedef intptr_t sptr_t;
-typedef uintptr_t uptr_t;
-typedef void Scintilla;
-
-/* ================================================================
-   Notification ring buffer (per-instance)
-   ================================================================ */
-#define NOTIFY_RING_SIZE 64
-
-typedef struct {
-    int    code;
-    long   position;
-    int    ch;
-    int    modifiers;
-    int    modification_type;
-    char   text[256];
-    long   length;
-    long   lines_added;
-    int    message;
-    long   line;
-    int    fold_level_now;
-    int    fold_level_prev;
-    int    margin;
-    int    list_type;
-    int    x;
-    int    y;
-    int    token;
-    int    updated;
-} notify_entry_t;
-
-typedef struct instance_node {
-    Scintilla           *view;
-    notify_entry_t       ring[NOTIFY_RING_SIZE];
-    int                  ring_head;
-    int                  ring_tail;
-    struct instance_node *next;
-} instance_node_t;
-
-static instance_node_t *instance_list = NULL;
-
-/* Current notification (static, for drain_one -> accessor pattern) */
-static notify_entry_t current_scn;
-
-/* Find instance node by view pointer */
-static instance_node_t *find_instance(Scintilla *view) {
-    instance_node_t *n = instance_list;
-    while (n) {
-        if (n->view == view) return n;
-        n = n->next;
-    }
-    return NULL;
-}
-
-/* Notification callback — called synchronously from C++ */
-static void notification_callback(Scintilla *view, int msg,
-                                  SCNotification *scn, void *userdata) {
-    instance_node_t *inst = find_instance(view);
-    if (!inst) return;
-
-    int next_head = (inst->ring_head + 1) % NOTIFY_RING_SIZE;
-    if (next_head == inst->ring_tail) {
-        /* Ring full — drop oldest */
-        inst->ring_tail = (inst->ring_tail + 1) % NOTIFY_RING_SIZE;
-    }
-
-    notify_entry_t *e = &inst->ring[inst->ring_head];
-    e->code              = scn->nmhdr.code;
-    e->position          = (long)scn->position;
-    e->ch                = scn->ch;
-    e->modifiers         = scn->modifiers;
-    e->modification_type = scn->modificationType;
-    e->length            = (long)scn->length;
-    e->lines_added       = (long)scn->linesAdded;
-    e->message           = scn->message;
-    e->line              = (long)scn->line;
-    e->fold_level_now    = scn->foldLevelNow;
-    e->fold_level_prev   = scn->foldLevelPrev;
-    e->margin            = scn->margin;
-    e->list_type         = scn->listType;
-    e->x                 = scn->x;
-    e->y                 = scn->y;
-    e->token             = scn->token;
-    e->updated           = scn->updated;
-
-    if (scn->text && scn->length > 0) {
-        int copy_len = scn->length < 255 ? scn->length : 255;
-        memcpy(e->text, scn->text, copy_len);
-        e->text[copy_len] = '\0';
-    } else {
-        e->text[0] = '\0';
-    }
-
-    inst->ring_head = next_head;
-}
-
-/* ================================================================
-   Instance lifecycle
-   ================================================================ */
-void *chez_scintilla_new(void) {
-    Scintilla *sci = scintilla_new(notification_callback, NULL);
-    if (!sci) return NULL;
-
-    instance_node_t *node = (instance_node_t *)malloc(sizeof(instance_node_t));
-    node->view      = sci;
-    node->ring_head = 0;
-    node->ring_tail = 0;
-    node->next      = instance_list;
-    instance_list   = node;
-
-    return (void *)sci;
-}
-
-void chez_scintilla_delete(void *handle) {
-    Scintilla *sci = (Scintilla *)handle;
-    /* Remove from instance list */
-    instance_node_t **pp = &instance_list;
-    while (*pp) {
-        if ((*pp)->view == sci) {
-            instance_node_t *rm = *pp;
-            *pp = rm->next;
-            free(rm);
-            break;
-        }
-        pp = &(*pp)->next;
-    }
-    scintilla_delete(sci);
-}
-
-/* ================================================================
-   Message passing
-   ================================================================ */
-long chez_scintilla_send_message(void *handle, unsigned int msg,
-                                 unsigned long wparam, long lparam) {
-    return (long)scintilla_send_message((Scintilla *)handle, msg,
-                                        (uptr_t)wparam, (sptr_t)lparam);
-}
-
-/* Send message with string as lparam (cast char* to sptr_t) */
-long chez_scintilla_send_message_string(void *handle, unsigned int msg,
-                                        unsigned long wparam, const char *str) {
-    return (long)scintilla_send_message((Scintilla *)handle, msg,
-                                        (uptr_t)wparam, (sptr_t)str);
-}
-
-/* Receive string: call msg twice (get length, then fill buffer) */
-#define RECV_BUF_SIZE 65536
-static char recv_buf[RECV_BUF_SIZE];
-
-const char *chez_scintilla_receive_string(void *handle, unsigned int msg,
-                                          unsigned long wparam) {
-    long len = (long)scintilla_send_message((Scintilla *)handle, msg,
-                                            (uptr_t)wparam, (sptr_t)NULL);
-    if (len <= 0) {
-        recv_buf[0] = '\0';
-        return recv_buf;
-    }
-    if (len >= RECV_BUF_SIZE) len = RECV_BUF_SIZE - 1;
-    scintilla_send_message((Scintilla *)handle, msg,
-                           (uptr_t)wparam, (sptr_t)recv_buf);
-    recv_buf[len] = '\0';
-    return recv_buf;
-}
-
-/* Set property: wparam = key string, lparam = value string */
-long chez_scintilla_set_property(void *handle, unsigned int msg,
-                                 const char *key, const char *value) {
-    return (long)scintilla_send_message((Scintilla *)handle, msg,
-                                        (uptr_t)key, (sptr_t)value);
-}
-
-/* ================================================================
-   Input
-   ================================================================ */
-void chez_scintilla_send_key(void *handle, int key,
-                              int shift, int ctrl, int alt) {
-    scintilla_send_key((Scintilla *)handle, key, shift, ctrl, alt);
-}
-
-int chez_scintilla_send_mouse(void *handle, int event, int button,
-                               int y, int x,
-                               int shift, int ctrl, int alt) {
-    return scintilla_send_mouse((Scintilla *)handle, event, button,
-                                y, x, shift, ctrl, alt);
-}
-
-/* ================================================================
-   Display
-   ================================================================ */
-void chez_scintilla_refresh(void *handle) {
-    scintilla_refresh((Scintilla *)handle);
-}
-
-void chez_scintilla_resize(void *handle, int width, int height) {
-    scintilla_resize((Scintilla *)handle, width, height);
-}
-
-void chez_scintilla_move(void *handle, int x, int y) {
-    scintilla_move((Scintilla *)handle, x, y);
-}
-
-/* ================================================================
-   Clipboard
-   ================================================================ */
-const char *chez_scintilla_get_clipboard(void *handle) {
-    int len = 0;
-    char *buf = scintilla_get_clipboard((Scintilla *)handle, &len);
-    if (!buf) return "";
-    /* Copy into static buffer so Chez can read it, then free */
-    if (len >= RECV_BUF_SIZE) len = RECV_BUF_SIZE - 1;
-    memcpy(recv_buf, buf, len);
-    recv_buf[len] = '\0';
-    free(buf);
-    return recv_buf;
-}
-
-/* ================================================================
-   Lexilla integration
-   ================================================================ */
-#define SCI_SETILEXER_MSG 4033
-
-void chez_scintilla_set_lexer_language(void *handle, const char *lang) {
-    ILexer5 *lexer = CreateLexer(lang);
-    scintilla_send_message((Scintilla *)handle, SCI_SETILEXER_MSG,
-                           0, (sptr_t)lexer);
-}
-
-/* ================================================================
-   Notification drain
-   ================================================================ */
-int chez_scintilla_drain_one(void *handle) {
-    instance_node_t *inst = find_instance((Scintilla *)handle);
-    if (!inst) return 0;
-    if (inst->ring_head == inst->ring_tail) return 0;
-
-    current_scn = inst->ring[inst->ring_tail];
-    inst->ring_tail = (inst->ring_tail + 1) % NOTIFY_RING_SIZE;
-    return 1;
-}
-
-int    chez_scn_code(void)              { return current_scn.code; }
-long   chez_scn_position(void)          { return current_scn.position; }
-int    chez_scn_ch(void)                { return current_scn.ch; }
-int    chez_scn_modifiers(void)         { return current_scn.modifiers; }
-int    chez_scn_modification_type(void) { return current_scn.modification_type; }
-const char *chez_scn_text(void)         { return current_scn.text; }
-long   chez_scn_length(void)            { return current_scn.length; }
-long   chez_scn_lines_added(void)       { return current_scn.lines_added; }
-int    chez_scn_message(void)           { return current_scn.message; }
-long   chez_scn_line(void)              { return current_scn.line; }
-int    chez_scn_fold_level_now(void)    { return current_scn.fold_level_now; }
-int    chez_scn_fold_level_prev(void)   { return current_scn.fold_level_prev; }
-int    chez_scn_margin(void)            { return current_scn.margin; }
-int    chez_scn_list_type(void)         { return current_scn.list_type; }
-int    chez_scn_x(void)                 { return current_scn.x; }
-int    chez_scn_y(void)                 { return current_scn.y; }
-int    chez_scn_token(void)             { return current_scn.token; }
-int    chez_scn_updated(void)           { return current_scn.updated; }
-
-/* ================================================================
-   Termbox wrappers
-   ================================================================ */
-static struct tb_event last_tb_event;
-
-int  chez_tb_init(void)           { return tb_init(); }
-void chez_tb_shutdown(void)       { tb_shutdown(); }
-int  chez_tb_width(void)          { return tb_width(); }
-int  chez_tb_height(void)         { return tb_height(); }
-void chez_tb_clear(void)          { tb_clear(); }
-void chez_tb_present(void)        { tb_present(); }
-void chez_tb_set_cursor(int x, int y) { tb_set_cursor(x, y); }
-
-int chez_tb_poll_event(void) {
-    return tb_poll_event(&last_tb_event);
-}
-
-int chez_tb_peek_event(int timeout_ms) {
-    return tb_peek_event(&last_tb_event, timeout_ms);
-}
-
-int          chez_tb_event_type(void) { return last_tb_event.type; }
-int          chez_tb_event_mod(void)  { return last_tb_event.mod; }
-int          chez_tb_event_key(void)  { return last_tb_event.key; }
-unsigned int chez_tb_event_ch(void)   { return last_tb_event.ch; }
-int          chez_tb_event_w(void)    { return last_tb_event.w; }
-int          chez_tb_event_h(void)    { return last_tb_event.h; }
-int          chez_tb_event_x(void)    { return last_tb_event.x; }
-int          chez_tb_event_y(void)    { return last_tb_event.y; }
-
-/* ================================================================
-   Termbox extended wrappers
-   ================================================================ */
-void chez_tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg) {
-    tb_change_cell(x, y, ch, fg, bg);
-}
-
-void chez_tb_set_clear_attributes(uint32_t fg, uint32_t bg) {
-    tb_set_clear_attributes(fg, bg);
-}
-
-int chez_tb_select_input_mode(int mode) {
-    return tb_select_input_mode(mode);
-}
-
-int chez_tb_select_output_mode(int mode) {
-    return tb_select_output_mode(mode);
-}
-
-void chez_tb_print_string(int x, int y, uint32_t fg, uint32_t bg, const char *str) {
-    uint32_t ch;
-    while (*str) {
-        int len = utf8_char_to_unicode(&ch, str);
-        if (len <= 0) break;
-        tb_change_cell(x, y, ch, fg, bg);
-        x++;
-        str += len;
-    }
-}
diff --git a/jerboa_scintilla_shim.c b/jerboa_scintilla_shim.c
new file mode 100644
index 0000000..0375d61
--- /dev/null
+++ b/jerboa_scintilla_shim.c
@@ -0,0 +1,341 @@
+/* jerboa_scintilla_shim.c — C shim for Chez Scheme FFI to Scintilla + termbox
+ *
+ * Extracted from gerbil-scintilla/ffi.ss c-declare block.
+ * Provides: instance lifecycle, message passing, notification ring buffer,
+ *           input, display, clipboard, lexilla, and termbox wrappers.
+ *
+ * All functions use simple C types (void*, int, long, const char*)
+ * compatible with Chez Scheme's foreign-procedure.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "Scintilla.h"
+#include "ScintillaTermbox.h"
+#include "Lexilla.h"
+#include "termbox.h"
+
+/* ================================================================
+   Types
+   ================================================================ */
+typedef intptr_t sptr_t;
+typedef uintptr_t uptr_t;
+typedef void Scintilla;
+
+/* ================================================================
+   Notification ring buffer (per-instance)
+   ================================================================ */
+#define NOTIFY_RING_SIZE 64
+
+typedef struct {
+    int    code;
+    long   position;
+    int    ch;
+    int    modifiers;
+    int    modification_type;
+    char   text[256];
+    long   length;
+    long   lines_added;
+    int    message;
+    long   line;
+    int    fold_level_now;
+    int    fold_level_prev;
+    int    margin;
+    int    list_type;
+    int    x;
+    int    y;
+    int    token;
+    int    updated;
+} notify_entry_t;
+
+typedef struct instance_node {
+    Scintilla           *view;
+    notify_entry_t       ring[NOTIFY_RING_SIZE];
+    int                  ring_head;
+    int                  ring_tail;
+    struct instance_node *next;
+} instance_node_t;
+
+static instance_node_t *instance_list = NULL;
+
+/* Current notification (static, for drain_one -> accessor pattern) */
+static notify_entry_t current_scn;
+
+/* Find instance node by view pointer */
+static instance_node_t *find_instance(Scintilla *view) {
+    instance_node_t *n = instance_list;
+    while (n) {
+        if (n->view == view) return n;
+        n = n->next;
+    }
+    return NULL;
+}
+
+/* Notification callback — called synchronously from C++ */
+static void notification_callback(Scintilla *view, int msg,
+                                  SCNotification *scn, void *userdata) {
+    instance_node_t *inst = find_instance(view);
+    if (!inst) return;
+
+    int next_head = (inst->ring_head + 1) % NOTIFY_RING_SIZE;
+    if (next_head == inst->ring_tail) {
+        /* Ring full — drop oldest */
+        inst->ring_tail = (inst->ring_tail + 1) % NOTIFY_RING_SIZE;
+    }
+
+    notify_entry_t *e = &inst->ring[inst->ring_head];
+    e->code              = scn->nmhdr.code;
+    e->position          = (long)scn->position;
+    e->ch                = scn->ch;
+    e->modifiers         = scn->modifiers;
+    e->modification_type = scn->modificationType;
+    e->length            = (long)scn->length;
+    e->lines_added       = (long)scn->linesAdded;
+    e->message           = scn->message;
+    e->line              = (long)scn->line;
+    e->fold_level_now    = scn->foldLevelNow;
+    e->fold_level_prev   = scn->foldLevelPrev;
+    e->margin            = scn->margin;
+    e->list_type         = scn->listType;
+    e->x                 = scn->x;
+    e->y                 = scn->y;
+    e->token             = scn->token;
+    e->updated           = scn->updated;
+
+    if (scn->text && scn->length > 0) {
+        int copy_len = scn->length < 255 ? scn->length : 255;
+        memcpy(e->text, scn->text, copy_len);
+        e->text[copy_len] = '\0';
+    } else {
+        e->text[0] = '\0';
+    }
+
+    inst->ring_head = next_head;
+}
+
+/* ================================================================
+   Instance lifecycle
+   ================================================================ */
+void *jerboa_scintilla_new(void) {
+    Scintilla *sci = scintilla_new(notification_callback, NULL);
+    if (!sci) return NULL;
+
+    instance_node_t *node = (instance_node_t *)malloc(sizeof(instance_node_t));
+    node->view      = sci;
+    node->ring_head = 0;
+    node->ring_tail = 0;
+    node->next      = instance_list;
+    instance_list   = node;
+
+    return (void *)sci;
+}
+
+void jerboa_scintilla_delete(void *handle) {
+    Scintilla *sci = (Scintilla *)handle;
+    /* Remove from instance list */
+    instance_node_t **pp = &instance_list;
+    while (*pp) {
+        if ((*pp)->view == sci) {
+            instance_node_t *rm = *pp;
+            *pp = rm->next;
+            free(rm);
+            break;
+        }
+        pp = &(*pp)->next;
+    }
+    scintilla_delete(sci);
+}
+
+/* ================================================================
+   Message passing
+   ================================================================ */
+long jerboa_scintilla_send_message(void *handle, unsigned int msg,
+                                 unsigned long wparam, long lparam) {
+    return (long)scintilla_send_message((Scintilla *)handle, msg,
+                                        (uptr_t)wparam, (sptr_t)lparam);
+}
+
+/* Send message with string as lparam (cast char* to sptr_t) */
+long jerboa_scintilla_send_message_string(void *handle, unsigned int msg,
+                                        unsigned long wparam, const char *str) {
+    return (long)scintilla_send_message((Scintilla *)handle, msg,
+                                        (uptr_t)wparam, (sptr_t)str);
+}
+
+/* Receive string: call msg twice (get length, then fill buffer) */
+#define RECV_BUF_SIZE 65536
+static char recv_buf[RECV_BUF_SIZE];
+
+const char *jerboa_scintilla_receive_string(void *handle, unsigned int msg,
+                                          unsigned long wparam) {
+    long len = (long)scintilla_send_message((Scintilla *)handle, msg,
+                                            (uptr_t)wparam, (sptr_t)NULL);
+    if (len <= 0) {
+        recv_buf[0] = '\0';
+        return recv_buf;
+    }
+    if (len >= RECV_BUF_SIZE) len = RECV_BUF_SIZE - 1;
+    scintilla_send_message((Scintilla *)handle, msg,
+                           (uptr_t)wparam, (sptr_t)recv_buf);
+    recv_buf[len] = '\0';
+    return recv_buf;
+}
+
+/* Set property: wparam = key string, lparam = value string */
+long jerboa_scintilla_set_property(void *handle, unsigned int msg,
+                                 const char *key, const char *value) {
+    return (long)scintilla_send_message((Scintilla *)handle, msg,
+                                        (uptr_t)key, (sptr_t)value);
+}
+
+/* ================================================================
+   Input
+   ================================================================ */
+void jerboa_scintilla_send_key(void *handle, int key,
+                              int shift, int ctrl, int alt) {
+    scintilla_send_key((Scintilla *)handle, key, shift, ctrl, alt);
+}
+
+int jerboa_scintilla_send_mouse(void *handle, int event, int button,
+                               int y, int x,
+                               int shift, int ctrl, int alt) {
+    return scintilla_send_mouse((Scintilla *)handle, event, button,
+                                y, x, shift, ctrl, alt);
+}
+
+/* ================================================================
+   Display
+   ================================================================ */
+void jerboa_scintilla_refresh(void *handle) {
+    scintilla_refresh((Scintilla *)handle);
+}
+
+void jerboa_scintilla_resize(void *handle, int width, int height) {
+    scintilla_resize((Scintilla *)handle, width, height);
+}
+
+void jerboa_scintilla_move(void *handle, int x, int y) {
+    scintilla_move((Scintilla *)handle, x, y);
+}
+
+/* ================================================================
+   Clipboard
+   ================================================================ */
+const char *jerboa_scintilla_get_clipboard(void *handle) {
+    int len = 0;
+    char *buf = scintilla_get_clipboard((Scintilla *)handle, &len);
+    if (!buf) return "";
+    /* Copy into static buffer so Chez can read it, then free */
+    if (len >= RECV_BUF_SIZE) len = RECV_BUF_SIZE - 1;
+    memcpy(recv_buf, buf, len);
+    recv_buf[len] = '\0';
+    free(buf);
+    return recv_buf;
+}
+
+/* ================================================================
+   Lexilla integration
+   ================================================================ */
+#define SCI_SETILEXER_MSG 4033
+
+void jerboa_scintilla_set_lexer_language(void *handle, const char *lang) {
+    ILexer5 *lexer = CreateLexer(lang);
+    scintilla_send_message((Scintilla *)handle, SCI_SETILEXER_MSG,
+                           0, (sptr_t)lexer);
+}
+
+/* ================================================================
+   Notification drain
+   ================================================================ */
+int jerboa_scintilla_drain_one(void *handle) {
+    instance_node_t *inst = find_instance((Scintilla *)handle);
+    if (!inst) return 0;
+    if (inst->ring_head == inst->ring_tail) return 0;
+
+    current_scn = inst->ring[inst->ring_tail];
+    inst->ring_tail = (inst->ring_tail + 1) % NOTIFY_RING_SIZE;
+    return 1;
+}
+
+int    jerboa_scn_code(void)              { return current_scn.code; }
+long   jerboa_scn_position(void)          { return current_scn.position; }
+int    jerboa_scn_ch(void)                { return current_scn.ch; }
+int    jerboa_scn_modifiers(void)         { return current_scn.modifiers; }
+int    jerboa_scn_modification_type(void) { return current_scn.modification_type; }
+const char *jerboa_scn_text(void)         { return current_scn.text; }
+long   jerboa_scn_length(void)            { return current_scn.length; }
+long   jerboa_scn_lines_added(void)       { return current_scn.lines_added; }
+int    jerboa_scn_message(void)           { return current_scn.message; }
+long   jerboa_scn_line(void)              { return current_scn.line; }
+int    jerboa_scn_fold_level_now(void)    { return current_scn.fold_level_now; }
+int    jerboa_scn_fold_level_prev(void)   { return current_scn.fold_level_prev; }
+int    jerboa_scn_margin(void)            { return current_scn.margin; }
+int    jerboa_scn_list_type(void)         { return current_scn.list_type; }
+int    jerboa_scn_x(void)                 { return current_scn.x; }
+int    jerboa_scn_y(void)                 { return current_scn.y; }
+int    jerboa_scn_token(void)             { return current_scn.token; }
+int    jerboa_scn_updated(void)           { return current_scn.updated; }
+
+/* ================================================================
+   Termbox wrappers
+   ================================================================ */
+static struct tb_event last_tb_event;
+
+int  jerboa_tb_init(void)           { return tb_init(); }
+void jerboa_tb_shutdown(void)       { tb_shutdown(); }
+int  jerboa_tb_width(void)          { return tb_width(); }
+int  jerboa_tb_height(void)         { return tb_height(); }
+void jerboa_tb_clear(void)          { tb_clear(); }
+void jerboa_tb_present(void)        { tb_present(); }
+void jerboa_tb_set_cursor(int x, int y) { tb_set_cursor(x, y); }
+
+int jerboa_tb_poll_event(void) {
+    return tb_poll_event(&last_tb_event);
+}
+
+int jerboa_tb_peek_event(int timeout_ms) {
+    return tb_peek_event(&last_tb_event, timeout_ms);
+}
+
+int          jerboa_tb_event_type(void) { return last_tb_event.type; }
+int          jerboa_tb_event_mod(void)  { return last_tb_event.mod; }
+int          jerboa_tb_event_key(void)  { return last_tb_event.key; }
+unsigned int jerboa_tb_event_ch(void)   { return last_tb_event.ch; }
+int          jerboa_tb_event_w(void)    { return last_tb_event.w; }
+int          jerboa_tb_event_h(void)    { return last_tb_event.h; }
+int          jerboa_tb_event_x(void)    { return last_tb_event.x; }
+int          jerboa_tb_event_y(void)    { return last_tb_event.y; }
+
+/* ================================================================
+   Termbox extended wrappers
+   ================================================================ */
+void jerboa_tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg) {
+    tb_change_cell(x, y, ch, fg, bg);
+}
+
+void jerboa_tb_set_clear_attributes(uint32_t fg, uint32_t bg) {
+    tb_set_clear_attributes(fg, bg);
+}
+
+int jerboa_tb_select_input_mode(int mode) {
+    return tb_select_input_mode(mode);
+}
+
+int jerboa_tb_select_output_mode(int mode) {
+    return tb_select_output_mode(mode);
+}
+
+void jerboa_tb_print_string(int x, int y, uint32_t fg, uint32_t bg, const char *str) {
+    uint32_t ch;
+    while (*str) {
+        int len = utf8_char_to_unicode(&ch, str);
+        if (len <= 0) break;
+        tb_change_cell(x, y, ch, fg, bg);
+        x++;
+        str += len;
+    }
+}
diff --git a/lib/jerboa-scintilla/autocomplete.sls b/lib/jerboa-scintilla/autocomplete.sls
new file mode 100644
index 0000000..393bd0a
--- /dev/null
+++ b/lib/jerboa-scintilla/autocomplete.sls
@@ -0,0 +1,147 @@
+#!chezscheme
+;;; Generated by jerbuild — DO NOT EDIT
+;;; Source: src/jerboa-scintilla/autocomplete.ss
+
+(library (jerboa-scintilla autocomplete)
+  (export editor-autocomplete-show editor-autocomplete-cancel
+   editor-autocomplete-active? editor-autocomplete-pos-start
+   editor-autocomplete-complete editor-autocomplete-select
+   editor-autocomplete-get-current editor-autocomplete-stops
+   editor-autocomplete-set-separator
+   editor-autocomplete-get-separator
+   editor-autocomplete-set-ignore-case
+   editor-autocomplete-get-ignore-case?
+   editor-autocomplete-set-choose-single
+   editor-autocomplete-get-choose-single?
+   editor-autocomplete-set-auto-hide
+   editor-autocomplete-get-auto-hide?
+   editor-autocomplete-set-drop-rest-of-word
+   editor-autocomplete-get-drop-rest-of-word?
+   editor-autocomplete-set-cancel-at-start
+   editor-autocomplete-get-cancel-at-start?
+   editor-autocomplete-set-order editor-autocomplete-get-order
+   editor-autocomplete-set-max-width
+   editor-autocomplete-get-max-width
+   editor-autocomplete-set-max-height
+   editor-autocomplete-get-max-height
+   editor-autocomplete-set-fill-ups editor-calltip-show
+   editor-calltip-cancel editor-calltip-active?
+   editor-calltip-pos-start editor-calltip-set-highlight
+   editor-calltip-set-background editor-calltip-set-foreground
+   editor-calltip-set-foreground-highlight)
+  (import
+    (except (chezscheme) make-hash-table hash-table? sort sort!
+     printf fprintf format path-extension path-absolute?
+     with-input-from-string with-output-to-string iota \x31;+
+     \x31;- partition make-date make-time meta atom?)
+    (jerboa prelude)
+    (jerboa-scintilla constants)
+    (jerboa-scintilla scintilla))
+  (def (editor-autocomplete-show editor len-entered items)
+       (send-message/string editor SCI_AUTOCSHOW items))
+  (def (editor-autocomplete-cancel editor)
+       (send-message editor SCI_AUTOCCANCEL))
+  (def (editor-autocomplete-active? editor)
+       (not (zero? (send-message editor SCI_AUTOCACTIVE))))
+  (def (editor-autocomplete-pos-start editor)
+       (send-message editor SCI_AUTOCPOSSTART))
+  (def (editor-autocomplete-complete editor)
+       (send-message editor SCI_AUTOCCOMPLETE))
+  (def (editor-autocomplete-select editor text)
+       (send-message/string editor SCI_AUTOCSELECT text))
+  (def (editor-autocomplete-get-current editor)
+       (send-message editor SCI_AUTOCGETCURRENT))
+  (def (editor-autocomplete-stops editor chars)
+       (send-message/string editor SCI_AUTOCSTOPS chars))
+  (def (editor-autocomplete-set-separator editor char-code)
+       (send-message editor SCI_AUTOCSETSEPARATOR char-code))
+  (def (editor-autocomplete-get-separator editor)
+       (send-message editor SCI_AUTOCGETSEPARATOR))
+  (def (editor-autocomplete-set-ignore-case editor ignore?)
+       (send-message
+         editor
+         SCI_AUTOCSETIGNORECASE
+         (if ignore? 1 0)))
+  (def (editor-autocomplete-get-ignore-case? editor)
+       (not (zero? (send-message editor SCI_AUTOCGETIGNORECASE))))
+  (def (editor-autocomplete-set-choose-single editor choose?)
+       (send-message
+         editor
+         SCI_AUTOCSETCHOOSESINGLE
+         (if choose? 1 0)))
+  (def (editor-autocomplete-get-choose-single? editor)
+       (not (zero?
+              (send-message editor SCI_AUTOCGETCHOOSESINGLE))))
+  (def (editor-autocomplete-set-auto-hide editor hide?)
+       (send-message editor SCI_AUTOCSETAUTOHIDE (if hide? 1 0)))
+  (def (editor-autocomplete-get-auto-hide? editor)
+       (not (zero? (send-message editor SCI_AUTOCGETAUTOHIDE))))
+  (def (editor-autocomplete-set-drop-rest-of-word
+         editor
+         drop?)
+       (send-message
+         editor
+         SCI_AUTOCSETDROPRESTOFWORD
+         (if drop? 1 0)))
+  (def (editor-autocomplete-get-drop-rest-of-word? editor)
+       (not (zero?
+              (send-message editor SCI_AUTOCGETDROPRESTOFWORD))))
+  (def (editor-autocomplete-set-cancel-at-start
+         editor
+         cancel?)
+       (send-message
+         editor
+         SCI_AUTOCSETCANCELATSTART
+         (if cancel? 1 0)))
+  (def (editor-autocomplete-get-cancel-at-start? editor)
+       (not (zero?
+              (send-message editor SCI_AUTOCGETCANCELATSTART))))
+  (def (editor-autocomplete-set-order editor order)
+       (send-message
+         editor
+         SCI_AUTOCSETORDER
+         (case order
+           [(presorted) SC_ORDER_PRESORTED]
+           [(perform-sort) SC_ORDER_PERFORMSORT]
+           [(custom) SC_ORDER_CUSTOM]
+           [else SC_ORDER_PRESORTED])))
+  (def (editor-autocomplete-get-order editor)
+       (let ([mode (send-message editor SCI_AUTOCGETORDER)])
+         (cond
+           [(= mode SC_ORDER_PRESORTED) 'presorted]
+           [(= mode SC_ORDER_PERFORMSORT) 'perform-sort]
+           [(= mode SC_ORDER_CUSTOM) 'custom]
+           [else 'presorted])))
+  (def (editor-autocomplete-set-max-width editor chars)
+       (send-message editor SCI_AUTOCSETMAXWIDTH chars))
+  (def (editor-autocomplete-get-max-width editor)
+       (send-message editor SCI_AUTOCGETMAXWIDTH))
+  (def (editor-autocomplete-set-max-height editor rows)
+       (send-message editor SCI_AUTOCSETMAXHEIGHT rows))
+  (def (editor-autocomplete-get-max-height editor)
+       (send-message editor SCI_AUTOCGETMAXHEIGHT))
+  (def (editor-autocomplete-set-fill-ups editor chars)
+       (send-message/string editor SCI_AUTOCSETFILLUPS chars))
+  (def (editor-calltip-show editor pos text)
+       (send-message/string editor SCI_CALLTIPSHOW text))
+  (def (editor-calltip-cancel editor)
+       (send-message editor SCI_CALLTIPCANCEL))
+  (def (editor-calltip-active? editor)
+       (not (zero? (send-message editor SCI_CALLTIPACTIVE))))
+  (def (editor-calltip-pos-start editor)
+       (send-message editor SCI_CALLTIPPOSSTART))
+  (def (editor-calltip-set-highlight
+         editor
+         highlight-start
+         highlight-end)
+       (send-message
+         editor
+         SCI_CALLTIPSETHLT
+         highlight-start
+         highlight-end))
+  (def (editor-calltip-set-background editor color)
+       (send-message editor SCI_CALLTIPSETBACK color))
+  (def (editor-calltip-set-foreground editor color)
+       (send-message editor SCI_CALLTIPSETFORE color))
+  (def (editor-calltip-set-foreground-highlight editor color)
+       (send-message editor SCI_CALLTIPSETFOREHLT color)))
diff --git a/lib/jerboa-scintilla/constants.sls b/lib/jerboa-scintilla/constants.sls
new file mode 100644
index 0000000..8edb4f8
--- /dev/null
+++ b/lib/jerboa-scintilla/constants.sls
@@ -0,0 +1,1184 @@
+#!chezscheme
+;;; Generated by jerbuild — DO NOT EDIT
+;;; Source: src/jerboa-scintilla/constants.ss
+
+(library (jerboa-scintilla constants)
+  (export SCI_SETTEXT SCI_GETTEXT SCI_GETTEXTLENGTH
+   SCI_GETCHARACTERPOINTER SCI_ADDTEXT SCI_ADDSTYLEDTEXT
+   SCI_INSERTTEXT SCI_CHANGEINSERTION SCI_CLEARALL
+   SCI_DELETERANGE SCI_CLEARDOCUMENTSTYLE SCI_APPENDTEXT
+   SCI_REPLACESEL SCI_TAB SCI_BACKTAB SCI_GETLENGTH
+   SCI_GETCHARAT SCI_GETCURRENTPOS SCI_GETANCHOR SCI_SETANCHOR
+   SCI_SETCURRENTPOS SCI_SETSEL SCI_GETSELTEXT SCI_SELECTALL
+   SCI_GOTOPOS SCI_GOTOLINE SCI_GETSELECTIONSTART
+   SCI_GETSELECTIONEND SCI_SETSELECTIONSTART
+   SCI_SETSELECTIONEND SCI_GETSELECTIONEMPTY
+   SCI_SETMOVEEXTENDSSELECTION SCI_GETMOVEEXTENDSSELECTION
+   SCI_GETLINE SCI_GETLINECOUNT SCI_LINEFROMPOSITION
+   SCI_POSITIONFROMLINE SCI_GETLINEENDPOSITION
+   SCI_LINESONSCREEN SCI_LINELENGTH SCI_GETCOLUMN
+   SCI_FINDCOLUMN SCI_POSITIONFROMPOINT
+   SCI_POSITIONFROMPOINTCLOSE SCI_POINTXFROMPOSITION
+   SCI_POINTYFROMPOSITION SCI_GETSTYLEAT SCI_STYLECLEARALL
+   SCI_STYLESETFORE SCI_STYLESETBACK SCI_STYLESETBOLD
+   SCI_STYLESETITALIC SCI_STYLESETSIZE SCI_STYLESETFONT
+   SCI_STYLESETEOLFILLED SCI_STYLERESETDEFAULT
+   SCI_STYLESETUNDERLINE SCI_STYLEGETFORE SCI_STYLEGETBACK
+   SCI_STYLEGETBOLD SCI_STYLEGETITALIC SCI_STYLEGETSIZE
+   SCI_STYLEGETFONT SCI_STYLEGETUNDERLINE SCI_STYLESETCASE
+   SCI_STYLEGETCASE SCI_STYLESETCHARACTERSET
+   SCI_STYLESETVISIBLE SCI_STYLEGETVISIBLE
+   SCI_STYLESETCHANGEABLE SCI_STYLEGETCHANGEABLE
+   SCI_STYLESETHOTSPOT SCI_STYLEGETHOTSPOT SCI_STYLESETWEIGHT
+   SCI_STYLEGETWEIGHT STYLE_DEFAULT STYLE_LINENUMBER
+   STYLE_BRACELIGHT STYLE_BRACEBAD STYLE_CONTROLCHAR