From 225fcb3830f896a2d8be820969b008bc8662b420 Mon Sep 17 00:00:00 2001 From: aos <25783780+aos@users.noreply.github.com> Date: Sun, 25 Jan 2026 14:08:11 -0500 Subject: [PATCH] feat: make session selector keybindings configurable (#948) I lost my ability to select up and down in the session selector because of some hardcoded keybindings again... So, I am adding these configurable keybindings so I can `ctrl+p` to select up :-) ### Changes Adds 4 new keybinding actions for the session picker (/resume): - `toggleSessionPath` (ctrl+p) - toggle path display - `toggleSessionSort` (ctrl+r) - toggle sort mode - `deleteSession` (ctrl+d) - delete selected session - `deleteSessionNoninvasive` (ctrl+backspace) - delete when query empty Refactors session-selector to use `kb.matches()` instead of hardcoded key checks. --- packages/coding-agent/README.md | 5 +++++ .../interactive/components/session-selector.ts | 18 +++++++++--------- packages/tui/src/keybindings.ts | 14 +++++++++++++- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 8b6019d0..999cd697 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -435,6 +435,11 @@ All keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Eac | `selectDown` | `down` | Move selection down in lists | | `selectConfirm` | `enter` | Confirm selection | | `selectCancel` | `escape`, `ctrl+c` | Cancel selection | +| `toggleSessionPath` | `ctrl+p` | Toggle path display in session picker | +| `toggleSessionSort` | `ctrl+s` | Toggle sort mode in session picker | +| `renameSession` | `ctrl+r` | Rename selected session | +| `deleteSession` | `ctrl+d` | Delete selected session | +| `deleteSessionNoninvasive` | `ctrl+backspace` | Delete session (when query empty) | **Example (Emacs-style):** diff --git a/packages/coding-agent/src/modes/interactive/components/session-selector.ts b/packages/coding-agent/src/modes/interactive/components/session-selector.ts index 9a6d85ab..49f6c8a0 100644 --- a/packages/coding-agent/src/modes/interactive/components/session-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/session-selector.ts @@ -17,7 +17,7 @@ import { import type { SessionInfo, SessionListProgress } from "../../../core/session-manager.js"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; -import { keyHint, rawKeyHint } from "./keybinding-hints.js"; +import { keyHint } from "./keybinding-hints.js"; import { filterAndSortSessions, type SortMode } from "./session-selector-search.js"; type SessionScope = "current" | "all"; @@ -153,12 +153,12 @@ class SessionSelectorHeader implements Component { const sep = theme.fg("muted", " · "); const hint1 = keyHint("tab", "scope") + sep + theme.fg("muted", 're: regex · "phrase" exact'); const hint2Parts = [ - rawKeyHint("ctrl+s", "sort"), - rawKeyHint("ctrl+d", "delete"), - rawKeyHint("ctrl+p", `path ${pathState}`), + keyHint("toggleSessionSort", "sort"), + keyHint("deleteSession", "delete"), + keyHint("toggleSessionPath", `path ${pathState}`), ]; if (this.showRenameHint) { - hint2Parts.push(rawKeyHint("ctrl+r", "rename")); + hint2Parts.push(keyHint("renameSession", "rename")); } const hint2 = hint2Parts.join(sep); hintLine1 = truncateToWidth(hint1, width, "…"); @@ -383,20 +383,20 @@ class SessionList implements Component, Focusable { return; } - if (matchesKey(keyData, "ctrl+s")) { + if (kb.matches(keyData, "toggleSessionSort")) { this.onToggleSort?.(); return; } // Ctrl+P: toggle path display - if (matchesKey(keyData, "ctrl+p")) { + if (kb.matches(keyData, "toggleSessionPath")) { this.showPath = !this.showPath; this.onTogglePath?.(this.showPath); return; } // Ctrl+D: initiate delete confirmation (useful on terminals that don't distinguish Ctrl+Backspace from Backspace) - if (matchesKey(keyData, "ctrl+d")) { + if (kb.matches(keyData, "deleteSession")) { this.startDeleteConfirmationForSelectedSession(); return; } @@ -412,7 +412,7 @@ class SessionList implements Component, Focusable { // Ctrl+Backspace: non-invasive convenience alias for delete // Only triggers deletion when the query is empty; otherwise it is forwarded to the input - if (matchesKey(keyData, "ctrl+backspace")) { + if (kb.matches(keyData, "deleteSessionNoninvasive")) { if (this.searchInput.getValue().length > 0) { this.searchInput.handleInput(keyData); this.filterSessions(this.searchInput.getValue()); diff --git a/packages/tui/src/keybindings.ts b/packages/tui/src/keybindings.ts index 54df1d2b..f1939c26 100644 --- a/packages/tui/src/keybindings.ts +++ b/packages/tui/src/keybindings.ts @@ -41,7 +41,13 @@ export type EditorAction = // Undo | "undo" // Tool output - | "expandTools"; + | "expandTools" + // Session + | "toggleSessionPath" + | "toggleSessionSort" + | "renameSession" + | "deleteSession" + | "deleteSessionNoninvasive"; // Re-export KeyId from keys.ts export type { KeyId }; @@ -95,6 +101,12 @@ export const DEFAULT_EDITOR_KEYBINDINGS: Required = { undo: "ctrl+-", // Tool output expandTools: "ctrl+o", + // Session + toggleSessionPath: "ctrl+p", + toggleSessionSort: "ctrl+s", + renameSession: "ctrl+r", + deleteSession: "ctrl+d", + deleteSessionNoninvasive: "ctrl+backspace", }; /**