feat: add support for command metadata

This commit is contained in:
CJ van den Berg 2024-09-17 22:57:21 +02:00
parent 6b43dd4f28
commit bdd16f43fb
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
6 changed files with 77 additions and 42 deletions

View file

@ -22,6 +22,12 @@ const Vtable = struct {
id: ID = ID_unknown, id: ID = ID_unknown,
name: []const u8, name: []const u8,
run: *const fn (self: *Vtable, ctx: Context) tp.result, 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 { 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 FunT: type = *const fn (T, ctx: Context) Result;
const Self = @This(); 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 .{ return .{
.vtbl = .{ .vtbl = .{
.run = run, .run = run,
.name = name, .name = name,
.meta = meta,
}, },
.f = f, .f = f,
.data = data, .data = data,
@ -142,6 +149,7 @@ fn CmdDef(comptime T: type) type {
const Fn = fn (T, Context) anyerror!void; const Fn = fn (T, Context) anyerror!void;
name: [:0]const u8, name: [:0]const u8,
f: *const Fn, f: *const Fn,
meta: Metadata,
}; };
} }
@ -150,6 +158,7 @@ fn getTargetType(comptime Namespace: type) type {
} }
fn getCommands(comptime Namespace: type) []const CmdDef(*getTargetType(Namespace)) { fn getCommands(comptime Namespace: type) []const CmdDef(*getTargetType(Namespace)) {
@setEvalBranchQuota(10_000);
comptime switch (@typeInfo(Namespace)) { comptime switch (@typeInfo(Namespace)) {
.Struct => |info| { .Struct => |info| {
var count = 0; var count = 0;
@ -164,7 +173,14 @@ fn getCommands(comptime Namespace: type) []const CmdDef(*getTargetType(Namespace
var i = 0; var i = 0;
for (info.decls) |decl| { for (info.decls) |decl| {
if (@TypeOf(@field(Namespace, decl.name)) == CmdDef(*Target).Fn) { 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; i += 1;
} }
} }
@ -208,7 +224,7 @@ pub fn Collection(comptime Namespace: type) type {
if (cmds.len == 0) if (cmds.len == 0)
@compileError("no commands found in type " ++ @typeName(Target) ++ " (did you mark them public?)"); @compileError("no commands found in type " ++ @typeName(Target) ++ " (did you mark them public?)");
inline for (cmds) |cmd| { 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(); try @field(self.fields, cmd.name).register();
} }
} }

View file

@ -12,6 +12,7 @@ pub const name = "󱊒 command";
pub const description = "command"; pub const description = "command";
pub const Entry = struct { pub const Entry = struct {
label: []const u8,
name: []const u8, name: []const u8,
id: command.ID, id: command.ID,
used_time: i64, used_time: i64,
@ -19,7 +20,13 @@ pub const Entry = struct {
pub fn load_entries(palette: *Type) !void { pub fn load_entries(palette: *Type) !void {
for (command.commands.items) |cmd_| if (cmd_) |p| { 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); var value = std.ArrayList(u8).init(palette.allocator);
defer value.deinit(); defer value.deinit();
const writer = value.writer(); const writer = value.writer();
try cbor.writeValue(writer, entry.name); try cbor.writeValue(writer, entry.label);
try cbor.writeValue(writer, entry.id);
try cbor.writeValue(writer, if (palette.hints) |hints| hints.get(entry.name) orelse "" else ""); try cbor.writeValue(writer, if (palette.hints) |hints| hints.get(entry.name) orelse "" else "");
if (matches) |matches_| try cbor.writeValue(writer, matches orelse &[_]usize{});
try cbor.writeValue(writer, matches_); try cbor.writeValue(writer, entry.id);
try palette.menu.add_item_with_handler(value.items, select); try palette.menu.add_item_with_handler(value.items, select);
palette.items += 1; palette.items += 1;
} }
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { 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 command_id: command.ID = undefined;
var iter = button.opts.label; 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; if (!(cbor.matchValue(&iter, cbor.extract(&command_id)) catch false)) return;
update_used_time(menu.*.opts.ctx, command_id); 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", "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 { fn sort_by_used_time(palette: *Type) void {

View file

@ -10,7 +10,7 @@ pub const name = " project";
pub const description = "project"; pub const description = "project";
pub const Entry = struct { pub const Entry = struct {
name: []const u8, label: []const u8,
}; };
pub const Match = struct { pub const Match = struct {
@ -21,7 +21,7 @@ pub const Match = struct {
pub fn deinit(palette: *Type) void { pub fn deinit(palette: *Type) void {
for (palette.entries.items) |entry| for (palette.entries.items) |entry|
palette.allocator.free(entry.name); palette.allocator.free(entry.label);
} }
pub fn load_entries(palette: *Type) !void { pub fn load_entries(palette: *Type) !void {
@ -32,7 +32,7 @@ pub fn load_entries(palette: *Type) !void {
while (len > 0) : (len -= 1) { while (len > 0) : (len -= 1) {
var name_: []const u8 = undefined; var name_: []const u8 = undefined;
if (try cbor.matchValue(&iter, cbor.extract(&name_))) { 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; } 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); var value = std.ArrayList(u8).init(palette.allocator);
defer value.deinit(); defer value.deinit();
const writer = value.writer(); const writer = value.writer();
try cbor.writeValue(writer, entry.name); try cbor.writeValue(writer, entry.label);
try cbor.writeValue(writer, if (palette.hints) |hints| hints.get(entry.name) orelse "" else ""); try cbor.writeValue(writer, matches orelse &[_]usize{});
if (matches) |matches_|
try cbor.writeValue(writer, matches_);
try palette.menu.add_item_with_handler(value.items, select); try palette.menu.add_item_with_handler(value.items, select);
palette.items += 1; palette.items += 1;
} }

View file

@ -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 { 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_label = 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; const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label;
button.plane.set_base_style(" ", style_base); button.plane.set_base_style(" ", style_label);
button.plane.erase(); button.plane.erase();
button.plane.home(); button.plane.home();
var command_name: []const u8 = undefined; var label: []const u8 = undefined;
var keybind_hint: []const u8 = undefined; var hint: []const u8 = undefined;
var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes
if (!(cbor.matchString(&iter, &command_name) catch false)) if (!(cbor.matchString(&iter, &label) catch false))
command_name = "#ERROR#"; label = "#ERROR#";
var command_id: command.ID = undefined; if (!(cbor.matchString(&iter, &hint) catch false))
if (!(cbor.matchValue(&iter, cbor.extract(&command_id)) catch false)) hint = "";
command_id = 0; button.plane.set_style(style_hint);
if (!(cbor.matchString(&iter, &keybind_hint) catch false))
keybind_hint = "";
button.plane.set_style(style_keybind);
const pointer = if (selected) "" else " "; const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {}; _ = button.plane.print("{s}", .{pointer}) catch {};
button.plane.set_style(style_base); button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{command_name}) catch {}; _ = button.plane.print("{s} ", .{label}) catch {};
button.plane.set_style(style_keybind); button.plane.set_style(style_hint);
_ = button.plane.print_aligned_right(0, "{s} ", .{keybind_hint}) catch {}; _ = button.plane.print_aligned_right(0, "{s} ", .{hint}) catch {};
var index: usize = 0; var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false; var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) { while (len > 0) : (len -= 1) {
@ -301,7 +298,7 @@ pub fn Create(options: type) type {
self.menu.reset_items(); self.menu.reset_items();
self.menu.selected = null; self.menu.selected = null;
for (self.entries.items) |entry| 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) { if (self.inputbox.text.items.len == 0) {
self.total_items = 0; self.total_items = 0;
@ -340,7 +337,7 @@ pub fn Create(options: type) type {
var matches = std.ArrayList(Match).init(self.allocator); var matches = std.ArrayList(Match).init(self.allocator);
for (self.entries.items) |*entry| { for (self.entries.items) |*entry| {
const match = searcher.scoreMatches(entry.name, query); const match = searcher.scoreMatches(entry.label, query);
if (match.score) |score| if (match.score) |score|
(try matches.addOne()).* = .{ (try matches.addOne()).* = .{
.entry = entry, .entry = entry,
@ -353,7 +350,7 @@ pub fn Create(options: type) type {
const less_fn = struct { const less_fn = struct {
fn less_fn(_: void, lhs: Match, rhs: Match) bool { fn less_fn(_: void, lhs: Match, rhs: Match) bool {
return if (lhs.score == rhs.score) return if (lhs.score == rhs.score)
lhs.entry.name.len < rhs.entry.name.len lhs.entry.label.len < rhs.entry.label.len
else else
lhs.score > rhs.score; lhs.score > rhs.score;
} }

View file

@ -12,6 +12,7 @@ pub const name = " theme";
pub const description = "theme"; pub const description = "theme";
pub const Entry = struct { pub const Entry = struct {
label: []const u8,
name: []const u8, name: []const u8,
}; };
@ -25,24 +26,28 @@ var previous_theme: ?[]const u8 = null;
pub fn load_entries(palette: *Type) !void { pub fn load_entries(palette: *Type) !void {
previous_theme = tui.current().theme.name; previous_theme = tui.current().theme.name;
for (Widget.themes) |theme| 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 { pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
var value = std.ArrayList(u8).init(palette.allocator); var value = std.ArrayList(u8).init(palette.allocator);
defer value.deinit(); defer value.deinit();
const writer = value.writer(); const writer = value.writer();
try cbor.writeValue(writer, entry.label);
try cbor.writeValue(writer, entry.name); try cbor.writeValue(writer, entry.name);
try cbor.writeValue(writer, if (palette.hints) |hints| hints.get(entry.name) orelse "" else ""); try cbor.writeValue(writer, matches orelse &[_]usize{});
if (matches) |matches_|
try cbor.writeValue(writer, matches_);
try palette.menu.add_item_with_handler(value.items, select); try palette.menu.add_item_with_handler(value.items, select);
palette.items += 1; palette.items += 1;
} }
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var description_: []const u8 = undefined;
var name_: []const u8 = undefined; var name_: []const u8 = undefined;
var iter = button.opts.label; var iter = button.opts.label;
if (!(cbor.matchString(&iter, &description_) catch false)) return;
if (!(cbor.matchString(&iter, &name_) 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", "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); 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 { pub fn updated(palette: *Type, button_: ?*Type.ButtonState) !void {
const button = button_ orelse return cancel(palette); const button = button_ orelse return cancel(palette);
var description_: []const u8 = undefined;
var name_: []const u8 = undefined; var name_: []const u8 = undefined;
var iter = button.opts.label; var iter = button.opts.label;
if (!(cbor.matchString(&iter, &description_) catch false)) return;
if (!(cbor.matchString(&iter, &name_) 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); tp.self_pid().send(.{ "cmd", "set_theme", .{name_} }) catch |e| palette.logger.err("theme_palette upated", e);
} }

View file

@ -223,9 +223,12 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
return; return;
var cmd: []const u8 = undefined; var cmd: []const u8 = undefined;
var cmd_id: command.ID = undefined;
var ctx: cmds.Ctx = .{}; var ctx: cmds.Ctx = .{};
if (try m.match(.{ "cmd", tp.extract(&cmd) })) if (try m.match(.{ "cmd", tp.extract(&cmd) }))
return command.executeName(cmd, ctx) catch |e| self.logger.err(cmd, e); 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; 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 }; ctx.args = .{ .buf = arg };
return command.executeName(cmd, ctx) catch |e| self.logger.err(cmd, e); 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"})) { if (try m.match(.{"quit"})) {
project_manager.shutdown(); project_manager.shutdown();
return; return;