From f759f149377942c4e04802c45162cda1c9bfb2b3 Mon Sep 17 00:00:00 2001 From: t Date: Sat, 4 Jul 2026 09:50:12 -0600 Subject: big cli/tui gaps project --- src/command.zig | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'src/command.zig') diff --git a/src/command.zig b/src/command.zig index 2e63cea..43997ed 100644 --- a/src/command.zig +++ b/src/command.zig @@ -53,6 +53,19 @@ pub const Context = struct { /// handler. `null` when no extensions loaded; Lua commands are only /// registered when it is non-null, so handlers may assume it is set. lua_rt: ?*anyopaque = null, + + /// The registry dispatching this command (for `/help` listings). + /// Stamped by `Registry.dispatch`; no wiring needed. + registry: ?*const Registry = null, + + /// The session store the active session came from (for `/new`). + session_store: panto.SessionStore, + + /// The live TUI `*tui_app.App`, when running under the TUI (same + /// `anyopaque` idiom as `lua_rt`: keeps this module free of TUI + /// concerns). Commands that steer the TUI (`/model`, `/new`, ...) + /// cast it back in `builtin_commands.zig`. Null in print mode. + tui: ?*anyopaque = null, }; /// A single slash command. @@ -120,6 +133,21 @@ pub const Registry = struct { }); } + /// Remove every Lua-backed command, keeping native commands in place. + /// Called when the session's Lua runtime is torn down (`/new`, + /// `/resume`): the handler refs die with that runtime, and the next + /// session's harvest re-registers its own set — so `/help` always + /// lists exactly one copy. + pub fn clearLua(self: *Registry) void { + var i: usize = self.commands.items.len; + while (i > 0) { + i -= 1; + if (self.commands.items[i].lua_ref != null) { + _ = self.commands.orderedRemove(i); + } + } + } + /// Find a command by name (without leading `/`). Null if absent. pub fn find(self: *const Registry, name: []const u8) ?*const Command { for (self.commands.items) |*cmd| { @@ -150,6 +178,7 @@ pub const Registry = struct { const args = std.mem.trim(u8, body[name_end..], " \t"); const cmd = self.find(name) orelse return Error.CommandNotFound; + ctx.registry = self; if (cmd.lua_ref) |ref| { try runLuaCommand(ref, args, ctx); return; @@ -231,6 +260,22 @@ test "registerLua records name, description, and ref; collides with builtins" { try testing.expectError(error.DuplicateCommand, reg.registerLua("compact", "x", 1)); } +test "clearLua removes Lua-backed commands, keeps natives, allows re-harvest" { + var reg = Registry.init(testing.allocator); + defer reg.deinit(); + + try reg.register(.{ .name = "compact", .description = "d", .run = testHandler }); + try reg.registerLua("hello", "d", 1); + try reg.registerLua("world", "d", 2); + + reg.clearLua(); + try testing.expectEqual(@as(usize, 1), reg.list().len); + try testing.expect(reg.find("compact") != null); + // A rebuilt session's harvest re-registers without DuplicateCommand. + try reg.registerLua("hello", "d", 3); + try testing.expectEqual(@as(?c_int, 3), reg.find("hello").?.lua_ref); +} + test "dispatch splits name and args" { var reg = Registry.init(testing.allocator); defer reg.deinit(); -- cgit v1.3