fix(explorer): 文件名搜索跳过 node_modules 等噪声目录

右侧文件窗口的全局文件名搜索会深度优先遍历整个工作区,原先只跳过
.git。含大型 node_modules 的仓库会先把 10 万条访问预算耗尽,导致
docs/ 等后序目录里的真实文件搜不到。

搜索改为跳过依赖、包管理存储和常见构建缓存目录(不匹配也不下降),
并补回归测试钉住 node_modules / dist 内的命中不会泄漏。
This commit is contained in:
leokuang
2026-08-23 02:50:37 +08:00
parent d9b8f15d9e
commit d0f194b67a
3 changed files with 61 additions and 12 deletions
+35 -8
View File
@@ -3,14 +3,14 @@
* Streams the tree with opendir and matches the query as a case-insensitive
* substring of each entry's NAME (paths stay relative to the search root —
* the client resolves them against the session cwd). No .gitignore semantics
* (this is a name lookup, not a code search), but `.git` directories are
* skipped outright (VCS internals are never useful results) and symlink
* directories are NOT descended (cycle safety).
* (this is a name lookup, not a code search), but known noise directories
* (`.git`, `node_modules`, package-manager stores, build caches) are
* skipped outright and symlink directories are NOT descended (cycle safety).
*
* Two performance budgets bound the walk: `maxMatches` (the client renders
* the flat list) and `maxVisited` (a runaway tree — a home directory root,
* a node_modules forest — must not stall the host). Exceeding either stops
* early with `truncated: true`.
* the flat list) and `maxVisited` (a runaway tree — a home directory root
* — must not stall the host). Exceeding either stops early with
* `truncated: true`.
*/
import { opendir } from 'node:fs/promises'
import { join, relative, sep } from 'node:path'
@@ -33,6 +33,33 @@ export interface FsSearchOptions {
const DEFAULT_MAX_MATCHES = 200
const DEFAULT_MAX_VISITED = 100_000
/**
* Directory names that are never useful filename-search results and would
* burn the visit budget before the walk reaches project files. Compared
* case-insensitively so `Node_Modules` / `.GIT` stay skipped on every
* platform. The directory itself is neither matched nor descended.
*/
const SEARCH_SKIP_DIRS = new Set([
'.git',
'node_modules',
'.pnpm-store',
'.yarn',
'.turbo',
'.turbopack',
'.next',
'.nuxt',
'.output',
'.cache',
'.parcel-cache',
'coverage',
'dist',
'build',
'out',
'.umi',
'.umi-production',
'.dumi',
])
/**
* Search `root` recursively for entries whose name contains `query`
* (case-insensitive).
@@ -63,8 +90,8 @@ export async function searchFiles(root: string, query: string, opts: FsSearchOpt
truncated = true
return
}
// .git is VCS-internal noise: never matched, never descended.
if (dirent.isDirectory() && dirent.name === '.git') continue
// Dependency / VCS / build-output forests: never matched, never descended.
if (dirent.isDirectory() && SEARCH_SKIP_DIRS.has(dirent.name.toLowerCase())) continue
if (dirent.name.toLowerCase().includes(needle)) {
matches.push(join(relative(root, dir), dirent.name))
if (matches.length >= maxMatches) {