derive tab states from tab modules/remove file level imports
This commit is contained in:
parent
53f80a28db
commit
2cc1c4d05e
1 changed files with 113 additions and 139 deletions
252
src/tui.zig
252
src/tui.zig
|
|
@ -14,14 +14,24 @@ comptime {
|
|||
}
|
||||
const theme = @import("tui/theme.zig");
|
||||
const chart = @import("tui/chart.zig");
|
||||
const portfolio_tab = @import("tui/portfolio_tab.zig");
|
||||
const quote_tab = @import("tui/quote_tab.zig");
|
||||
const performance_tab = @import("tui/performance_tab.zig");
|
||||
const options_tab = @import("tui/options_tab.zig");
|
||||
const earnings_tab = @import("tui/earnings_tab.zig");
|
||||
const analysis_tab = @import("tui/analysis_tab.zig");
|
||||
const history_tab = @import("tui/history_tab.zig");
|
||||
const projections_tab = @import("tui/projections_tab.zig");
|
||||
|
||||
/// Single source of truth for tab modules. Each entry is the
|
||||
/// imported tab module; the field name is the tab's tag (must match
|
||||
/// the `Tab` enum variant). `TabStates` is derived from this
|
||||
/// registry at comptime — adding a new tab is a single edit here
|
||||
/// (plus the matching `Tab` enum variant + label, until those are
|
||||
/// derived too).
|
||||
const tab_modules = .{
|
||||
.portfolio = @import("tui/portfolio_tab.zig"),
|
||||
.quote = @import("tui/quote_tab.zig"),
|
||||
.performance = @import("tui/performance_tab.zig"),
|
||||
.options = @import("tui/options_tab.zig"),
|
||||
.earnings = @import("tui/earnings_tab.zig"),
|
||||
.analysis = @import("tui/analysis_tab.zig"),
|
||||
.history = @import("tui/history_tab.zig"),
|
||||
.projections = @import("tui/projections_tab.zig"),
|
||||
};
|
||||
|
||||
/// Comptime-generated table of single-character grapheme slices with static lifetime.
|
||||
/// This avoids dangling pointers from stack-allocated temporaries in draw functions.
|
||||
const ascii_g = blk: {
|
||||
|
|
@ -272,63 +282,27 @@ pub const ChartState = struct {
|
|||
|
||||
/// Per-tab state, owned by `App` and accessed as `app.states.<tab>`.
|
||||
///
|
||||
/// Each tab module defines its own `State` struct (see
|
||||
/// `src/tui/tab_framework.zig` for the contract). This aggregator
|
||||
/// holds one of each — adding a tab means adding one field here and
|
||||
/// one entry in the `tab_modules` registry below.
|
||||
/// Per-tab private state aggregator, derived at comptime from the
|
||||
/// `tab_modules` registry. One field per registered tab; the field
|
||||
/// name matches the registry tag (and the `Tab` enum variant), and
|
||||
/// the type is that tab module's `State`.
|
||||
///
|
||||
/// Migration in progress: tabs are being moved off App's flat field
|
||||
/// pile into per-tab State structs one tab at a time. Tabs not yet
|
||||
/// migrated still have their fields directly on `App`. See
|
||||
/// `TODO.md`'s "Refactor: TUI tab framework" entry.
|
||||
pub const TabStates = struct {
|
||||
earnings: earnings_tab.State = .{},
|
||||
analysis: analysis_tab.State = .{},
|
||||
quote: quote_tab.State = .{},
|
||||
performance: performance_tab.State = .{},
|
||||
options: options_tab.State = .{},
|
||||
history: history_tab.State = .{},
|
||||
projections: projections_tab.State = .{},
|
||||
portfolio: portfolio_tab.State = .{},
|
||||
};
|
||||
|
||||
/// Comptime registry of all tab modules conforming to the
|
||||
/// framework contract. Each entry pairs a registry name with the
|
||||
/// imported tab module. The validator below walks this list at
|
||||
/// comptime and asserts every entry conforms to the contract;
|
||||
/// missing or wrong-shape decls produce build errors with the
|
||||
/// full expected signature.
|
||||
///
|
||||
/// **This is the only place the validator is invoked.** Tab
|
||||
/// modules don't have to opt in individually — adding a tab here
|
||||
/// is the same act as registering it for validation. If you forget
|
||||
/// to add a tab here, you'd also forget the dispatcher wiring (the
|
||||
/// step-3 walker will iterate this same list), so a missed entry
|
||||
/// is loud at integration time.
|
||||
///
|
||||
/// As tabs migrate to the framework, add them here. Pre-migration
|
||||
/// tabs (portfolio, history, projections) live outside the
|
||||
/// registry and are dispatched ad-hoc until they migrate.
|
||||
///
|
||||
/// **TODO: this explicit registry is transitional.** Once all tabs
|
||||
/// have migrated AND the dispatcher walker (step 3) lands, the
|
||||
/// goal is for adding a tab to `tui.zig` (the `_tab` import + the
|
||||
/// `TabStates` field + the `Tab` enum variant) to *automatically*
|
||||
/// validate it — no separate `tab_modules` literal to keep in
|
||||
/// sync. The likely shape: a comptime walk over `Tab` enum
|
||||
/// variants that resolves each to its `_tab` module via a name
|
||||
/// convention, eliminating the parallel registry. The current
|
||||
/// list-based form is a stopgap while the framework is being
|
||||
/// proven out tab-by-tab.
|
||||
const tab_modules = .{
|
||||
.earnings = earnings_tab,
|
||||
.analysis = analysis_tab,
|
||||
.quote = quote_tab,
|
||||
.performance = performance_tab,
|
||||
.options = options_tab,
|
||||
.history = history_tab,
|
||||
.projections = projections_tab,
|
||||
.portfolio = portfolio_tab,
|
||||
/// Adding a tab is one edit: append it to `tab_modules` (and add the
|
||||
/// matching `Tab` enum variant + `label`). `TabStates` updates
|
||||
/// automatically.
|
||||
pub const TabStates = blk: {
|
||||
const reg_fields = std.meta.fields(@TypeOf(tab_modules));
|
||||
var names: [reg_fields.len][]const u8 = undefined;
|
||||
var types: [reg_fields.len]type = undefined;
|
||||
var attrs: [reg_fields.len]std.builtin.Type.StructField.Attributes = undefined;
|
||||
for (reg_fields, 0..) |f, i| {
|
||||
const Module = @field(tab_modules, f.name);
|
||||
const default: Module.State = .{};
|
||||
names[i] = f.name;
|
||||
types[i] = Module.State;
|
||||
attrs[i] = .{ .default_value_ptr = &default };
|
||||
}
|
||||
break :blk @Struct(.auto, null, &names, &types, &attrs);
|
||||
};
|
||||
|
||||
comptime {
|
||||
|
|
@ -466,7 +440,7 @@ pub const PortfolioData = struct {
|
|||
/// and portfolio (per-account display).
|
||||
///
|
||||
/// **Cross-tab mutation note.** Analysis-tab refresh
|
||||
/// (`analysis_tab.tab.reload`) clears this field so the next
|
||||
/// (`tab_modules.analysis.tab.reload`) clears this field so the next
|
||||
/// load re-reads `accounts.srf` from disk (the user may have
|
||||
/// edited it). Portfolio-tab consumers re-read this field on
|
||||
/// every render, so the clear-and-reload doesn't require a
|
||||
|
|
@ -483,7 +457,7 @@ pub const PortfolioData = struct {
|
|||
/// (max of each symbol's last cached candle date). Drives the
|
||||
/// "as of close on YYYY-MM-DD" line under the portfolio totals.
|
||||
/// Computed as a side effect of the portfolio-prices loop in
|
||||
/// `portfolio_tab.loadPortfolioData`; null when no symbols have
|
||||
/// `tab_modules.portfolio.loadPortfolioData`; null when no symbols have
|
||||
/// cached candles.
|
||||
latest_quote_date: ?zfin.Date = null,
|
||||
|
||||
|
|
@ -521,7 +495,7 @@ pub const App = struct {
|
|||
/// Per-portfolio shared data (loaded portfolio file, summary,
|
||||
/// account map, watchlist prices, historical snapshots). See
|
||||
/// `PortfolioData` above. Reloaded by
|
||||
/// `portfolio_tab.reloadPortfolioFile` on file changes.
|
||||
/// `tab_modules.portfolio.reloadPortfolioFile` on file changes.
|
||||
portfolio: PortfolioData = .{},
|
||||
/// Captured at App init and refreshed at tab change. Using a cached
|
||||
/// date (rather than calling the clock on every render) keeps render
|
||||
|
|
@ -642,8 +616,8 @@ pub const App = struct {
|
|||
// mouse.row maps directly to content line index
|
||||
// (same convention as portfolio click handling).
|
||||
const content_row = @as(usize, @intCast(mouse.row));
|
||||
if (content_row >= portfolio_tab.account_picker_header_lines) {
|
||||
const item_idx = content_row - portfolio_tab.account_picker_header_lines;
|
||||
if (content_row >= tab_modules.portfolio.account_picker_header_lines) {
|
||||
const item_idx = content_row - tab_modules.portfolio.account_picker_header_lines;
|
||||
if (item_idx < total_items) {
|
||||
self.account_picker_cursor = item_idx;
|
||||
self.applyAccountPickerSelection();
|
||||
|
|
@ -950,7 +924,7 @@ pub const App = struct {
|
|||
self.setStatus("As-of cleared — showing live");
|
||||
}
|
||||
|
||||
projections_tab.tab.reload(&self.states.projections, self) catch {};
|
||||
tab_modules.projections.tab.reload(&self.states.projections, self) catch {};
|
||||
|
||||
self.mode = .normal;
|
||||
self.input_len = 0;
|
||||
|
|
@ -1146,7 +1120,7 @@ pub const App = struct {
|
|||
self.mode = .normal;
|
||||
self.states.portfolio.cursor = 0;
|
||||
self.scroll_offset = 0;
|
||||
portfolio_tab.rebuildPortfolioRows(&self.states.portfolio, self);
|
||||
tab_modules.portfolio.rebuildPortfolioRows(&self.states.portfolio, self);
|
||||
|
||||
if (self.states.portfolio.account_filter) |af| {
|
||||
var tmp_buf: [256]u8 = undefined;
|
||||
|
|
@ -1195,7 +1169,7 @@ pub const App = struct {
|
|||
// 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 (history_tab.handleCompareKey(&self.states.history, self, ctx, key)) return;
|
||||
if (tab_modules.history.handleCompareKey(&self.states.history, self, ctx, key)) return;
|
||||
}
|
||||
|
||||
// Escape: clear account filter on portfolio tab, clear as-of
|
||||
|
|
@ -1205,7 +1179,7 @@ pub const App = struct {
|
|||
self.setAccountFilter(null);
|
||||
self.states.portfolio.cursor = 0;
|
||||
self.scroll_offset = 0;
|
||||
portfolio_tab.rebuildPortfolioRows(&self.states.portfolio, self);
|
||||
tab_modules.portfolio.rebuildPortfolioRows(&self.states.portfolio, self);
|
||||
self.setStatus("Filter cleared: showing all accounts");
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
|
|
@ -1213,7 +1187,7 @@ pub const App = struct {
|
|||
self.states.projections.as_of = null;
|
||||
self.states.projections.as_of_requested = null;
|
||||
self.states.projections.overlay_actuals = false;
|
||||
projections_tab.tab.reload(&self.states.projections, self) catch {};
|
||||
tab_modules.projections.tab.reload(&self.states.projections, self) catch {};
|
||||
self.setStatus("As-of cleared — showing live");
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
|
|
@ -1233,7 +1207,7 @@ pub const App = struct {
|
|||
.select_symbol => {
|
||||
// 's' selects the current portfolio row's symbol as the active symbol
|
||||
if (self.active_tab == .portfolio) {
|
||||
portfolio_tab.tab.handleAction(&self.states.portfolio, self, portfolio_tab.Action.select_symbol);
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.select_symbol);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
|
|
@ -1277,13 +1251,13 @@ pub const App = struct {
|
|||
},
|
||||
.expand_collapse => {
|
||||
if (self.active_tab == .portfolio) {
|
||||
portfolio_tab.tab.handleAction(&self.states.portfolio, self, portfolio_tab.Action.expand_collapse);
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.expand_collapse);
|
||||
return ctx.consumeAndRedraw();
|
||||
} else if (self.active_tab == .options) {
|
||||
options_tab.tab.handleAction(&self.states.options, self, options_tab.Action.expand_collapse);
|
||||
tab_modules.options.tab.handleAction(&self.states.options, self, tab_modules.options.Action.expand_collapse);
|
||||
return ctx.consumeAndRedraw();
|
||||
} else if (self.active_tab == .history) {
|
||||
history_tab.tab.handleAction(&self.states.history, self, history_tab.Action.expand_collapse);
|
||||
tab_modules.history.tab.handleAction(&self.states.history, self, tab_modules.history.Action.expand_collapse);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
|
|
@ -1329,13 +1303,13 @@ pub const App = struct {
|
|||
},
|
||||
.collapse_all_calls => {
|
||||
if (self.active_tab == .options) {
|
||||
options_tab.tab.handleAction(&self.states.options, self, options_tab.Action.collapse_all_calls);
|
||||
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) {
|
||||
options_tab.tab.handleAction(&self.states.options, self, options_tab.Action.collapse_all_puts);
|
||||
tab_modules.options.tab.handleAction(&self.states.options, self, tab_modules.options.Action.collapse_all_puts);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
|
|
@ -1344,45 +1318,45 @@ pub const App = struct {
|
|||
const n = @intFromEnum(action) - @intFromEnum(keybinds.Action.options_filter_1) + 1;
|
||||
// Route through tab.handleAction so the dispatch
|
||||
// table is consistent with the framework. Each
|
||||
// filter_N maps directly to options_tab.Action.filter_N.
|
||||
const tab_action: options_tab.Action = @enumFromInt(@intFromEnum(options_tab.Action.filter_1) + n - 1);
|
||||
options_tab.tab.handleAction(&self.states.options, self, tab_action);
|
||||
// filter_N maps directly to tab_modules.options.Action.filter_N.
|
||||
const tab_action: tab_modules.options.Action = @enumFromInt(@intFromEnum(tab_modules.options.Action.filter_1) + n - 1);
|
||||
tab_modules.options.tab.handleAction(&self.states.options, self, tab_action);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.chart_timeframe_next => {
|
||||
if (self.active_tab == .quote) {
|
||||
quote_tab.tab.handleAction(&self.states.quote, self, quote_tab.Action.chart_timeframe_next);
|
||||
tab_modules.quote.tab.handleAction(&self.states.quote, self, tab_modules.quote.Action.chart_timeframe_next);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.chart_timeframe_prev => {
|
||||
if (self.active_tab == .quote) {
|
||||
quote_tab.tab.handleAction(&self.states.quote, self, quote_tab.Action.chart_timeframe_prev);
|
||||
tab_modules.quote.tab.handleAction(&self.states.quote, self, tab_modules.quote.Action.chart_timeframe_prev);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.history_metric_next => {
|
||||
if (self.active_tab == .history) {
|
||||
history_tab.tab.handleAction(&self.states.history, self, history_tab.Action.metric_next);
|
||||
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) {
|
||||
history_tab.tab.handleAction(&self.states.history, self, history_tab.Action.resolution_next);
|
||||
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) {
|
||||
portfolio_tab.tab.handleAction(&self.states.portfolio, self, portfolio_tab.Action.sort_col_next);
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.sort_col_next);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.sort_col_prev => {
|
||||
if (self.active_tab == .portfolio) {
|
||||
portfolio_tab.tab.handleAction(&self.states.portfolio, self, portfolio_tab.Action.sort_col_prev);
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.sort_col_prev);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
|
|
@ -1397,29 +1371,29 @@ pub const App = struct {
|
|||
// select_symbol). The action name stays `sort_reverse`
|
||||
// because portfolio was the first consumer.
|
||||
if (self.active_tab == .portfolio) {
|
||||
portfolio_tab.tab.handleAction(&self.states.portfolio, self, portfolio_tab.Action.sort_reverse);
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.sort_reverse);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
if (self.active_tab == .projections) {
|
||||
projections_tab.tab.handleAction(&self.states.projections, self, projections_tab.Action.overlay_actuals);
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.overlay_actuals);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.account_filter => {
|
||||
if (self.active_tab == .portfolio) {
|
||||
portfolio_tab.tab.handleAction(&self.states.portfolio, self, portfolio_tab.Action.open_account_picker);
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.open_account_picker);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.toggle_chart => {
|
||||
if (self.active_tab == .projections) {
|
||||
projections_tab.tab.handleAction(&self.states.projections, self, projections_tab.Action.toggle_chart);
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.toggle_chart);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.toggle_events => {
|
||||
if (self.active_tab == .projections) {
|
||||
projections_tab.tab.handleAction(&self.states.projections, self, projections_tab.Action.toggle_events);
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.toggle_events);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
|
|
@ -1428,7 +1402,7 @@ pub const App = struct {
|
|||
// let the same key flow to their own handlers (none
|
||||
// currently bind plain 'd').
|
||||
if (self.active_tab == .projections) {
|
||||
projections_tab.tab.handleAction(&self.states.projections, self, projections_tab.Action.as_of_input);
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.as_of_input);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
|
|
@ -1442,7 +1416,7 @@ pub const App = struct {
|
|||
if (self.active_tab == .history) {
|
||||
const hs = &self.states.history;
|
||||
if (hs.compare_view == null and hs.table_row_count > 0) {
|
||||
history_tab.toggleSelectionAt(hs, self, hs.cursor);
|
||||
tab_modules.history.toggleSelectionAt(hs, self, hs.cursor);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
}
|
||||
|
|
@ -1451,16 +1425,16 @@ pub const App = struct {
|
|||
if (self.active_tab == .history) {
|
||||
const hs = &self.states.history;
|
||||
if (hs.compare_view != null) {
|
||||
history_tab.clearCompareState(hs, self);
|
||||
tab_modules.history.clearCompareState(hs, self);
|
||||
} else {
|
||||
history_tab.commitCompareExternal(hs, self);
|
||||
tab_modules.history.commitCompareExternal(hs, self);
|
||||
}
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.compare_cancel => {
|
||||
if (self.active_tab == .history) {
|
||||
history_tab.clearCompareState(&self.states.history, self);
|
||||
tab_modules.history.clearCompareState(&self.states.history, self);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
|
|
@ -1558,19 +1532,19 @@ pub const App = struct {
|
|||
self.states.quote.chart.freeCache(self.allocator); // Invalidate indicator cache
|
||||
},
|
||||
.earnings => {
|
||||
earnings_tab.tab.reload(&self.states.earnings, self) catch {};
|
||||
tab_modules.earnings.tab.reload(&self.states.earnings, self) catch {};
|
||||
},
|
||||
.options => {
|
||||
options_tab.tab.reload(&self.states.options, self) catch {};
|
||||
tab_modules.options.tab.reload(&self.states.options, self) catch {};
|
||||
},
|
||||
.analysis => {
|
||||
analysis_tab.tab.reload(&self.states.analysis, self) catch {};
|
||||
tab_modules.analysis.tab.reload(&self.states.analysis, self) catch {};
|
||||
},
|
||||
.history => {
|
||||
history_tab.tab.reload(&self.states.history, self) catch {};
|
||||
tab_modules.history.tab.reload(&self.states.history, self) catch {};
|
||||
},
|
||||
.projections => {
|
||||
projections_tab.tab.reload(&self.states.projections, self) catch {};
|
||||
tab_modules.projections.tab.reload(&self.states.projections, self) catch {};
|
||||
},
|
||||
}
|
||||
self.loadTabData();
|
||||
|
|
@ -1595,34 +1569,34 @@ pub const App = struct {
|
|||
pub fn loadTabData(self: *App) void {
|
||||
switch (self.active_tab) {
|
||||
.portfolio => {
|
||||
portfolio_tab.tab.activate(&self.states.portfolio, self) catch {};
|
||||
tab_modules.portfolio.tab.activate(&self.states.portfolio, self) catch {};
|
||||
},
|
||||
.quote, .performance => {
|
||||
if (self.symbol.len == 0) return;
|
||||
performance_tab.tab.activate(&self.states.performance, self) catch {};
|
||||
tab_modules.performance.tab.activate(&self.states.performance, self) catch {};
|
||||
},
|
||||
.earnings => {
|
||||
if (self.symbol.len == 0) return;
|
||||
earnings_tab.tab.activate(&self.states.earnings, self) catch {};
|
||||
tab_modules.earnings.tab.activate(&self.states.earnings, self) catch {};
|
||||
},
|
||||
.options => {
|
||||
if (self.symbol.len == 0) return;
|
||||
options_tab.tab.activate(&self.states.options, self) catch {};
|
||||
tab_modules.options.tab.activate(&self.states.options, self) catch {};
|
||||
},
|
||||
.analysis => {
|
||||
analysis_tab.tab.activate(&self.states.analysis, self) catch {};
|
||||
tab_modules.analysis.tab.activate(&self.states.analysis, self) catch {};
|
||||
},
|
||||
.history => {
|
||||
history_tab.tab.activate(&self.states.history, self) catch {};
|
||||
tab_modules.history.tab.activate(&self.states.history, self) catch {};
|
||||
},
|
||||
.projections => {
|
||||
projections_tab.tab.activate(&self.states.projections, self) catch {};
|
||||
tab_modules.projections.tab.activate(&self.states.projections, self) catch {};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loadPortfolioData(self: *App) void {
|
||||
portfolio_tab.loadPortfolioData(&self.states.portfolio, self);
|
||||
tab_modules.portfolio.loadPortfolioData(&self.states.portfolio, self);
|
||||
}
|
||||
|
||||
pub fn setStatus(self: *App, msg: []const u8) void {
|
||||
|
|
@ -1643,19 +1617,19 @@ pub const App = struct {
|
|||
|
||||
fn deinitData(self: *App) void {
|
||||
self.symbol_data.deinit(self.allocator);
|
||||
earnings_tab.tab.deinit(&self.states.earnings, self);
|
||||
options_tab.tab.deinit(&self.states.options, self);
|
||||
portfolio_tab.tab.deinit(&self.states.portfolio, self);
|
||||
tab_modules.earnings.tab.deinit(&self.states.earnings, self);
|
||||
tab_modules.options.tab.deinit(&self.states.options, self);
|
||||
tab_modules.portfolio.tab.deinit(&self.states.portfolio, self);
|
||||
self.account_search_matches.deinit(self.allocator);
|
||||
analysis_tab.tab.deinit(&self.states.analysis, self);
|
||||
tab_modules.analysis.tab.deinit(&self.states.analysis, self);
|
||||
self.portfolio.deinit(self.allocator);
|
||||
history_tab.tab.deinit(&self.states.history, self);
|
||||
projections_tab.tab.deinit(&self.states.projections, self);
|
||||
quote_tab.tab.deinit(&self.states.quote, self);
|
||||
tab_modules.history.tab.deinit(&self.states.history, self);
|
||||
tab_modules.projections.tab.deinit(&self.states.projections, self);
|
||||
tab_modules.quote.tab.deinit(&self.states.quote, self);
|
||||
}
|
||||
|
||||
fn reloadPortfolioFile(self: *App) void {
|
||||
portfolio_tab.reloadPortfolioFile(&self.states.portfolio, self);
|
||||
tab_modules.portfolio.reloadPortfolioFile(&self.states.portfolio, self);
|
||||
}
|
||||
|
||||
// ── Drawing ──────────────────────────────────────────────────
|
||||
|
|
@ -1753,7 +1727,7 @@ pub const App = struct {
|
|||
if (self.mode == .help) {
|
||||
try self.drawStyledContent(ctx.arena, buf, width, height, try self.buildHelpStyledLines(ctx.arena));
|
||||
} else if (self.mode == .account_picker or self.mode == .account_search) {
|
||||
try portfolio_tab.drawAccountPicker(&self.states.portfolio, self, ctx.arena, buf, width, height);
|
||||
try tab_modules.portfolio.drawAccountPicker(&self.states.portfolio, self, ctx.arena, buf, width, height);
|
||||
} else {
|
||||
switch (self.active_tab) {
|
||||
.portfolio => try self.drawPortfolioContent(ctx.arena, buf, width, height),
|
||||
|
|
@ -1779,7 +1753,7 @@ pub const App = struct {
|
|||
const start = @min(self.scroll_offset, if (lines.len > 0) lines.len - 1 else 0);
|
||||
try self.drawStyledContent(ctx.arena, buf, width, height, lines[start..]);
|
||||
},
|
||||
.projections => try projections_tab.drawContent(&self.states.projections, self, ctx, buf, width, height),
|
||||
.projections => try tab_modules.projections.drawContent(&self.states.projections, self, ctx, buf, width, height),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1898,13 +1872,13 @@ pub const App = struct {
|
|||
// ── Portfolio content ─────────────────────────────────────────
|
||||
|
||||
fn drawPortfolioContent(self: *App, arena: std.mem.Allocator, buf: []vaxis.Cell, width: u16, height: u16) !void {
|
||||
return portfolio_tab.drawContent(&self.states.portfolio, self, arena, buf, width, height);
|
||||
return tab_modules.portfolio.drawContent(&self.states.portfolio, self, arena, buf, width, height);
|
||||
}
|
||||
|
||||
// ── Options content (with cursor/scroll) ─────────────────────
|
||||
|
||||
fn drawOptionsContent(self: *App, arena: std.mem.Allocator, buf: []vaxis.Cell, width: u16, height: u16) !void {
|
||||
const styled_lines = try options_tab.buildStyledLines(self, arena);
|
||||
const styled_lines = try tab_modules.options.buildStyledLines(self, arena);
|
||||
const start = @min(self.scroll_offset, if (styled_lines.len > 0) styled_lines.len - 1 else 0);
|
||||
try self.drawStyledContent(arena, buf, width, height, styled_lines[start..]);
|
||||
}
|
||||
|
|
@ -1912,29 +1886,29 @@ pub const App = struct {
|
|||
// ── Quote tab ────────────────────────────────────────────────
|
||||
|
||||
fn drawQuoteContent(self: *App, ctx: vaxis.vxfw.DrawContext, buf: []vaxis.Cell, width: u16, height: u16) !void {
|
||||
return quote_tab.drawContent(self, ctx, buf, width, height);
|
||||
return tab_modules.quote.drawContent(self, ctx, buf, width, height);
|
||||
}
|
||||
|
||||
// ── Performance tab ──────────────────────────────────────────
|
||||
|
||||
fn buildPerfStyledLines(self: *App, arena: std.mem.Allocator) ![]const StyledLine {
|
||||
return performance_tab.buildStyledLines(self, arena);
|
||||
return tab_modules.performance.buildStyledLines(self, arena);
|
||||
}
|
||||
|
||||
// ── Earnings tab ─────────────────────────────────────────────
|
||||
|
||||
fn buildEarningsStyledLines(self: *App, arena: std.mem.Allocator) ![]const StyledLine {
|
||||
return earnings_tab.buildStyledLines(self, arena);
|
||||
return tab_modules.earnings.buildStyledLines(self, arena);
|
||||
}
|
||||
|
||||
// ── Analysis tab ────────────────────────────────────────────
|
||||
|
||||
fn buildAnalysisStyledLines(self: *App, arena: std.mem.Allocator) ![]const StyledLine {
|
||||
return analysis_tab.buildStyledLines(self, arena);
|
||||
return tab_modules.analysis.buildStyledLines(self, arena);
|
||||
}
|
||||
|
||||
fn buildHistoryStyledLines(self: *App, arena: std.mem.Allocator) ![]const StyledLine {
|
||||
return history_tab.buildStyledLines(&self.states.history, self, arena);
|
||||
return tab_modules.history.buildStyledLines(&self.states.history, self, arena);
|
||||
}
|
||||
|
||||
// ── Help ─────────────────────────────────────────────────────
|
||||
|
|
@ -2159,14 +2133,14 @@ pub const freeWatchlist = cli.freeWatchlist;
|
|||
comptime {
|
||||
_ = keybinds;
|
||||
_ = theme;
|
||||
_ = portfolio_tab;
|
||||
_ = quote_tab;
|
||||
_ = performance_tab;
|
||||
_ = options_tab;
|
||||
_ = earnings_tab;
|
||||
_ = analysis_tab;
|
||||
_ = history_tab;
|
||||
_ = projections_tab;
|
||||
_ = tab_modules.portfolio;
|
||||
_ = tab_modules.quote;
|
||||
_ = tab_modules.performance;
|
||||
_ = tab_modules.options;
|
||||
_ = tab_modules.earnings;
|
||||
_ = tab_modules.analysis;
|
||||
_ = tab_modules.history;
|
||||
_ = tab_modules.projections;
|
||||
}
|
||||
|
||||
/// Entry point for the interactive TUI.
|
||||
|
|
@ -2270,7 +2244,7 @@ pub fn run(
|
|||
// History tab requires explicit init (allocator-backed hash map);
|
||||
// other tabs use field defaults. The corresponding deinit lives
|
||||
// in `App.deinitData`.
|
||||
try history_tab.tab.init(&app_inst.states.history, app_inst);
|
||||
try tab_modules.history.tab.init(&app_inst.states.history, app_inst);
|
||||
|
||||
if (portfolio_path) |path| {
|
||||
const file_data = std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(10 * 1024 * 1024)) catch null;
|
||||
|
|
@ -2353,7 +2327,7 @@ pub fn run(
|
|||
// first. Cheap (pure compute + cache reads) once prices are
|
||||
// already in hand.
|
||||
if (app_inst.portfolio.file != null) {
|
||||
portfolio_tab.loadPortfolioData(&app_inst.states.portfolio, app_inst);
|
||||
tab_modules.portfolio.loadPortfolioData(&app_inst.states.portfolio, app_inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue