-
Notifications
You must be signed in to change notification settings - Fork 37.1k
Description
Does this issue occur when all extensions are disabled?: Yes
- VS Code Version: 1.106.3-bf9252a2fb45be6893dd8870c0bf37e2e1766d61-x64
- OS Version: Windows 11-Build-26200.7309
Steps to Reproduce:
- Open a repo in VS Code
- Create or add a folder containing an extremely large number of files (mine contains ~300M)
- Click the folder in the Explorer
- The UI becomes unresponsive until the folder is collapsed
Issue Description:
When a Git repository contains a folder with a massive number of files, expanding that folder in the VS Code Explorer causes the entire UI to freeze or become unresponsive. The slowdown continues until the folder is collapsed again.
In my case the folder contains roughly 300 million files, and as soon as I click to expand it, VS Code attempts to enumerate everything and becomes sluggish. The system becomes usable again only after collapsing the folder.
Issue Behavior:
- Clicking the folder expands it and tries to display the entire file list
- VS Code becomes slow and UI almost freezes
- Performance returns only when the folder is collapsed
Expected Behavior:
- Lazily load directory entries instead of fetching all at once
- Load in chunks or pages
- Provide a way to jump or scroll to deeper sections without preloading all files
- Show partial listing first and continue loading in background
Suggestion/Feature Request:
Implement incremental directory loading or virtualized list rendering for directories with extreme file counts. Showing millions of files at once is not necessary for usability.
Profiling and Functions Responsible:
I did some preliminary profiling and look into the function responsible for it.
It appears to be these functions:
vscode/src/vs/base/browser/ui/tree/asyncDataTree.ts
Lines 1071 to 1080 in a9bcdfb
| private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> { | |
| if (this.disposables.isDisposed) { | |
| return; // tree disposed during refresh, again (#228211) | |
| } | |
| await this.refreshNode(node, recursive, viewStateContext); | |
| if (this.disposables.isDisposed) { | |
| return; // tree disposed during refresh (#199264) | |
| } | |
| this.render(node, viewStateContext, options); | |
| } |
vscode/src/vs/base/browser/ui/tree/asyncDataTree.ts
Lines 1127 to 1169 in a9bcdfb
| private async doRefreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<IAsyncDataTreeNode<TInput, T>[]> { | |
| node.hasChildren = !!this.dataSource.hasChildren(node.element); | |
| let childrenPromise: Promise<Iterable<T>>; | |
| if (!node.hasChildren) { | |
| childrenPromise = Promise.resolve(Iterable.empty()); | |
| } else { | |
| const children = this.doGetChildren(node); | |
| if (isIterable(children)) { | |
| childrenPromise = Promise.resolve(children); | |
| } else { | |
| const slowTimeout = timeout(800); | |
| slowTimeout.then(() => { | |
| node.slow = true; | |
| this._onDidChangeNodeSlowState.fire(node); | |
| }, _ => null); | |
| childrenPromise = children.finally(() => slowTimeout.cancel()); | |
| } | |
| } | |
| try { | |
| const children = await childrenPromise; | |
| return this.setChildren(node, children, recursive, viewStateContext); | |
| } catch (err) { | |
| if (node !== this.root && this.tree.hasElement(node)) { | |
| this.tree.collapse(node); | |
| } | |
| if (isCancellationError(err)) { | |
| return []; | |
| } | |
| throw err; | |
| } finally { | |
| if (node.slow) { | |
| node.slow = false; | |
| this._onDidChangeNodeSlowState.fire(node); | |
| } | |
| } | |
| } |