From dca4563b9af611f5a04302b7739d93dce531a4c0 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Thu, 20 Mar 2025 12:14:32 -0400 Subject: [PATCH] Fixes to file search (#1846) --- src/services/search/file-search.ts | 89 +++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/src/services/search/file-search.ts b/src/services/search/file-search.ts index ef8d0bd959..68624dedc9 100644 --- a/src/services/search/file-search.ts +++ b/src/services/search/file-search.ts @@ -15,6 +15,7 @@ async function executeRipgrepForFiles( const args = [ "--files", "--follow", + "--hidden", "-g", "!**/node_modules/**", "-g", @@ -32,18 +33,29 @@ async function executeRipgrepForFiles( crlfDelay: Infinity, }) - const results: { path: string; type: "file" | "folder"; label?: string }[] = [] + const fileResults: { path: string; type: "file" | "folder"; label?: string }[] = [] + const dirSet = new Set() // Track unique directory paths let count = 0 rl.on("line", (line) => { if (count < limit) { try { const relativePath = path.relative(workspacePath, line) - results.push({ + + // Add the file itself + fileResults.push({ path: relativePath, type: "file", label: path.basename(relativePath), }) + + // Extract and store all parent directory paths + let dirPath = path.dirname(relativePath) + while (dirPath && dirPath !== "." && dirPath !== "/") { + dirSet.add(dirPath) + dirPath = path.dirname(dirPath) + } + count++ } catch (error) { // Silently ignore errors processing individual paths @@ -60,10 +72,18 @@ async function executeRipgrepForFiles( }) rl.on("close", () => { - if (errorOutput && results.length === 0) { + if (errorOutput && fileResults.length === 0) { reject(new Error(`ripgrep process error: ${errorOutput}`)) } else { - resolve(results) + // Convert directory set to array of directory objects + const dirResults = Array.from(dirSet).map((dirPath) => ({ + path: dirPath, + type: "folder" as const, + label: path.basename(dirPath), + })) + + // Combine files and directories and resolve + resolve([...fileResults, ...dirResults]) } }) @@ -86,40 +106,67 @@ export async function searchWorkspaceFiles( throw new Error("Could not find ripgrep binary") } - const allFiles = await executeRipgrepForFiles(rgPath, workspacePath, 5000) + // Get all files and directories (from our modified function) + const allItems = await executeRipgrepForFiles(rgPath, workspacePath, 5000) + // If no query, just return the top items if (!query.trim()) { - return allFiles.slice(0, limit) + return allItems.slice(0, limit) } - const searchItems = allFiles.map((file) => ({ - original: file, - searchStr: `${file.path} ${file.label || ""}`, + // Create search items for all files AND directories + const searchItems = allItems.map((item) => ({ + original: item, + searchStr: `${item.path} ${item.label || ""}`, })) + // Run fzf search on all items const fzf = new Fzf(searchItems, { selector: (item) => item.searchStr, }) - const results = fzf - .find(query) - .slice(0, limit) - .map((result) => result.item.original) + // Get all matching results from fzf + const fzfResults = fzf.find(query) - const resultsWithDirectoryCheck = await Promise.all( - results.map(async (result) => { + // First, sort all results by path length (shortest first) + fzfResults.sort((a, b) => { + return a.item.original.path.length - b.item.original.path.length + }) + + // Take the top N (limit) shortest results + const shortestResults = fzfResults.slice(0, limit).map((result) => result.item.original) + + // Verify types of the shortest results + const verifiedResults = await Promise.all( + shortestResults.map(async (result) => { const fullPath = path.join(workspacePath, result.path) - const isDirectory = fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory() - - return { - ...result, - type: isDirectory ? ("folder" as const) : ("file" as const), + // Verify if the path exists and is actually a directory + if (fs.existsSync(fullPath)) { + const isDirectory = fs.lstatSync(fullPath).isDirectory() + return { + ...result, + type: isDirectory ? ("folder" as const) : ("file" as const), + } } + // If path doesn't exist, keep original type + return result }), ) - return resultsWithDirectoryCheck + // Final sort to put directories first within the shortest results + verifiedResults.sort((a, b) => { + if (a.type === "folder" && b.type !== "folder") { + return -1 + } + if (a.type !== "folder" && b.type === "folder") { + return 1 + } + return 0 // Keep original sorting by path length + }) + + return verifiedResults } catch (error) { + console.error("Error in searchWorkspaceFiles:", error) return [] } }