aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/ccdjit-improvements.md68
-rw-r--r--docs/plugin-api.md329
2 files changed, 397 insertions, 0 deletions
diff --git a/docs/ccdjit-improvements.md b/docs/ccdjit-improvements.md
new file mode 100644
index 0000000..4b48589
--- /dev/null
+++ b/docs/ccdjit-improvements.md
@@ -0,0 +1,68 @@
+# CCDJIT improvements status for Ecex plugins
+
+Ecex now treats plugin state as a host-owned capability, but several plugin API
+constraints still exist because the JIT-to-host boundary is fragile in places.
+
+## Resolved in current CCDJIT
+
+- Multidimensional numeric arrays are usable from plugin code again.
+- Ecex added `ecex_plugin_slot_i32_get_2d` and
+ `ecex_plugin_slot_i32_set_2d` for host-owned board/grid state.
+- Higher-arity host calls are usable again; Ecex now exposes
+ `ecex_draw_plugin_text_rect_i` as a richer integer/text helper.
+- Normal allocation lowering is fixed. Ecex still requires durable plugin state
+ to live in plugin slots, plugin objects, or plugin text so cleanup and
+ cross-plugin access stay host-owned.
+- Ecex config loading now uses `ccdjit_compile_file_module` and looks up
+ `ecex_config_init` directly instead of appending a synthetic `main`.
+
+## ABI conformance tests
+
+Add a small CCDJIT test suite that calls host functions through generated code
+and covers:
+
+- integer and pointer arguments and return values;
+- float and double arguments and return values;
+- callbacks with six or more arguments;
+- structs and struct-field reads/writes;
+- arrays of string literals and `const char *` array indexing;
+- long-lived callback function pointers retained after compile-time setup.
+
+These should be standalone CCDJIT tests, not only Ecex integration tests.
+
+## Safe ABI profile
+
+Document or expose a safe host-call profile that embedders can target. Ecex
+currently favors integer-only plugin helpers, host-owned objects, and copy-based
+text because those paths are predictable.
+
+Static completion lists should use `ecex_define_word_completion_provider` plus
+`ecex_completion_provider_add_words`. That passes one string literal blob over
+the host boundary and avoids depending on JIT-side `const char *` array
+indexing.
+
+Runtime plugin callbacks should avoid variadic C library calls such as
+`snprintf`. Build strings with fixed-arity helpers or host APIs instead; the C
+tools plugin uses append helpers for this reason.
+
+## Diagnostics
+
+Improve diagnostics for:
+
+- unresolved host symbols;
+- unsafe implicit symbol resolution;
+- callback signatures that cannot be called reliably;
+- sandbox failures versus compile/type failures.
+
+## Module loading shape
+
+Ecex now uses CCDJIT's module-oriented compile path for config files. The
+remaining upstream opportunity is a first-class example/test for this embedder
+flow: compile a module, validate an init symbol, call it, retain callback code
+for later renderer/animation/mouse dispatch, and release it after host cleanup.
+
+## Capability registration
+
+Ecex has a large flat host symbol table. CCDJIT should provide examples or
+helpers for registering named capability groups so hosts can expose smaller,
+auditable APIs to plugin code.
diff --git a/docs/plugin-api.md b/docs/plugin-api.md
new file mode 100644
index 0000000..04851ed
--- /dev/null
+++ b/docs/plugin-api.md
@@ -0,0 +1,329 @@
+# Ecex Plugin API
+
+Ecex config/plugin files are C code compiled through CCDJIT and run in the
+editor process. Plugins are powerful, so the API boundary must stay explicit:
+long-lived plugin state belongs in the plugin registry, callbacks use
+host-owned objects, and cross-plugin access is read-only by explicit export.
+
+This document is the convention plugins must follow. Tetris, render-demo, and
+Markdown are the reference implementations.
+
+## Registry Convention
+
+Every plugin must register a stable string id before it allocates persistent
+state or registers callbacks:
+
+```c
+ECEX_PLUGIN_BEGIN(ecex_demo_plugin, "demo")
+ ECEX_CONFIG_COMMAND("demo", cmd_demo);
+ECEX_PLUGIN_END
+```
+
+`ECEX_PLUGIN_BEGIN` creates an `ecex_plugin_t *plugin` handle. That handle is
+the plugin's namespace in the registry. Do not use arbitrary owner pointers,
+global variables, raw malloc state, or host-specific addresses as namespaces for
+durable plugin state.
+
+Plugin ids must be stable, lowercase, and specific. Use names such as
+`markdown`, `tetris`, or `render-demo`. Changing an id changes the registry
+namespace, so treat ids as public API.
+
+## State Rules
+
+Use these storage paths:
+
+- Plugin slots: durable scalar/array state that must survive across commands,
+ renders, animation ticks, mouse callbacks, and cleanup.
+- Plugin objects: callback userdata structs passed to render, animation, mouse,
+ file-handler, or interactive callbacks.
+- Plugin text: arbitrary strings that the renderer will later resolve by
+ plugin/id.
+- Local stack variables: short-lived scratch values inside one callback only.
+
+Do not use these for durable plugin state:
+
+- `malloc`, `calloc`, or `free` directly.
+- `ecex_config_alloc`, `ecex_config_calloc`, or `ecex_config_free` for callback
+ userdata.
+- Raw editor/global static variables for mutable plugin state.
+- Host helpers created for one plugin, such as game rules or shape lookup, when
+ the plugin can own the logic itself.
+
+Current CCDJIT can lower normal allocation paths, but Ecex still treats direct
+allocation as scratch/setup memory. Anything referenced after plugin init
+returns must be in the plugin registry.
+
+## Plugin Slots
+
+Use slots for named persistent values:
+
+```c
+ecex_plugin_slot_i32_set_scalar(plugin, "score", 10);
+int score = ecex_plugin_slot_i32_get_scalar(plugin, "score", 0);
+
+int *board = ecex_plugin_slot_alloc(plugin, "board", 200, sizeof(int));
+ecex_plugin_slot_i32_set_2d(plugin, "board", 10, 3, 4, 7);
+int cell = ecex_plugin_slot_i32_get_2d(plugin, "board", 10, 3, 4, 0);
+```
+
+Rules:
+
+- Slot names are local to the plugin id.
+- Slots are private by default.
+- A plugin may mutate only its own slots.
+- Other plugins may read only explicitly exported slots.
+- Cross-plugin reads must copy into caller-owned storage.
+
+Export example:
+
+```c
+ecex_plugin_slot_set_export_flags(plugin, "score", ECEX_PLUGIN_I32, ECEX_PLUGIN_EXPORT_READ);
+```
+
+Read example:
+
+```c
+int score = 0;
+size_t len = 0;
+if (ecex_plugin_slot_read_exported(ed, "tetris", "score", &score, sizeof(score), &len) == 0) {
+ /* score copied from the exported slot */
+}
+```
+
+Never expose a mutable pointer to another plugin's slot.
+
+## Plugin Objects
+
+Use plugin objects for callback userdata:
+
+```c
+typedef struct demo_state {
+ ecex_plugin_t *plugin;
+} demo_state_t;
+
+demo_state_t *s = ecex_plugin_object_calloc(plugin, "state", 1, sizeof(*s));
+s->plugin = plugin;
+```
+
+Free the object from the callback destructor:
+
+```c
+static void demo_free(void *userdata) {
+ demo_state_t *s = (demo_state_t *)userdata;
+ if (!s) return;
+ ecex_plugin_object_free(s->plugin, s);
+}
+```
+
+Objects are tracked by the plugin runtime. Use
+`ecex_plugin_object_i32_get/set` and `ecex_plugin_object_ptr_get/set` when a
+plugin needs offset-based field access that avoids fragile JIT struct lowering.
+
+## Plugin Text
+
+Plugins must not pass arbitrary JIT-owned strings, stack buffers, or parser
+scratch memory into the real font renderer. Copy text into plugin text storage,
+then draw by plugin/id:
+
+```c
+ecex_plugin_text_set(plugin, 1, "hello", -1);
+ecex_draw_plugin_text_i(ctx, plugin, 1, x, y);
+ecex_draw_plugin_text_rect_i(ctx, plugin, 1, x, y, w, h, 8, 0x20242cff, 0xf4f1deff);
+```
+
+For buffer titles:
+
+```c
+ecex_plugin_text_set_from_buffer_title(plugin, 1, buffer);
+```
+
+Free text owned by a callback object during cleanup when appropriate:
+
+```c
+ecex_plugin_text_free_all(plugin);
+```
+
+## Rendered Plugin Callbacks
+
+Rendered plugins should use host-driven callbacks:
+
+```c
+ecex_buffer_set_renderer(buffer, draw_fn, state, free_fn, ECEX_RENDER_REPLACE_CONTENT);
+ecex_buffer_set_animation_ms(buffer, tick_fn, state, 0, 60);
+ecex_buffer_set_mouse_handler(buffer, mouse_fn, state, 0);
+```
+
+Use the millisecond animation API from plugins. Avoid the double-based animation
+API from CCDJIT plugin code.
+
+Use integer drawing helpers:
+
+- `ecex_draw_color_rgba8_i`
+- `ecex_draw_rect_i`
+- `ecex_draw_rect_outline_i`
+- `ecex_draw_line_i`
+- `ecex_draw_plugin_text_i`
+- `ecex_draw_plugin_text_rect_i`
+- `ecex_draw_label_i`
+- `ecex_draw_stat_i`
+
+The current CCDJIT ABI handles higher-arity host calls, so rectangle/text
+helpers can use richer signatures again. Prefer host-owned text ids over raw
+JIT string pointers for anything rendered after the current callback returns.
+
+## Hooks
+
+Plugins can register named hooks. Re-registering the same name replaces the old
+hook, which keeps config reloads from stacking duplicate callbacks:
+
+```c
+ecex_add_command_hook(ed, "demo", hook_fn, state, free_fn);
+ecex_add_prefix_hook(ed, "demo", prefix_fn, state, free_fn);
+ecex_add_buffer_hook(ed, "demo", buffer_fn, state, free_fn);
+```
+
+Command hooks receive `ECEX_COMMAND_HOOK_BEFORE` and
+`ECEX_COMMAND_HOOK_AFTER` around `ecex_execute_command`. Prefix hooks receive
+`ECEX_PREFIX_HOOK_BEGIN`, `UPDATE`, `CANCEL`, `FINISH`, and `UNDEFINED` for
+multi-key sequences such as `C-x`. Buffer hooks receive
+`ECEX_BUFFER_HOOK_CREATE`, `SWITCH`, `SAVE`, `KILL`, and `MODE_CHANGE`.
+
+Use `ecex_describe_key_prefix` and `ecex_message` for keymap helper plugins.
+`config/which_key_plugin.c` is the reference example: it displays the next
+available keys while a prefix sequence is active.
+
+Plugins that need external tools can check them before registering commands or
+providers:
+
+```c
+if (ecex_plugin_require_dependency(ed, "c-tools", "clangd") != 0) return 0;
+```
+
+## Completion Providers
+
+Plugins can register named completion providers. Providers receive the
+identifier prefix at point and return one candidate plus a score. Mode-specific
+providers pass a major-mode name; pass `0` for a global provider:
+
+```c
+ecex_add_completion_provider(ed, "demo-complete", "c-mode", provider_fn, state, free_fn);
+ecex_complete_at_point(ed);
+```
+
+For fixed word lists from JIT configs, prefer host-owned word providers:
+
+```c
+ecex_define_word_completion_provider(ed, "demo-words", "c-mode", ECEX_COMPLETION_DEFAULT);
+ecex_completion_provider_add_words(ed, "demo-words", "malloc\nfree\nsizeof\n");
+ecex_completion_provider_set_detail(ed, "demo-words", "C runtime symbol");
+```
+
+`ecex_completion_provider_add_words` accepts a whitespace-separated string
+literal, copies the words on the host, and avoids depending on JIT string-array
+indexing. Provider details are optional minibuffer labels shown while cycling.
+
+The built-in `indent-for-tab-command` is bound to `TAB`; `config/c_mode_plugin.c`
+defines `c-mode`, overrides `TAB` with `c-indent-line`, registers C-family file
+handlers, and adds clangd completion when `clangd` is installed. Clangd
+completions use label details when available, so functions display signatures
+and return types. `config/ecex_api_completion_plugin.c` is the `ecex-mode`
+plugin; it registers global ecex API completions and special-cases `ed->` field
+names using word completion providers.
+
+```c
+ecex_add_clangd_completion_provider(ed, "c-mode-clangd", "c-mode");
+```
+
+C-mode syntax highlighting is host-rendered for normal editable buffers once a
+buffer is assigned `c-mode`.
+
+`config/c_tools_plugin.c` registers C tooling commands and mode bindings:
+
+- `C-x c l` runs `clangd --check=<file>` for clangd/LSP diagnostics.
+- `C-x c k` runs `${CC:-clang} -fsyntax-only -Wall -Wextra <file>` as a
+ compiler linter, adding `./include` when this checkout has `include/ecex.h`.
+- `C-x c e` runs the linter with `./include` forced for ecex development.
+- `C-x c s` opens a small status/help buffer.
+
+## File Handlers
+
+File handlers are plugin-owned registrations:
+
+```c
+ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".md", md_file_handler));
+```
+
+The handler callback may attach a renderer, set a major mode, or leave the
+buffer unchanged. Handler state must follow the same registry convention as all
+other plugin state.
+
+## Minimal Rendered Plugin
+
+```c
+#include "ecex.h"
+
+typedef struct demo_state {
+ ecex_plugin_t *plugin;
+} demo_state_t;
+
+static int demo_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, void *userdata) {
+ (void)ed;
+ (void)buffer;
+ demo_state_t *s = (demo_state_t *)userdata;
+ if (!s || !ctx) return 0;
+
+ ecex_draw_color_rgba8_i(ctx, 20, 22, 28, 255);
+ ecex_draw_rect_i(ctx, 0, 0, (int)ctx->w, (int)ctx->h);
+ return 0;
+}
+
+static void demo_free(void *userdata) {
+ demo_state_t *s = (demo_state_t *)userdata;
+ if (!s) return;
+ ecex_plugin_object_free(s->plugin, s);
+}
+
+static int cmd_demo(ecex_t *ed) {
+ ecex_plugin_t *plugin = ecex_plugin_find(ed, "demo");
+ if (!plugin) return -1;
+
+ buffer_t *buffer = ecex_create_interactive_buffer(ed, "*demo*");
+ if (!buffer) return -1;
+
+ demo_state_t *state = ecex_plugin_object_calloc(plugin, "state", 1, sizeof(*state));
+ if (!state) return -1;
+ state->plugin = plugin;
+
+ if (ecex_buffer_set_renderer(buffer, demo_draw, state, demo_free, ECEX_RENDER_REPLACE_CONTENT) != 0) {
+ ecex_plugin_object_free(plugin, state);
+ return -1;
+ }
+
+ return ecex_switch_buffer(ed, "*demo*");
+}
+
+ECEX_PLUGIN_BEGIN(ecex_demo_plugin, "demo")
+ ECEX_CONFIG_COMMAND("demo", cmd_demo);
+ECEX_PLUGIN_END
+```
+
+## Cleanup Order
+
+Renderer, animation, mouse, file-handler, and command callbacks can be JIT
+function pointers. Ecex frees buffers before freeing JIT modules so callback
+destructors still point to live code. Plugin destructors should stay short:
+free plugin objects/text/slots through registry APIs and avoid complex editor
+mutation.
+
+## Logging
+
+Plugin logging is optional. Normal runs are quiet. Enable logs with:
+
+```sh
+ECEX_LOG=1
+ECEX_TRACE_CALLBACKS=1
+ECEX_TRACE_TETRIS=1
+```
+
+`ECEX_TRACE_CALLBACKS=1` also logs host callback entry/exit for renderer,
+animation, and mouse dispatch.