migrate history tab to new keybindings
This commit is contained in:
parent
23a11ddded
commit
0b2dad0cf1
3 changed files with 71 additions and 187 deletions
80
src/tui.zig
80
src/tui.zig
|
|
@ -1243,19 +1243,12 @@ pub const App = struct {
|
|||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
|
||||
// History-tab compare intercept.
|
||||
//
|
||||
// `s` / space / `c` / escape have existing global bindings
|
||||
// (select_symbol, collapse_all_calls, plus the account-filter
|
||||
// escape handler below) that would otherwise handle (or
|
||||
// silently consume) these keys. This intercept runs first when
|
||||
// the user is in the history tab so compare behavior wins.
|
||||
if (self.active_tab == .history) {
|
||||
if (tab_modules.history.handleCompareKey(&self.states.history, self, ctx, key)) return;
|
||||
}
|
||||
|
||||
// Escape: clear account filter on portfolio tab, clear as-of
|
||||
// on projections tab, no-op otherwise.
|
||||
// Escape: portfolio tab clears its account filter; projections
|
||||
// tab clears its as-of date. Other tabs fall through to the
|
||||
// global keymap (no global Esc binding) and then to the
|
||||
// tab-local fallback dispatcher (e.g. history binds Esc to
|
||||
// `compare_cancel`). When portfolio/projections migrate,
|
||||
// their Esc handling moves into tab-local actions too.
|
||||
if (key.codepoint == vaxis.Key.escape) {
|
||||
if (self.active_tab == .portfolio and self.states.portfolio.account_filter != null) {
|
||||
self.setAccountFilter(null);
|
||||
|
|
@ -1273,7 +1266,9 @@ pub const App = struct {
|
|||
self.setStatus("As-of cleared — showing live");
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
return;
|
||||
// Fall through — no tab-specific handler. Esc isn't
|
||||
// globally bound, so matchAction returns null and the
|
||||
// tab-local fallback gets a chance.
|
||||
}
|
||||
|
||||
const action = self.keymap.matchAction(key) orelse {
|
||||
|
|
@ -1294,13 +1289,6 @@ pub const App = struct {
|
|||
self.input_len = 0;
|
||||
return ctx.consumeAndRedraw();
|
||||
},
|
||||
.select_symbol => {
|
||||
// 's' selects the current portfolio row's symbol as the active symbol
|
||||
if (self.active_tab == .portfolio) {
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.select_symbol);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.refresh => {
|
||||
self.refreshCurrentTab();
|
||||
return ctx.consumeAndRedraw();
|
||||
|
|
@ -1391,12 +1379,6 @@ pub const App = struct {
|
|||
self.reloadPortfolioFile();
|
||||
return ctx.consumeAndRedraw();
|
||||
},
|
||||
.collapse_all_calls => {
|
||||
if (self.active_tab == .options) {
|
||||
tab_modules.options.tab.handleAction(&self.states.options, self, tab_modules.options.Action.collapse_all_calls);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.collapse_all_puts => {
|
||||
if (self.active_tab == .options) {
|
||||
tab_modules.options.tab.handleAction(&self.states.options, self, tab_modules.options.Action.collapse_all_puts);
|
||||
|
|
@ -1414,18 +1396,6 @@ pub const App = struct {
|
|||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.history_metric_next => {
|
||||
if (self.active_tab == .history) {
|
||||
tab_modules.history.tab.handleAction(&self.states.history, self, tab_modules.history.Action.metric_next);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.history_resolution_next => {
|
||||
if (self.active_tab == .history) {
|
||||
tab_modules.history.tab.handleAction(&self.states.history, self, tab_modules.history.Action.resolution_next);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.sort_col_next => {
|
||||
if (self.active_tab == .portfolio) {
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.sort_col_next);
|
||||
|
|
@ -1484,38 +1454,6 @@ pub const App = struct {
|
|||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
// History-tab compare actions are normally intercepted in
|
||||
// `handleCompareKey` before `matchAction` runs (because the
|
||||
// default 's'/'c'/space/escape key bindings belong to other
|
||||
// actions). These cases exist so the switch is exhaustive
|
||||
// and so future user-supplied keybindings targeting these
|
||||
// action names work correctly.
|
||||
.compare_select => {
|
||||
if (self.active_tab == .history) {
|
||||
const hs = &self.states.history;
|
||||
if (hs.compare_view == null and hs.table_row_count > 0) {
|
||||
tab_modules.history.toggleSelectionAt(hs, self, hs.cursor);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
.compare_commit => {
|
||||
if (self.active_tab == .history) {
|
||||
const hs = &self.states.history;
|
||||
if (hs.compare_view != null) {
|
||||
tab_modules.history.clearCompareState(hs, self);
|
||||
} else {
|
||||
tab_modules.history.commitCompareExternal(hs, self);
|
||||
}
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.compare_cancel => {
|
||||
if (self.active_tab == .history) {
|
||||
tab_modules.history.clearCompareState(&self.states.history, self);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@
|
|||
//! `src/history.zig` (snapshot IO). Compare composition is delegated
|
||||
//! to `src/compare.zig`; compare rendering to `src/views/compare.zig`.
|
||||
//!
|
||||
//! Keybinds:
|
||||
//! - `m` cycles chart metric (`history_metric_next`)
|
||||
//! - `t` cycles resolution (`history_resolution_next`)
|
||||
//! - `s` / space / `c` / Esc — compare (intercepted in `tui.zig`
|
||||
//! before matchAction, see `handleCompareKey` below)
|
||||
//! Keybinds: see the `Action` enum + `default_bindings` below.
|
||||
|
||||
const std = @import("std");
|
||||
const vaxis = @import("vaxis");
|
||||
|
|
@ -60,13 +56,20 @@ const StyledLine = tui.StyledLine;
|
|||
// (only meaningful for non-daily, non-live
|
||||
// rows with finer-tier breakdown).
|
||||
//
|
||||
// `s` / space / `c` / Esc are ALSO history-local (compare-mode
|
||||
// selection toggle, commit, and cancel) but are still routed via
|
||||
// the legacy global `keybinds.Action.compare_*` variants and the
|
||||
// pre-dispatch `handleCompareKey` intercept in tui.zig. Those are
|
||||
// the next layer of the migration (scoped keymaps, TODO step 3) —
|
||||
// once scoped keymaps land, both the global variants and the
|
||||
// intercept disappear and these become per-tab actions.
|
||||
// History tab keybinds:
|
||||
// - `m` : cycle the focused metric (liquid →
|
||||
// illiquid → net_worth).
|
||||
// - `t` : cycle the resolution (cascading →
|
||||
// daily → weekly → monthly → cascading).
|
||||
// - Enter : expand/collapse the bucket at the cursor
|
||||
// (only meaningful for non-daily, non-live
|
||||
// rows with finer-tier breakdown).
|
||||
// - `s` / space : toggle inclusion of the cursor row in
|
||||
// the compare-selection set (0, 1, or 2).
|
||||
// - `c` : commit compare (requires 2 rows
|
||||
// selected) or exit an active compare view.
|
||||
// - Esc : cancel compare view or clear pending
|
||||
// selections.
|
||||
|
||||
pub const Action = enum {
|
||||
/// Toggle expansion of the bucket at the cursor. No-op for
|
||||
|
|
@ -76,6 +79,15 @@ pub const Action = enum {
|
|||
metric_next,
|
||||
/// Cycle the resolution: cascading → daily → weekly → monthly → cascading.
|
||||
resolution_next,
|
||||
/// Toggle inclusion of the cursor row in the compare selection set.
|
||||
/// Disabled while a compare view is up. Bound to `s` and space.
|
||||
compare_select,
|
||||
/// Run compare if exactly 2 rows are selected (otherwise status
|
||||
/// hint), or exit an active compare view. Bound to `c`.
|
||||
compare_commit,
|
||||
/// Cancel compare: drop active compare view if up, else clear
|
||||
/// pending selections. No-op when nothing is active. Bound to Esc.
|
||||
compare_cancel,
|
||||
};
|
||||
|
||||
// ── Tab-private state ─────────────────────────────────────────
|
||||
|
|
@ -156,12 +168,19 @@ pub const tab = struct {
|
|||
.{ .action = .expand_collapse, .key = .{ .codepoint = vaxis.Key.enter } },
|
||||
.{ .action = .metric_next, .key = .{ .codepoint = 'm' } },
|
||||
.{ .action = .resolution_next, .key = .{ .codepoint = 't' } },
|
||||
.{ .action = .compare_select, .key = .{ .codepoint = 's' } },
|
||||
.{ .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(.{
|
||||
.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 = &.{
|
||||
|
|
@ -201,6 +220,37 @@ pub const tab = struct {
|
|||
.expand_collapse => _ = toggleTierAtCursor(state, app),
|
||||
.metric_next => cycleMetric(state),
|
||||
.resolution_next => cycleResolution(state),
|
||||
.compare_select => {
|
||||
// Disabled while a compare view is up — Esc to return first.
|
||||
if (state.compare_view != null) return;
|
||||
if (state.table_row_count == 0) return;
|
||||
toggleSelection(state, app, state.cursor);
|
||||
},
|
||||
.compare_commit => {
|
||||
// If a compare view is up, treat `c` as toggle-off
|
||||
// (mirrors Esc semantics on this tab).
|
||||
if (state.compare_view != null) {
|
||||
clearCompareView(state, app);
|
||||
clearSelections(state);
|
||||
app.setStatus("");
|
||||
return;
|
||||
}
|
||||
commitCompare(state, app);
|
||||
},
|
||||
.compare_cancel => {
|
||||
// Esc semantics: drop active compare view if up,
|
||||
// else clear pending selections, else no-op.
|
||||
if (state.compare_view != null) {
|
||||
clearCompareView(state, app);
|
||||
clearSelections(state);
|
||||
app.setStatus("");
|
||||
return;
|
||||
}
|
||||
if (selectionCount(state) > 0) {
|
||||
clearSelections(state);
|
||||
app.setStatus("");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -447,26 +497,6 @@ fn setSelectionStatus(state: *const State, app: *App) void {
|
|||
}
|
||||
}
|
||||
|
||||
/// Public entry for the `compare_select` action dispatched via
|
||||
/// matchAction. Internally delegates to the same toggle function the
|
||||
/// intercept uses.
|
||||
pub fn toggleSelectionAt(state: *State, app: *App, idx: usize) void {
|
||||
toggleSelection(state, app, idx);
|
||||
}
|
||||
|
||||
/// Public entry for the `compare_commit` action.
|
||||
pub fn commitCompareExternal(state: *State, app: *App) void {
|
||||
commitCompare(state, app);
|
||||
}
|
||||
|
||||
/// Public entry for the `compare_cancel` action or internal Esc path:
|
||||
/// clear selections and any active compare view.
|
||||
pub fn clearCompareState(state: *State, app: *App) void {
|
||||
clearCompareView(state, app);
|
||||
clearSelections(state);
|
||||
app.setStatus("");
|
||||
}
|
||||
|
||||
// ── Compare commit ───────────────────────────────────────────
|
||||
|
||||
/// Attempt to run compare. Called when the user presses `c`.
|
||||
|
|
@ -663,63 +693,6 @@ fn aggregateFromSummary(
|
|||
}
|
||||
}
|
||||
|
||||
// ── Compare key handler (intercepted from tui.zig) ───────────
|
||||
|
||||
/// Intercepted before `matchAction` runs when the active tab is
|
||||
/// history. Returns true if the key was consumed.
|
||||
///
|
||||
/// TODO(scoped-keymaps): once scoped keymaps land (TODO step 3),
|
||||
/// this intercept goes away — `s`/space/`c`/Esc become
|
||||
/// per-tab keybinds bound to local Action variants in
|
||||
/// `default_bindings`, and the global keymap stops binding them.
|
||||
/// The pre-dispatch interception in `tui.zig` (the call to
|
||||
/// `handleCompareKey`) gets deleted alongside.
|
||||
pub fn handleCompareKey(state: *State, app: *App, ctx: *vaxis.vxfw.EventContext, key: vaxis.Key) bool {
|
||||
// Escape: exit compare view, or clear selections.
|
||||
if (key.codepoint == vaxis.Key.escape) {
|
||||
if (state.compare_view != null) {
|
||||
clearCompareView(state, app);
|
||||
clearSelections(state);
|
||||
app.setStatus("");
|
||||
ctx.consumeAndRedraw();
|
||||
return true;
|
||||
}
|
||||
if (selectionCount(state) > 0) {
|
||||
clearSelections(state);
|
||||
app.setStatus("");
|
||||
ctx.consumeAndRedraw();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 's' or space: toggle selection on the cursor row.
|
||||
if (key.matches('s', .{}) or key.matches(vaxis.Key.space, .{})) {
|
||||
// Disabled while the compare view is up — Esc to return first.
|
||||
if (state.compare_view != null) return false;
|
||||
if (state.table_row_count == 0) return false;
|
||||
toggleSelection(state, app, state.cursor);
|
||||
ctx.consumeAndRedraw();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 'c': commit compare, or exit compare view if already active.
|
||||
if (key.matches('c', .{})) {
|
||||
if (state.compare_view != null) {
|
||||
clearCompareView(state, app);
|
||||
clearSelections(state);
|
||||
app.setStatus("");
|
||||
ctx.consumeAndRedraw();
|
||||
return true;
|
||||
}
|
||||
commitCompare(state, app);
|
||||
ctx.consumeAndRedraw();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ── Table row model ──────────────────────────────────────────
|
||||
|
||||
/// One row in the rendered recent-snapshots table.
|
||||
|
|
|
|||
|
|
@ -24,11 +24,9 @@ pub const Action = enum {
|
|||
select_next,
|
||||
select_prev,
|
||||
expand_collapse,
|
||||
select_symbol,
|
||||
symbol_input,
|
||||
help,
|
||||
reload_portfolio,
|
||||
collapse_all_calls,
|
||||
collapse_all_puts,
|
||||
options_filter_1,
|
||||
options_filter_2,
|
||||
|
|
@ -39,17 +37,6 @@ pub const Action = enum {
|
|||
options_filter_7,
|
||||
options_filter_8,
|
||||
options_filter_9,
|
||||
history_metric_next,
|
||||
history_resolution_next,
|
||||
/// History tab: toggle inclusion of the row under the cursor in the
|
||||
/// compare-selection set (0, 1, or 2 rows). Defaults: 's' and space.
|
||||
compare_select,
|
||||
/// History tab: run compare if exactly 2 rows are selected; otherwise
|
||||
/// status-bar hint. Default: 'c'.
|
||||
compare_commit,
|
||||
/// History tab: clear the current compare view (return to timeline)
|
||||
/// or clear pending selections. Default: escape.
|
||||
compare_cancel,
|
||||
sort_col_next,
|
||||
sort_col_prev,
|
||||
sort_reverse,
|
||||
|
|
@ -179,12 +166,9 @@ pub const global_default_bindings = [_]Binding{
|
|||
.{ .action = .select_prev, .key = .{ .codepoint = 'k' } },
|
||||
.{ .action = .select_prev, .key = .{ .codepoint = vaxis.Key.up } },
|
||||
.{ .action = .expand_collapse, .key = .{ .codepoint = vaxis.Key.enter } },
|
||||
.{ .action = .select_symbol, .key = .{ .codepoint = 's' } },
|
||||
.{ .action = .select_symbol, .key = .{ .codepoint = vaxis.Key.space } },
|
||||
.{ .action = .symbol_input, .key = .{ .codepoint = '/' } },
|
||||
.{ .action = .help, .key = .{ .codepoint = '?' } },
|
||||
.{ .action = .reload_portfolio, .key = .{ .codepoint = 'R' } },
|
||||
.{ .action = .collapse_all_calls, .key = .{ .codepoint = 'c' } },
|
||||
.{ .action = .collapse_all_puts, .key = .{ .codepoint = 'p' } },
|
||||
.{ .action = .options_filter_1, .key = .{ .codepoint = '1', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .options_filter_2, .key = .{ .codepoint = '2', .mods = .{ .ctrl = true } } },
|
||||
|
|
@ -195,17 +179,6 @@ pub const global_default_bindings = [_]Binding{
|
|||
.{ .action = .options_filter_7, .key = .{ .codepoint = '7', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .options_filter_8, .key = .{ .codepoint = '8', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .options_filter_9, .key = .{ .codepoint = '9', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .history_metric_next, .key = .{ .codepoint = 'm' } },
|
||||
.{ .action = .history_resolution_next, .key = .{ .codepoint = 't' } },
|
||||
// Compare bindings live AFTER the existing `s`/`c`/space bindings
|
||||
// so `matchAction` (first-match-wins) returns the original action
|
||||
// in non-history tabs. The history-tab intercept in `tui.zig`
|
||||
// routes these keys directly to `handleCompareKey` before
|
||||
// `matchAction` is consulted.
|
||||
.{ .action = .compare_select, .key = .{ .codepoint = 's' } },
|
||||
.{ .action = .compare_select, .key = .{ .codepoint = vaxis.Key.space } },
|
||||
.{ .action = .compare_commit, .key = .{ .codepoint = 'c' } },
|
||||
.{ .action = .compare_cancel, .key = .{ .codepoint = vaxis.Key.escape } },
|
||||
.{ .action = .sort_col_next, .key = .{ .codepoint = '>' } },
|
||||
.{ .action = .sort_col_prev, .key = .{ .codepoint = '<' } },
|
||||
.{ .action = .sort_reverse, .key = .{ .codepoint = 'o' } },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue