fix(explorer): make filename search find all workspace files (#21423)

* fix(explorer): search file names through runtime

* fix(explorer): keep filename search results complete

* fix(explorer): narrow runtime search change

* test(explorer): remove unsupported local search assertion

* fix(explorer): fence filename search results
This commit is contained in:
Neil
2026-09-18 04:05:23 -07:00
committed by GitHub
parent 7909dad7ba
commit 01beadbcf0
3 changed files with 66 additions and 6 deletions
@@ -26,6 +26,8 @@ export type RuntimeFileListState = {
loading: boolean
loadError: string | null
truncated?: boolean
/** Query that produced `files`; null means a request is still settling. */
resolvedQuery?: string | null
operationOwner?: FileExplorerOperationOwner
}
@@ -145,6 +147,7 @@ export function useRuntimeFileListForWorktree({
const [loading, setLoading] = useState(false)
const [loadError, setLoadError] = useState<string | null>(null)
const [truncated, setTruncated] = useState(false)
const [resolvedQuery, setResolvedQuery] = useState<string | null | undefined>(undefined)
const [listedOperationOwner, setListedOperationOwner] = useState<FileExplorerOperationOwner>({
kind: 'unresolved'
})
@@ -205,7 +208,7 @@ export function useRuntimeFileListForWorktree({
useEffect(() => {
if (!enabled) {
setLoading(false)
setTruncated(false)
setResolvedQuery(null)
setListedOperationOwner({ kind: 'unresolved' })
return
}
@@ -213,9 +216,10 @@ export function useRuntimeFileListForWorktree({
if (!target.canList || !worktreeId || !worktreePath || !operationRouteAvailable) {
setFiles([])
setListedOperationOwner({ kind: 'unresolved' })
setLoadError(operationRouteAvailable ? null : getFileExplorerOwnerUnresolvedMessage())
setLoadError(!operationRouteAvailable ? getFileExplorerOwnerUnresolvedMessage() : null)
setLoading(false)
setTruncated(false)
setResolvedQuery(null)
return
}
@@ -223,6 +227,7 @@ export function useRuntimeFileListForWorktree({
const requestKeyChanged = lastRequestKeyRef.current !== requestKey
if (requestKeyChanged) {
setFiles([])
setResolvedQuery(null)
}
lastRequestKeyRef.current = requestKey
setLoadError(null)
@@ -231,6 +236,7 @@ export function useRuntimeFileListForWorktree({
if (usesRuntimePathSearch && (remoteQuery.length === 0 || remoteQueryTooLarge)) {
setFiles([])
setLoading(false)
setResolvedQuery(remoteQuery)
setListedOperationOwner(operationOwnerRef.current)
return
}
@@ -277,6 +283,7 @@ export function useRuntimeFileListForWorktree({
if (!cancelled) {
setFiles(result.files)
setTruncated(result.truncated)
setResolvedQuery(usesRuntimePathSearch ? remoteQuery : undefined)
setListedOperationOwner(requestOperationOwner)
}
})
@@ -284,6 +291,7 @@ export function useRuntimeFileListForWorktree({
if (!cancelled) {
setFiles([])
setTruncated(false)
setResolvedQuery(usesRuntimePathSearch ? remoteQuery : null)
setLoadError(cleanRuntimeFileListError(error))
}
})
@@ -323,6 +331,7 @@ export function useRuntimeFileListForWorktree({
loading: loading || connectionPending,
loadError,
truncated,
resolvedQuery,
operationOwner: listedOperationOwner
}
}
@@ -0,0 +1,45 @@
// @vitest-environment happy-dom
import { act, cleanup, renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useAppStore } from '@/store'
import type { RuntimeFileListState } from '@/components/quick-open-file-list'
import { useFileExplorerNameFilter } from './use-file-explorer-name-filter'
const useRuntimeFileListForWorktreeMock = vi.hoisted(() => vi.fn())
vi.mock('@/components/quick-open-file-list', () => ({
useRuntimeFileListForWorktree: useRuntimeFileListForWorktreeMock
}))
const emptyState: RuntimeFileListState = {
files: [],
loading: false,
loadError: null
}
describe('useFileExplorerNameFilter', () => {
beforeEach(() => {
useRuntimeFileListForWorktreeMock.mockReset().mockReturnValue(emptyState)
useAppStore.setState({ activeWorktreeId: 'worktree-1' })
})
afterEach(() => {
cleanup()
})
it('passes the active filename query to the runtime path search', () => {
const { result } = renderHook(() =>
useFileExplorerNameFilter({ isFilesViewActive: true, activeWorktreeId: 'worktree-1' })
)
act(() => result.current.setNameFilterQuery('AppDelegate.swift'))
expect(useRuntimeFileListForWorktreeMock).toHaveBeenLastCalledWith({
enabled: true,
worktreeId: 'worktree-1',
query: 'AppDelegate.swift'
})
expect(result.current.nameFilterSource?.query).toBe('AppDelegate.swift')
})
})
@@ -45,7 +45,8 @@ export function useFileExplorerNameFilter({
}, [hasNameFilter])
const nameFilterFiles = useRuntimeFileListForWorktree({
enabled: hasNameFilter && !nameFilterQueryTooLarge,
worktreeId: activeWorktreeId
worktreeId: activeWorktreeId,
query: nameFilterQuery
})
const nameFilterSource = useMemo(
() =>
@@ -55,9 +56,13 @@ export function useFileExplorerNameFilter({
operationOwner: nameFilterFiles.operationOwner,
relativePaths: nameFilterQueryTooLarge
? []
: nameFilterFiles.loading && nameFilterFiles.files.length === 0
? null
: nameFilterFiles.files
: nameFilterFiles.resolvedQuery === nameFilterQuery.trim()
? nameFilterFiles.loading
? null
: nameFilterFiles.files
: nameFilterFiles.loading
? null
: []
}
: null,
[
@@ -65,6 +70,7 @@ export function useFileExplorerNameFilter({
nameFilterFiles.files,
nameFilterFiles.loading,
nameFilterFiles.operationOwner,
nameFilterFiles.resolvedQuery,
nameFilterQuery,
nameFilterQueryTooLarge
]