diff --git a/src/tui/command.zig b/src/tui/command.zig index 9d620ca..7b69697 100644 --- a/src/tui/command.zig +++ b/src/tui/command.zig @@ -22,6 +22,12 @@ const Vtable = struct { id: ID = ID_unknown, name: []const u8, run: *const fn (self: *Vtable, ctx: Context) tp.result, + meta: Metadata, +}; + +const Metadata = struct { + description: []const u8 = &[_]u8{}, + interactive: bool = true, }; pub fn Closure(comptime T: type) type { @@ -33,11 +39,12 @@ pub fn Closure(comptime T: type) type { const FunT: type = *const fn (T, ctx: Context) Result; const Self = @This(); - pub fn init(f: FunT, data: T, name: []const u8) Self { + pub fn init(f: FunT, data: T, name: []const u8, meta: Metadata) Self { return .{ .vtbl = .{ .run = run, .name = name, + .meta = meta, }, .f = f, .data = data, @@ -142,6 +149,7 @@ fn CmdDef(comptime T: type) type { const Fn = fn (T, Context) anyerror!void; name: [:0]const u8, f: *const Fn, + meta: Metadata, }; } @@ -150,6 +158,7 @@ fn getTargetType(comptime Namespace: type) type { } fn getCommands(comptime Namespace: type) []const CmdDef(*getTargetType(Namespace)) { + @setEvalBranchQuota(10_000); comptime switch (@typeInfo(Namespace)) { .Struct => |info| { var count = 0; @@ -164,7 +173,14 @@ fn getCommands(comptime Namespace: type) []const CmdDef(*getTargetType(Namespace var i = 0; for (info.decls) |decl| { if (@TypeOf(@field(Namespace, decl.name)) == CmdDef(*Target).Fn) { - cmds[i] = .{ .f = &@field(Namespace, decl.name), .name = decl.name }; + cmds[i] = .{ + .f = &@field(Namespace, decl.name), + .name = decl.name, + .meta = if (@hasDecl(Namespace, decl.name ++ "_meta")) + @field(Namespace, decl.name ++ "_meta") + else + .{}, + }; i += 1; } } @@ -208,7 +224,7 @@ pub fn Collection(comptime Namespace: type) type { if (cmds.len == 0) @compileError("no commands found in type " ++ @typeName(Target) ++ " (did you mark them public?)"); inline for (cmds) |cmd| { - @field(self.fields, cmd.name) = Closure(*Target).init(cmd.f, targetPtr, cmd.name); + @field(self.fields, cmd.name) = Closure(*Target).init(cmd.f, targetPtr, cmd.name, cmd.meta); try @field(self.fields, cmd.name).register(); } } diff --git a/src/tui/mode/overlay/command_palette.zig b/src/tui/mode/overlay/command_palette.zig index 9bcc4c9..56216be 100644 --- a/src/tui/mode/overlay/command_palette.zig +++ b/src/tui/mode/overlay/command_palette.zig @@ -12,6 +12,7 @@ pub const name = "󱊒 command"; pub const description = "command"; pub const Entry = struct { + label: []const u8, name: []const u8, id: command.ID, used_time: i64, @@ -19,7 +20,13 @@ pub const Entry = struct { pub fn load_entries(palette: *Type) !void { for (command.commands.items) |cmd_| if (cmd_) |p| { - (try palette.entries.addOne()).* = .{ .name = p.name, .id = p.id, .used_time = 0 }; + if (p.meta.interactive) + (try palette.entries.addOne()).* = .{ + .label = if (p.meta.description.len > 0) p.meta.description else p.name, + .name = p.name, + .id = p.id, + .used_time = 0, + }; }; } @@ -27,24 +34,27 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v var value = std.ArrayList(u8).init(palette.allocator); defer value.deinit(); const writer = value.writer(); - try cbor.writeValue(writer, entry.name); - try cbor.writeValue(writer, entry.id); + try cbor.writeValue(writer, entry.label); try cbor.writeValue(writer, if (palette.hints) |hints| hints.get(entry.name) orelse "" else ""); - if (matches) |matches_| - try cbor.writeValue(writer, matches_); + try cbor.writeValue(writer, matches orelse &[_]usize{}); + try cbor.writeValue(writer, entry.id); try palette.menu.add_item_with_handler(value.items, select); palette.items += 1; } fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { - var command_name: []const u8 = undefined; + var unused: []const u8 = undefined; var command_id: command.ID = undefined; var iter = button.opts.label; - if (!(cbor.matchString(&iter, &command_name) catch false)) return; + if (!(cbor.matchString(&iter, &unused) catch false)) return; + if (!(cbor.matchString(&iter, &unused) catch false)) return; + var len = cbor.decodeArrayHeader(&iter) catch return; + while (len > 0) : (len -= 1) + cbor.skipValue(&iter) catch break; if (!(cbor.matchValue(&iter, cbor.extract(&command_id)) catch false)) return; update_used_time(menu.*.opts.ctx, command_id); tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("navigate", e); - tp.self_pid().send(.{ "cmd", command_name, .{} }) catch |e| menu.*.opts.ctx.logger.err("navigate", e); + tp.self_pid().send(.{ "cmd", command_id, .{} }) catch |e| menu.*.opts.ctx.logger.err("navigate", e); } fn sort_by_used_time(palette: *Type) void { diff --git a/src/tui/mode/overlay/open_recent_project.zig b/src/tui/mode/overlay/open_recent_project.zig index e69a68e..07f8ddc 100644 --- a/src/tui/mode/overlay/open_recent_project.zig +++ b/src/tui/mode/overlay/open_recent_project.zig @@ -10,7 +10,7 @@ pub const name = " project"; pub const description = "project"; pub const Entry = struct { - name: []const u8, + label: []const u8, }; pub const Match = struct { @@ -21,7 +21,7 @@ pub const Match = struct { pub fn deinit(palette: *Type) void { for (palette.entries.items) |entry| - palette.allocator.free(entry.name); + palette.allocator.free(entry.label); } pub fn load_entries(palette: *Type) !void { @@ -32,7 +32,7 @@ pub fn load_entries(palette: *Type) !void { while (len > 0) : (len -= 1) { var name_: []const u8 = undefined; if (try cbor.matchValue(&iter, cbor.extract(&name_))) { - (try palette.entries.addOne()).* = .{ .name = try palette.allocator.dupe(u8, name_) }; + (try palette.entries.addOne()).* = .{ .label = try palette.allocator.dupe(u8, name_) }; } else return error.InvalidMessageField; } } @@ -41,10 +41,8 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v var value = std.ArrayList(u8).init(palette.allocator); defer value.deinit(); const writer = value.writer(); - try cbor.writeValue(writer, entry.name); - try cbor.writeValue(writer, if (palette.hints) |hints| hints.get(entry.name) orelse "" else ""); - if (matches) |matches_| - try cbor.writeValue(writer, matches_); + try cbor.writeValue(writer, entry.label); + try cbor.writeValue(writer, matches orelse &[_]usize{}); try palette.menu.add_item_with_handler(value.items, select); palette.items += 1; } diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index 98846de..f06e943 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -108,28 +108,25 @@ pub fn Create(options: type) type { } fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { - const style_base = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget; - const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base; - button.plane.set_base_style(" ", style_base); + const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget; + const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label; + button.plane.set_base_style(" ", style_label); button.plane.erase(); button.plane.home(); - var command_name: []const u8 = undefined; - var keybind_hint: []const u8 = undefined; + var label: []const u8 = undefined; + var hint: []const u8 = undefined; var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes - if (!(cbor.matchString(&iter, &command_name) catch false)) - command_name = "#ERROR#"; - var command_id: command.ID = undefined; - if (!(cbor.matchValue(&iter, cbor.extract(&command_id)) catch false)) - command_id = 0; - if (!(cbor.matchString(&iter, &keybind_hint) catch false)) - keybind_hint = ""; - button.plane.set_style(style_keybind); + if (!(cbor.matchString(&iter, &label) catch false)) + label = "#ERROR#"; + if (!(cbor.matchString(&iter, &hint) catch false)) + hint = ""; + button.plane.set_style(style_hint); const pointer = if (selected) "⏵" else " "; _ = button.plane.print("{s}", .{pointer}) catch {}; - button.plane.set_style(style_base); - _ = button.plane.print("{s} ", .{command_name}) catch {}; - button.plane.set_style(style_keybind); - _ = button.plane.print_aligned_right(0, "{s} ", .{keybind_hint}) catch {}; + button.plane.set_style(style_label); + _ = button.plane.print("{s} ", .{label}) catch {}; + button.plane.set_style(style_hint); + _ = button.plane.print_aligned_right(0, "{s} ", .{hint}) catch {}; var index: usize = 0; var len = cbor.decodeArrayHeader(&iter) catch return false; while (len > 0) : (len -= 1) { @@ -301,7 +298,7 @@ pub fn Create(options: type) type { self.menu.reset_items(); self.menu.selected = null; for (self.entries.items) |entry| - self.longest = @max(self.longest, entry.name.len); + self.longest = @max(self.longest, entry.label.len); if (self.inputbox.text.items.len == 0) { self.total_items = 0; @@ -340,7 +337,7 @@ pub fn Create(options: type) type { var matches = std.ArrayList(Match).init(self.allocator); for (self.entries.items) |*entry| { - const match = searcher.scoreMatches(entry.name, query); + const match = searcher.scoreMatches(entry.label, query); if (match.score) |score| (try matches.addOne()).* = .{ .entry = entry, @@ -353,7 +350,7 @@ pub fn Create(options: type) type { const less_fn = struct { fn less_fn(_: void, lhs: Match, rhs: Match) bool { return if (lhs.score == rhs.score) - lhs.entry.name.len < rhs.entry.name.len + lhs.entry.label.len < rhs.entry.label.len else lhs.score > rhs.score; } diff --git a/src/tui/mode/overlay/theme_palette.zig b/src/tui/mode/overlay/theme_palette.zig index 9415986..f3b08d4 100644 --- a/src/tui/mode/overlay/theme_palette.zig +++ b/src/tui/mode/overlay/theme_palette.zig @@ -12,6 +12,7 @@ pub const name = " theme"; pub const description = "theme"; pub const Entry = struct { + label: []const u8, name: []const u8, }; @@ -25,24 +26,28 @@ var previous_theme: ?[]const u8 = null; pub fn load_entries(palette: *Type) !void { previous_theme = tui.current().theme.name; for (Widget.themes) |theme| - (try palette.entries.addOne()).* = .{ .name = theme.name }; + (try palette.entries.addOne()).* = .{ + .label = theme.description, + .name = theme.name, + }; } pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void { var value = std.ArrayList(u8).init(palette.allocator); defer value.deinit(); const writer = value.writer(); + try cbor.writeValue(writer, entry.label); try cbor.writeValue(writer, entry.name); - try cbor.writeValue(writer, if (palette.hints) |hints| hints.get(entry.name) orelse "" else ""); - if (matches) |matches_| - try cbor.writeValue(writer, matches_); + try cbor.writeValue(writer, matches orelse &[_]usize{}); try palette.menu.add_item_with_handler(value.items, select); palette.items += 1; } fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { + var description_: []const u8 = undefined; var name_: []const u8 = undefined; var iter = button.opts.label; + if (!(cbor.matchString(&iter, &description_) catch false)) return; if (!(cbor.matchString(&iter, &name_) catch false)) return; tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("theme_palette", e); tp.self_pid().send(.{ "cmd", "set_theme", .{name_} }) catch |e| menu.*.opts.ctx.logger.err("theme_palette", e); @@ -50,8 +55,10 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { pub fn updated(palette: *Type, button_: ?*Type.ButtonState) !void { const button = button_ orelse return cancel(palette); + var description_: []const u8 = undefined; var name_: []const u8 = undefined; var iter = button.opts.label; + if (!(cbor.matchString(&iter, &description_) catch false)) return; if (!(cbor.matchString(&iter, &name_) catch false)) return; tp.self_pid().send(.{ "cmd", "set_theme", .{name_} }) catch |e| palette.logger.err("theme_palette upated", e); } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index ba872de..bc40fca 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -223,9 +223,12 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void { return; var cmd: []const u8 = undefined; + var cmd_id: command.ID = undefined; var ctx: cmds.Ctx = .{}; if (try m.match(.{ "cmd", tp.extract(&cmd) })) return command.executeName(cmd, ctx) catch |e| self.logger.err(cmd, e); + if (try m.match(.{ "cmd", tp.extract(&cmd_id) })) + return command.execute(cmd_id, ctx) catch |e| self.logger.err("command", e); var arg: []const u8 = undefined; @@ -233,6 +236,10 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void { ctx.args = .{ .buf = arg }; return command.executeName(cmd, ctx) catch |e| self.logger.err(cmd, e); } + if (try m.match(.{ "cmd", tp.extract(&cmd_id), tp.extract_cbor(&arg) })) { + ctx.args = .{ .buf = arg }; + return command.execute(cmd_id, ctx) catch |e| self.logger.err("command", e); + } if (try m.match(.{"quit"})) { project_manager.shutdown(); return;