- add `chat.new` and `editor.openFavorite` to keybinding contracts and server defaults - use shared keybinding matching in sidebar/chat view instead of hardcoded Cmd/Ctrl combos - document new commands/defaults and extend server/web keybinding tests
Unified · Split
| @@ -19,7 +19,9 @@ The file must be a JSON array of rules: · 18 unmodified lines | ||
| 19 | 19 | [ |
| 20 | 20 | { "key": "mod+j", "command": "terminal.toggle" }, |
| 21 | 21 | { "key": "mod+d", "command": "terminal.split", "when": "terminalFocus" }, |
| 22 | − { "key": "mod+shift+d", "command": "terminal.new", "when": "terminalFocus" } | |
| 22 | + { "key": "mod+shift+d", "command": "terminal.new", "when": "terminalFocus" }, | |
| 23 | + { "key": "mod+shift+o", "command": "chat.new" }, | |
| 24 | + { "key": "mod+o", "command": "editor.openFavorite" } | |
| 23 | 25 | ] |
| 24 | 26 | ``` |
| 25 | 27 | |
| @@ -38,6 +40,8 @@ Invalid rules are ignored. Invalid config files are ignored. Warnings are logged · 12 unmodified lines | ||
| 38 | 40 | - `terminal.toggle`: open/close terminal drawer |
| 39 | 41 | - `terminal.split`: split terminal (in focused terminal context by default) |
| 40 | 42 | - `terminal.new`: create new terminal (in focused terminal context by default) |
| 43 | +- `chat.new`: create a new chat thread for the active project | |
| 44 | +- `editor.openFavorite`: open current project/worktree in the last-used editor | |
| 41 | 45 | |
| 42 | 46 | ## Key Syntax |
| 43 | 47 | |
| @@ -85,6 +89,8 @@ Built-in defaults are resolved on the server and sent to the web client via `ser · 41 unmodified lines | ||
| 85 | 89 | - `terminal.toggle`: `mod+j` |
| 86 | 90 | - `terminal.split`: `mod+d` when `terminalFocus` |
| 87 | 91 | - `terminal.new`: `mod+shift+d` when `terminalFocus` |
| 92 | +- `chat.new`: `mod+shift+o` | |
| 93 | +- `editor.openFavorite`: `mod+o` | |
| 88 | 94 | |
| 89 | 95 | If you define any rules for a command, the default rule for that command is removed on the server. |
| 90 | 96 | |
| @@ -313,6 +313,8 @@ describe("WebSocket Server", () => { · 312 unmodified lines | ||
| 313 | 313 | { key: "mod+j", command: "terminal.toggle" }, |
| 314 | 314 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 315 | 315 | { key: "mod+shift+d", command: "terminal.new", when: "terminalFocus" }, |
| 316 | + { key: "mod+shift+o", command: "chat.new" }, | |
| 317 | + { key: "mod+o", command: "editor.openFavorite" }, | |
| 316 | 318 | ], |
| 317 | 319 | }); |
| 318 | 320 | }); |
| @@ -347,6 +349,8 @@ describe("WebSocket Server", () => { · 28 unmodified lines | ||
| 347 | 349 | expect(response.result).toEqual({ |
| 348 | 350 | cwd: "/my/workspace", |
| 349 | 351 | keybindings: [ |
| 352 | + { key: "mod+shift+o", command: "chat.new" }, | |
| 353 | + { key: "mod+o", command: "editor.openFavorite" }, | |
| 350 | 354 | { key: "cmd+j", command: "terminal.toggle" }, |
| 351 | 355 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 352 | 356 | { key: "mod+shift+d", command: "terminal.new", when: "terminalFocus" }, |
| @@ -386,6 +390,8 @@ describe("WebSocket Server", () => { · 33 unmodified lines | ||
| 386 | 390 | keybindings: [ |
| 387 | 391 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 388 | 392 | { key: "mod+shift+d", command: "terminal.new", when: "terminalFocus" }, |
| 393 | + { key: "mod+shift+o", command: "chat.new" }, | |
| 394 | + { key: "mod+o", command: "editor.openFavorite" }, | |
| 389 | 395 | { key: "mod+j", command: "terminal.toggle" }, |
| 390 | 396 | ], |
| 391 | 397 | }); |
| @@ -426,6 +432,8 @@ describe("WebSocket Server", () => { · 34 unmodified lines | ||
| 426 | 432 | { key: "mod+j", command: "terminal.toggle" }, |
| 427 | 433 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 428 | 434 | { key: "mod+shift+d", command: "terminal.new", when: "terminalFocus" }, |
| 435 | + { key: "mod+shift+o", command: "chat.new" }, | |
| 436 | + { key: "mod+o", command: "editor.openFavorite" }, | |
| 429 | 437 | ], |
| 430 | 438 | }); |
| 431 | 439 | expect( |
| @@ -461,6 +469,8 @@ describe("WebSocket Server", () => { · 29 unmodified lines | ||
| 461 | 469 | { key: "mod+j", command: "terminal.toggle" }, |
| 462 | 470 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 463 | 471 | { key: "mod+shift+d", command: "terminal.new", when: "terminalFocus" }, |
| 472 | + { key: "mod+shift+o", command: "chat.new" }, | |
| 473 | + { key: "mod+o", command: "editor.openFavorite" }, | |
| 464 | 474 | ], |
| 465 | 475 | }); |
| 466 | 476 | expect( |
| @@ -74,6 +74,8 @@ const DEFAULT_KEYBINDINGS: KeybindingsConfig = [ · 73 unmodified lines | ||
| 74 | 74 | { key: "mod+j", command: "terminal.toggle" }, |
| 75 | 75 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 76 | 76 | { key: "mod+shift+d", command: "terminal.new", when: "terminalFocus" }, |
| 77 | + { key: "mod+shift+o", command: "chat.new" }, | |
| 78 | + { key: "mod+o", command: "editor.openFavorite" }, | |
| 77 | 79 | ]; |
| 78 | 80 | |
| 79 | 81 | function mergeWithDefaultKeybindings(custom: KeybindingsConfig): KeybindingsConfig { |
| @@ -54,6 +54,7 @@ import type { ChatImageAttachment } from "../types"; · 53 unmodified lines | ||
| 54 | 54 | import BranchToolbar from "./BranchToolbar"; |
| 55 | 55 | import GitActionsControl from "./GitActionsControl"; |
| 56 | 56 | import { |
| 57 | + isOpenFavoriteEditorShortcut, | |
| 57 | 58 | isTerminalNewShortcut, |
| 58 | 59 | isTerminalSplitShortcut, |
| 59 | 60 | isTerminalToggleShortcut, |
| @@ -263,20 +264,20 @@ export default function ChatView() { · 203 unmodified lines | ||
| 263 | 264 | } as const); |
| 264 | 265 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; |
| 265 | 266 | const branchesQuery = useQuery(gitBranchesQueryOptions(api, gitCwd)); |
| 266 | − const terminalKeybindingsQuery = useQuery({ | |
| 267 | + const keybindingsQuery = useQuery({ | |
| 267 | 268 | ...serverConfigQueryOptions(api), |
| 268 | 269 | select: (config) => config.keybindings, |
| 269 | 270 | }); |
| 270 | − const terminalKeybindings = terminalKeybindingsQuery.data ?? EMPTY_KEYBINDINGS; | |
| 271 | + const keybindings = keybindingsQuery.data ?? EMPTY_KEYBINDINGS; | |
| 271 | 272 | // Default true while loading to avoid toolbar flicker. |
| 272 | 273 | const isGitRepo = branchesQuery.data?.isRepo ?? true; |
| 273 | 274 | const splitTerminalShortcutLabel = useMemo( |
| 274 | − () => shortcutLabelForCommand(terminalKeybindings, "terminal.split"), | |
| 275 | − [terminalKeybindings], | |
| 275 | + () => shortcutLabelForCommand(keybindings, "terminal.split"), | |
| 276 | + [keybindings], | |
| 276 | 277 | ); |
| 277 | 278 | const newTerminalShortcutLabel = useMemo( |
| 278 | − () => shortcutLabelForCommand(terminalKeybindings, "terminal.new"), | |
| 279 | − [terminalKeybindings], | |
| 279 | + () => shortcutLabelForCommand(keybindings, "terminal.new"), | |
| 280 | + [keybindings], | |
| 280 | 281 | ); |
| 281 | 282 | |
| 282 | 283 | const envLocked = Boolean( |
| @@ -527,14 +528,14 @@ export default function ChatView() { · 244 unmodified lines | ||
| 527 | 528 | terminalOpen: Boolean(activeThread?.terminalOpen), |
| 528 | 529 | }; |
| 529 | 530 | |
| 530 | − if (isTerminalToggleShortcut(event, terminalKeybindings, { context: shortcutContext })) { | |
| 531 | + if (isTerminalToggleShortcut(event, keybindings, { context: shortcutContext })) { | |
| 531 | 532 | event.preventDefault(); |
| 532 | 533 | event.stopPropagation(); |
| 533 | 534 | toggleTerminalVisibility(); |
| 534 | 535 | return; |
| 535 | 536 | } |
| 536 | 537 | |
| 537 | − if (isTerminalSplitShortcut(event, terminalKeybindings, { context: shortcutContext })) { | |
| 538 | + if (isTerminalSplitShortcut(event, keybindings, { context: shortcutContext })) { | |
| 538 | 539 | event.preventDefault(); |
| 539 | 540 | event.stopPropagation(); |
| 540 | 541 | if (!activeThread?.terminalOpen) { |
| @@ -548,7 +549,7 @@ export default function ChatView() { · 7 unmodified lines | ||
| 548 | 549 | return; |
| 549 | 550 | } |
| 550 | 551 | |
| 551 | − if (!isTerminalNewShortcut(event, terminalKeybindings, { context: shortcutContext })) return; | |
| 552 | + if (!isTerminalNewShortcut(event, keybindings, { context: shortcutContext })) return; | |
| 552 | 553 | event.preventDefault(); |
| 553 | 554 | event.stopPropagation(); |
| 554 | 555 | if (!activeThread?.terminalOpen) { |
| @@ -568,7 +569,7 @@ export default function ChatView() { · 13 unmodified lines | ||
| 568 | 569 | createNewTerminal, |
| 569 | 570 | dispatch, |
| 570 | 571 | splitTerminal, |
| 571 | − terminalKeybindings, | |
| 572 | + keybindings, | |
| 572 | 573 | toggleTerminalVisibility, |
| 573 | 574 | ]); |
| 574 | 575 | |
| @@ -937,7 +938,7 @@ export default function ChatView() { · 362 unmodified lines | ||
| 937 | 938 | </div> |
| 938 | 939 | <div className="flex items-center gap-3"> |
| 939 | 940 | {/* Open in editor */} |
| 940 | − {activeProject && <OpenInPicker />} | |
| 941 | + {activeProject && <OpenInPicker keybindings={keybindings} />} | |
| 941 | 942 | {/* Git actions */} |
| 942 | 943 | {activeProject && <GitActionsControl api={api} gitCwd={gitCwd} />} |
| 943 | 944 | |
| @@ -1519,7 +1520,7 @@ function ReasoningEffortPicker(props: { · 575 unmodified lines | ||
| 1519 | 1520 | ); |
| 1520 | 1521 | } |
| 1521 | 1522 | |
| 1522 | −function OpenInPicker() { | |
| 1523 | +function OpenInPicker({ keybindings }: { keybindings: KeybindingsConfig }) { | |
| 1523 | 1524 | const [lastEditor, setLastEditor] = useState<EditorId>(() => { |
| 1524 | 1525 | const stored = localStorage.getItem(LAST_EDITOR_KEY); |
| 1525 | 1526 | return EDITORS.some((e) => e.id === stored) ? (stored as EditorId) : EDITORS[0].id; |
| @@ -1560,20 +1561,23 @@ function OpenInPicker() { · 34 unmodified lines | ||
| 1560 | 1561 | [api, activeProject, activeThread, lastEditor, setLastEditor], |
| 1561 | 1562 | ); |
| 1562 | 1563 | |
| 1563 | − // Cmd+O / Ctrl+O to open in last-used editor | |
| 1564 | + const openFavoriteEditorShortcutLabel = useMemo( | |
| 1565 | + () => shortcutLabelForCommand(keybindings, "editor.openFavorite"), | |
| 1566 | + [keybindings], | |
| 1567 | + ); | |
| 1568 | + | |
| 1564 | 1569 | useEffect(() => { |
| 1565 | 1570 | const handler = (e: globalThis.KeyboardEvent) => { |
| 1566 | − if (e.key === "o" && (e.metaKey || e.ctrlKey) && !e.shiftKey) { | |
| 1567 | − if (api && activeProject) { | |
| 1568 | − e.preventDefault(); | |
| 1569 | − const cwd = activeThread?.worktreePath ?? activeProject.cwd; | |
| 1570 | − void api.shell.openInEditor(cwd, lastEditor); | |
| 1571 | − } | |
| 1572 | − } | |
| 1571 | + if (!isOpenFavoriteEditorShortcut(e, keybindings)) return; | |
| 1572 | + if (!api || !activeProject) return; | |
| 1573 | + | |
| 1574 | + e.preventDefault(); | |
| 1575 | + const cwd = activeThread?.worktreePath ?? activeProject.cwd; | |
| 1576 | + void api.shell.openInEditor(cwd, lastEditor); | |
| 1573 | 1577 | }; |
| 1574 | 1578 | window.addEventListener("keydown", handler); |
| 1575 | 1579 | return () => window.removeEventListener("keydown", handler); |
| 1576 | − }, [api, activeProject, activeThread, lastEditor]); | |
| 1580 | + }, [api, activeProject, activeThread, keybindings, lastEditor]); | |
| 1577 | 1581 | |
| 1578 | 1582 | return ( |
| 1579 | 1583 | <Group aria-label="Subscription actions"> |
| @@ -1591,10 +1595,8 @@ function OpenInPicker() { · 11 unmodified lines | ||
| 1591 | 1595 | <MenuItem key={value} onClick={() => openInEditor(value)}> |
| 1592 | 1596 | <Icon aria-hidden="true" className="text-muted-foreground" /> |
| 1593 | 1597 | {label} |
| 1594 | − {value === lastEditor && ( | |
| 1595 | − <MenuShortcut> | |
| 1596 | − {navigator.platform.includes("Mac") ? "\u2318O" : "Ctrl+O"} | |
| 1597 | − </MenuShortcut> | |
| 1598 | + {value === lastEditor && openFavoriteEditorShortcutLabel && ( | |
| 1599 | + <MenuShortcut>{openFavoriteEditorShortcutLabel}</MenuShortcut> | |
| 1598 | 1600 | )} |
| 1599 | 1601 | </MenuItem> |
| 1600 | 1602 | ))} |
| @@ -1,11 +1,13 @@ | ||
| 1 | 1 | import { MonitorIcon, MoonIcon, SunIcon, TerminalIcon } from "lucide-react"; |
| 2 | 2 | import { useCallback, useEffect, useMemo, useState } from "react"; |
| 3 | −import { useMutation, useQueryClient } from "@tanstack/react-query"; | |
| 3 | +import type { KeybindingsConfig } from "@t3tools/contracts"; | |
| 4 | +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; | |
| 4 | 5 | import { isElectron } from "../env"; |
| 5 | 6 | import { useTheme } from "../hooks/useTheme"; |
| 6 | 7 | import { DEFAULT_MODEL } from "../model-logic"; |
| 7 | 8 | import { derivePendingApprovals } from "../session-logic"; |
| 8 | 9 | import { useStore } from "../store"; |
| 10 | +import { isChatNewShortcut } from "../keybindings"; | |
| 9 | 11 | import { |
| 10 | 12 | DEFAULT_THREAD_TERMINAL_HEIGHT, |
| 11 | 13 | DEFAULT_THREAD_TERMINAL_ID, |
| @@ -14,9 +16,12 @@ import { · 2 unmodified lines | ||
| 14 | 16 | } from "../types"; |
| 15 | 17 | import { useNativeApi } from "../hooks/useNativeApi"; |
| 16 | 18 | import { gitRemoveWorktreeMutationOptions } from "../lib/gitReactQuery"; |
| 19 | +import { serverConfigQueryOptions } from "../lib/serverReactQuery"; | |
| 17 | 20 | import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; |
| 18 | 21 | |
| 19 | 22 | const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const; |
| 23 | +const EMPTY_KEYBINDINGS: KeybindingsConfig = []; | |
| 24 | + | |
| 20 | 25 | function formatRelativeTime(iso: string): string { |
| 21 | 26 | const diff = Date.now() - new Date(iso).getTime(); |
| 22 | 27 | const minutes = Math.floor(diff / 60_000); |
| @@ -112,6 +117,11 @@ function terminalStatusIndicator(thread: Thread): TerminalStatusIndicator | null · 89 unmodified lines | ||
| 112 | 117 | export default function Sidebar() { |
| 113 | 118 | const { state, dispatch } = useStore(); |
| 114 | 119 | const api = useNativeApi(); |
| 120 | + const keybindingsQuery = useQuery({ | |
| 121 | + ...serverConfigQueryOptions(api), | |
| 122 | + select: (config) => config.keybindings, | |
| 123 | + }); | |
| 124 | + const keybindings = keybindingsQuery.data ?? EMPTY_KEYBINDINGS; | |
| 115 | 125 | const queryClient = useQueryClient(); |
| 116 | 126 | const removeWorktreeMutation = useMutation(gitRemoveWorktreeMutationOptions({ api, queryClient })); |
| 117 | 127 | const { theme, setTheme } = useTheme(); |
| @@ -324,13 +334,7 @@ export default function Sidebar() { · 206 unmodified lines | ||
| 324 | 334 | |
| 325 | 335 | useEffect(() => { |
| 326 | 336 | const onWindowKeyDown = (event: KeyboardEvent) => { |
| 327 | − const isNewThreadShortcut = | |
| 328 | − event.metaKey && | |
| 329 | − event.shiftKey && | |
| 330 | − !event.ctrlKey && | |
| 331 | − !event.altKey && | |
| 332 | − event.key.toLowerCase() === "o"; | |
| 333 | − if (!isNewThreadShortcut) return; | |
| 337 | + if (!isChatNewShortcut(event, keybindings)) return; | |
| 334 | 338 | |
| 335 | 339 | const activeThread = state.threads.find((t) => t.id === state.activeThreadId); |
| 336 | 340 | const projectId = activeThread?.projectId ?? state.projects[0]?.id; |
| @@ -344,7 +348,7 @@ export default function Sidebar() { · 7 unmodified lines | ||
| 344 | 348 | return () => { |
| 345 | 349 | window.removeEventListener("keydown", onWindowKeyDown); |
| 346 | 350 | }; |
| 347 | − }, [handleNewThread, state.activeThreadId, state.projects, state.threads]); | |
| 351 | + }, [handleNewThread, keybindings, state.activeThreadId, state.projects, state.threads]); | |
| 348 | 352 | |
| 349 | 353 | return ( |
| 350 | 354 | <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-border bg-card"> |
| @@ -3,6 +3,8 @@ import { describe, expect, it } from "vitest"; · 2 unmodified lines | ||
| 3 | 3 | import type { KeybindingsConfig } from "@t3tools/contracts"; |
| 4 | 4 | import { |
| 5 | 5 | formatShortcutLabel, |
| 6 | + isChatNewShortcut, | |
| 7 | + isOpenFavoriteEditorShortcut, | |
| 6 | 8 | isTerminalClearShortcut, |
| 7 | 9 | isTerminalNewShortcut, |
| 8 | 10 | isTerminalSplitShortcut, |
| @@ -26,6 +28,8 @@ const DEFAULT_BINDINGS: KeybindingsConfig = [ · 17 unmodified lines | ||
| 26 | 28 | { key: "mod+j", command: "terminal.toggle" }, |
| 27 | 29 | { key: "mod+d", command: "terminal.split", when: "terminalFocus" }, |
| 28 | 30 | { key: "mod+shift+d", command: "terminal.new", when: "terminalFocus" }, |
| 31 | + { key: "mod+shift+o", command: "chat.new" }, | |
| 32 | + { key: "mod+o", command: "editor.openFavorite" }, | |
| 29 | 33 | ]; |
| 30 | 34 | |
| 31 | 35 | describe("isTerminalToggleShortcut", () => { |
| @@ -112,6 +116,41 @@ describe("shortcutLabelForCommand", () => { · 80 unmodified lines | ||
| 112 | 116 | ]; |
| 113 | 117 | expect(shortcutLabelForCommand(bindings, "terminal.split", "Linux")).toBe("Ctrl+Shift+\\"); |
| 114 | 118 | }); |
| 119 | + | |
| 120 | + it("returns labels for non-terminal commands", () => { | |
| 121 | + expect(shortcutLabelForCommand(DEFAULT_BINDINGS, "chat.new", "MacIntel")).toBe("⇧⌘O"); | |
| 122 | + expect(shortcutLabelForCommand(DEFAULT_BINDINGS, "editor.openFavorite", "Linux")).toBe( | |
| 123 | + "Ctrl+O", | |
| 124 | + ); | |
| 125 | + }); | |
| 126 | +}); | |
| 127 | + | |
| 128 | +describe("chat/editor shortcuts", () => { | |
| 129 | + it("matches chat.new shortcut", () => { | |
| 130 | + expect( | |
| 131 | + isChatNewShortcut(event({ key: "o", metaKey: true, shiftKey: true }), DEFAULT_BINDINGS, { | |
| 132 | + platform: "MacIntel", | |
| 133 | + }), | |
| 134 | + ).toBe(true); | |
| 135 | + expect( | |
| 136 | + isChatNewShortcut(event({ key: "o", ctrlKey: true, shiftKey: true }), DEFAULT_BINDINGS, { | |
| 137 | + platform: "Linux", | |
| 138 | + }), | |
| 139 | + ).toBe(true); | |
| 140 | + }); | |
| 141 | + | |
| 142 | + it("matches editor.openFavorite shortcut", () => { | |
| 143 | + expect( | |
| 144 | + isOpenFavoriteEditorShortcut(event({ key: "o", metaKey: true }), DEFAULT_BINDINGS, { | |
| 145 | + platform: "MacIntel", | |
| 146 | + }), | |
| 147 | + ).toBe(true); | |
| 148 | + expect( | |
| 149 | + isOpenFavoriteEditorShortcut(event({ key: "o", ctrlKey: true }), DEFAULT_BINDINGS, { | |
| 150 | + platform: "Linux", | |
| 151 | + }), | |
| 152 | + ).toBe(true); | |
| 153 | + }); | |
| 115 | 154 | }); |
| 116 | 155 | |
| 117 | 156 | describe("formatShortcutLabel", () => { |
| @@ -14,7 +14,7 @@ export interface ShortcutMatchContext { · 13 unmodified lines | ||
| 14 | 14 | [key: string]: boolean; |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | −export type ResolvedTerminalKeybindings = KeybindingsConfig; | |
| 17 | +export type ResolvedKeybindings = KeybindingsConfig; | |
| 18 | 18 | |
| 19 | 19 | interface ParsedShortcut { |
| 20 | 20 | key: string; |
| @@ -301,7 +301,7 @@ function resolveContext(options: ShortcutMatchOptions | undefined): ShortcutMatc · 280 unmodified lines | ||
| 301 | 301 | |
| 302 | 302 | function matchesCommandShortcut( |
| 303 | 303 | event: ShortcutEventLike, |
| 304 | − keybindings: ResolvedTerminalKeybindings, | |
| 304 | + keybindings: ResolvedKeybindings, | |
| 305 | 305 | command: KeybindingCommand, |
| 306 | 306 | options?: ShortcutMatchOptions, |
| 307 | 307 | ): boolean { |
| @@ -356,7 +356,7 @@ export function formatShortcutLabel(shortcutValue: string, platform = navigator. · 48 unmodified lines | ||
| 356 | 356 | } |
| 357 | 357 | |
| 358 | 358 | export function shortcutLabelForCommand( |
| 359 | − keybindings: ResolvedTerminalKeybindings, | |
| 359 | + keybindings: ResolvedKeybindings, | |
| 360 | 360 | command: KeybindingCommand, |
| 361 | 361 | platform = navigator.platform, |
| 362 | 362 | ): string | null { |
| @@ -370,7 +370,7 @@ export function shortcutLabelForCommand( · 7 unmodified lines | ||
| 370 | 370 | |
| 371 | 371 | export function isTerminalToggleShortcut( |
| 372 | 372 | event: ShortcutEventLike, |
| 373 | − keybindings: ResolvedTerminalKeybindings, | |
| 373 | + keybindings: ResolvedKeybindings, | |
| 374 | 374 | options?: ShortcutMatchOptions, |
| 375 | 375 | ): boolean { |
| 376 | 376 | return matchesCommandShortcut(event, keybindings, "terminal.toggle", options); |
| @@ -378,7 +378,7 @@ export function isTerminalToggleShortcut( · 1 unmodified line | ||
| 378 | 378 | |
| 379 | 379 | export function isTerminalSplitShortcut( |
| 380 | 380 | event: ShortcutEventLike, |
| 381 | − keybindings: ResolvedTerminalKeybindings, | |
| 381 | + keybindings: ResolvedKeybindings, | |
| 382 | 382 | options?: ShortcutMatchOptions, |
| 383 | 383 | ): boolean { |
| 384 | 384 | return matchesCommandShortcut(event, keybindings, "terminal.split", options); |
| @@ -386,12 +386,28 @@ export function isTerminalSplitShortcut( · 1 unmodified line | ||
| 386 | 386 | |
| 387 | 387 | export function isTerminalNewShortcut( |
| 388 | 388 | event: ShortcutEventLike, |
| 389 | − keybindings: ResolvedTerminalKeybindings, | |
| 389 | + keybindings: ResolvedKeybindings, | |
| 390 | 390 | options?: ShortcutMatchOptions, |
| 391 | 391 | ): boolean { |
| 392 | 392 | return matchesCommandShortcut(event, keybindings, "terminal.new", options); |
| 393 | 393 | } |
| 394 | 394 | |
| 395 | +export function isChatNewShortcut( | |
| 396 | + event: ShortcutEventLike, | |
| 397 | + keybindings: ResolvedKeybindings, | |
| 398 | + options?: ShortcutMatchOptions, | |
| 399 | +): boolean { | |
| 400 | + return matchesCommandShortcut(event, keybindings, "chat.new", options); | |
| 401 | +} | |
| 402 | + | |
| 403 | +export function isOpenFavoriteEditorShortcut( | |
| 404 | + event: ShortcutEventLike, | |
| 405 | + keybindings: ResolvedKeybindings, | |
| 406 | + options?: ShortcutMatchOptions, | |
| 407 | +): boolean { | |
| 408 | + return matchesCommandShortcut(event, keybindings, "editor.openFavorite", options); | |
| 409 | +} | |
| 410 | + | |
| 395 | 411 | export function isTerminalClearShortcut( |
| 396 | 412 | event: ShortcutEventLike, |
| 397 | 413 | platform = navigator.platform, |
| @@ -4,6 +4,8 @@ export const keybindingCommandSchema = z.enum([ · 3 unmodified lines | ||
| 4 | 4 | "terminal.toggle", |
| 5 | 5 | "terminal.split", |
| 6 | 6 | "terminal.new", |
| 7 | + "chat.new", | |
| 8 | + "editor.openFavorite", | |
| 7 | 9 | ]); |
| 8 | 10 | |
| 9 | 11 | const keybindingValueSchema = z.string().trim().min(1).max(64); |