Skip to content

Latest commit

 

History

History
65 lines (49 loc) · 4.79 KB

File metadata and controls

65 lines (49 loc) · 4.79 KB

Keymap System

This document describes how keyboard input is mapped to commands in TaskWarrior GPUI.

Components

  • src/keymap/command.rs: Command enum lists every action the UI can handle.
  • src/keymap/context.rs: ContextId defines the active key contexts (Global, Table, TableHeaders, SidebarProjects, SidebarTags, Modal, FilterBar, TextInput).
  • src/keymap/chord.rs: Key, Mods, KeyChord normalize keys. KeyChord::from_gpui builds chords from gpui::KeyDownEvent, KeyChord::parse parses strings like Ctrl+F, and Display formats chords as strings.
  • src/keymap/keymap.rs: KeymapLayer stores ContextId -> (KeyChord -> Command) bindings. KeymapStack resolves by checking the top-most layer first and falling back to Global if nothing matches.
  • src/keymap/defaults.rs: build_default_keymap defines all default bindings and is the only layer pushed today.
  • src/keymap/active_context.rs: FocusTarget maps UI focus to ContextId.
  • src/keymap/dispatcher.rs: CommandDispatcher trait abstracts command handling.

Runtime flow

  1. The root UI in src/view/app_layout.rs registers on_key_down on the app container.
  2. App::handle_key_down in src/app.rs receives the event and:
    • Converts it to KeyChord via KeyChord::from_gpui.
    • Computes the active ContextId via App::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 TextInput or FilterBar based on TaskTable::get_active_filter_context.
      • Otherwise it uses FocusTarget::to_context.
    • Resolves the command with KeymapStack::resolve, which checks the latest layer first and falls back to Global.
  3. If the modal is open, App::handle_key_down only allows CloseModal, SaveModal, Sync, and modal scroll commands; all other commands are ignored.
  4. Some commands are handled inline in App::handle_key_down (focus transitions around the table, filter bar, and search input).
  5. Everything else is routed through CommandDispatcher:
    • App implements it in src/dispatcher.rs and forwards commands to TaskTable or Sidebar based on focus.
    • TaskTable and Sidebar implement CommandDispatcher to 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 (in src/keymap/active_context.rs) represents which major area owns focus (table, headers, sidebars).
  • App::active_context (in src/app.rs) converts that focus into a ContextId and overrides it when:
    • The modal is open (ContextId::Modal).
    • The filter bar is active (ContextId::TextInput or ContextId::FilterBar based on TaskTable::get_active_filter_context).
  • KeymapStack::resolve uses that ContextId to find a command, so the same key (like j) 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_down is 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_down or helper methods on App (open_task_detail, open_selected_task).
  • All other commands go through App's CommandDispatcher implementation in src/dispatcher.rs, which decides whether to route the command to TaskTable, Sidebar, or perform app-level actions (sync, close modal, scroll modal, filter state changes).

Event flow (short)

  1. UI emits KeyDownEvent on the root container.
  2. App::handle_key_down converts it to KeyChord, resolves ContextId, and maps to a Command.
  3. App either:
    • Handles the command directly (focus changes, modal open/close), or
    • Dispatches it to TaskTable/Sidebar, which update their local state and call cx.notify().

Implementing a new keymap entry

  1. Add a new Command variant in src/keymap/command.rs.
  2. Bind a key in src/keymap/defaults.rs (or create a new KeymapLayer and push it on KeymapStack in App::run).
  3. Handle the command in App::handle_key_down (for focus/app-level behavior) or in src/dispatcher.rs and the appropriate component dispatcher (TaskTable or Sidebar).
  4. Update docs/keyboard-shortcuts.md to 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 on Ctrl+H/Ctrl+L so focus-navigation shortcuts still reach the app-level keymap.
  • src/keymap/mod.rs still contains legacy KeyBinding/TableAction/GlobalAction types for compatibility; they are not used in the current flow.