Skip to content

Commit b0d0562

Browse files
committed
Use canonical uri for openTextDocument api, microsoft#93368
1 parent 4d854d4 commit b0d0562

9 files changed

Lines changed: 94 additions & 34 deletions

File tree

extensions/vscode-api-tests/src/memfs.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ class Directory implements vscode.FileStat {
4848

4949
export type Entry = File | Directory;
5050

51-
export class MemFS implements vscode.FileSystemProvider {
51+
export class TestFS implements vscode.FileSystemProvider {
5252

53-
readonly scheme = 'fake-fs';
53+
constructor(
54+
readonly scheme: string,
55+
readonly isCaseSensitive: boolean
56+
) { }
5457

5558
readonly root = new Directory('');
5659

@@ -161,12 +164,22 @@ export class MemFS implements vscode.FileSystemProvider {
161164
let parts = uri.path.split('/');
162165
let entry: Entry = this.root;
163166
for (const part of parts) {
167+
const partLow = part.toLowerCase();
164168
if (!part) {
165169
continue;
166170
}
167171
let child: Entry | undefined;
168172
if (entry instanceof Directory) {
169-
child = entry.entries.get(part);
173+
if (this.isCaseSensitive) {
174+
child = entry.entries.get(part);
175+
} else {
176+
for (let [key, value] of entry.entries) {
177+
if (key.toLowerCase() === partLow) {
178+
child = value;
179+
break;
180+
}
181+
}
182+
}
170183
}
171184
if (!child) {
172185
if (!silent) {

extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as vscode from 'vscode';
88
import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled } from '../utils';
99
import { join, posix, basename } from 'path';
1010
import * as fs from 'fs';
11+
import { TestFS } from '../memfs';
1112

1213
suite('vscode API - workspace', () => {
1314

@@ -163,6 +164,40 @@ suite('vscode API - workspace', () => {
163164
});
164165
});
165166

167+
test('openTextDocument, actual casing first', async function () {
168+
169+
const fs = new TestFS('this-fs', false);
170+
const reg = vscode.workspace.registerFileSystemProvider(fs.scheme, fs, { isCaseSensitive: fs.isCaseSensitive });
171+
172+
let uriOne = vscode.Uri.parse('this-fs:/one');
173+
let uriTwo = vscode.Uri.parse('this-fs:/two');
174+
let uriONE = vscode.Uri.parse('this-fs:/ONE'); // same resource, different uri
175+
let uriTWO = vscode.Uri.parse('this-fs:/TWO');
176+
177+
fs.writeFile(uriOne, Buffer.from('one'), { create: true, overwrite: true });
178+
fs.writeFile(uriTwo, Buffer.from('two'), { create: true, overwrite: true });
179+
180+
// lower case (actual case) comes first
181+
let docOne = await vscode.workspace.openTextDocument(uriOne);
182+
assert.equal(docOne.uri.toString(), uriOne.toString());
183+
184+
let docONE = await vscode.workspace.openTextDocument(uriONE);
185+
assert.equal(docONE === docOne, true);
186+
assert.equal(docONE.uri.toString(), uriOne.toString());
187+
assert.equal(docONE.uri.toString() !== uriONE.toString(), true); // yep
188+
189+
// upper case (NOT the actual case) comes first
190+
let docTWO = await vscode.workspace.openTextDocument(uriTWO);
191+
assert.equal(docTWO.uri.toString(), uriTWO.toString());
192+
193+
let docTwo = await vscode.workspace.openTextDocument(uriTwo);
194+
assert.equal(docTWO === docTwo, true);
195+
assert.equal(docTwo.uri.toString(), uriTWO.toString());
196+
assert.equal(docTwo.uri.toString() !== uriTwo.toString(), true); // yep
197+
198+
reg.dispose();
199+
});
200+
166201
test('eol, read', () => {
167202
const a = createRandomFile('foo\nbar\nbar').then(file => {
168203
return vscode.workspace.openTextDocument(file).then(doc => {

extensions/vscode-api-tests/src/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { MemFS } from './memfs';
7+
import { TestFS } from './memfs';
88
import * as assert from 'assert';
99

1010
export function rndName() {
1111
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
1212
}
1313

14-
export const testFs = new MemFS();
15-
vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs);
14+
export const testFs = new TestFS('fake-fs', true);
15+
vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs, { isCaseSensitive: testFs.isCaseSensitive });
1616

1717
export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, ext = ''): Promise<vscode.Uri> {
1818
let fakeFile: vscode.Uri;

src/vs/workbench/api/browser/mainThreadDocuments.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocum
1616
import { ITextEditorModel } from 'vs/workbench/common/editor';
1717
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
1818
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
19-
import { toLocalResource, isEqualOrParent } from 'vs/base/common/resources';
19+
import { toLocalResource, isEqualOrParent, extUri } from 'vs/base/common/resources';
2020
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
21+
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
2122

2223
export class BoundModelReferenceCollection {
2324

@@ -78,6 +79,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
7879
private readonly _textFileService: ITextFileService;
7980
private readonly _fileService: IFileService;
8081
private readonly _environmentService: IWorkbenchEnvironmentService;
82+
private readonly _uriIdentityService: IUriIdentityService;
8183

8284
private readonly _toDispose = new DisposableStore();
8385
private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
@@ -93,13 +95,15 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
9395
@IFileService fileService: IFileService,
9496
@ITextModelService textModelResolverService: ITextModelService,
9597
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
98+
@IUriIdentityService uriIdentityService: IUriIdentityService,
9699
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
97100
) {
98101
this._modelService = modelService;
99102
this._textModelResolverService = textModelResolverService;
100103
this._textFileService = textFileService;
101104
this._fileService = fileService;
102105
this._environmentService = environmentService;
106+
this._uriIdentityService = uriIdentityService;
103107

104108
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments);
105109

@@ -179,55 +183,58 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
179183
return this._textFileService.save(URI.revive(uri)).then(target => !!target);
180184
}
181185

182-
$tryOpenDocument(_uri: UriComponents): Promise<any> {
183-
const uri = URI.revive(_uri);
184-
if (!uri.scheme || !(uri.fsPath || uri.authority)) {
186+
$tryOpenDocument(uriData: UriComponents): Promise<URI> {
187+
const inputUri = URI.revive(uriData);
188+
if (!inputUri.scheme || !(inputUri.fsPath || inputUri.authority)) {
185189
return Promise.reject(new Error(`Invalid uri. Scheme and authority or path must be set.`));
186190
}
187191

188-
let promise: Promise<boolean>;
189-
switch (uri.scheme) {
192+
const canonicalUri = this._uriIdentityService.asCanonicalUri(inputUri);
193+
194+
let promise: Promise<URI>;
195+
switch (canonicalUri.scheme) {
190196
case Schemas.untitled:
191-
promise = this._handleUntitledScheme(uri);
197+
promise = this._handleUntitledScheme(canonicalUri);
192198
break;
193199
case Schemas.file:
194200
default:
195-
promise = this._handleAsResourceInput(uri);
201+
promise = this._handleAsResourceInput(canonicalUri);
196202
break;
197203
}
198204

199-
return promise.then(success => {
200-
if (!success) {
201-
return Promise.reject(new Error('cannot open ' + uri.toString()));
202-
} else if (!this._modelIsSynced.has(uri.toString())) {
203-
return Promise.reject(new Error('cannot open ' + uri.toString() + '. Detail: Files above 50MB cannot be synchronized with extensions.'));
205+
return promise.then(documentUri => {
206+
if (!documentUri) {
207+
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}`));
208+
} else if (!extUri.isEqual(documentUri, canonicalUri)) {
209+
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: Actual document opened as ${documentUri.toString()}`));
210+
} else if (!this._modelIsSynced.has(canonicalUri.toString())) {
211+
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: Files above 50MB cannot be synchronized with extensions.`));
204212
} else {
205-
return undefined;
213+
return canonicalUri;
206214
}
207215
}, err => {
208-
return Promise.reject(new Error('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err)));
216+
return Promise.reject(new Error(`cannot open ${canonicalUri.toString()}. Detail: ${toErrorMessage(err)}`));
209217
});
210218
}
211219

212220
$tryCreateDocument(options?: { language?: string, content?: string }): Promise<URI> {
213221
return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined);
214222
}
215223

216-
private _handleAsResourceInput(uri: URI): Promise<boolean> {
224+
private _handleAsResourceInput(uri: URI): Promise<URI> {
217225
return this._textModelResolverService.createModelReference(uri).then(ref => {
218226
this._modelReferenceCollection.add(uri, ref);
219-
const result = !!ref.object;
220-
return result;
227+
return ref.object.textEditorModel.uri;
221228
});
222229
}
223230

224-
private _handleUntitledScheme(uri: URI): Promise<boolean> {
231+
private _handleUntitledScheme(uri: URI): Promise<URI> {
225232
const asLocalUri = toLocalResource(uri, this._environmentService.configuration.remoteAuthority);
226233
return this._fileService.resolve(asLocalUri).then(stats => {
227234
// don't create a new file ontop of an existing file
228235
return Promise.reject(new Error('file already exists'));
229236
}, err => {
230-
return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined).then(resource => !!resource);
237+
return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined);
231238
});
232239
}
233240

src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
2828
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
2929
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
3030
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
31+
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
3132

3233
namespace delta {
3334

@@ -328,11 +329,12 @@ export class MainThreadDocumentsAndEditors {
328329
@IBulkEditService bulkEditService: IBulkEditService,
329330
@IPanelService panelService: IPanelService,
330331
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
331-
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
332+
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
333+
@IUriIdentityService uriIdentityService: IUriIdentityService,
332334
) {
333335
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors);
334336

335-
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, workingCopyFileService));
337+
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService));
336338
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
337339

338340
const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService));

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,8 +715,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
715715
}
716716

717717
return uriPromise.then(uri => {
718-
return extHostDocuments.ensureDocumentData(uri).then(() => {
719-
return extHostDocuments.getDocument(uri);
718+
return extHostDocuments.ensureDocumentData(uri).then(documentData => {
719+
return documentData.document;
720720
});
721721
});
722722
},

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export interface MainThreadDocumentContentProvidersShape extends IDisposable {
220220

221221
export interface MainThreadDocumentsShape extends IDisposable {
222222
$tryCreateDocument(options?: { language?: string; content?: string; }): Promise<UriComponents>;
223-
$tryOpenDocument(uri: UriComponents): Promise<void>;
223+
$tryOpenDocument(uri: UriComponents): Promise<UriComponents>;
224224
$trySaveDocument(uri: UriComponents): Promise<boolean>;
225225
}
226226

src/vs/workbench/api/common/extHostDocuments.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
8484

8585
let promise = this._documentLoader.get(uri.toString());
8686
if (!promise) {
87-
promise = this._proxy.$tryOpenDocument(uri).then(() => {
87+
promise = this._proxy.$tryOpenDocument(uri).then(uriData => {
8888
this._documentLoader.delete(uri.toString());
89-
return assertIsDefined(this._documentsAndEditors.getDocument(uri));
89+
const canonicalUri = URI.revive(uriData);
90+
return assertIsDefined(this._documentsAndEditors.getDocument(canonicalUri));
9091
}, err => {
9192
this._documentLoader.delete(uri.toString());
9293
return Promise.reject(err);

src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
2626
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
2727
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
2828
import { TestTextResourcePropertiesService, TestWorkingCopyFileService } from 'vs/workbench/test/common/workbenchTestServices';
29+
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
2930

3031
suite('MainThreadDocumentsAndEditors', () => {
3132

@@ -89,7 +90,8 @@ suite('MainThreadDocumentsAndEditors', () => {
8990
}
9091
},
9192
TestEnvironmentService,
92-
new TestWorkingCopyFileService()
93+
new TestWorkingCopyFileService(),
94+
new UriIdentityService(fileService),
9395
);
9496
});
9597

0 commit comments

Comments
 (0)