Make workspace search global (#2748)
This commit is contained in:
parent
05af0e92ef
commit
d6013ee404
|
|
@ -25,6 +25,7 @@ import { getWorktreeStatus, getWorktreeStatusLabel } from '@/lib/worktree-status
|
|||
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
|
||||
import { findWorktreeById } from '@/store/slices/worktree-helpers'
|
||||
import {
|
||||
getWorktreePaletteSearchScope,
|
||||
searchWorktrees,
|
||||
type MatchRange,
|
||||
type PaletteSearchResult
|
||||
|
|
@ -200,16 +201,10 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
|||
|
||||
const hasQuery = deferredQuery.trim().length > 0
|
||||
|
||||
// Why: keep the jump palette aligned with the sidebar. Surfacing hidden
|
||||
// default-branch or sleeping workspaces here would reintroduce entries the
|
||||
// user asked the workspace navigation surfaces to omit.
|
||||
// Drift warning: this check must stay in lockstep with the sidebar's
|
||||
// filter in computeVisibleWorktreeIds (visible-worktrees.ts). Both
|
||||
// surfaces share isDefaultBranchWorkspace so the predicate can't drift,
|
||||
// but adding a new filter axis (e.g. a second toggle) here would need
|
||||
// the matching change in the sidebar pipeline — otherwise Cmd+J and
|
||||
// the sidebar will show different lists.
|
||||
const visibleWorktrees = useMemo(
|
||||
// Why: the empty-query palette mirrors sidebar filters so opening Search
|
||||
// starts from the same quiet list. Typed search switches to the global
|
||||
// non-archived scope below.
|
||||
const emptyQueryVisibleWorktrees = useMemo(
|
||||
() =>
|
||||
allWorktrees.filter((worktree) => {
|
||||
if (worktree.isArchived) {
|
||||
|
|
@ -246,11 +241,21 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
|||
const { visibleWorktreesForState, switchableWorktreesForRows } = useMemo(
|
||||
() =>
|
||||
orderEmptyQueryWorktrees({
|
||||
visibleWorktrees,
|
||||
visibleWorktrees: emptyQueryVisibleWorktrees,
|
||||
activeWorktreeId,
|
||||
lastVisitedAtByWorktreeId
|
||||
}),
|
||||
[visibleWorktrees, activeWorktreeId, lastVisitedAtByWorktreeId]
|
||||
[emptyQueryVisibleWorktrees, activeWorktreeId, lastVisitedAtByWorktreeId]
|
||||
)
|
||||
|
||||
const searchScopeWorktrees = useMemo(
|
||||
() =>
|
||||
getWorktreePaletteSearchScope({
|
||||
hasQuery,
|
||||
allWorktrees,
|
||||
emptyQueryWorktrees: switchableWorktreesForRows
|
||||
}),
|
||||
[allWorktrees, hasQuery, switchableWorktreesForRows]
|
||||
)
|
||||
|
||||
// Why: typed queries still route through sortWorktreesSmart — switcher
|
||||
|
|
@ -259,7 +264,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
|||
() =>
|
||||
hasQuery
|
||||
? sortWorktreesSmart(
|
||||
visibleWorktrees,
|
||||
searchScopeWorktrees,
|
||||
tabsByWorktree,
|
||||
repoMap,
|
||||
agentStatusByPaneKey,
|
||||
|
|
@ -268,11 +273,10 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
|||
migrationUnsupportedByPtyId,
|
||||
terminalLayoutsByTabId
|
||||
)
|
||||
: switchableWorktreesForRows,
|
||||
: searchScopeWorktrees,
|
||||
[
|
||||
hasQuery,
|
||||
visibleWorktrees,
|
||||
switchableWorktreesForRows,
|
||||
searchScopeWorktrees,
|
||||
tabsByWorktree,
|
||||
repoMap,
|
||||
agentStatusByPaneKey,
|
||||
|
|
@ -500,6 +504,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
|||
// because the only visible worktree is the currently active one.
|
||||
// See docs/cmd-j-empty-query-ordering.md.
|
||||
const hasAnyWorktrees = visibleWorktreesForState.length > 0
|
||||
const hasAnySearchableWorktrees = hasQuery ? searchScopeWorktrees.length > 0 : hasAnyWorktrees
|
||||
const hasAnyBrowserPages = browserPageEntries.length > 0
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -857,7 +862,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
|||
|
||||
const resultCount = selectableItems.length
|
||||
const emptyState = (() => {
|
||||
if ((hasAnyWorktrees || hasAnyBrowserPages) && hasQuery) {
|
||||
if ((hasAnySearchableWorktrees || hasAnyBrowserPages) && hasQuery) {
|
||||
return {
|
||||
title: 'No results match your search',
|
||||
subtitle: 'Try a name, branch, repo, port, comment, PR, page title, or URL.'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { searchWorktrees } from './worktree-palette-search'
|
||||
import { getWorktreePaletteSearchScope, searchWorktrees } from './worktree-palette-search'
|
||||
import type { Repo, Worktree } from '../../../shared/types'
|
||||
|
||||
function makeWorktree(overrides: Partial<Worktree> = {}): Worktree {
|
||||
|
|
@ -39,6 +39,33 @@ const repoMap = new Map<string, Repo>([
|
|||
])
|
||||
|
||||
describe('worktree-palette-search', () => {
|
||||
it('uses the filtered recent list for empty queries', () => {
|
||||
const visible = makeWorktree({ id: 'visible' })
|
||||
const hidden = makeWorktree({ id: 'hidden-by-filter' })
|
||||
|
||||
const scope = getWorktreePaletteSearchScope({
|
||||
hasQuery: false,
|
||||
allWorktrees: [visible, hidden],
|
||||
emptyQueryWorktrees: [visible]
|
||||
})
|
||||
|
||||
expect(scope.map((worktree) => worktree.id)).toEqual(['visible'])
|
||||
})
|
||||
|
||||
it('uses all non-archived worktrees for typed queries', () => {
|
||||
const visible = makeWorktree({ id: 'visible' })
|
||||
const hiddenByFilter = makeWorktree({ id: 'hidden-by-filter' })
|
||||
const archived = makeWorktree({ id: 'archived', isArchived: true })
|
||||
|
||||
const scope = getWorktreePaletteSearchScope({
|
||||
hasQuery: true,
|
||||
allWorktrees: [visible, hiddenByFilter, archived],
|
||||
emptyQueryWorktrees: [visible]
|
||||
})
|
||||
|
||||
expect(scope.map((worktree) => worktree.id)).toEqual(['visible', 'hidden-by-filter'])
|
||||
})
|
||||
|
||||
it('returns every worktree with no match metadata for an empty query', () => {
|
||||
const results = searchWorktrees([makeWorktree()], '', repoMap, null, null)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ export type PaletteSearchResult = {
|
|||
supportingText: PaletteSupportingText | null
|
||||
}
|
||||
|
||||
export function getWorktreePaletteSearchScope(args: {
|
||||
hasQuery: boolean
|
||||
allWorktrees: readonly Worktree[]
|
||||
emptyQueryWorktrees: readonly Worktree[]
|
||||
}): Worktree[] {
|
||||
if (!args.hasQuery) {
|
||||
return [...args.emptyQueryWorktrees]
|
||||
}
|
||||
|
||||
// Why: sidebar filters keep the default list quiet, but explicit search is
|
||||
// a recovery path for sleeping/default-branch workspaces hidden by filters.
|
||||
return args.allWorktrees.filter((worktree) => !worktree.isArchived)
|
||||
}
|
||||
|
||||
type PRCacheEntry = { data?: { number: number; title: string } | null } | undefined
|
||||
type IssueCacheEntry = { data?: { number: number; title: string } | null } | undefined
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue