an experimental irc client

lua: add option to add custom commands

Add a way to add a custom command (and callback) from lua

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>

+36 -3
+6
docs/comlink.lua
··· 63 63 ---@param body string Body of the notification 64 64 function comlink.notify(title, body) end 65 65 66 + ---Add a custom command to comlink 67 + --- 68 + ---@param name string Name of the command 69 + ---@param fn function Function to call when command is enterred 70 + function comlink.add_command(name, fn) end 71 + 66 72 return comlink
+7 -3
src/app.zig
··· 249 249 const content = try input.toOwnedSlice(); 250 250 defer self.alloc.free(content); 251 251 if (content[0] == '/') 252 - self.handleCommand(buffer, content) catch |err| { 252 + self.handleCommand(lua_state, buffer, content) catch |err| { 253 253 log.err("couldn't handle command: {}", .{err}); 254 254 } 255 255 else { ··· 751 751 } 752 752 753 753 /// handle a command 754 - pub fn handleCommand(self: *App, buffer: irc.Buffer, cmd: []const u8) !void { 754 + pub fn handleCommand(self: *App, lua_state: *Lua, buffer: irc.Buffer, cmd: []const u8) !void { 755 755 const command: comlink.Command = blk: { 756 756 const start: u1 = if (cmd[0] == '/') 1 else 0; 757 757 const end = mem.indexOfScalar(u8, cmd, ' ') orelse cmd.len; 758 - break :blk comlink.Command.fromString(cmd[start..end]) orelse return error.UnknownCommand; 758 + if (comlink.Command.fromString(cmd[start..end])) |builtin| 759 + break :blk builtin; 760 + if (comlink.Command.user_commands.get(cmd[start..end])) |ref| 761 + return lua.execFn(lua_state, ref); 762 + return error.UnknownCommand; 759 763 }; 760 764 var buf: [1024]u8 = undefined; 761 765 const client: *irc.Client = switch (buffer) {
+2
src/comlink.zig
··· 31 31 redraw, 32 32 lua_function: i32, 33 33 34 + pub var user_commands: std.StringHashMap(i32) = undefined; 35 + 34 36 /// only contains void commands 35 37 const map = std.StaticStringMap(Command).initComptime(.{ 36 38 .{ "quote", .quote },
+7
src/completer.zig
··· 109 109 self.cmd = true; 110 110 const commands = std.meta.fieldNames(Command); 111 111 for (commands) |cmd| { 112 + if (std.mem.eql(u8, cmd, "lua_function")) continue; 112 113 if (std.ascii.startsWithIgnoreCase(cmd, self.word[1..])) { 113 114 try self.options.append(cmd); 115 + } 116 + } 117 + var iter = Command.user_commands.keyIterator(); 118 + while (iter.next()) |cmd| { 119 + if (std.ascii.startsWithIgnoreCase(cmd.*, self.word[1..])) { 120 + try self.options.append(cmd.*); 114 121 } 115 122 } 116 123 }
+11
src/lua.zig
··· 115 115 .{ .name = "connect", .func = ziglua.wrap(connect) }, 116 116 .{ .name = "log", .func = ziglua.wrap(log) }, 117 117 .{ .name = "notify", .func = ziglua.wrap(notify) }, 118 + .{ .name = "add_command", .func = ziglua.wrap(addCommand) }, 118 119 }; 119 120 lua.newLibTable(&fns); // [table] 120 121 lua.setFuncs(&fns, 0); // [table] ··· 285 286 lua.pop(2); // [] 286 287 app.vx.notify(app.tty.anyWriter(), title, body) catch 287 288 lua.raiseErrorStr("couldn't write notification", .{}); 289 + return 0; 290 + } 291 + 292 + /// Add a user command to the command list 293 + fn addCommand(lua: *Lua) i32 { 294 + lua.argCheck(lua.isString(1), 1, "expected a string"); // [string, function] 295 + lua.argCheck(lua.isFunction(2), 2, "expected a function"); // [string, function] 296 + const ref = lua.ref(registry_index) catch lua.raiseErrorStr("couldn't ref function", .{}); 297 + const cmd = lua.toString(1) catch unreachable; 298 + comlink.Command.user_commands.put(cmd, ref) catch lua.raiseErrorStr("out of memory", .{}); 288 299 return 0; 289 300 } 290 301 };
+3
src/main.zig
··· 64 64 }, 65 65 } 66 66 67 + comlink.Command.user_commands = std.StringHashMap(i32).init(alloc); 68 + defer comlink.Command.user_commands.deinit(); 69 + 67 70 const lua = try Lua.init(&alloc); 68 71 defer lua.deinit(); 69 72