mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
parent
23ac679d4f
commit
6612ae0f8b
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
|
@ -100,5 +100,9 @@
|
|||
"list",
|
||||
"git",
|
||||
"sash"
|
||||
]
|
||||
],
|
||||
"explorer.experimental.fileNesting.patterns": {
|
||||
"*.js": "$(capture).*.js",
|
||||
"bootstrap.js": "bootstrap-*.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export class ExplorerService implements IExplorerService {
|
|||
this._sortOrder = this.configurationService.getValue('explorer.sortOrder');
|
||||
this._lexicographicOptions = this.configurationService.getValue('explorer.sortOrderLexicographicOptions');
|
||||
|
||||
this.model = new ExplorerModel(this.contextService, this.uriIdentityService, this.fileService);
|
||||
this.model = new ExplorerModel(this.contextService, this.uriIdentityService, this.fileService, this.configurationService);
|
||||
this.disposables.add(this.model);
|
||||
this.disposables.add(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e)));
|
||||
|
||||
|
@ -107,7 +107,7 @@ export class ExplorerService implements IExplorerService {
|
|||
this.onFileChangesScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>())));
|
||||
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>(), e)));
|
||||
this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(async e => {
|
||||
let affected = false;
|
||||
this.model.roots.forEach(r => {
|
||||
|
@ -253,7 +253,7 @@ export class ExplorerService implements IExplorerService {
|
|||
const stat = await this.fileService.resolve(root.resource, options);
|
||||
|
||||
// Convert to model
|
||||
const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo);
|
||||
const modelStat = ExplorerItem.create(this.fileService, this.configurationService, stat, undefined, options.resolveTo);
|
||||
// Update Input with disk Stat
|
||||
ExplorerItem.mergeLocalWithDisk(modelStat, root);
|
||||
const item = root.find(resource);
|
||||
|
@ -299,12 +299,12 @@ export class ExplorerService implements IExplorerService {
|
|||
if (!p.isDirectoryResolved) {
|
||||
const stat = await this.fileService.resolve(p.resource, { resolveMetadata });
|
||||
if (stat) {
|
||||
const modelStat = ExplorerItem.create(this.fileService, stat, p.parent);
|
||||
const modelStat = ExplorerItem.create(this.fileService, this.configurationService, stat, p.parent);
|
||||
ExplorerItem.mergeLocalWithDisk(modelStat, p);
|
||||
}
|
||||
}
|
||||
|
||||
const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent);
|
||||
const childElement = ExplorerItem.create(this.fileService, this.configurationService, addedElement, p.parent);
|
||||
// Make sure to remove any previous version of the file if any
|
||||
p.removeChild(childElement);
|
||||
p.addChild(childElement);
|
||||
|
@ -366,6 +366,10 @@ export class ExplorerService implements IExplorerService {
|
|||
private async onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): Promise<void> {
|
||||
let shouldRefresh = false;
|
||||
|
||||
if (event?.affectedKeys.some(x => x.startsWith('explorer.experimental.fileNesting.'))) {
|
||||
shouldRefresh = true;
|
||||
}
|
||||
|
||||
const configSortOrder = configuration?.explorer?.sortOrder || SortOrder.Default;
|
||||
if (this._sortOrder !== configSortOrder) {
|
||||
shouldRefresh = this._sortOrder !== undefined;
|
||||
|
|
|
@ -777,6 +777,7 @@ function onErrorWithRetry(notificationService: INotificationService, error: unkn
|
|||
async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise<void> {
|
||||
const explorerService = accessor.get(IExplorerService);
|
||||
const fileService = accessor.get(IFileService);
|
||||
const configService = accessor.get(IConfigurationService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
|
@ -813,7 +814,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
|
|||
throw new Error('Parent folder is readonly.');
|
||||
}
|
||||
|
||||
const newStat = new NewExplorerItem(fileService, folder, isFolder);
|
||||
const newStat = new NewExplorerItem(fileService, configService, folder, isFolder);
|
||||
folder.addChild(newStat);
|
||||
|
||||
const onSuccess = async (value: string): Promise<void> => {
|
||||
|
|
|
@ -451,6 +451,36 @@ configurationRegistry.registerConfiguration({
|
|||
],
|
||||
'description': nls.localize('copyRelativePathSeparator', "The path separation character used when copying relative file paths."),
|
||||
'default': 'auto'
|
||||
},
|
||||
'explorer.experimental.fileNesting.enabled': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize('fileNestingEnabled', "Experimental. Controls whether file nesting is enabled in the explorer. File nesting allows for related files in a directory to be visually grouped together under a single parent file."),
|
||||
'default': false,
|
||||
},
|
||||
'explorer.experimental.fileNesting.expand': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize('fileNestingExpand', "Experimental. Controls whether file nests are automatically expended. `#explorer.fileNesting.enabled#` must be set for this to take effect."),
|
||||
'default': true,
|
||||
},
|
||||
'explorer.experimental.fileNesting.patterns': {
|
||||
'type': 'object',
|
||||
'markdownDescription': nls.localize('fileNestingPatterns', "Experimental. Controls nesting of files in the explorer. `#explorer.fileNesting.enabled#` must be set for this to take effect. Each key describes a parent file pattern and each value should be a comma separated list of children file patterns that will be nested under the parent.\n\nA single `*` in a parent pattern may be used to capture any substring, which can then be matched against using `$(capture)` in a child pattern. Child patterns may also contain one `*` to match any substring."),
|
||||
patternProperties: {
|
||||
'^[^*]*\\*?[^*]*$': {
|
||||
description: nls.localize('fileNesting.description', "Key patterns may contain a single `*` capture group which matches any string. Each value pattern may contain one `$(capture)` token to be substituted with the parent capture group and one `*` token to match any string"),
|
||||
type: 'string',
|
||||
pattern: '^([^,*]*\\*?[^,*]*)(, ?[^,*]*\\*?[^,*]*)*$',
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
'default': {
|
||||
'*.ts': '$(capture).js, $(capture).d.ts',
|
||||
'*.js': '$(capture).js.map, $(capture).min.js, $(capture).d.ts',
|
||||
'*.jsx': '$(capture).js',
|
||||
'*.tsx': '$(capture).ts',
|
||||
'tsconfig.json': 'tsconfig.*.json',
|
||||
'package.json': 'package-lock.json, .npmrc, yarn.lock, .yarnrc',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -56,6 +56,7 @@ import { Codicon } from 'vs/base/common/codicons';
|
|||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
interface IExplorerViewColors extends IColorMapping {
|
||||
listDropBackground?: ColorValue | undefined;
|
||||
|
@ -185,6 +186,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
|
|||
@IMenuService private readonly menuService: IMenuService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IExplorerService private readonly explorerService: IExplorerService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IClipboardService private clipboardService: IClipboardService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
|
@ -380,6 +382,8 @@ export class ExplorerView extends ViewPane implements IExplorerView {
|
|||
|
||||
const isCompressionEnabled = () => this.configurationService.getValue<boolean>('explorer.compactFolders');
|
||||
|
||||
const getFileNestingSettings = () => this.configurationService.getValue<IFilesConfiguration>().explorer.experimental.fileNesting;
|
||||
|
||||
this.tree = <WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>>this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer],
|
||||
this.instantiationService.createInstance(ExplorerDataSource), {
|
||||
compressionEnabled: isCompressionEnabled(),
|
||||
|
@ -405,7 +409,26 @@ export class ExplorerView extends ViewPane implements IExplorerView {
|
|||
filter: this.filter,
|
||||
sorter: this.instantiationService.createInstance(FileSorter),
|
||||
dnd: this.instantiationService.createInstance(FileDragAndDrop),
|
||||
collapseByDefault: (e) => {
|
||||
if (e instanceof ExplorerItem) {
|
||||
if (e.hasNests && getFileNestingSettings().expand) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
autoExpandSingleChildren: true,
|
||||
expandOnlyOnTwistieClick: (e: unknown) => {
|
||||
if (e instanceof ExplorerItem) {
|
||||
if (e.hasNests) {
|
||||
return true;
|
||||
}
|
||||
else if (this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT,
|
||||
overrideStyles: {
|
||||
listBackground: SIDE_BAR_BACKGROUND
|
||||
|
@ -600,9 +623,23 @@ export class ExplorerView extends ViewPane implements IExplorerView {
|
|||
}
|
||||
|
||||
const toRefresh = item || this.tree.getInput();
|
||||
return this.tree.updateChildren(toRefresh, recursive, false, {
|
||||
diffIdentityProvider: identityProvider
|
||||
});
|
||||
if (this.configurationService.getValue<IFilesConfiguration>()?.explorer?.experimental?.fileNesting?.enabled) {
|
||||
return (async () => {
|
||||
try {
|
||||
await this.tree.updateChildren(toRefresh, recursive, false, {
|
||||
diffIdentityProvider: identityProvider
|
||||
});
|
||||
} catch (e) {
|
||||
this.notificationService.error(e);
|
||||
console.error('Unepxected error', e, 'in refreshing explorer. This may be due to experimental file nesting.');
|
||||
return;
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
return this.tree.updateChildren(toRefresh, recursive, false, {
|
||||
diffIdentityProvider: identityProvider
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
override getOptimalWidth(): number {
|
||||
|
|
|
@ -75,6 +75,7 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
|
|||
|
||||
constructor(
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IConfigurationService private readonly configService: IConfigurationService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
|
@ -83,7 +84,7 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
|
|||
) { }
|
||||
|
||||
hasChildren(element: ExplorerItem | ExplorerItem[]): boolean {
|
||||
return Array.isArray(element) || element.isDirectory;
|
||||
return Array.isArray(element) || element.hasChildren;
|
||||
}
|
||||
|
||||
getChildren(element: ExplorerItem | ExplorerItem[]): Promise<ExplorerItem[]> {
|
||||
|
@ -106,7 +107,7 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
|
|||
if (element instanceof ExplorerItem && element.isRoot) {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
|
||||
// Single folder create a dummy explorer item to show error
|
||||
const placeholder = new ExplorerItem(element.resource, this.fileService, undefined, false);
|
||||
const placeholder = new ExplorerItem(element.resource, this.fileService, this.configService, undefined, undefined, false);
|
||||
placeholder.isError = true;
|
||||
return [placeholder];
|
||||
} else {
|
||||
|
|
195
src/vs/workbench/contrib/files/common/explorerFileNestingTrie.ts
Normal file
195
src/vs/workbench/contrib/files/common/explorerFileNestingTrie.ts
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* A sort of double-ended trie, used to efficiently query for matches to "star" patterns, where
|
||||
* a given key representas a parent and may contain a capturing group ("*"), which can then be
|
||||
* referenced via the token "$(capture)" in associated child patterns.
|
||||
*
|
||||
* The generated tree will have at most two levels, as subtrees are flattened rather than nested.
|
||||
*
|
||||
* Example:
|
||||
* The config: [
|
||||
* [ *.ts , [ $(capture).*.ts ; $(capture).js ] ]
|
||||
* [ *.js , [ $(capture).min.js ] ] ]
|
||||
* Nests the files: [ a.ts ; a.d.ts ; a.js ; a.min.js ; b.ts ; b.min.js ]
|
||||
* As:
|
||||
* - a.ts => [ a.d.ts ; a.js ; a.min.js ]
|
||||
* - b.ts => [ ]
|
||||
* - b.min.ts => [ ]
|
||||
*/
|
||||
export class ExplorerFileNestingTrie {
|
||||
private root = new PreTrie();
|
||||
|
||||
constructor(config: [string, string[]][]) {
|
||||
for (const [parentPattern, childPatterns] of config) {
|
||||
for (const childPattern of childPatterns) {
|
||||
this.root.add(parentPattern, childPattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.root.toString();
|
||||
}
|
||||
|
||||
nest(files: string[]): Map<string, Set<string>> {
|
||||
const parentFinder = new PreTrie();
|
||||
|
||||
for (const potentialParent of files) {
|
||||
const children = this.root.get(potentialParent);
|
||||
for (const child of children) {
|
||||
parentFinder.add(child, potentialParent);
|
||||
}
|
||||
}
|
||||
|
||||
const findAllRootAncestors = (file: string, seen: Set<string> = new Set()): string[] => {
|
||||
if (seen.has(file)) { return []; }
|
||||
seen.add(file);
|
||||
|
||||
const ancestors = parentFinder.get(file);
|
||||
if (ancestors.length === 0) {
|
||||
return [file];
|
||||
}
|
||||
|
||||
if (ancestors.length === 1 && ancestors[0] === file) {
|
||||
return [file];
|
||||
}
|
||||
|
||||
return ancestors.flatMap(a => findAllRootAncestors(a, seen));
|
||||
};
|
||||
|
||||
const result = new Map<string, Set<string>>();
|
||||
for (const file of files) {
|
||||
let ancestors = findAllRootAncestors(file);
|
||||
if (ancestors.length === 0) { ancestors = [file]; }
|
||||
for (const ancestor of ancestors) {
|
||||
let existing = result.get(ancestor);
|
||||
if (!existing) { result.set(ancestor, existing = new Set()); }
|
||||
if (file !== ancestor) {
|
||||
existing.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/** Export for test only. */
|
||||
export class PreTrie {
|
||||
private value: SufTrie = new SufTrie();
|
||||
|
||||
private map: Map<string, PreTrie> = new Map();
|
||||
|
||||
constructor() { }
|
||||
|
||||
add(key: string, value: string) {
|
||||
if (key === '') {
|
||||
this.value.add(key, value);
|
||||
} else if (key[0] === '*') {
|
||||
this.value.add(key, value);
|
||||
} else {
|
||||
const head = key[0];
|
||||
const rest = key.slice(1);
|
||||
let existing = this.map.get(head);
|
||||
if (!existing) {
|
||||
this.map.set(head, existing = new PreTrie());
|
||||
}
|
||||
existing.add(rest, value);
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string): string[] {
|
||||
const results: string[] = [];
|
||||
results.push(...this.value.get(key));
|
||||
|
||||
const head = key[0];
|
||||
const rest = key.slice(1);
|
||||
const existing = this.map.get(head);
|
||||
if (existing) {
|
||||
results.push(...existing.get(rest));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
toString(indentation = ''): string {
|
||||
const lines = [];
|
||||
if (this.value.hasItems) {
|
||||
lines.push('* => \n' + this.value.toString(indentation + ' '));
|
||||
}
|
||||
[...this.map.entries()].map(([key, trie]) =>
|
||||
lines.push('^' + key + ' => \n' + trie.toString(indentation + ' ')));
|
||||
return lines.map(l => indentation + l).join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/** Export for test only. */
|
||||
export class SufTrie {
|
||||
private star: string[] = [];
|
||||
private epsilon: string[] = [];
|
||||
|
||||
private map: Map<string, SufTrie> = new Map();
|
||||
hasItems: boolean = false;
|
||||
|
||||
constructor() { }
|
||||
|
||||
add(key: string, value: string) {
|
||||
this.hasItems = true;
|
||||
if (key === '*') {
|
||||
this.star.push(value);
|
||||
} else if (key === '') {
|
||||
this.epsilon.push(value);
|
||||
} else {
|
||||
const tail = key[key.length - 1];
|
||||
const rest = key.slice(0, key.length - 1);
|
||||
if (tail === '*') {
|
||||
throw Error('Unexpected star in SufTrie key: ' + key);
|
||||
} else {
|
||||
let existing = this.map.get(tail);
|
||||
if (!existing) {
|
||||
this.map.set(tail, existing = new SufTrie());
|
||||
}
|
||||
existing.add(rest, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string): string[] {
|
||||
const results: string[] = [];
|
||||
if (key === '') {
|
||||
results.push(...this.epsilon);
|
||||
}
|
||||
if (this.star.length) {
|
||||
results.push(...this.star.map(x => x.replace('$(capture)', key)));
|
||||
}
|
||||
|
||||
const tail = key[key.length - 1];
|
||||
const rest = key.slice(0, key.length - 1);
|
||||
const existing = this.map.get(tail);
|
||||
if (existing) {
|
||||
results.push(...existing.get(rest));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
toString(indentation = ''): string {
|
||||
const lines = [];
|
||||
if (this.star.length) {
|
||||
lines.push('* => ' + this.star.join('; '));
|
||||
}
|
||||
|
||||
if (this.epsilon.length) {
|
||||
// allow-any-unicode-next-line
|
||||
lines.push('ε => ' + this.epsilon.join('; '));
|
||||
}
|
||||
|
||||
[...this.map.entries()].map(([key, trie]) =>
|
||||
lines.push(key + '$' + ' => \n' + trie.toString(indentation + ' ')));
|
||||
|
||||
return lines.map(l => indentation + l).join('\n');
|
||||
}
|
||||
}
|
|
@ -15,8 +15,11 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
|||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { SortOrder } from 'vs/workbench/contrib/files/common/files';
|
||||
import { IFilesConfiguration, SortOrder } from 'vs/workbench/contrib/files/common/files';
|
||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
import { ExplorerFileNestingTrie } from 'vs/workbench/contrib/files/common/explorerFileNestingTrie';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
|
||||
export class ExplorerModel implements IDisposable {
|
||||
|
||||
|
@ -27,10 +30,11 @@ export class ExplorerModel implements IDisposable {
|
|||
constructor(
|
||||
private readonly contextService: IWorkspaceContextService,
|
||||
private readonly uriIdentityService: IUriIdentityService,
|
||||
fileService: IFileService
|
||||
fileService: IFileService,
|
||||
configService: IConfigurationService,
|
||||
) {
|
||||
const setRoots = () => this._roots = this.contextService.getWorkspace().folders
|
||||
.map(folder => new ExplorerItem(folder.uri, fileService, undefined, true, false, false, folder.name));
|
||||
.map(folder => new ExplorerItem(folder.uri, fileService, configService, undefined, true, false, false, folder.name));
|
||||
setRoots();
|
||||
|
||||
this._listener = this.contextService.onDidChangeWorkspaceFolders(() => {
|
||||
|
@ -83,9 +87,12 @@ export class ExplorerItem {
|
|||
public isError = false;
|
||||
private _isExcluded = false;
|
||||
|
||||
private nestedChildren: ExplorerItem[] | undefined;
|
||||
|
||||
constructor(
|
||||
public resource: URI,
|
||||
private readonly fileService: IFileService,
|
||||
private readonly configService: IConfigurationService,
|
||||
private _parent: ExplorerItem | undefined,
|
||||
private _isDirectory?: boolean,
|
||||
private _isSymbolicLink?: boolean,
|
||||
|
@ -112,6 +119,14 @@ export class ExplorerItem {
|
|||
this._isExcluded = value;
|
||||
}
|
||||
|
||||
get hasChildren() {
|
||||
return this.isDirectory || this.hasNests;
|
||||
}
|
||||
|
||||
get hasNests() {
|
||||
return !!(this.nestedChildren?.length);
|
||||
}
|
||||
|
||||
get isDirectoryResolved(): boolean {
|
||||
return this._isDirectoryResolved;
|
||||
}
|
||||
|
@ -179,8 +194,8 @@ export class ExplorerItem {
|
|||
return this === this.root;
|
||||
}
|
||||
|
||||
static create(fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {
|
||||
const stat = new ExplorerItem(raw.resource, fileService, parent, raw.isDirectory, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, !raw.isFile && !raw.isDirectory);
|
||||
static create(fileService: IFileService, configService: IConfigurationService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {
|
||||
const stat = new ExplorerItem(raw.resource, fileService, configService, parent, raw.isDirectory, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, !raw.isFile && !raw.isDirectory);
|
||||
|
||||
// Recursively add children if present
|
||||
if (stat.isDirectory) {
|
||||
|
@ -195,7 +210,7 @@ export class ExplorerItem {
|
|||
// Recurse into children
|
||||
if (raw.children) {
|
||||
for (let i = 0, len = raw.children.length; i < len; i++) {
|
||||
const child = ExplorerItem.create(fileService, raw.children[i], stat, resolveTo);
|
||||
const child = ExplorerItem.create(fileService, configService, raw.children[i], stat, resolveTo);
|
||||
stat.addChild(child);
|
||||
}
|
||||
}
|
||||
|
@ -282,6 +297,9 @@ export class ExplorerItem {
|
|||
}
|
||||
|
||||
async fetchChildren(sortOrder: SortOrder): 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'
|
||||
|
@ -289,7 +307,7 @@ export class ExplorerItem {
|
|||
this.isError = false;
|
||||
try {
|
||||
const stat = await this.fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
|
||||
const resolved = ExplorerItem.create(this.fileService, stat, this);
|
||||
const resolved = ExplorerItem.create(this.fileService, this.configService, stat, this);
|
||||
ExplorerItem.mergeLocalWithDisk(resolved, this);
|
||||
} catch (e) {
|
||||
this.isError = true;
|
||||
|
@ -299,9 +317,46 @@ export class ExplorerItem {
|
|||
}
|
||||
|
||||
const items: ExplorerItem[] = [];
|
||||
this.children.forEach(child => {
|
||||
items.push(child);
|
||||
});
|
||||
if (nestingConfig.enabled) {
|
||||
const patterns = Object.entries(nestingConfig.patterns).map(
|
||||
([parentPattern, childrenPatterns]) =>
|
||||
[parentPattern.trim(), childrenPatterns.split(',').map(p => p.trim())] as [string, string[]]);
|
||||
|
||||
const nester = new ExplorerFileNestingTrie(patterns);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const nested = nester.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;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [_, dirEntryItem] of dirChildren.values()) {
|
||||
items.push(dirEntryItem);
|
||||
}
|
||||
} else {
|
||||
this.children.forEach(child => {
|
||||
items.push(child);
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@ -410,8 +465,8 @@ export class ExplorerItem {
|
|||
}
|
||||
|
||||
export class NewExplorerItem extends ExplorerItem {
|
||||
constructor(fileService: IFileService, parent: ExplorerItem, isDirectory: boolean) {
|
||||
super(URI.file(''), fileService, parent, isDirectory);
|
||||
constructor(fileService: IFileService, configService: IConfigurationService, parent: ExplorerItem, isDirectory: boolean) {
|
||||
super(URI.file(''), fileService, configService, parent, isDirectory);
|
||||
this._isDirectoryResolved = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,13 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb
|
|||
badges: boolean;
|
||||
};
|
||||
incrementalNaming: 'simple' | 'smart';
|
||||
experimental: {
|
||||
fileNesting: {
|
||||
enabled: boolean;
|
||||
expand: boolean;
|
||||
patterns: { [parent: string]: string }
|
||||
}
|
||||
}
|
||||
};
|
||||
editor: IEditorOptions;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,409 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { PreTrie, ExplorerFileNestingTrie, SufTrie } from 'vs/workbench/contrib/files/common/explorerFileNestingTrie';
|
||||
import * as assert from 'assert';
|
||||
|
||||
suite('SufTrie', () => {
|
||||
test('exactMatches', () => {
|
||||
const t = new SufTrie();
|
||||
t.add('.npmrc', 'MyKey');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['MyKey']);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), []);
|
||||
});
|
||||
|
||||
test('starMatches', () => {
|
||||
const t = new SufTrie();
|
||||
t.add('*.npmrc', 'MyKey');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['MyKey']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['MyKey']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['MyKey']);
|
||||
});
|
||||
|
||||
test('starSubstitutes', () => {
|
||||
const t = new SufTrie();
|
||||
t.add('*.npmrc', '$(capture).json');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['.json']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['a.json']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['a.b.c.d.json']);
|
||||
});
|
||||
|
||||
test('multiMatches', () => {
|
||||
const t = new SufTrie();
|
||||
t.add('*.npmrc', 'Key1');
|
||||
t.add('*.json', 'Key2');
|
||||
t.add('*d.npmrc', 'Key3');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['Key1']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('.json'), ['Key2']);
|
||||
assert.deepStrictEqual(t.get('a.json'), ['Key2']);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['Key1']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['Key1', 'Key3']);
|
||||
});
|
||||
|
||||
test('multiSubstitutes', () => {
|
||||
const t = new SufTrie();
|
||||
t.add('*.npmrc', 'Key1.$(capture).js');
|
||||
t.add('*.json', 'Key2.$(capture).js');
|
||||
t.add('*d.npmrc', 'Key3.$(capture).js');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['Key1..js']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('.json'), ['Key2..js']);
|
||||
assert.deepStrictEqual(t.get('a.json'), ['Key2.a.js']);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['Key1.a.js']);
|
||||
assert.deepStrictEqual(t.get('a.b.cd.npmrc'), ['Key1.a.b.cd.js', 'Key3.a.b.c.js']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['Key1.a.b.c.d.js', 'Key3.a.b.c..js']);
|
||||
});
|
||||
});
|
||||
|
||||
suite('PreTrie', () => {
|
||||
test('exactMatches', () => {
|
||||
const t = new PreTrie();
|
||||
t.add('.npmrc', 'MyKey');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['MyKey']);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), []);
|
||||
});
|
||||
|
||||
test('starMatches', () => {
|
||||
const t = new PreTrie();
|
||||
t.add('*.npmrc', 'MyKey');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['MyKey']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['MyKey']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['MyKey']);
|
||||
});
|
||||
|
||||
test('starSubstitutes', () => {
|
||||
const t = new PreTrie();
|
||||
t.add('*.npmrc', '$(capture).json');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['.json']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['a.json']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['a.b.c.d.json']);
|
||||
});
|
||||
|
||||
test('multiMatches', () => {
|
||||
const t = new PreTrie();
|
||||
t.add('*.npmrc', 'Key1');
|
||||
t.add('*.json', 'Key2');
|
||||
t.add('*d.npmrc', 'Key3');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['Key1']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('.json'), ['Key2']);
|
||||
assert.deepStrictEqual(t.get('a.json'), ['Key2']);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['Key1']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['Key1', 'Key3']);
|
||||
});
|
||||
|
||||
test('multiSubstitutes', () => {
|
||||
const t = new PreTrie();
|
||||
t.add('*.npmrc', 'Key1.$(capture).js');
|
||||
t.add('*.json', 'Key2.$(capture).js');
|
||||
t.add('*d.npmrc', 'Key3.$(capture).js');
|
||||
assert.deepStrictEqual(t.get('.npmrc'), ['Key1..js']);
|
||||
assert.deepStrictEqual(t.get('npmrc'), []);
|
||||
assert.deepStrictEqual(t.get('.npmrcs'), []);
|
||||
assert.deepStrictEqual(t.get('.json'), ['Key2..js']);
|
||||
assert.deepStrictEqual(t.get('a.json'), ['Key2.a.js']);
|
||||
assert.deepStrictEqual(t.get('a.npmrc'), ['Key1.a.js']);
|
||||
assert.deepStrictEqual(t.get('a.b.cd.npmrc'), ['Key1.a.b.cd.js', 'Key3.a.b.c.js']);
|
||||
assert.deepStrictEqual(t.get('a.b.c.d.npmrc'), ['Key1.a.b.c.d.js', 'Key3.a.b.c..js']);
|
||||
});
|
||||
|
||||
|
||||
test('emptyMatches', () => {
|
||||
const t = new PreTrie();
|
||||
t.add('package*json', 'package');
|
||||
assert.deepStrictEqual(t.get('package.json'), ['package']);
|
||||
assert.deepStrictEqual(t.get('packagejson'), ['package']);
|
||||
assert.deepStrictEqual(t.get('package-lock.json'), ['package']);
|
||||
});
|
||||
});
|
||||
|
||||
suite('StarTrie', () => {
|
||||
const assertMapEquals = (actual: Map<string, Set<string>>, expected: Record<string, string[]>) => {
|
||||
const actualStr = [...actual.entries()].map(e => `${e[0]} => [${[...e[1].keys()].join()}]`);
|
||||
const expectedStr = Object.entries(expected).map(e => `${e[0]}: [${[e[1]].join()}]`);
|
||||
const bigMsg = actualStr + '===' + expectedStr;
|
||||
assert.strictEqual(actual.size, Object.keys(expected).length, bigMsg);
|
||||
for (const parent of actual.keys()) {
|
||||
const act = actual.get(parent)!;
|
||||
const exp = expected[parent];
|
||||
const str = [...act.keys()].join() + '===' + exp.join();
|
||||
const msg = bigMsg + '\n' + str;
|
||||
assert(act.size === exp.length, msg);
|
||||
for (const child of exp) {
|
||||
assert(act.has(child), msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test('does added extension nesting', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['*', ['$(capture).*']],
|
||||
]);
|
||||
const nesting = t.nest([
|
||||
'file',
|
||||
'file.json',
|
||||
'boop.test',
|
||||
'boop.test1',
|
||||
'boop.test.1',
|
||||
'beep',
|
||||
'beep.test1',
|
||||
'beep.boop.test1',
|
||||
'beep.boop.test2',
|
||||
'beep.boop.a',
|
||||
]);
|
||||
assertMapEquals(nesting, {
|
||||
'file': ['file.json'],
|
||||
'boop.test': ['boop.test.1'],
|
||||
'boop.test1': [],
|
||||
'beep': ['beep.test1', 'beep.boop.test1', 'beep.boop.test2', 'beep.boop.a']
|
||||
});
|
||||
});
|
||||
|
||||
test('does ext specific nesting', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['*.ts', ['$(capture).js']],
|
||||
['*.js', ['$(capture).map']],
|
||||
]);
|
||||
const nesting = t.nest([
|
||||
'a.ts',
|
||||
'a.js',
|
||||
'a.jss',
|
||||
'ab.js',
|
||||
'b.js',
|
||||
'b.map',
|
||||
'c.ts',
|
||||
'c.js',
|
||||
'c.map',
|
||||
'd.ts',
|
||||
'd.map',
|
||||
]);
|
||||
assertMapEquals(nesting, {
|
||||
'a.ts': ['a.js'],
|
||||
'ab.js': [],
|
||||
'a.jss': [],
|
||||
'b.js': ['b.map'],
|
||||
'c.ts': ['c.js', 'c.map'],
|
||||
'd.ts': [],
|
||||
'd.map': [],
|
||||
});
|
||||
});
|
||||
|
||||
test('handles loops', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['*.a', ['$(capture).b', '$(capture).c']],
|
||||
['*.b', ['$(capture).a']],
|
||||
['*.c', ['$(capture).d']],
|
||||
|
||||
['*.aa', ['$(capture).bb']],
|
||||
['*.bb', ['$(capture).cc', '$(capture).dd']],
|
||||
['*.cc', ['$(capture).aa']],
|
||||
['*.dd', ['$(capture).ee']],
|
||||
]);
|
||||
const nesting = t.nest([
|
||||
'.a', '.b', '.c', '.d',
|
||||
'a.a', 'a.b', 'a.d',
|
||||
'a.aa', 'a.bb', 'a.cc',
|
||||
'b.aa', 'b.bb',
|
||||
'c.bb', 'c.cc',
|
||||
'd.aa', 'd.cc',
|
||||
'e.aa', 'e.bb', 'e.dd', 'e.ee',
|
||||
'f.aa', 'f.bb', 'f.cc', 'f.dd', 'f.ee',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting, {
|
||||
'.a': [], '.b': [], '.c': [], '.d': [],
|
||||
'a.a': [], 'a.b': [], 'a.d': [],
|
||||
'a.aa': [], 'a.bb': [], 'a.cc': [],
|
||||
'b.aa': ['b.bb'],
|
||||
'c.bb': ['c.cc'],
|
||||
'd.cc': ['d.aa'],
|
||||
'e.aa': ['e.bb', 'e.dd', 'e.ee'],
|
||||
'f.aa': [], 'f.bb': [], 'f.cc': [], 'f.dd': [], 'f.ee': []
|
||||
});
|
||||
});
|
||||
|
||||
test('does general bidirectional suffix matching', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['*-vsdoc.js', ['$(capture).js']],
|
||||
['*.js', [`$(capture)-vscdoc.js`]],
|
||||
]);
|
||||
|
||||
const nesting = t.nest([
|
||||
'a-vsdoc.js',
|
||||
'a.js',
|
||||
'b.js',
|
||||
'b-vscdoc.js',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting, {
|
||||
'a-vsdoc.js': ['a.js'],
|
||||
'b.js': ['b-vscdoc.js'],
|
||||
});
|
||||
});
|
||||
|
||||
test('does general bidirectional prefix matching', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['vsdoc-*.js', ['$(capture).js']],
|
||||
['*.js', [`vscdoc-$(capture).js`]],
|
||||
]);
|
||||
|
||||
const nesting = t.nest([
|
||||
'vsdoc-a.js',
|
||||
'a.js',
|
||||
'b.js',
|
||||
'vscdoc-b.js',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting, {
|
||||
'vsdoc-a.js': ['a.js'],
|
||||
'b.js': ['vscdoc-b.js'],
|
||||
});
|
||||
});
|
||||
|
||||
test('does general bidirectional general matching', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['foo-*-bar.js', ['$(capture).js']],
|
||||
['*.js', [`bib-$(capture)-bap.js`]],
|
||||
]);
|
||||
|
||||
const nesting = t.nest([
|
||||
'foo-a-bar.js',
|
||||
'a.js',
|
||||
'b.js',
|
||||
'bib-b-bap.js',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting, {
|
||||
'foo-a-bar.js': ['a.js'],
|
||||
'b.js': ['bib-b-bap.js'],
|
||||
});
|
||||
});
|
||||
|
||||
test('does extension specific path segment matching', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['*.js', ['$(capture).*.js']],
|
||||
]);
|
||||
|
||||
const nesting = t.nest([
|
||||
'foo.js',
|
||||
'foo.test.js',
|
||||
'fooTest.js',
|
||||
'bar.js.js',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting, {
|
||||
'foo.js': ['foo.test.js'],
|
||||
'fooTest.js': [],
|
||||
'bar.js.js': [],
|
||||
});
|
||||
});
|
||||
|
||||
test('does exact match nesting', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['package.json', ['.npmrc', 'npm-shrinkwrap.json', 'yarn.lock', '.yarnclean', '.yarnignore', '.yarn-integrity', '.yarnrc']],
|
||||
['bower.json', ['.bowerrc']],
|
||||
]);
|
||||
|
||||
const nesting = t.nest([
|
||||
'package.json',
|
||||
'.npmrc', 'npm-shrinkwrap.json', 'yarn.lock',
|
||||
'.bowerrc',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting, {
|
||||
'package.json': [
|
||||
'.npmrc', 'npm-shrinkwrap.json', 'yarn.lock'],
|
||||
'.bowerrc': [],
|
||||
});
|
||||
});
|
||||
|
||||
test('eslint test', () => {
|
||||
const t = new ExplorerFileNestingTrie([
|
||||
['.eslintrc*', ['.eslint*']],
|
||||
]);
|
||||
|
||||
const nesting1 = t.nest([
|
||||
'.eslintrc.json',
|
||||
'.eslintignore',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting1, {
|
||||
'.eslintrc.json': ['.eslintignore'],
|
||||
});
|
||||
|
||||
const nesting2 = t.nest([
|
||||
'.eslintrc',
|
||||
'.eslintignore',
|
||||
]);
|
||||
|
||||
assertMapEquals(nesting2, {
|
||||
'.eslintrc': ['.eslintignore'],
|
||||
});
|
||||
});
|
||||
|
||||
test.skip('is fast', () => {
|
||||
const bigNester = new ExplorerFileNestingTrie([
|
||||
['*', ['$(capture).*']],
|
||||
['*.js', ['$(capture).*.js', '$(capture).map']],
|
||||
['*.jsx', ['$(capture).js']],
|
||||
['*.ts', ['$(capture).js', '$(capture).*.ts']],
|
||||
['*.tsx', ['$(capture).js']],
|
||||
['*.css', ['$(capture).*.css', '$(capture).map']],
|
||||
['*.html', ['$(capture).*.html']],
|
||||
['*.htm', ['$(capture).*.htm']],
|
||||
['*.less', ['$(capture).*.less', '$(capture).css']],
|
||||
['*.scss', ['$(capture).*.scss', '$(capture).css']],
|
||||
['*.sass', ['$(capture).css']],
|
||||
['*.styl', ['$(capture).css']],
|
||||
['*.coffee', ['$(capture).*.coffee', '$(capture).js']],
|
||||
['*.iced', ['$(capture).*.iced', '$(capture).js']],
|
||||
['*.config', ['$(capture).*.config']],
|
||||
['*.cs', ['$(capture).*.cs', '$(capture).cs.d.ts']],
|
||||
['*.vb', ['$(capture).*.vb']],
|
||||
['*.json', ['$(capture).*.json']],
|
||||
['*.md', ['$(capture).html']],
|
||||
['*.mdown', ['$(capture).html']],
|
||||
['*.markdown', ['$(capture).html']],
|
||||
['*.mdwn', ['$(capture).html']],
|
||||
['*.svg', ['$(capture).svgz']],
|
||||
['*.a', ['$(capture).b']],
|
||||
['*.b', ['$(capture).a']],
|
||||
['*.resx', ['$(capture).designer.cs']],
|
||||
['package.json', ['.npmrc', 'npm-shrinkwrap.json', 'yarn.lock', '.yarnclean', '.yarnignore', '.yarn-integrity', '.yarnrc']],
|
||||
['bower.json', ['.bowerrc']],
|
||||
['*-vsdoc.js', ['$(capture).js']],
|
||||
['*.tt', ['$(capture).*']]
|
||||
]);
|
||||
|
||||
const bigFiles = Array.from({ length: 50000 / 6 }).map((_, i) => [
|
||||
'file' + i + '.js',
|
||||
'file' + i + '.map',
|
||||
'file' + i + '.css',
|
||||
'file' + i + '.ts',
|
||||
'file' + i + '.d.ts',
|
||||
'file' + i + '.jsx',
|
||||
]).flat();
|
||||
|
||||
const start = performance.now();
|
||||
// const _bigResult =
|
||||
bigNester.nest(bigFiles);
|
||||
const end = performance.now();
|
||||
assert(end - start < 1000, 'too slow...' + (end - start));
|
||||
// console.log(bigResult)
|
||||
});
|
||||
});
|
|
@ -11,13 +11,15 @@ import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions
|
|||
import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
|
||||
import { toResource } from 'vs/base/test/common/utils';
|
||||
import { TestFileService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
|
||||
|
||||
suite('Files - View Model', function () {
|
||||
|
||||
const fileService = new TestFileService();
|
||||
const configService = new TestConfigurationService();
|
||||
function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem {
|
||||
return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, false, name, mtime);
|
||||
return new ExplorerItem(toResource.call(this, path), fileService, configService, undefined, isFolder, false, false, name, mtime);
|
||||
}
|
||||
|
||||
const pathService = new TestPathService();
|
||||
|
@ -248,19 +250,19 @@ suite('Files - View Model', function () {
|
|||
});
|
||||
|
||||
test('Merge Local with Disk', function () {
|
||||
const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, false, 'to', Date.now());
|
||||
const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, false, 'to', Date.now());
|
||||
const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, configService, undefined, true, false, false, 'to', Date.now());
|
||||
const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, configService, undefined, true, false, false, 'to', Date.now());
|
||||
|
||||
// Merge Properties
|
||||
ExplorerItem.mergeLocalWithDisk(merge2, merge1);
|
||||
assert.strictEqual(merge1.mtime, merge2.mtime);
|
||||
|
||||
// Merge Child when isDirectoryResolved=false is a no-op
|
||||
merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, false, 'foo.html', Date.now()));
|
||||
merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, configService, undefined, true, false, false, 'foo.html', Date.now()));
|
||||
ExplorerItem.mergeLocalWithDisk(merge2, merge1);
|
||||
|
||||
// Merge Child with isDirectoryResolved=true
|
||||
const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, false, 'foo.html', Date.now());
|
||||
const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, configService, undefined, true, false, false, 'foo.html', Date.now());
|
||||
merge2.removeChild(child);
|
||||
merge2.addChild(child);
|
||||
(<any>merge2)._isDirectoryResolved = true;
|
||||
|
|
|
@ -14,12 +14,14 @@ import { CompressedNavigationController } from 'vs/workbench/contrib/files/brows
|
|||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { provideDecorations } from 'vs/workbench/contrib/files/browser/views/explorerDecorationsProvider';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
const $ = dom.$;
|
||||
|
||||
const fileService = new TestFileService();
|
||||
const configService = new TestConfigurationService();
|
||||
|
||||
function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number, isSymLink = false, isUnknown = false): ExplorerItem {
|
||||
return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, isSymLink, false, name, mtime, isUnknown);
|
||||
return new ExplorerItem(toResource.call(this, path), fileService, configService, undefined, isFolder, isSymLink, false, name, mtime, isUnknown);
|
||||
}
|
||||
|
||||
suite('Files - ExplorerView', () => {
|
||||
|
|
Loading…
Reference in a new issue