summaryrefslogtreecommitdiff
path: root/src/command.zig
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-07-04 09:50:12 -0600
committert <t@tjp.lol>2026-07-05 16:23:49 -0600
commitf759f149377942c4e04802c45162cda1c9bfb2b3 (patch)
tree397dd2fc35839d8fcbf6a5c237bee363c6ed3c07 /src/command.zig
parent1ed07e2e4473b91c669c062bbfef6bb499f7d2b7 (diff)
big cli/tui gaps project
Diffstat (limited to 'src/command.zig')
-rw-r--r--src/command.zig45
1 files changed, 45 insertions, 0 deletions
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();