getDefaultBindingBlocks function

List<KeybindingBlock> getDefaultBindingBlocks()

Returns the default keybinding blocks in config format. These are loaded first, then user keybindings.json overrides them.

Implementation

List<KeybindingBlock> getDefaultBindingBlocks() => [
  // ── Global ──
  const KeybindingBlock(
    context: 'Global',
    bindings: {
      // ctrl+c and ctrl+d use special time-based double-press handling.
      // They ARE defined here so the resolver can find them, but they
      // CANNOT be rebound by users.
      'ctrl+c': 'app:interrupt',
      'ctrl+d': 'app:exit',
      'ctrl+l': 'app:redraw',
      'ctrl+t': 'app:toggleTodos',
      'ctrl+o': 'app:toggleTranscript',
      'ctrl+shift+o': 'app:toggleTeammatePreview',
      'ctrl+r': 'history:search',
      'ctrl+shift+f': 'app:globalSearch',
      'cmd+shift+f': 'app:globalSearch',
      'ctrl+shift+p': 'app:quickOpen',
      'cmd+shift+p': 'app:quickOpen',
    },
  ),

  // ── Chat ──
  KeybindingBlock(
    context: 'Chat',
    bindings: {
      'escape': 'chat:cancel',
      // ctrl+x chord prefix avoids shadowing readline editing keys.
      'ctrl+x ctrl+k': 'chat:killAgents',
      _modeCycleKey: 'chat:cycleMode',
      'meta+p': 'chat:modelPicker',
      'meta+o': 'chat:fastMode',
      'meta+t': 'chat:thinkingToggle',
      'enter': 'chat:submit',
      'up': 'history:previous',
      'down': 'history:next',
      // Undo has two bindings:
      // - ctrl+_ for legacy terminals (send \x1f control char)
      // - ctrl+shift+- for Kitty protocol
      'ctrl+_': 'chat:undo',
      'ctrl+shift+-': 'chat:undo',
      // External editor bindings
      'ctrl+x ctrl+e': 'chat:externalEditor',
      'ctrl+g': 'chat:externalEditor',
      'ctrl+s': 'chat:stash',
      // Image paste shortcut (platform-specific)
      _imagePasteKey: 'chat:imagePaste',
      'shift+up': 'chat:messageActions',
    },
  ),

  // ── Autocomplete ──
  const KeybindingBlock(
    context: 'Autocomplete',
    bindings: {
      'tab': 'autocomplete:accept',
      'escape': 'autocomplete:dismiss',
      'up': 'autocomplete:previous',
      'down': 'autocomplete:next',
    },
  ),

  // ── Settings ──
  const KeybindingBlock(
    context: 'Settings',
    bindings: {
      'escape': 'confirm:no',
      'up': 'select:previous',
      'down': 'select:next',
      'k': 'select:previous',
      'j': 'select:next',
      'ctrl+p': 'select:previous',
      'ctrl+n': 'select:next',
      'space': 'select:accept',
      'enter': 'settings:close',
      '/': 'settings:search',
      'r': 'settings:retry',
    },
  ),

  // ── Confirmation / Permission Dialog ──
  const KeybindingBlock(
    context: 'Confirmation',
    bindings: {
      'y': 'confirm:yes',
      'n': 'confirm:no',
      'enter': 'confirm:yes',
      'escape': 'confirm:no',
      'up': 'confirm:previous',
      'down': 'confirm:next',
      'tab': 'confirm:nextField',
      'space': 'confirm:toggle',
      'shift+tab': 'confirm:cycleMode',
      'ctrl+e': 'confirm:toggleExplanation',
      'ctrl+d': 'permission:toggleDebug',
    },
  ),

  // ── Tabs ──
  const KeybindingBlock(
    context: 'Tabs',
    bindings: {
      'tab': 'tabs:next',
      'shift+tab': 'tabs:previous',
      'right': 'tabs:next',
      'left': 'tabs:previous',
    },
  ),

  // ── Transcript ──
  const KeybindingBlock(
    context: 'Transcript',
    bindings: {
      'ctrl+e': 'transcript:toggleShowAll',
      'ctrl+c': 'transcript:exit',
      'escape': 'transcript:exit',
      'q': 'transcript:exit',
    },
  ),

  // ── History Search ──
  const KeybindingBlock(
    context: 'HistorySearch',
    bindings: {
      'ctrl+r': 'historySearch:next',
      'escape': 'historySearch:accept',
      'tab': 'historySearch:accept',
      'ctrl+c': 'historySearch:cancel',
      'enter': 'historySearch:execute',
    },
  ),

  // ── Task ──
  const KeybindingBlock(
    context: 'Task',
    bindings: {'ctrl+b': 'task:background'},
  ),

  // ── Theme Picker ──
  const KeybindingBlock(
    context: 'ThemePicker',
    bindings: {'ctrl+t': 'theme:toggleSyntaxHighlighting'},
  ),

  // ── Scroll ──
  const KeybindingBlock(
    context: 'Scroll',
    bindings: {
      'pageup': 'scroll:pageUp',
      'pagedown': 'scroll:pageDown',
      'wheelup': 'scroll:lineUp',
      'wheeldown': 'scroll:lineDown',
      'ctrl+home': 'scroll:top',
      'ctrl+end': 'scroll:bottom',
      'ctrl+shift+c': 'selection:copy',
      'cmd+c': 'selection:copy',
    },
  ),

  // ── Help ──
  const KeybindingBlock(context: 'Help', bindings: {'escape': 'help:dismiss'}),

  // ── Attachments ──
  const KeybindingBlock(
    context: 'Attachments',
    bindings: {
      'right': 'attachments:next',
      'left': 'attachments:previous',
      'backspace': 'attachments:remove',
      'delete': 'attachments:remove',
      'down': 'attachments:exit',
      'escape': 'attachments:exit',
    },
  ),

  // ── Footer ──
  const KeybindingBlock(
    context: 'Footer',
    bindings: {
      'up': 'footer:up',
      'ctrl+p': 'footer:up',
      'down': 'footer:down',
      'ctrl+n': 'footer:down',
      'right': 'footer:next',
      'left': 'footer:previous',
      'enter': 'footer:openSelected',
      'escape': 'footer:clearSelection',
    },
  ),

  // ── Message Selector (rewind dialog) ──
  const KeybindingBlock(
    context: 'MessageSelector',
    bindings: {
      'up': 'messageSelector:up',
      'down': 'messageSelector:down',
      'k': 'messageSelector:up',
      'j': 'messageSelector:down',
      'ctrl+p': 'messageSelector:up',
      'ctrl+n': 'messageSelector:down',
      'ctrl+up': 'messageSelector:top',
      'shift+up': 'messageSelector:top',
      'meta+up': 'messageSelector:top',
      'shift+k': 'messageSelector:top',
      'ctrl+down': 'messageSelector:bottom',
      'shift+down': 'messageSelector:bottom',
      'meta+down': 'messageSelector:bottom',
      'shift+j': 'messageSelector:bottom',
      'enter': 'messageSelector:select',
    },
  ),

  // ── Message Actions ──
  const KeybindingBlock(
    context: 'MessageActions',
    bindings: {
      'up': 'messageActions:prev',
      'down': 'messageActions:next',
      'k': 'messageActions:prev',
      'j': 'messageActions:next',
      'meta+up': 'messageActions:top',
      'meta+down': 'messageActions:bottom',
      'super+up': 'messageActions:top',
      'super+down': 'messageActions:bottom',
      'shift+up': 'messageActions:prevUser',
      'shift+down': 'messageActions:nextUser',
      'escape': 'messageActions:escape',
      'ctrl+c': 'messageActions:ctrlc',
      'enter': 'messageActions:enter',
      'c': 'messageActions:c',
      'p': 'messageActions:p',
    },
  ),

  // ── Diff Dialog ──
  const KeybindingBlock(
    context: 'DiffDialog',
    bindings: {
      'escape': 'diff:dismiss',
      'left': 'diff:previousSource',
      'right': 'diff:nextSource',
      'up': 'diff:previousFile',
      'down': 'diff:nextFile',
      'enter': 'diff:viewDetails',
    },
  ),

  // ── Model Picker ──
  const KeybindingBlock(
    context: 'ModelPicker',
    bindings: {
      'left': 'modelPicker:decreaseEffort',
      'right': 'modelPicker:increaseEffort',
    },
  ),

  // ── Select (used by /model, /resume, permission prompts, etc.) ──
  const KeybindingBlock(
    context: 'Select',
    bindings: {
      'up': 'select:previous',
      'down': 'select:next',
      'j': 'select:next',
      'k': 'select:previous',
      'ctrl+n': 'select:next',
      'ctrl+p': 'select:previous',
      'enter': 'select:accept',
      'escape': 'select:cancel',
    },
  ),

  // ── Plugin ──
  const KeybindingBlock(
    context: 'Plugin',
    bindings: {'space': 'plugin:toggle', 'i': 'plugin:install'},
  ),
];