From 6c3a021bb9d8af013f17ca28e1fa1abafb298f2b Mon Sep 17 00:00:00 2001 From: Jonathan van Rij Date: Wed, 24 Jun 2026 13:59:58 +0200 Subject: [PATCH] update --- src/renderer/src/App.tsx | 12 ++++--- src/renderer/src/overlays.tsx | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 347de98..ea069ff 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -5,7 +5,7 @@ import type { ContextTarget } from './components' import { Editor, SplitView } from './editor' import type { Cursor, Mode, Selection } from './editor' import { Terminal, lid } from './terminals' -import { ConfirmModal, ContextMenu, HelpModal, HistoryModal, NamePopup, PassPopup, SearchModal, Toasts } from './overlays' +import { ConfirmModal, ContextMenu, HelpModal, HistoryModal, NamePopup, PassPopup, ProjectsModal, SearchModal, Toasts } from './overlays' import { ProjectLauncher } from './launcher' import type { Menu, Toast } from './overlays' import type { FileNode, GitStatus } from './types' @@ -79,7 +79,7 @@ export function App(): React.ReactElement { const [openDirs, setOpenDirs] = useState>(new Set()) const [cursor, setCursor] = useState(null) const [selection, setSelection] = useState(null) - const [overlay, setOverlay] = useState<'search' | 'history' | 'help' | null>(null) + const [overlay, setOverlay] = useState<'search' | 'history' | 'help' | 'projects' | null>(null) const [searchInit, setSearchInit] = useState('') // seed query for ⌘F-with-selection // Most-recently-opened files, newest first, de-duplicated. Drives the ⌘↓/⌘↑ navigator. const [history, setHistory] = useState([]) @@ -659,8 +659,8 @@ export function App(): React.ReactElement { const meta = e.metaKey || e.ctrlKey const ae = document.activeElement as HTMLElement | null const inField = !!ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA') - // The history navigator owns the keyboard while open (it listens in capture phase). - if (overlay === 'history') return + // The history / project navigators own the keyboard while open (capture phase). + if (overlay === 'history' || overlay === 'projects') return // An open context menu owns the keyboard (arrows / ↵ / esc handled there). if (menu) return const inPanel = activePanel === 'git' || activePanel === 'tree' @@ -692,6 +692,9 @@ export function App(): React.ReactElement { setHistInitSel(e.key === 'ArrowDown' ? Math.min(1, history.length - 1) : 0) setOverlay('history') } + // ⇧⌘O opens the recent-project history picker (⌘O — opening a new folder — + // is the native File-menu accelerator, so the renderer is free to own ⇧⌘O). + else if (meta && e.shiftKey && e.key.toLowerCase() === 'o') { e.preventDefault(); setOverlay('projects') } // ⌘F opens the unified search (it covers file names too), seeded with the // current selection when there is one. else if (meta && e.key.toLowerCase() === 'f') { e.preventDefault(); setSearchInit(selectedSearchText()); setOverlay('search') } @@ -847,6 +850,7 @@ export function App(): React.ReactElement { onCancel={() => setNewFolderPopup(null)} />} {overlay === 'search' && openFile(p, { line: n })} onClose={() => setOverlay(null)} changeSet={changeSet} activePath={active} activeText={bufferText(active)} showHidden={showHidden} />} {overlay === 'history' && setOverlay(null)} changeSet={changeSet} />} + {overlay === 'projects' && actions.openProjectPath(p)} onClose={() => setOverlay(null)} />} {overlay === 'help' && setOverlay(null)} />} {confirm && setConfirm(null)} />} {menu && setMenu(null)} />} diff --git a/src/renderer/src/overlays.tsx b/src/renderer/src/overlays.tsx index a9a1ee6..313c24e 100644 --- a/src/renderer/src/overlays.tsx +++ b/src/renderer/src/overlays.tsx @@ -1,6 +1,7 @@ /* Overlays: combined search (content + file names), context menu, toast, pass-popup */ import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react' import { useProject } from './project' +import type { RecentProject } from './project' import { fuzzy } from './fuzzy' import { FileIcon, Icon } from './components' import type { OpenFile } from './components' @@ -347,6 +348,64 @@ export function HistoryModal({ history, initialSel, onOpen, onClose, changeSet } ) } +/* Project history picker (⇧⌘O). Same keyboard model as the launcher and the + * recent-files navigator: ⌘↑/⌘↓ (or plain arrows) move, ↵ switches the current + * window to that project, esc closes. The currently-open project is filtered + * out — reopening it is a no-op. */ +export function ProjectsModal({ recents, currentRoot, onOpen, onClose }: { + recents: RecentProject[] + currentRoot: string | null + onOpen: (path: string) => void + onClose: () => void +}): React.ReactElement { + const list = useMemo(() => recents.filter((p) => p.path !== currentRoot), [recents, currentRoot]) + const [sel, setSel] = useState(0) + const selRef = useRef(sel); selRef.current = sel + const listRef = useRef(null) + + useEffect(() => { + function onKey(e: KeyboardEvent): void { + if (e.key === 'ArrowDown') { e.preventDefault(); setSel((s) => Math.min(s + 1, list.length - 1)) } + else if (e.key === 'ArrowUp') { e.preventDefault(); setSel((s) => Math.max(s - 1, 0)) } + else if (e.key === 'Enter') { e.preventDefault(); const p = list[selRef.current]; if (p) { onOpen(p.path); onClose() } } + else if (e.key === 'Escape') { e.preventDefault(); onClose() } + } + window.addEventListener('keydown', onKey, true) + return () => window.removeEventListener('keydown', onKey, true) + }, [list, onOpen, onClose]) + + useEffect(() => { + const el = listRef.current && listRef.current.querySelector('.hist-row.sel') + if (el) el.scrollIntoView({ block: 'nearest' }) + }, [sel]) + + return ( +
+
e.stopPropagation()}> +
+ {Icon.reveal({ style: { color: 'var(--fg-3)' } })} + Open recent project + {list.length} project{list.length === 1 ? '' : 's'} · ⌘↓ ⌘↑ +
+
+ {list.length === 0 &&
No other recent projects
} + {list.map((p, i) => ( +
setSel(i)} + onClick={() => { onOpen(p.path); onClose() }}> + {Icon.reveal()} +
+ {p.name} + {p.path} +
+
+ ))} +
+
+
+ ) +} + /* Keyboard-shortcuts reference (opened from the title-bar ? button). */ const SHORTCUTS: { keys: string[]; label: string }[] = [ { keys: ['⌘', 'F'], label: 'Search contents & names (seeded by selection)' }, @@ -368,6 +427,7 @@ const SHORTCUTS: { keys: string[]; label: string }[] = [ { keys: ['⌘', 'W'], label: 'Close the current file' }, { keys: ['⌘', 'D'], label: 'Delete the current file (confirm)' }, { keys: ['⌘', 'O'], label: 'Open a project folder' }, + { keys: ['⇧', '⌘', 'O'], label: 'Open a recent project (history picker)' }, { keys: ['Esc'], label: 'Close an overlay / split view' }, ]