mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix: keep @ file mention responsive on very large projects (#600)
Typing @ in the chat input to reference a file froze the app on huge workspaces. Two compounding causes: 1. The autocomplete hook fed fileFilterQuery straight into the React Query key, so every keystroke fired a new directory_suggestions request and a fresh BFS scan on the daemon. Added a 180 ms debounce that mirrors the existing pattern in new-workspace-screen so rapid typing coalesces into one request. 2. searchWorkspaceEntries only ignored node_modules, so dist, build, target, out, coverage, vendor and __pycache__ each consumed the 5,000-entry scan budget before any real source file was reached. Extended WORKSPACE_IGNORED_DIRECTORY_NAMES with those names and added a regression test that proves a deep source file is still reachable when those directories are full of bait entries. Refs #599 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import type { AutocompleteOption } from "@/components/ui/autocomplete";
|
||||
import { useAgentCommandsQuery, type DraftCommandConfig } from "./use-agent-commands-query";
|
||||
@@ -185,6 +185,12 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut
|
||||
);
|
||||
const showFileAutocomplete = activeFileMention !== null;
|
||||
const fileFilterQuery = activeFileMention?.query ?? "";
|
||||
const [debouncedFileFilterQuery, setDebouncedFileFilterQuery] = useState(fileFilterQuery);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedFileFilterQuery(fileFilterQuery), 180);
|
||||
return () => clearTimeout(timer);
|
||||
}, [fileFilterQuery]);
|
||||
|
||||
const normalizedDraftConfig = useMemo(
|
||||
() => normalizeDraftCommandConfig(draftConfig),
|
||||
@@ -229,14 +235,21 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut
|
||||
});
|
||||
|
||||
const fileSuggestionsQuery = useQuery({
|
||||
queryKey: ["directorySuggestions", serverId, autocompleteCwd, fileFilterQuery, true, true],
|
||||
queryKey: [
|
||||
"directorySuggestions",
|
||||
serverId,
|
||||
autocompleteCwd,
|
||||
debouncedFileFilterQuery,
|
||||
true,
|
||||
true,
|
||||
],
|
||||
queryFn: async (): Promise<DirectorySuggestionEntry[]> => {
|
||||
if (!client) {
|
||||
throw new Error("Daemon client unavailable");
|
||||
}
|
||||
const response = await client.getDirectorySuggestions({
|
||||
cwd: autocompleteCwd,
|
||||
query: fileFilterQuery,
|
||||
query: debouncedFileFilterQuery,
|
||||
limit: 50,
|
||||
includeFiles: true,
|
||||
includeDirectories: true,
|
||||
|
||||
@@ -265,4 +265,34 @@ describe("searchWorkspaceEntries", () => {
|
||||
});
|
||||
expect(results.some((entry) => entry.path.startsWith("node_modules/"))).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores common build/cache directories so large generated trees do not exhaust scan budget", async () => {
|
||||
mkdirSync(path.join(workspaceDir, "packages", "app", "src"), { recursive: true });
|
||||
writeFileSync(path.join(workspaceDir, "packages", "app", "src", "needle.ts"), "");
|
||||
|
||||
const heavyDirs = ["dist", "build", "target", "out", "coverage", "vendor", "__pycache__"];
|
||||
for (const heavyDir of heavyDirs) {
|
||||
for (let index = 0; index < 30; index += 1) {
|
||||
mkdirSync(path.join(workspaceDir, heavyDir, `bundle-${index}`), { recursive: true });
|
||||
writeFileSync(path.join(workspaceDir, heavyDir, `bundle-${index}`, "needle.ts"), "");
|
||||
}
|
||||
}
|
||||
|
||||
const results = await searchWorkspaceEntries({
|
||||
cwd: workspaceDir,
|
||||
query: "needle.ts",
|
||||
limit: 20,
|
||||
includeFiles: true,
|
||||
includeDirectories: true,
|
||||
maxEntriesScanned: 80,
|
||||
});
|
||||
|
||||
expect(results).toContainEqual({
|
||||
path: "packages/app/src/needle.ts",
|
||||
kind: "file",
|
||||
});
|
||||
for (const heavyDir of heavyDirs) {
|
||||
expect(results.some((entry) => entry.path.startsWith(`${heavyDir}/`))).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +73,16 @@ const directoryListCache = new Map<string, DirectoryListCacheEntry>();
|
||||
const workspaceEntryListCache = new Map<string, WorkspaceEntryListCacheEntry>();
|
||||
const NO_SEGMENT_INDEX = Number.MAX_SAFE_INTEGER;
|
||||
const NO_MATCH_OFFSET = Number.MAX_SAFE_INTEGER;
|
||||
const WORKSPACE_IGNORED_DIRECTORY_NAMES = new Set(["node_modules"]);
|
||||
const WORKSPACE_IGNORED_DIRECTORY_NAMES = new Set([
|
||||
"node_modules",
|
||||
"dist",
|
||||
"build",
|
||||
"target",
|
||||
"out",
|
||||
"coverage",
|
||||
"vendor",
|
||||
"__pycache__",
|
||||
]);
|
||||
|
||||
export async function searchHomeDirectories(
|
||||
options: SearchHomeDirectoriesOptions,
|
||||
|
||||
Reference in New Issue
Block a user