fix: exclude venv and other dependency dirs from @file search (#682)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -6,6 +6,47 @@ interface FileMatch {
|
||||
type: "file" | "dir" | "url";
|
||||
}
|
||||
|
||||
/**
|
||||
* Directories to exclude from file search autocomplete.
|
||||
* These are common dependency/build directories that cause lag when searched.
|
||||
* All values are lowercase for case-insensitive matching (Windows compatibility).
|
||||
*/
|
||||
const IGNORED_DIRECTORIES = new Set([
|
||||
// JavaScript/Node
|
||||
"node_modules",
|
||||
"dist",
|
||||
"build",
|
||||
".next",
|
||||
".nuxt",
|
||||
"bower_components",
|
||||
|
||||
// Python
|
||||
"venv",
|
||||
".venv",
|
||||
"__pycache__",
|
||||
".tox",
|
||||
"env",
|
||||
|
||||
// Build outputs
|
||||
"target", // Rust/Maven/Java
|
||||
"out",
|
||||
"coverage",
|
||||
".cache",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check if a directory entry should be excluded from search results.
|
||||
* Uses case-insensitive matching for Windows compatibility.
|
||||
*/
|
||||
function shouldExcludeEntry(entry: string): boolean {
|
||||
// Skip hidden files/directories (starts with .)
|
||||
if (entry.startsWith(".")) {
|
||||
return true;
|
||||
}
|
||||
// Case-insensitive check for Windows compatibility
|
||||
return IGNORED_DIRECTORIES.has(entry.toLowerCase());
|
||||
}
|
||||
|
||||
export function debounce<T extends (...args: never[]) => unknown>(
|
||||
func: T,
|
||||
wait: number,
|
||||
@@ -40,13 +81,8 @@ function searchDirectoryRecursive(
|
||||
const entries = readdirSync(dir);
|
||||
|
||||
for (const entry of entries) {
|
||||
// Skip hidden files and common ignore patterns
|
||||
if (
|
||||
entry.startsWith(".") ||
|
||||
entry === "node_modules" ||
|
||||
entry === "dist" ||
|
||||
entry === "build"
|
||||
) {
|
||||
// Skip hidden files and common dependency/build directories
|
||||
if (shouldExcludeEntry(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -151,12 +187,14 @@ export async function searchFiles(
|
||||
|
||||
// Filter entries matching the search pattern
|
||||
// If pattern is empty, show all entries (for when user just types "@")
|
||||
const matchingEntries =
|
||||
searchPattern.length === 0
|
||||
? entries
|
||||
: entries.filter((entry) =>
|
||||
entry.toLowerCase().includes(searchPattern.toLowerCase()),
|
||||
);
|
||||
// Also exclude common dependency/build directories
|
||||
const matchingEntries = entries
|
||||
.filter((entry) => !shouldExcludeEntry(entry))
|
||||
.filter(
|
||||
(entry) =>
|
||||
searchPattern.length === 0 ||
|
||||
entry.toLowerCase().includes(searchPattern.toLowerCase()),
|
||||
);
|
||||
|
||||
// Get stats for each matching entry
|
||||
for (const entry of matchingEntries.slice(0, 50)) {
|
||||
|
||||
@@ -133,6 +133,67 @@ test("searchFiles skips node_modules (deep)", async () => {
|
||||
expect(results.some((r) => r.path.includes("index.ts"))).toBe(true);
|
||||
});
|
||||
|
||||
test("searchFiles skips venv directories (deep)", async () => {
|
||||
const originalCwd = process.cwd();
|
||||
process.chdir(TEST_DIR);
|
||||
|
||||
// Create venv directory (Python virtual environment)
|
||||
mkdirSync(join(TEST_DIR, "venv/lib"), { recursive: true });
|
||||
writeFileSync(join(TEST_DIR, "venv/lib/module.py"), "# python");
|
||||
|
||||
// Also test .venv (common alternative)
|
||||
mkdirSync(join(TEST_DIR, ".venv/lib"), { recursive: true });
|
||||
writeFileSync(join(TEST_DIR, ".venv/lib/other.py"), "# python");
|
||||
|
||||
const results = await searchFiles("module", true);
|
||||
|
||||
process.chdir(originalCwd);
|
||||
|
||||
// Should not find files in venv or .venv
|
||||
expect(results.some((r) => r.path.includes("venv"))).toBe(false);
|
||||
expect(results.some((r) => r.path.includes(".venv"))).toBe(false);
|
||||
});
|
||||
|
||||
test("searchFiles skips excluded directories in shallow search", async () => {
|
||||
const originalCwd = process.cwd();
|
||||
process.chdir(TEST_DIR);
|
||||
|
||||
// Create excluded directories
|
||||
mkdirSync(join(TEST_DIR, "node_modules"), { recursive: true });
|
||||
mkdirSync(join(TEST_DIR, "venv"), { recursive: true });
|
||||
mkdirSync(join(TEST_DIR, "__pycache__"), { recursive: true });
|
||||
|
||||
const results = await searchFiles("", false);
|
||||
|
||||
process.chdir(originalCwd);
|
||||
|
||||
// Should not include excluded directories in shallow search
|
||||
expect(results.some((r) => r.path === "node_modules")).toBe(false);
|
||||
expect(results.some((r) => r.path === "venv")).toBe(false);
|
||||
expect(results.some((r) => r.path === "__pycache__")).toBe(false);
|
||||
// But should still include non-excluded directories
|
||||
expect(results.some((r) => r.path === "src")).toBe(true);
|
||||
});
|
||||
|
||||
test("searchFiles uses case-insensitive exclusion for directory names", async () => {
|
||||
const originalCwd = process.cwd();
|
||||
process.chdir(TEST_DIR);
|
||||
|
||||
// Create directory with different casing (Windows-style)
|
||||
// This tests that Node_Modules or NODE_MODULES would be excluded
|
||||
mkdirSync(join(TEST_DIR, "Node_Modules/pkg"), { recursive: true });
|
||||
writeFileSync(join(TEST_DIR, "Node_Modules/pkg/test.js"), "module");
|
||||
|
||||
const results = await searchFiles("test", true);
|
||||
|
||||
process.chdir(originalCwd);
|
||||
|
||||
// Should not find files in Node_Modules (case-insensitive match to node_modules)
|
||||
expect(
|
||||
results.some((r) => r.path.toLowerCase().includes("node_modules")),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("searchFiles handles relative path queries", async () => {
|
||||
const originalCwd = process.cwd();
|
||||
process.chdir(TEST_DIR);
|
||||
|
||||
Reference in New Issue
Block a user