clean up tui tab framework
This commit is contained in:
parent
3d176daaaa
commit
4297fda67a
11 changed files with 169 additions and 168 deletions
24
src/tui.zig
24
src/tui.zig
|
|
@ -107,7 +107,7 @@ const tab_labels = blk: {
|
|||
var arr: [reg_fields.len][]const u8 = undefined;
|
||||
for (reg_fields, 0..) |f, i| {
|
||||
const Module = @field(tab_modules, f.name);
|
||||
arr[i] = std.fmt.comptimePrint(" {d}:{s} ", .{ i + 1, Module.tab.label });
|
||||
arr[i] = std.fmt.comptimePrint(" {d}:{s} ", .{ i + 1, Module.meta.label });
|
||||
}
|
||||
break :blk arr;
|
||||
};
|
||||
|
|
@ -285,7 +285,7 @@ comptime {
|
|||
@setEvalBranchQuota(20000);
|
||||
for (std.meta.fields(@TypeOf(tab_modules))) |field| {
|
||||
const Module = @field(tab_modules, field.name);
|
||||
for (Module.tab.default_bindings) |binding| {
|
||||
for (Module.meta.default_bindings) |binding| {
|
||||
for (keybinds.global_default_bindings) |global| {
|
||||
if (binding.key.codepoint == global.key.codepoint and
|
||||
std.meta.eql(binding.key.mods, global.key.mods))
|
||||
|
|
@ -838,7 +838,7 @@ pub const App = struct {
|
|||
}
|
||||
|
||||
// No user overrides — use the tab's default_bindings.
|
||||
for (Module.tab.default_bindings) |binding| {
|
||||
for (Module.meta.default_bindings) |binding| {
|
||||
if (key.matches(binding.key.codepoint, binding.key.mods)) {
|
||||
Module.tab.handleAction(state_ptr, self, binding.action);
|
||||
return true;
|
||||
|
|
@ -898,7 +898,7 @@ pub const App = struct {
|
|||
}
|
||||
|
||||
// No overrides — read from the tab's default_bindings.
|
||||
for (Module.tab.default_bindings) |binding| {
|
||||
for (Module.meta.default_bindings) |binding| {
|
||||
if (!std.mem.eql(u8, @tagName(binding.action), action_tag_name)) continue;
|
||||
var key_buf: [32]u8 = undefined;
|
||||
const s = keybinds.formatKeyCombo(binding.key, &key_buf) orelse continue;
|
||||
|
|
@ -1487,11 +1487,11 @@ pub const App = struct {
|
|||
inline for (std.meta.fields(@TypeOf(tab_modules))) |field| {
|
||||
if (std.mem.eql(u8, field.name, @tagName(self.active_tab))) {
|
||||
const Module = @field(tab_modules, field.name);
|
||||
for (Module.tab.status_hints) |hint_action| {
|
||||
for (Module.meta.status_hints) |hint_action| {
|
||||
const action_name = @tagName(hint_action);
|
||||
const keys = try self.keysForTabAction(arena, field.name, action_name);
|
||||
if (keys.len == 0) continue;
|
||||
const label = Module.tab.action_labels.get(hint_action);
|
||||
const label = Module.meta.action_labels.get(hint_action);
|
||||
if (label.len == 0) continue;
|
||||
try fragments.append(arena, .{ .key = keys[0], .label = label });
|
||||
}
|
||||
|
|
@ -1806,7 +1806,7 @@ pub const App = struct {
|
|||
const tab_actions = comptime std.enums.values(Module.Action);
|
||||
inline for (tab_actions) |action| {
|
||||
const action_name = @tagName(action);
|
||||
const label = Module.tab.action_labels.get(action);
|
||||
const label = Module.meta.action_labels.get(action);
|
||||
if (label.len > 0) {
|
||||
const keys = try self.keysForTabAction(arena, field.name, action_name);
|
||||
const keys_str = if (keys.len == 0)
|
||||
|
|
@ -2016,12 +2016,12 @@ fn writeDefaultKeys(out: *std.Io.Writer) !void {
|
|||
|
||||
inline for (std.meta.fields(@TypeOf(tab_modules))) |field| {
|
||||
const Module = @field(tab_modules, field.name);
|
||||
if (Module.tab.default_bindings.len == 0) continue;
|
||||
if (Module.meta.default_bindings.len == 0) continue;
|
||||
|
||||
const heading = "Tab: " ++ field.name;
|
||||
try keybinds.printSectionHeader(out, heading);
|
||||
|
||||
for (Module.tab.default_bindings) |binding| {
|
||||
for (Module.meta.default_bindings) |binding| {
|
||||
try keybinds.printScopedBinding(
|
||||
out,
|
||||
field.name,
|
||||
|
|
@ -2495,7 +2495,7 @@ test "writeDefaultKeys: every registered tab with default_bindings has a section
|
|||
|
||||
inline for (std.meta.fields(@TypeOf(tab_modules))) |field| {
|
||||
const Module = @field(tab_modules, field.name);
|
||||
if (Module.tab.default_bindings.len == 0) continue;
|
||||
if (Module.meta.default_bindings.len == 0) continue;
|
||||
|
||||
const heading = "# ── Tab: " ++ field.name ++ " ──";
|
||||
if (std.mem.indexOf(u8, out, heading) == null) {
|
||||
|
|
@ -2504,7 +2504,7 @@ test "writeDefaultKeys: every registered tab with default_bindings has a section
|
|||
}
|
||||
|
||||
// And every binding for that tab must show up as a `scope::<tab>,action::<name>` line.
|
||||
inline for (Module.tab.default_bindings) |binding| {
|
||||
inline for (Module.meta.default_bindings) |binding| {
|
||||
const needle = "scope::" ++ field.name ++ ",action::" ++ @tagName(binding.action);
|
||||
if (std.mem.indexOf(u8, out, needle) == null) {
|
||||
std.debug.print("missing binding line: {s}\n", .{needle});
|
||||
|
|
@ -2523,7 +2523,7 @@ test "writeDefaultKeys: tab sections appear in tab_modules declaration order" {
|
|||
var prev_pos: usize = 0;
|
||||
inline for (std.meta.fields(@TypeOf(tab_modules))) |field| {
|
||||
const Module = @field(tab_modules, field.name);
|
||||
if (Module.tab.default_bindings.len == 0) continue;
|
||||
if (Module.meta.default_bindings.len == 0) continue;
|
||||
|
||||
const heading = "# ── Tab: " ++ field.name ++ " ──";
|
||||
const pos = std.mem.indexOf(u8, out, heading) orelse return error.MissingTabSection;
|
||||
|
|
|
|||
|
|
@ -35,17 +35,17 @@ pub const State = struct {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "Analysis",
|
||||
.default_bindings = &.{},
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).initFill(""),
|
||||
.status_hints = &.{},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Analysis";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{};
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).initFill("");
|
||||
pub const status_hints: []const Action = &.{};
|
||||
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
_ = app;
|
||||
state.* = .{};
|
||||
|
|
|
|||
|
|
@ -43,22 +43,20 @@ pub const State = struct {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "Earnings",
|
||||
// No tab-local bindings — refresh is global. Empty placeholder.
|
||||
.default_bindings = &.{},
|
||||
// One label per Action variant — also empty.
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).initFill(""),
|
||||
// Status-line hints — empty.
|
||||
.status_hints = &.{},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Earnings";
|
||||
|
||||
/// No tab-local bindings — refresh is global. Empty placeholder.
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{};
|
||||
|
||||
/// One label per Action variant — also empty.
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).initFill("");
|
||||
|
||||
/// Status-line hints — empty.
|
||||
pub const status_hints: []const Action = &.{};
|
||||
|
||||
/// One-time construction. State already has zero-initialized
|
||||
/// defaults via field defaults; nothing to allocate up front.
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
|
|
|
|||
|
|
@ -157,14 +157,9 @@ pub const State = struct {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "History";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "History",
|
||||
.default_bindings = &.{
|
||||
.{ .action = .expand_collapse, .key = .{ .codepoint = vaxis.Key.enter } },
|
||||
.{ .action = .metric_next, .key = .{ .codepoint = 'm' } },
|
||||
.{ .action = .resolution_next, .key = .{ .codepoint = 't' } },
|
||||
|
|
@ -172,21 +167,24 @@ pub const tab = struct {
|
|||
.{ .action = .compare_select, .key = .{ .codepoint = vaxis.Key.space } },
|
||||
.{ .action = .compare_commit, .key = .{ .codepoint = 'c' } },
|
||||
.{ .action = .compare_cancel, .key = .{ .codepoint = vaxis.Key.escape } },
|
||||
};
|
||||
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
},
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
.expand_collapse = "Expand/collapse bucket",
|
||||
.metric_next = "Cycle metric",
|
||||
.resolution_next = "Cycle resolution",
|
||||
.compare_select = "Toggle compare selection",
|
||||
.compare_commit = "Compare selected rows",
|
||||
.compare_cancel = "Cancel compare",
|
||||
});
|
||||
|
||||
pub const status_hints: []const Action = &.{
|
||||
}),
|
||||
.status_hints = &.{
|
||||
.metric_next,
|
||||
.resolution_next,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
state.* = .{
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ pub fn defaults() KeyMap {
|
|||
/// Display labels for each global Action variant. Used by the help
|
||||
/// overlay and status-line hint to render human-readable names
|
||||
/// alongside the resolved key bindings. Parallel in shape to each
|
||||
/// tab module's own `tab.action_labels`.
|
||||
/// tab module's own `meta.action_labels`.
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
.quit = "Quit",
|
||||
.refresh = "Refresh",
|
||||
|
|
|
|||
|
|
@ -82,14 +82,9 @@ pub const State = struct {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Options";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "Options",
|
||||
.default_bindings = &.{
|
||||
.{ .action = .expand_collapse, .key = .{ .codepoint = vaxis.Key.enter } },
|
||||
.{ .action = .collapse_all_calls, .key = .{ .codepoint = 'c' } },
|
||||
.{ .action = .collapse_all_puts, .key = .{ .codepoint = 'p' } },
|
||||
|
|
@ -102,9 +97,8 @@ pub const tab = struct {
|
|||
.{ .action = .filter_7, .key = .{ .codepoint = '7', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .filter_8, .key = .{ .codepoint = '8', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .filter_9, .key = .{ .codepoint = '9', .mods = .{ .ctrl = true } } },
|
||||
};
|
||||
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
},
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
.expand_collapse = "Expand/collapse row",
|
||||
.collapse_all_calls = "Toggle all calls",
|
||||
.collapse_all_puts = "Toggle all puts",
|
||||
|
|
@ -117,12 +111,16 @@ pub const tab = struct {
|
|||
.filter_7 = "Filter +/- 7 NTM",
|
||||
.filter_8 = "Filter +/- 8 NTM",
|
||||
.filter_9 = "Filter +/- 9 NTM",
|
||||
});
|
||||
|
||||
pub const status_hints: []const Action = &.{
|
||||
}),
|
||||
.status_hints = &.{
|
||||
.collapse_all_calls,
|
||||
.collapse_all_puts,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
_ = app;
|
||||
|
|
|
|||
|
|
@ -29,17 +29,17 @@ pub const State = struct {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "Performance",
|
||||
.default_bindings = &.{},
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).initFill(""),
|
||||
.status_hints = &.{},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Performance";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{};
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).initFill("");
|
||||
pub const status_hints: []const Action = &.{};
|
||||
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
_ = app;
|
||||
state.* = .{};
|
||||
|
|
|
|||
|
|
@ -251,15 +251,9 @@ pub const State = struct {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar. Composed by the framework
|
||||
/// into `" {N}:{label} "` where N is the registry index.
|
||||
pub const label: []const u8 = "Portfolio";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "Portfolio",
|
||||
.default_bindings = &.{
|
||||
.{ .action = .expand_collapse, .key = .{ .codepoint = vaxis.Key.enter } },
|
||||
.{ .action = .sort_col_next, .key = .{ .codepoint = '>' } },
|
||||
.{ .action = .sort_col_prev, .key = .{ .codepoint = '<' } },
|
||||
|
|
@ -268,9 +262,8 @@ pub const tab = struct {
|
|||
.{ .action = .clear_account_filter, .key = .{ .codepoint = vaxis.Key.escape } },
|
||||
.{ .action = .select_symbol, .key = .{ .codepoint = 's' } },
|
||||
.{ .action = .select_symbol, .key = .{ .codepoint = vaxis.Key.space } },
|
||||
};
|
||||
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
},
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
.expand_collapse = "Expand/collapse position",
|
||||
.sort_col_next = "Sort: next column",
|
||||
.sort_col_prev = "Sort: previous column",
|
||||
|
|
@ -278,14 +271,18 @@ pub const tab = struct {
|
|||
.open_account_picker = "Filter by account",
|
||||
.clear_account_filter = "Clear account filter",
|
||||
.select_symbol = "Select symbol",
|
||||
});
|
||||
|
||||
pub const status_hints: []const Action = &.{
|
||||
}),
|
||||
.status_hints = &.{
|
||||
.sort_col_prev,
|
||||
.sort_col_next,
|
||||
.sort_reverse,
|
||||
.open_account_picker,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
_ = app;
|
||||
|
|
|
|||
|
|
@ -198,14 +198,9 @@ pub const Modal = enum {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Projections";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "Projections",
|
||||
.default_bindings = &.{
|
||||
.{ .action = .overlay_actuals, .key = .{ .codepoint = 'o' } },
|
||||
.{ .action = .toggle_chart, .key = .{ .codepoint = 'v' } },
|
||||
.{ .action = .toggle_events, .key = .{ .codepoint = 'e' } },
|
||||
|
|
@ -213,9 +208,8 @@ pub const tab = struct {
|
|||
.{ .action = .clear_as_of, .key = .{ .codepoint = vaxis.Key.escape } },
|
||||
.{ .action = .toggle_convergence, .key = .{ .codepoint = 'c' } },
|
||||
.{ .action = .toggle_return_backtest, .key = .{ .codepoint = 'b' } },
|
||||
};
|
||||
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
},
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
.overlay_actuals = "Toggle actuals overlay",
|
||||
.toggle_chart = "Toggle chart visibility",
|
||||
.toggle_events = "Toggle lifecycle events",
|
||||
|
|
@ -223,15 +217,19 @@ pub const tab = struct {
|
|||
.clear_as_of = "Clear as-of date",
|
||||
.toggle_convergence = "Toggle convergence sub-view",
|
||||
.toggle_return_backtest = "Toggle return back-test sub-view",
|
||||
});
|
||||
|
||||
pub const status_hints: []const Action = &.{
|
||||
}),
|
||||
.status_hints = &.{
|
||||
.toggle_chart,
|
||||
.toggle_events,
|
||||
.as_of_input,
|
||||
.toggle_convergence,
|
||||
.toggle_return_backtest,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
_ = app;
|
||||
|
|
|
|||
|
|
@ -98,27 +98,25 @@ pub const State = struct {
|
|||
|
||||
// ── Tab framework contract ────────────────────────────────────
|
||||
|
||||
pub const meta: framework.TabMeta(Action) = .{
|
||||
.label = "Quote",
|
||||
.default_bindings = &.{
|
||||
.{ .action = .chart_timeframe_next, .key = .{ .codepoint = ']' } },
|
||||
.{ .action = .chart_timeframe_prev, .key = .{ .codepoint = '[' } },
|
||||
},
|
||||
.action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
.chart_timeframe_next = "Chart: next timeframe",
|
||||
.chart_timeframe_prev = "Chart: previous timeframe",
|
||||
}),
|
||||
.status_hints = &.{
|
||||
.chart_timeframe_next,
|
||||
},
|
||||
};
|
||||
|
||||
pub const tab = struct {
|
||||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Quote";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
.{ .action = .chart_timeframe_next, .key = .{ .codepoint = ']' } },
|
||||
.{ .action = .chart_timeframe_prev, .key = .{ .codepoint = '[' } },
|
||||
};
|
||||
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
.chart_timeframe_next = "Chart: next timeframe",
|
||||
.chart_timeframe_prev = "Chart: previous timeframe",
|
||||
});
|
||||
|
||||
pub const status_hints: []const Action = &.{
|
||||
.chart_timeframe_next,
|
||||
};
|
||||
|
||||
pub fn init(state: *State, app: *App) !void {
|
||||
_ = app;
|
||||
state.* = .{};
|
||||
|
|
|
|||
|
|
@ -12,25 +12,17 @@
|
|||
//! pub const Action = enum { /* tab-local keybind actions */ };
|
||||
//! pub const State = struct { /* tab-private state */ };
|
||||
//!
|
||||
//! pub const meta: framework.TabMeta(Action) = .{
|
||||
//! .label = "...",
|
||||
//! .default_bindings = &.{ ... },
|
||||
//! .action_labels = std.enums.EnumArray(Action, []const u8).init(.{ ... }),
|
||||
//! .status_hints = &.{ ... },
|
||||
//! };
|
||||
//!
|
||||
//! pub const tab = struct {
|
||||
//! pub const ActionT = Action;
|
||||
//! pub const StateT = State;
|
||||
//!
|
||||
//! /// Display name for the tab bar. The framework composes
|
||||
//! /// this into `" {N}:{label} "` where N is the tab's
|
||||
//! /// 1-indexed registry position.
|
||||
//! pub const label: []const u8 = "...";
|
||||
//!
|
||||
//! /// Default keybindings for this tab.
|
||||
//! pub const default_bindings: []const TabBinding(Action) = &.{ ... };
|
||||
//!
|
||||
//! /// One label per Action variant for the help overlay.
|
||||
//! pub const action_labels =
|
||||
//! std.enums.EnumArray(Action, []const u8).init(.{ ... });
|
||||
//!
|
||||
//! /// Subset of actions that show in the status-line hint.
|
||||
//! pub const status_hints: []const Action = &.{ ... };
|
||||
//!
|
||||
//! // ── Lifecycle hooks (required) ──────────────────────────
|
||||
//! pub fn init(state: *State, app: *App) !void { ... }
|
||||
//! pub fn deinit(state: *State, app: *App) void { ... }
|
||||
|
|
@ -91,13 +83,20 @@
|
|||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! Tabs without keybind actions ship with `Action = enum {}` and
|
||||
//! empty `default_bindings` / `action_labels` / `status_hints`.
|
||||
//! No implicit defaults — the contract is fully explicit for
|
||||
//! action-related fields and lifecycle hooks. The event hooks
|
||||
//! (`handleKey`, `handleMouse`, `handlePaste`) and context-change
|
||||
//! hooks (`onSymbolChange`) are the exception: their absence means
|
||||
//! "this tab doesn't process that event class."
|
||||
//! `meta` carries the four declarative fields; `tab` carries the
|
||||
//! function hooks. Functions can't be bundled into a struct value,
|
||||
//! so the data/behavior split is a Zig-language concession rather
|
||||
//! than a stylistic preference. The split mirrors `commands/framework.zig`'s
|
||||
//! `Meta` for command modules.
|
||||
//!
|
||||
//! Tabs without keybind actions ship with `Action = enum {}`,
|
||||
//! `default_bindings = &.{}`, `action_labels =
|
||||
//! std.enums.EnumArray(Action, []const u8).initFill("")`, and
|
||||
//! `status_hints = &.{}`. No implicit defaults — the contract is
|
||||
//! fully explicit for action-related fields and lifecycle hooks.
|
||||
//! The event hooks (`handleKey`, `handleMouse`, `handlePaste`) and
|
||||
//! context-change hooks (`onSymbolChange`) are the exception:
|
||||
//! their absence means "this tab doesn't process that event class."
|
||||
//!
|
||||
//! Lifecycle hooks that aren't meaningful for a given tab can use
|
||||
//! the `noop*` factory helpers below to inherit no-op
|
||||
|
|
@ -133,6 +132,41 @@ pub fn TabBinding(comptime ActionT: type) type {
|
|||
};
|
||||
}
|
||||
|
||||
/// Per-tab declarative metadata. Mirrors `commands/framework.zig`'s
|
||||
/// `Meta`: every tab module declares
|
||||
/// `pub const meta: framework.TabMeta(Action) = .{ ... };` with all
|
||||
/// fields populated. The compiler enforces the contract via the
|
||||
/// field types; `validateTabModule` reduces to a single
|
||||
/// `expectDeclWithType` check on the declaration's type.
|
||||
///
|
||||
/// Function hooks (init, deinit, activate, ..., handleAction, the
|
||||
/// optional event hooks) live on the separate `pub const tab =
|
||||
/// struct { ... };` namespace because Zig structs can't carry
|
||||
/// `pub fn` declarations as values. The split is deliberate: data
|
||||
/// in `meta`, behavior in `tab`.
|
||||
///
|
||||
/// Generic over `ActionT` so `default_bindings`, `action_labels`,
|
||||
/// and `status_hints` can be type-checked at comptime against the
|
||||
/// tab's own Action enum.
|
||||
pub fn TabMeta(comptime ActionT: type) type {
|
||||
return struct {
|
||||
/// Display name for the tab bar. The framework composes
|
||||
/// this into `" {N}:{label} "` where N is the tab's
|
||||
/// 1-indexed registry position.
|
||||
label: []const u8,
|
||||
/// Default keybindings for this tab. Tabs without
|
||||
/// keybind actions ship with `&.{}`.
|
||||
default_bindings: []const TabBinding(ActionT),
|
||||
/// One label per Action variant for the help overlay.
|
||||
/// Tabs without keybind actions use
|
||||
/// `std.enums.EnumArray(Action, []const u8).init(.{})`.
|
||||
action_labels: std.enums.EnumArray(ActionT, []const u8),
|
||||
/// Subset of actions that show in the status-line hint.
|
||||
/// Tabs without status hints use `&.{}`.
|
||||
status_hints: []const ActionT,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returned by a tab's optional `statusOverride` hook to take
|
||||
/// over the status-bar row while a tab-internal modal is active
|
||||
/// (account picker, date input, etc).
|
||||
|
|
@ -268,38 +302,18 @@ pub fn validateTabModule(comptime Module: type) void {
|
|||
@compileError("Tab module `" ++ mod_name ++ "`: `tab` is missing `pub const StateT = State;`");
|
||||
}
|
||||
|
||||
// ── Binding / label / hint constants ───────────────────
|
||||
// ── meta (top-level decl, struct value) ────────────────
|
||||
// One check covers all four declarative fields. Field
|
||||
// types are enforced by `TabMeta(Action)`'s declaration;
|
||||
// missing fields surface as Zig's native "missing field"
|
||||
// comptime errors pointing at the call site.
|
||||
validator.expectDeclWithType(
|
||||
"Tab module",
|
||||
mod_name,
|
||||
tab_decl,
|
||||
"label",
|
||||
[]const u8,
|
||||
"pub const label: []const u8 = \"...\";",
|
||||
);
|
||||
validator.expectDeclWithType(
|
||||
"Tab module",
|
||||
mod_name,
|
||||
tab_decl,
|
||||
"default_bindings",
|
||||
[]const TabBinding(Action),
|
||||
"pub const default_bindings: []const TabBinding(Action) = &.{ ... };",
|
||||
);
|
||||
validator.expectDeclWithType(
|
||||
"Tab module",
|
||||
mod_name,
|
||||
tab_decl,
|
||||
"action_labels",
|
||||
std.enums.EnumArray(Action, []const u8),
|
||||
"pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{ ... });",
|
||||
);
|
||||
validator.expectDeclWithType(
|
||||
"Tab module",
|
||||
mod_name,
|
||||
tab_decl,
|
||||
"status_hints",
|
||||
[]const Action,
|
||||
"pub const status_hints: []const Action = &.{ ... };",
|
||||
Module,
|
||||
"meta",
|
||||
TabMeta(Action),
|
||||
"pub const meta: framework.TabMeta(Action) = .{ .label = \"...\", .default_bindings = &.{ ... }, .action_labels = std.enums.EnumArray(Action, []const u8).init(.{ ... }), .status_hints = &.{ ... } };",
|
||||
);
|
||||
|
||||
// ── Lifecycle hooks (required) ─────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue