Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions apps/web/src/components/files/FileBrowserPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { joinHostPath } from "./filePath";
import { createFileTreeDragMentionController } from "./fileTreeDragMention";
import { areAllDirectoriesExpanded, setAllDirectoriesExpanded } from "./fileTreeExpansion";
import { buildFileTreePathUpdates } from "./fileTreePathReconciliation";
import { loadFileAncestors } from "./loadFileAncestors";
import { useDirectoryEntries } from "./useDirectoryEntries";
import { useProjectPathSearch } from "~/state/queries";

Expand Down Expand Up @@ -366,12 +367,7 @@ export default function FileBrowserPanel({
useEffect(() => {
if (!selectedPath) return;
const controller = new AbortController();
void (async () => {
const segments = selectedPath.split("/");
for (let index = 0; index < segments.length && !controller.signal.aborted; index++) {
await load(segments.slice(0, index).join("/"));
}
})();
void loadFileAncestors(selectedPath, load, { signal: controller.signal });
return () => {
controller.abort();
};
Expand Down
39 changes: 39 additions & 0 deletions apps/web/src/components/files/loadFileAncestors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect, it } from "vite-plus/test";
import { loadFileAncestors } from "./loadFileAncestors";

function queuedLoader() {
const paths: string[] = [];
const resolve: Array<() => void> = [];
const load = (path: string) => {
paths.push(path);
return new Promise<void>((done) => resolve.push(done));
};
return { paths, resolve, load };
}

it("bounds loads to four and finishes children even when parents finish last", async () => {
const queue = queuedLoader();
const loading = loadFileAncestors("a/b/c/d/e/f/file.ts", queue.load, new AbortController());
for (let index = 3; index < 6; index++) {
queue.resolve[index]?.();
await Promise.resolve();
expect(queue.paths).toHaveLength(index + 2);
}
expect(queue.paths).toEqual(["", "a", "a/b", "a/b/c", "a/b/c/d", "a/b/c/d/e", "a/b/c/d/e/f"]);
for (const resolve of queue.resolve) resolve();
await loading;
});

it("stops aborted reveals while a new selection loads independently", async () => {
const old = queuedLoader();
const controller = new AbortController();
const oldReveal = loadFileAncestors("old/a/b/c/d/file.ts", old.load, controller);
controller.abort();
const fresh = queuedLoader();
const newReveal = loadFileAncestors("new/file.ts", fresh.load, new AbortController());
for (const resolve of [...old.resolve, ...fresh.resolve]) resolve();
await Promise.all([oldReveal, newReveal]);
await loadFileAncestors("aborted/path/file.ts", old.load, controller);
expect(old.paths).toEqual(["", "old", "old/a", "old/a/b"]);
expect(fresh.paths).toEqual(["", "new"]);
});
15 changes: 15 additions & 0 deletions apps/web/src/components/files/loadFileAncestors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** The loader owns deduplication and the shared request cap, including other tree loads. */
export async function loadFileAncestors(
selectedPath: string,
load: (directoryPath: string) => Promise<void>,
options: { readonly signal: AbortSignal },
) {
const segments = selectedPath.split("/");
let next = 0;
const worker = async () => {
while (next < segments.length && !options.signal.aborted) {
await load(segments.slice(0, next++).join("/"));
}
};
await Promise.all(Array.from({ length: Math.min(4, segments.length) }, worker));
}
Loading