diff --git a/ffi/readline.lua b/ffi/readline.lua index fdcad90..682203a 100644 --- a/ffi/readline.lua +++ b/ffi/readline.lua @@ -1,6 +1,7 @@ -- ffi/readline.lua — GNU readline binding. --- Phase 0: readline + add_history + EOF handling. Phase 1: custom key bindings. --- See docs/PHASE0.md §9. +-- Phase 0: readline + add_history + EOF handling. +-- Phase 1: custom key bindings via rl_bind_keyseq. +-- See docs/PHASE0.md §9 and docs/PHASE1.md §7. local ffi = require("ffi") @@ -8,6 +9,9 @@ ffi.cdef[[ char *readline(const char *prompt); void add_history(const char *line); void free(void *ptr); + +typedef int (*rl_command_func_t)(int, int); +int rl_bind_keyseq(const char *keyseq, rl_command_func_t function); ]] -- libreadline-dev (which ships the unversioned `libreadline.so` symlink) is @@ -47,4 +51,26 @@ function M.add_history(line) end end +-- Bind `seq` (e.g. "\\C-n") to a Lua function that runs when the user types +-- that key sequence at the readline prompt. The Lua fn takes no arguments +-- (readline passes count + key, but Phase 1 consumers don't need them). +-- Callback trampolines are pinned in module-local state so they outlive the +-- M.bind call — readline retains the function pointer indefinitely. +local _bound = {} + +function M.bind(seq, fn) + if _bound[seq] then + _bound[seq]:free() + end + local cb = ffi.cast("rl_command_func_t", function(_count, _key) + local ok, err = pcall(fn) + if not ok then + io.stderr:write("ffi/readline bind handler error: " .. tostring(err) .. "\n") + end + return 0 + end) + _bound[seq] = cb + return rl.rl_bind_keyseq(seq, cb) == 0 +end + return M diff --git a/repl.lua b/repl.lua index 9c82ad2..b84b5d8 100644 --- a/repl.lua +++ b/repl.lua @@ -67,6 +67,13 @@ function M.run(config) return ("[aish:%s]> "):format(active_name) end + -- Phase 1 reserved-key wiring (PHASE1.md §7). The mechanism is real; the + -- handlers are placeholders that emit a status. Phase 3 (Norris) is the + -- first consumer that replaces the body with real work. + rl.bind("\\C-n", function() + renderer.status("Norris mode not yet implemented (Phase 3)") + end) + local function status_evictions(n) if n and n > 0 then renderer.status(("oldest %d turns evicted"):format(n))