diff --git a/src/App.svelte b/src/App.svelte index 5c54df2..bfab03a 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -5940,6 +5940,67 @@ // ── Event handlers ───────────────────────────────────────────────────────── + // ── Main view shortcuts ──────────────────────────────────────────────────── + // Ctrl/Cmd + 1…4 jump to the four main views in the order of the tab bar. + const mainViewShortcuts: AppView[] = ["management", "repository", "review-center", "issues"]; + + function mainViewShortcutIndex(event: KeyboardEvent): number { + if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return 0; + const code = event.code ?? ""; + const digit = code.startsWith("Digit") + ? Number(code.slice(5)) + : code.startsWith("Numpad") + ? Number(code.slice(6)) + : Number(event.key); + if (!Number.isInteger(digit) || digit < 1 || digit > mainViewShortcuts.length) return 0; + return digit; + } + + // Capture phase: dialogs and editors trap keys on their own nodes, and the + // view shortcut must not depend on the event reaching window afterwards. + function handleViewShortcutKeydown(event: KeyboardEvent) { + const index = mainViewShortcutIndex(event); + if (index === 0) return; + if (import.meta.env.DEV) { + console.debug("[gitty] view shortcut", { key: event.key, code: event.code, index, blocked: mainViewSwitchBlocked(), activeView }); + } + event.preventDefault(); + if (!event.repeat) switchMainView(index); + } + + // Anything modal owns the keyboard, so the view stays where it is. + function mainViewSwitchBlocked(): boolean { + return isBusy + || commandPaletteOpen || helpOpen || globalSearchOpen || appSettingsOpen + || cloneDialogOpen || initRepositoryDialogOpen || credDialogOpen + || compareDialogOpen || compareSelectOpen || fileHistoryDialogOpen + || resolveDialogOpen || interactiveRebaseOpen || reflogOpen || bisectOpen + || worktreeDialogOpen || submoduleDialogOpen || gitLfsDialogOpen + || linePatchOpen || aiReviewOpen + || pendingSubmoduleInitialization !== null || pendingDiscard !== null + || newBranchCommit !== null || renameBranchTarget !== null + || deleteBranchTarget !== null || commitNoteTarget !== null; + } + + function switchMainView(index: number) { + if (mainViewSwitchBlocked()) return; + const view = mainViewShortcuts[index - 1]; + if (!view || view === activeView) return; + closeRepoTabContextMenu(); + if (view === "management") { + openRepoManagement(); + } else if (view === "review-center") { + openReviewCenter(); + } else if (view === "issues") { + activeView = "issues"; + } else { + // Without an open repository the tab bar would ask for a folder; a + // keyboard shortcut should never pop up a native file dialog. + const path = repoTabs.find((tab) => sameRepoPath(tab.path, activeRepoPath))?.path ?? repoTabs[0]?.path; + if (path) void selectRepoTab(path); + } + } + function handleWindowKeydown(event: KeyboardEvent) { if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") { event.preventDefault(); @@ -5992,7 +6053,7 @@