Skip to content

Commit

Permalink
Add fast path for synchronously resolved children (nested children)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jackson Kearl committed Feb 16, 2022
1 parent 93a2a2f commit 079a688
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 49 deletions.
11 changes: 8 additions & 3 deletions src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,19 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
return Array.isArray(element) || element.hasChildren;
}

getChildren(element: ExplorerItem | ExplorerItem[]): Promise<ExplorerItem[]> {
getChildren(element: ExplorerItem | ExplorerItem[]): ExplorerItem[] | Promise<ExplorerItem[]> {
if (Array.isArray(element)) {
return Promise.resolve(element);
return element;
}

const wasError = element.isError;
const sortOrder = this.explorerService.sortOrderConfiguration.sortOrder;
const promise = element.fetchChildren(sortOrder).then(
const children = element.fetchChildren(sortOrder);
if (Array.isArray(children)) {
// fast path when children are known sync (i.e. nested children)
return children;
}
const promise = children.then(
children => {
// Clear previous error decoration on root folder
if (element instanceof ExplorerItem && element.isRoot && !element.isError && wasError && this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER) {
Expand Down
95 changes: 49 additions & 46 deletions src/vs/workbench/contrib/files/common/explorerModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,63 +296,66 @@ export class ExplorerItem {
return this.children.get(this.getPlatformAwareName(name));
}

async fetchChildren(sortOrder: SortOrder): Promise<ExplorerItem[]> {
fetchChildren(sortOrder: SortOrder): ExplorerItem[] | Promise<ExplorerItem[]> {
const nestingConfig = this.configService.getValue<IFilesConfiguration>().explorer.experimental.fileNesting;
if (nestingConfig.enabled && this.nestedChildren) { return this.nestedChildren; }

if (!this._isDirectoryResolved) {
// Resolve metadata only when the mtime is needed since this can be expensive
// Mtime is only used when the sort order is 'modified'
const resolveMetadata = sortOrder === SortOrder.Modified;
this.isError = false;
try {
const stat = await this.fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
const resolved = ExplorerItem.create(this.fileService, this.configService, stat, this);
ExplorerItem.mergeLocalWithDisk(resolved, this);
} catch (e) {
this.isError = true;
throw e;
}
this._isDirectoryResolved = true;
}
// fast path when the children can be resolved sync
if (nestingConfig.enabled && this.nestedChildren) { return this.nestedChildren; }

const items: ExplorerItem[] = [];
if (nestingConfig.enabled) {
const fileChildren: [string, ExplorerItem][] = [];
const dirChildren: [string, ExplorerItem][] = [];
for (const child of this.children.entries()) {
if (child[1].isDirectory) {
dirChildren.push(child);
} else {
fileChildren.push(child);
return (async () => {
if (!this._isDirectoryResolved) {
// Resolve metadata only when the mtime is needed since this can be expensive
// Mtime is only used when the sort order is 'modified'
const resolveMetadata = sortOrder === SortOrder.Modified;
this.isError = false;
try {
const stat = await this.fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
const resolved = ExplorerItem.create(this.fileService, this.configService, stat, this);
ExplorerItem.mergeLocalWithDisk(resolved, this);
} catch (e) {
this.isError = true;
throw e;
}
this._isDirectoryResolved = true;
}

const nested = this.buildFileNester().nest(fileChildren.map(([name]) => name));
const items: ExplorerItem[] = [];
if (nestingConfig.enabled) {
const fileChildren: [string, ExplorerItem][] = [];
const dirChildren: [string, ExplorerItem][] = [];
for (const child of this.children.entries()) {
if (child[1].isDirectory) {
dirChildren.push(child);
} else {
fileChildren.push(child);
}
}

for (const [fileEntryName, fileEntryItem] of fileChildren) {
const nestedItems = nested.get(fileEntryName);
if (nestedItems !== undefined) {
fileEntryItem.nestedChildren = [];
for (const name of nestedItems.keys()) {
fileEntryItem.nestedChildren.push(assertIsDefined(this.children.get(name)));
const nested = this.buildFileNester().nest(fileChildren.map(([name]) => name));

for (const [fileEntryName, fileEntryItem] of fileChildren) {
const nestedItems = nested.get(fileEntryName);
if (nestedItems !== undefined) {
fileEntryItem.nestedChildren = [];
for (const name of nestedItems.keys()) {
fileEntryItem.nestedChildren.push(assertIsDefined(this.children.get(name)));
}
items.push(fileEntryItem);
} else {
fileEntryItem.nestedChildren = undefined;
}
items.push(fileEntryItem);
} else {
fileEntryItem.nestedChildren = undefined;
}
}

for (const [_, dirEntryItem] of dirChildren.values()) {
items.push(dirEntryItem);
for (const [_, dirEntryItem] of dirChildren.values()) {
items.push(dirEntryItem);
}
} else {
this.children.forEach(child => {
items.push(child);
});
}
} else {
this.children.forEach(child => {
items.push(child);
});
}

return items;
return items;
})();
}

// TODO:@jkearl, share one nester across all explorer items and only build on config change
Expand Down

0 comments on commit 079a688

Please sign in to comment.