Add a new keymap doc that explains contexts, focus scope, command routing, event flow, and how to add bindings. Update keyboard shortcuts to match current modal and search input bindings
4.8 KiB
4.8 KiB
Keymap System
This document describes how keyboard input is mapped to commands in TaskWarrior GPUI.
Components
src/keymap/command.rs:Commandenum lists every action the UI can handle.src/keymap/context.rs:ContextIddefines the active key contexts (Global, Table, TableHeaders, SidebarProjects, SidebarTags, Modal, FilterBar, TextInput).src/keymap/chord.rs:Key,Mods,KeyChordnormalize keys.KeyChord::from_gpuibuilds chords fromgpui::KeyDownEvent,KeyChord::parseparses strings likeCtrl+F, andDisplayformats chords as strings.src/keymap/keymap.rs:KeymapLayerstoresContextId -> (KeyChord -> Command)bindings.KeymapStackresolves by checking the top-most layer first and falling back toGlobalif nothing matches.src/keymap/defaults.rs:build_default_keymapdefines all default bindings and is the only layer pushed today.src/keymap/active_context.rs:FocusTargetmaps UI focus toContextId.src/keymap/dispatcher.rs:CommandDispatchertrait abstracts command handling.
Runtime flow
- The root UI in
src/view/app_layout.rsregisterson_key_downon the app container. App::handle_key_downinsrc/app.rsreceives the event and:- Converts it to
KeyChordviaKeyChord::from_gpui. - Computes the active
ContextIdviaApp::active_context:- If the task detail modal is open, the context is
Modal. - If focus is on the table and the filter bar is active, the context becomes
TextInputorFilterBarbased onTaskTable::get_active_filter_context. - Otherwise it uses
FocusTarget::to_context.
- If the task detail modal is open, the context is
- Resolves the command with
KeymapStack::resolve, which checks the latest layer first and falls back toGlobal.
- Converts it to
- If the modal is open,
App::handle_key_downonly allowsCloseModal,SaveModal,Sync, and modal scroll commands; all other commands are ignored. - Some commands are handled inline in
App::handle_key_down(focus transitions around the table, filter bar, and search input). - Everything else is routed through
CommandDispatcher:Appimplements it insrc/dispatcher.rsand forwards commands toTaskTableorSidebarbased on focus.TaskTableandSidebarimplementCommandDispatcherto apply selection, filter, and navigation changes.
Focus, context, and scope
The same keymap can yield different commands depending on the active context. The context is computed from focus and UI state:
FocusTarget(insrc/keymap/active_context.rs) represents which major area owns focus (table, headers, sidebars).App::active_context(insrc/app.rs) converts that focus into aContextIdand overrides it when:- The modal is open (
ContextId::Modal). - The filter bar is active (
ContextId::TextInputorContextId::FilterBarbased onTaskTable::get_active_filter_context).
- The modal is open (
KeymapStack::resolveuses thatContextIdto find a command, so the same key (likej) can mean "select next row" in the table context or "scroll down" in the modal context.
How commands are interpreted by App
App::handle_key_downis the gatekeeper: it resolves the command, enforces modal-only commands when the modal is open, and handles focus-related commands itself.- Commands that mutate app-level state (focus switching, opening the modal) are handled directly in
App::handle_key_downor helper methods onApp(open_task_detail,open_selected_task). - All other commands go through
App'sCommandDispatcherimplementation insrc/dispatcher.rs, which decides whether to route the command toTaskTable,Sidebar, or perform app-level actions (sync, close modal, scroll modal, filter state changes).
Event flow (short)
- UI emits
KeyDownEventon the root container. App::handle_key_downconverts it toKeyChord, resolvesContextId, and maps to aCommand.Appeither:- Handles the command directly (focus changes, modal open/close), or
- Dispatches it to
TaskTable/Sidebar, which update their local state and callcx.notify().
Implementing a new keymap entry
- Add a new
Commandvariant insrc/keymap/command.rs. - Bind a key in
src/keymap/defaults.rs(or create a newKeymapLayerand push it onKeymapStackinApp::run). - Handle the command in
App::handle_key_down(for focus/app-level behavior) or insrc/dispatcher.rsand the appropriate component dispatcher (TaskTableorSidebar). - Update
docs/keyboard-shortcuts.mdto reflect the new binding.
Notes
- The search input (
src/components/input/mod.rs) handles text editing directly and updates filters on change; it early-returns onCtrl+H/Ctrl+Lso focus-navigation shortcuts still reach the app-level keymap. src/keymap/mod.rsstill contains legacyKeyBinding/TableAction/GlobalActiontypes for compatibility; they are not used in the current flow.