Skip to content

Commit 8bc277f

Browse files
committed
explorer: read all capabilites like a proper gentlemen
#48258
1 parent 94b8435 commit 8bc277f

File tree

8 files changed

+58
-75
lines changed

8 files changed

+58
-75
lines changed

src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
88
import { IEditorViewState } from 'vs/editor/common/editorCommon';
99
import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor';
1010
import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles';
11-
import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
11+
import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
1212
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
1313
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
1414
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -25,7 +25,6 @@ import { timeout } from 'vs/base/common/async';
2525
import { withNullAsUndefined } from 'vs/base/common/types';
2626
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
2727
import { isEqualOrParent, joinPath } from 'vs/base/common/resources';
28-
import { IExplorerService } from 'vs/workbench/contrib/files/common/files';
2928

3029
export class FileEditorTracker extends Disposable implements IWorkbenchContribution {
3130

@@ -44,7 +43,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
4443
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
4544
@IHostService private readonly hostService: IHostService,
4645
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
47-
@IExplorerService private readonly explorerService: IExplorerService
4846
) {
4947
super();
5048

@@ -108,7 +106,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
108106
if (oldResource.toString() === resource.toString()) {
109107
reopenFileResource = newResource; // file got moved
110108
} else {
111-
const index = this.getIndexOfPath(resource.path, oldResource.path, this.explorerService.shouldIgnoreCase(resource));
109+
const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive);
110+
const index = this.getIndexOfPath(resource.path, oldResource.path, ignoreCase);
112111
reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved
113112
}
114113

src/vs/workbench/contrib/files/browser/fileActions.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -870,8 +870,9 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
870870
throw new Error('Parent folder is readonly.');
871871
}
872872

873-
const newStat = new NewExplorerItem(explorerService, folder, isFolder);
874-
await folder.fetchChildren(fileService, explorerService);
873+
const newStat = new NewExplorerItem(fileService, folder, isFolder);
874+
const sortOrder = explorerService.sortOrder;
875+
await folder.fetchChildren(sortOrder);
875876

876877
folder.addChild(newStat);
877878

src/vs/workbench/contrib/files/browser/views/explorerViewer.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob';
99
import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
1010
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
1111
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
12-
import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
12+
import { IFileService, FileKind, FileOperationError, FileOperationResult, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
1313
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
1414
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
1515
import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
@@ -90,12 +90,13 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
9090
return Promise.resolve(element);
9191
}
9292

93-
const promise = element.fetchChildren(this.fileService, this.explorerService).then(undefined, e => {
93+
const sortOrder = this.explorerService.sortOrder;
94+
const promise = element.fetchChildren(sortOrder).then(undefined, e => {
9495

9596
if (element instanceof ExplorerItem && element.isRoot) {
9697
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
9798
// Single folder create a dummy explorer item to show error
98-
const placeholder = new ExplorerItem(element.resource, this.explorerService, undefined, false);
99+
const placeholder = new ExplorerItem(element.resource, this.fileService, undefined, false);
99100
placeholder.isError = true;
100101
return [placeholder];
101102
} else {
@@ -921,17 +922,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
921922

922923
// Check for name collisions
923924
const targetNames = new Set<string>();
925+
const caseSensitive = this.fileService.hasCapability(target.resource, FileSystemProviderCapabilities.PathCaseSensitive);
924926
if (targetStat.children) {
925-
const ignoreCase = this.explorerService.shouldIgnoreCase(target.resource);
926927
targetStat.children.forEach(child => {
927-
targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name);
928+
targetNames.add(caseSensitive ? child.name : child.name.toLowerCase());
928929
});
929930
}
930931

931932
// Run add in sequence
932933
const addPromisesFactory: ITask<Promise<void>>[] = [];
933934
await Promise.all(resources.map(async resource => {
934-
if (targetNames.has(this.explorerService.shouldIgnoreCase(resource) ? basename(resource).toLowerCase() : basename(resource))) {
935+
if (targetNames.has(caseSensitive ? basename(resource) : basename(resource).toLowerCase())) {
935936
const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource)));
936937
if (!confirmationResult.confirmed) {
937938
return;

src/vs/workbench/contrib/files/common/explorerModel.ts

+16-18
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
1414
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
1515
import { memoize } from 'vs/base/common/decorators';
1616
import { Emitter, Event } from 'vs/base/common/event';
17-
import { IExplorerService, SortOrder } from 'vs/workbench/contrib/files/common/files';
1817
import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources';
18+
import { SortOrder } from 'vs/workbench/contrib/files/common/files';
1919

2020
export class ExplorerModel implements IDisposable {
2121

@@ -25,10 +25,10 @@ export class ExplorerModel implements IDisposable {
2525

2626
constructor(
2727
private readonly contextService: IWorkspaceContextService,
28-
explorerService: IExplorerService
28+
fileService: IFileService
2929
) {
3030
const setRoots = () => this._roots = this.contextService.getWorkspace().folders
31-
.map(folder => new ExplorerItem(folder.uri, explorerService, undefined, true, false, false, folder.name));
31+
.map(folder => new ExplorerItem(folder.uri, fileService, undefined, true, false, folder.name));
3232
setRoots();
3333

3434
this._listener = this.contextService.onDidChangeWorkspaceFolders(() => {
@@ -83,11 +83,10 @@ export class ExplorerItem {
8383

8484
constructor(
8585
public resource: URI,
86-
private readonly explorerService: IExplorerService,
86+
private readonly fileService: IFileService,
8787
private _parent: ExplorerItem | undefined,
8888
private _isDirectory?: boolean,
8989
private _isSymbolicLink?: boolean,
90-
private _isReadonly?: boolean,
9190
private _name: string = basenameOrAuthority(resource),
9291
private _mtime?: number,
9392
) {
@@ -112,7 +111,7 @@ export class ExplorerItem {
112111
}
113112

114113
get isReadonly(): boolean {
115-
return !!this._isReadonly;
114+
return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly);
116115
}
117116

118117
get mtime(): number | undefined {
@@ -158,8 +157,8 @@ export class ExplorerItem {
158157
return this === this.root;
159158
}
160159

161-
static create(explorerService: IExplorerService, fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {
162-
const stat = new ExplorerItem(raw.resource, explorerService, parent, raw.isDirectory, raw.isSymbolicLink, fileService.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime);
160+
static create(fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {
161+
const stat = new ExplorerItem(raw.resource, fileService, parent, raw.isDirectory, raw.isSymbolicLink, raw.name, raw.mtime);
163162

164163
// Recursively add children if present
165164
if (stat.isDirectory) {
@@ -174,7 +173,7 @@ export class ExplorerItem {
174173
// Recurse into children
175174
if (raw.children) {
176175
for (let i = 0, len = raw.children.length; i < len; i++) {
177-
const child = ExplorerItem.create(explorerService, fileService, raw.children[i], stat, resolveTo);
176+
const child = ExplorerItem.create(fileService, raw.children[i], stat, resolveTo);
178177
stat.addChild(child);
179178
}
180179
}
@@ -208,7 +207,6 @@ export class ExplorerItem {
208207
local._mtime = disk.mtime;
209208
local._isDirectoryResolved = disk._isDirectoryResolved;
210209
local._isSymbolicLink = disk.isSymbolicLink;
211-
local._isReadonly = disk.isReadonly;
212210
local.isError = disk.isError;
213211

214212
// Merge Children if resolved
@@ -259,14 +257,14 @@ export class ExplorerItem {
259257
return this.children.get(this.getPlatformAwareName(name));
260258
}
261259

262-
async fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise<ExplorerItem[]> {
260+
async fetchChildren(sortOrder: SortOrder): Promise<ExplorerItem[]> {
263261
if (!this._isDirectoryResolved) {
264262
// Resolve metadata only when the mtime is needed since this can be expensive
265263
// Mtime is only used when the sort order is 'modified'
266-
const resolveMetadata = explorerService.sortOrder === SortOrder.Modified;
264+
const resolveMetadata = sortOrder === SortOrder.Modified;
267265
try {
268-
const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
269-
const resolved = ExplorerItem.create(explorerService, fileService, stat, this);
266+
const stat = await this.fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata });
267+
const resolved = ExplorerItem.create(this.fileService, stat, this);
270268
ExplorerItem.mergeLocalWithDisk(resolved, this);
271269
} catch (e) {
272270
this.isError = true;
@@ -306,7 +304,7 @@ export class ExplorerItem {
306304
}
307305

308306
private getPlatformAwareName(name: string): string {
309-
return this.explorerService.shouldIgnoreCase(this.resource) ? name.toLowerCase() : name;
307+
return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.PathCaseSensitive) ? name : name.toLowerCase();
310308
}
311309

312310
/**
@@ -356,7 +354,7 @@ export class ExplorerItem {
356354
find(resource: URI): ExplorerItem | null {
357355
// Return if path found
358356
// For performance reasons try to do the comparison as fast as possible
359-
const ignoreCase = this.explorerService.shouldIgnoreCase(resource);
357+
const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive);
360358
if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) &&
361359
(ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) {
362360
return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length, ignoreCase);
@@ -397,7 +395,7 @@ export class ExplorerItem {
397395
}
398396

399397
export class NewExplorerItem extends ExplorerItem {
400-
constructor(explorerService: IExplorerService, parent: ExplorerItem, isDirectory: boolean) {
401-
super(URI.file(''), explorerService, parent, isDirectory);
398+
constructor(fileService: IFileService, parent: ExplorerItem, isDirectory: boolean) {
399+
super(URI.file(''), fileService, parent, isDirectory);
402400
}
403401
}

src/vs/workbench/contrib/files/common/explorerService.ts

+15-29
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
99
import { IExplorerService, IFilesConfiguration, SortOrder, IContextProvider } from 'vs/workbench/contrib/files/common/files';
1010
import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
1111
import { URI } from 'vs/base/common/uri';
12-
import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
13-
import { dirname, hasToIgnoreCase } from 'vs/base/common/resources';
12+
import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files';
13+
import { dirname } from 'vs/base/common/resources';
1414
import { memoize } from 'vs/base/common/decorators';
1515
import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
1616
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -42,7 +42,6 @@ export class ExplorerService implements IExplorerService {
4242
private _sortOrder: SortOrder;
4343
private cutItems: ExplorerItem[] | undefined;
4444
private contextProvider: IContextProvider | undefined;
45-
private fileSystemProviderCaseSensitivity = new Map<string, boolean>();
4645
private model: ExplorerModel;
4746

4847
constructor(
@@ -55,25 +54,21 @@ export class ExplorerService implements IExplorerService {
5554
) {
5655
this._sortOrder = this.configurationService.getValue('explorer.sortOrder');
5756

58-
this.model = new ExplorerModel(this.contextService, this);
57+
this.model = new ExplorerModel(this.contextService, this.fileService);
5958
this.disposables.add(this.model);
6059
this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e)));
6160
this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e)));
6261
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>())));
63-
this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => {
64-
const provider = e.provider;
65-
if (e.added && provider) {
66-
const alreadyRegistered = this.fileSystemProviderCaseSensitivity.has(e.scheme);
67-
const readCapability = () => this.fileSystemProviderCaseSensitivity.set(e.scheme, !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive));
68-
readCapability();
69-
70-
if (alreadyRegistered) {
71-
// A file system provider got re-registered, we should update all file stats since they might change (got read-only)
72-
this.model.roots.forEach(r => r.forgetChildren());
73-
this._onDidChangeItem.fire({ recursive: true });
74-
} else {
75-
this.disposables.add(provider.onDidChangeCapabilities(() => readCapability()));
62+
this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(e => {
63+
let affected = false;
64+
this.model.roots.forEach(r => {
65+
if (r.resource.scheme === e.scheme) {
66+
affected = true;
67+
r.forgetChildren();
7668
}
69+
});
70+
if (affected) {
71+
this._onDidChangeItem.fire({ recursive: true });
7772
}
7873
}));
7974
this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire()));
@@ -131,15 +126,6 @@ export class ExplorerService implements IExplorerService {
131126
return fileEventsFilter;
132127
}
133128

134-
shouldIgnoreCase(resource: URI): boolean {
135-
const caseSensitive = this.fileSystemProviderCaseSensitivity.get(resource.scheme);
136-
if (typeof caseSensitive === 'undefined') {
137-
return hasToIgnoreCase(resource);
138-
}
139-
140-
return !caseSensitive;
141-
}
142-
143129
// IExplorerService methods
144130

145131
findClosest(resource: URI): ExplorerItem | null {
@@ -200,7 +186,7 @@ export class ExplorerService implements IExplorerService {
200186
const stat = await this.fileService.resolve(rootUri, options);
201187

202188
// Convert to model
203-
const modelStat = ExplorerItem.create(this, this.fileService, stat, undefined, options.resolveTo);
189+
const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo);
204190
// Update Input with disk Stat
205191
ExplorerItem.mergeLocalWithDisk(modelStat, root);
206192
const item = root.find(resource);
@@ -244,11 +230,11 @@ export class ExplorerService implements IExplorerService {
244230
const thenable: Promise<IFileStat | undefined> = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata });
245231
thenable.then(stat => {
246232
if (stat) {
247-
const modelStat = ExplorerItem.create(this, this.fileService, stat, p.parent);
233+
const modelStat = ExplorerItem.create(this.fileService, stat, p.parent);
248234
ExplorerItem.mergeLocalWithDisk(modelStat, p);
249235
}
250236

251-
const childElement = ExplorerItem.create(this, this.fileService, addedElement, p.parent);
237+
const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent);
252238
// Make sure to remove any previous version of the file if any
253239
p.removeChild(childElement);
254240
p.addChild(childElement);

src/vs/workbench/contrib/files/common/files.ts

-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export interface IExplorerService {
5555
refresh(): void;
5656
setToCopy(stats: ExplorerItem[], cut: boolean): void;
5757
isCut(stat: ExplorerItem): boolean;
58-
shouldIgnoreCase(resource: URI): boolean;
5958

6059
/**
6160
* Selects and reveal the file element provided by the given resource if its found in the explorer.

src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts

+7-14
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,11 @@ import { join } from 'vs/base/common/path';
1010
import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions';
1111
import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
1212
import { toResource } from 'vs/base/test/common/utils';
13-
import { hasToIgnoreCase } from 'vs/base/common/resources';
14-
import { IExplorerService } from 'vs/workbench/contrib/files/common/files';
15-
16-
class MockExplorerService {
17-
shouldIgnoreCase(resource: URI) {
18-
return hasToIgnoreCase(resource);
19-
}
20-
}
21-
const mockExplorerService = new MockExplorerService() as IExplorerService;
13+
import { TestFileService } from 'vs/workbench/test/workbenchTestServices';
2214

15+
const fileService = new TestFileService();
2316
function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem {
24-
return new ExplorerItem(toResource.call(this, path), mockExplorerService, undefined, isFolder, false, false, name, mtime);
17+
return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, name, mtime);
2518
}
2619

2720
suite('Files - View Model', function () {
@@ -252,19 +245,19 @@ suite('Files - View Model', function () {
252245
});
253246

254247
test('Merge Local with Disk', function () {
255-
const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now());
256-
const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now());
248+
const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now());
249+
const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now());
257250

258251
// Merge Properties
259252
ExplorerItem.mergeLocalWithDisk(merge2, merge1);
260253
assert.strictEqual(merge1.mtime, merge2.mtime);
261254

262255
// Merge Child when isDirectoryResolved=false is a no-op
263-
merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now()));
256+
merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now()));
264257
ExplorerItem.mergeLocalWithDisk(merge2, merge1);
265258

266259
// Merge Child with isDirectoryResolved=true
267-
const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now());
260+
const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now());
268261
merge2.removeChild(child);
269262
merge2.addChild(child);
270263
(<any>merge2)._isDirectoryResolved = true;

0 commit comments

Comments
 (0)