diff --git a/crates/github/src/api.rs b/crates/github/src/api.rs index 3a2fb827..05c20aa5 100644 --- a/crates/github/src/api.rs +++ b/crates/github/src/api.rs @@ -40,9 +40,31 @@ pub fn github_check_cli_status(app: AppHandle) -> Result { @@ -60,7 +58,6 @@ export async function deleteFileOrDirectory(path: string): Promise { export async function readDirectoryContents(path: string): Promise { try { const entries = await platformReadDirectory(path); - const workspaceRoot = useFileSystemStore.getState().rootFolderPath; const filteredEntries = (entries as any[]).filter((entry: any) => { const name = entry.name || "Unknown"; @@ -68,34 +65,19 @@ export async function readDirectoryContents(path: string): Promise return !shouldIgnore(name, isDir); }); - const entriesWithSymlinkInfo = await Promise.all( - filteredEntries.map(async (entry: any) => { - const entryPath = entry.path || `${path}/${entry.name}`; - - try { - const symlinkInfo = await getSymlinkInfo(entryPath, workspaceRoot); - - return { - name: entry.name || "Unknown", - path: entryPath, - isDir: symlinkInfo.is_symlink ? false : entry.is_dir || false, - children: undefined, - isSymlink: symlinkInfo.is_symlink, - symlinkTarget: symlinkInfo.target, - }; - } catch (error) { - console.error(`Failed to get symlink info for ${entryPath}:`, error); - return { - name: entry.name || "Unknown", - path: entryPath, - isDir: entry.is_dir || false, - children: undefined, - }; - } - }), - ); - - return entriesWithSymlinkInfo; + // Skip per-entry symlink resolution during initial directory read. + // Symlink info is resolved lazily when a file is opened (handleFileSelect) + // or a directory is expanded, avoiding hundreds of stat syscalls that + // cause significant lag on large projects (see #572). + return filteredEntries.map((entry: any) => { + const entryPath = entry.path || `${path}/${entry.name}`; + return { + name: entry.name || "Unknown", + path: entryPath, + isDir: entry.is_dir || false, + children: undefined, + }; + }); } catch (error) { throw new Error(`Failed to read directory ${path}: ${error}`); } diff --git a/src/features/file-system/controllers/store.ts b/src/features/file-system/controllers/store.ts index 9523ba47..06102fae 100644 --- a/src/features/file-system/controllers/store.ts +++ b/src/features/file-system/controllers/store.ts @@ -42,7 +42,6 @@ import { createNewFile, deleteFileOrDirectory, readDirectoryContents, - readFileContent, } from "./file-operations"; import { addFileToTree, @@ -791,38 +790,6 @@ export const useFileSystemStore = createSelectors( ); fileOpenBenchmark.finish(path, "binary-buffer-opened"); } else { - if (!path.startsWith("remote://")) { - try { - const fileData = await readFile(resolvedPath); - - if (isStaleRequest()) return; - - if (isBinaryContent(fileData)) { - openBuffer( - path, - fileName, - "", - false, - undefined, - false, - false, - undefined, - false, - false, - false, - undefined, - false, - false, - true, - ); - fileOpenBenchmark.finish(path, "binary-sniff-buffer-opened"); - return; - } - } catch (error) { - console.error("Failed to inspect file bytes before opening:", error); - } - } - // Check if external editor is enabled for text files const { settings } = useSettingsStore.getState(); const { openExternalEditorBuffer } = useBufferStore.getState().actions; @@ -867,7 +834,38 @@ export const useFileSystemStore = createSelectors( filePath: remotePath, }); } else { - content = await readFileContent(resolvedPath); + // Read file as binary first to perform binary sniffing without + // a separate read pass (avoids reading large files twice, see #572). + const fileData = await readFile(resolvedPath); + fileOpenBenchmark.mark(path, "file-read-bytes", `${fileData.length} bytes`); + + if (isStaleRequest()) return; + + if (isBinaryContent(fileData)) { + openBuffer( + path, + fileName, + "", + false, + undefined, + false, + false, + undefined, + false, + false, + false, + undefined, + false, + false, + true, + ); + fileOpenBenchmark.finish(path, "binary-sniff-buffer-opened"); + return; + } + + // Decode the already-read bytes instead of reading the file again + const decoder = new TextDecoder("utf-8"); + content = decoder.decode(fileData); } fileOpenBenchmark.mark(path, "file-read", `${content.length} chars`); @@ -1914,6 +1912,14 @@ export const useFileSystemStore = createSelectors( useWorkspaceTabsStore.getState().setActiveProjectTab(projectId); + // Close old project's buffers BEFORE loading new project to prevent + // race conditions between session save and restore, and to ensure + // terminal PTY processes from the old workspace are cleaned up + // before new ones are spawned. + if (currentBufferIds.length > 0) { + bufferActions.closeBuffersBatch(currentBufferIds, true); + } + if (remoteTabInfo) { const reconnected = await get().handleOpenRemoteProject( remoteTabInfo.connectionId, @@ -2060,10 +2066,6 @@ export const useFileSystemStore = createSelectors( })(); } - if (currentBufferIds.length > 0) { - bufferActions.closeBuffersBatch(currentBufferIds, true); - } - set((state) => { state.isSwitchingProject = false; });