readline: bind() via rl_bind_keyseq; repl reserves \C-n no-op
Phase 1 readline binding wiring per PHASE1.md §7.
ffi/readline:
M.bind(seq, lua_fn) -> bool
Wraps lua_fn as a C callback (signature `int (int, int)` per
readline's rl_command_func_t) and registers it via
rl_bind_keyseq(seq, cb). Returns true on success (rl returns 0).
Trampolines are pinned in module-local state so they outlive the
bind call — readline retains the function pointer for the process
lifetime. Rebinding the same seq frees the previous trampoline.
Bound handlers are pcall-wrapped so a Lua error doesn't crash
readline's input loop.
repl:
Binds \C-n to a no-op that emits
"[aish] Norris mode not yet implemented (Phase 3)"
Verifies the mechanism end-to-end; Phase 3 (Norris autonomous mode)
replaces the body with the actual toggle.
Smoke covers bind / rebind-same-seq (exercises the :free path) /
bind-different-seq with no errors. Live keyboard verification waits
on user-test.
Phase 1's 8(+1) inner loop is now functionally through `implement`;
next inner phase is `verify` (review pass) followed by memory-update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+28
-2
@@ -1,6 +1,7 @@
|
|||||||
-- ffi/readline.lua — GNU readline binding.
|
-- ffi/readline.lua — GNU readline binding.
|
||||||
-- Phase 0: readline + add_history + EOF handling. Phase 1: custom key bindings.
|
-- Phase 0: readline + add_history + EOF handling.
|
||||||
-- See docs/PHASE0.md §9.
|
-- Phase 1: custom key bindings via rl_bind_keyseq.
|
||||||
|
-- See docs/PHASE0.md §9 and docs/PHASE1.md §7.
|
||||||
|
|
||||||
local ffi = require("ffi")
|
local ffi = require("ffi")
|
||||||
|
|
||||||
@@ -8,6 +9,9 @@ ffi.cdef[[
|
|||||||
char *readline(const char *prompt);
|
char *readline(const char *prompt);
|
||||||
void add_history(const char *line);
|
void add_history(const char *line);
|
||||||
void free(void *ptr);
|
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
|
-- libreadline-dev (which ships the unversioned `libreadline.so` symlink) is
|
||||||
@@ -47,4 +51,26 @@ function M.add_history(line)
|
|||||||
end
|
end
|
||||||
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
|
return M
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ function M.run(config)
|
|||||||
return ("[aish:%s]> "):format(active_name)
|
return ("[aish:%s]> "):format(active_name)
|
||||||
end
|
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)
|
local function status_evictions(n)
|
||||||
if n and n > 0 then
|
if n and n > 0 then
|
||||||
renderer.status(("oldest %d turns evicted"):format(n))
|
renderer.status(("oldest %d turns evicted"):format(n))
|
||||||
|
|||||||
Reference in New Issue
Block a user