add function for tab local key dispatch and ctrl+f/ctrl+b keybinds
This commit is contained in:
parent
bb0bb64da1
commit
126ad53fd5
2 changed files with 54 additions and 4 deletions
50
src/tui.zig
50
src/tui.zig
|
|
@ -325,6 +325,15 @@ comptime {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(scoped-keybinds): comptime validator that tabs don't bind
|
||||
// keys already bound in the global keymap. Skipped in commit 1
|
||||
// because tabs (portfolio/options/history) currently declare Enter
|
||||
// as a tab-local binding for `expand_collapse` while Enter remains
|
||||
// globally bound. The validator gets added at the END of the
|
||||
// migration (after commit 8 removes the global Enter binding) so
|
||||
// it doesn't block intermediate commits. Runtime check on user
|
||||
// keys.srf still rejects conflicts at TUI startup.
|
||||
|
||||
/// Per-symbol fetched data. Owned by `App` and accessed as
|
||||
/// `app.symbol_data.*`. Populated by whichever tab fetches first
|
||||
/// (typically the perf or quote tab); consumed by every tab that
|
||||
|
|
@ -799,6 +808,37 @@ pub const App = struct {
|
|||
return false;
|
||||
}
|
||||
|
||||
/// Tab-local keybind dispatch. Walks the active tab's
|
||||
/// `default_bindings` looking for a key match. On match,
|
||||
/// invokes `tab.handleAction(state, app, action)` and returns
|
||||
/// `true`. Returns `false` if no binding matched.
|
||||
///
|
||||
/// Called as a fallback AFTER the global keymap; under the
|
||||
/// "globals always win" rule, tabs are forbidden (by validator
|
||||
/// at comptime, by user-config check at runtime) from binding
|
||||
/// keys that are already global, so a key reaching here is by
|
||||
/// definition not a global keybind.
|
||||
///
|
||||
/// Adding a tab-local action: declare it in the tab's `Action`
|
||||
/// enum, bind it in `default_bindings`, and `handleAction` runs
|
||||
/// it. No edit to `tui.zig` required.
|
||||
fn dispatchTabLocalKey(self: *App, key: vaxis.Key) bool {
|
||||
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.default_bindings) |binding| {
|
||||
if (key.matches(binding.key.codepoint, binding.key.mods)) {
|
||||
const state_ptr = &@field(self.states, field.name);
|
||||
Module.tab.handleAction(state_ptr, self, binding.action);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Outcome of a single keypress in an input-mode buffer (symbol
|
||||
/// input, date input, etc.). Returned by `handleInputBuffer` so
|
||||
/// the per-mode caller only needs to wire up the `committed`
|
||||
|
|
@ -1207,7 +1247,15 @@ pub const App = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
const action = self.keymap.matchAction(key) orelse return;
|
||||
const action = self.keymap.matchAction(key) orelse {
|
||||
// No global binding matched. Fall back to tab-local
|
||||
// dispatch — the active tab may bind this key in its
|
||||
// `default_bindings`. Globals win (no overlap allowed,
|
||||
// enforced by validator + user-config check), so
|
||||
// reaching here means the key is purely tab-local.
|
||||
if (self.dispatchTabLocalKey(key)) return ctx.consumeAndRedraw();
|
||||
return;
|
||||
};
|
||||
switch (action) {
|
||||
.quit => {
|
||||
ctx.quit = true;
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ pub const KeyMap = struct {
|
|||
|
||||
// ── Defaults ─────────────────────────────────────────────────
|
||||
|
||||
const default_bindings = [_]Binding{
|
||||
pub const global_default_bindings = [_]Binding{
|
||||
.{ .action = .quit, .key = .{ .codepoint = 'q' } },
|
||||
.{ .action = .quit, .key = .{ .codepoint = 'c', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .refresh, .key = .{ .codepoint = 'r' } },
|
||||
|
|
@ -125,7 +125,9 @@ const default_bindings = [_]Binding{
|
|||
.{ .action = .scroll_top, .key = .{ .codepoint = 'g' } },
|
||||
.{ .action = .scroll_bottom, .key = .{ .codepoint = 'G' } },
|
||||
.{ .action = .page_down, .key = .{ .codepoint = vaxis.Key.page_down } },
|
||||
.{ .action = .page_down, .key = .{ .codepoint = 'f', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .page_up, .key = .{ .codepoint = vaxis.Key.page_up } },
|
||||
.{ .action = .page_up, .key = .{ .codepoint = 'b', .mods = .{ .ctrl = true } } },
|
||||
.{ .action = .select_next, .key = .{ .codepoint = 'j' } },
|
||||
.{ .action = .select_next, .key = .{ .codepoint = vaxis.Key.down } },
|
||||
.{ .action = .select_prev, .key = .{ .codepoint = 'k' } },
|
||||
|
|
@ -173,7 +175,7 @@ const default_bindings = [_]Binding{
|
|||
};
|
||||
|
||||
pub fn defaults() KeyMap {
|
||||
return .{ .bindings = &default_bindings };
|
||||
return .{ .bindings = &global_default_bindings };
|
||||
}
|
||||
|
||||
// ── SRF serialization ────────────────────────────────────────
|
||||
|
|
@ -323,7 +325,7 @@ pub fn printDefaults(io: std.Io) !void {
|
|||
try out.writeAll("# F1-F12, insert, delete\n");
|
||||
try out.writeAll("# Multiple lines with the same action = multiple bindings.\n");
|
||||
|
||||
for (default_bindings) |b| {
|
||||
for (global_default_bindings) |b| {
|
||||
var key_buf: [32]u8 = undefined;
|
||||
const key_str = formatKeyCombo(b.key, &key_buf) orelse continue;
|
||||
try out.print("action::{s},key::{s}\n", .{ @tagName(b.action), key_str });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue