mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
add IExtUri, ExtUri default implementation, and IUriIdentityService, https://github.com/microsoft/vscode/issues/93368
This commit is contained in:
50
src/vs/workbench/services/uriIdentity/common/uriIdentity.ts
Normal file
50
src/vs/workbench/services/uriIdentity/common/uriIdentity.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtUri } from 'vs/base/common/resources';
|
||||
|
||||
export interface IUriIdentity {
|
||||
readonly pathHierarchical: boolean;
|
||||
readonly ignorePathCasing: boolean;
|
||||
}
|
||||
|
||||
export const IUriIdentityService = createDecorator<IUriIdentityService>('IUriIdentityService');
|
||||
|
||||
export interface IUriIdentityService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Uri extensions that are aware of casing.
|
||||
*/
|
||||
readonly extUri: IExtUri;
|
||||
|
||||
/**
|
||||
* Returns a canonical uri for the given resource. Different uris can point to the same
|
||||
* resource. That's because of casing or missing normalization, e.g the following uris
|
||||
* are different but refer to the same document (because windows paths are not case-sensitive)
|
||||
*
|
||||
* ```txt
|
||||
* file:///c:/foo/bar.txt
|
||||
* file:///c:/FOO/BAR.txt
|
||||
* ```
|
||||
*
|
||||
* This function should be invoked when feeding uris into the system that represent the truth,
|
||||
* e.g document uris or marker-to-document associations etc. This function should NOT be called
|
||||
* to pretty print a label nor to sanitize a uri.
|
||||
*
|
||||
* Samples:
|
||||
*
|
||||
* | in | out | |
|
||||
* |---|---|---|
|
||||
* | `file:///foo/bar/../bar` | `file:///foo/bar` | n/a |
|
||||
* | `file:///foo/bar/../bar#frag` | `file:///foo/bar#frag` | keep fragment |
|
||||
* | `file:///foo/BAR` | `file:///foo/bar` | assume ignore case |
|
||||
* | `file:///foo/bar/../BAR?q=2` | `file:///foo/BAR?q=2` | query makes it a different document |
|
||||
*/
|
||||
asCanonicalUri(uri: URI): URI;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { binarySearch } from 'vs/base/common/arrays';
|
||||
import { ExtUri, IExtUri, normalizePath } from 'vs/base/common/resources';
|
||||
|
||||
export class UriIdentityService implements IUriIdentityService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
readonly extUri: IExtUri;
|
||||
private _canonicalUris: URI[] = []; // use SkipList or BinaryTree instead of array...
|
||||
|
||||
constructor(@IFileService private readonly _fileService: IFileService) {
|
||||
|
||||
// assume path casing matters unless the file system provider spec'ed the opposite
|
||||
const ignorePathCasing = (uri: URI): boolean => {
|
||||
|
||||
// perf@jrieken cache this information
|
||||
if (this._fileService.canHandleResource(uri)) {
|
||||
return !this._fileService.hasCapability(uri, FileSystemProviderCapabilities.PathCaseSensitive);
|
||||
}
|
||||
|
||||
// this defaults to false which is a good default for
|
||||
// * virtual documents
|
||||
// * in-memory uris
|
||||
// * all kind of "private" schemes
|
||||
return false;
|
||||
};
|
||||
this.extUri = new ExtUri(ignorePathCasing);
|
||||
}
|
||||
|
||||
asCanonicalUri(uri: URI): URI {
|
||||
|
||||
// todo@jrieken there is more to it than just comparing
|
||||
// * ASYNC!?
|
||||
// * windows 8.3-filenames
|
||||
// * substr-drives...
|
||||
// * sym links?
|
||||
// * fetch real casing?
|
||||
|
||||
// (1) normalize URI
|
||||
if (this._fileService.canHandleResource(uri)) {
|
||||
uri = normalizePath(uri);
|
||||
}
|
||||
|
||||
// (2) find the uri in its canonical form or use this uri to define it
|
||||
// perf@jrieken
|
||||
// * using a SkipList or BinaryTree for faster insertion
|
||||
const idx = binarySearch(this._canonicalUris, uri, (a, b) => this.extUri.compare(a, b, true));
|
||||
if (idx >= 0) {
|
||||
return this._canonicalUris[idx].with({ fragment: uri.fragment });
|
||||
}
|
||||
|
||||
// using slice/concat is faster than splice
|
||||
const before = this._canonicalUris.slice(0, ~idx);
|
||||
const after = this._canonicalUris.slice(~idx);
|
||||
this._canonicalUris = before.concat(uri.with({ fragment: null }), after);
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IUriIdentityService, UriIdentityService, true);
|
||||
@@ -0,0 +1,94 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
|
||||
import { mock } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
suite('URI Identity', function () {
|
||||
|
||||
class FakeFileService extends mock<IFileService>() {
|
||||
|
||||
constructor(readonly data: Map<string, FileSystemProviderCapabilities>) {
|
||||
super();
|
||||
}
|
||||
canHandleResource(uri: URI) {
|
||||
return this.data.has(uri.scheme);
|
||||
}
|
||||
hasCapability(uri: URI, flag: FileSystemProviderCapabilities): boolean {
|
||||
const mask = this.data.get(uri.scheme) ?? 0;
|
||||
return Boolean(mask & flag);
|
||||
}
|
||||
}
|
||||
|
||||
let _service: UriIdentityService;
|
||||
|
||||
setup(function () {
|
||||
_service = new UriIdentityService(new FakeFileService(new Map([
|
||||
['bar', FileSystemProviderCapabilities.PathCaseSensitive],
|
||||
['foo', 0]
|
||||
])));
|
||||
});
|
||||
|
||||
function assertCanonical(input: URI, expected: URI, service: UriIdentityService = _service) {
|
||||
const actual = service.asCanonicalUri(input);
|
||||
assert.equal(actual.toString(), expected.toString());
|
||||
assert.ok(service.extUri.isEqual(actual, expected));
|
||||
}
|
||||
|
||||
test('extUri (isEqual)', function () {
|
||||
let a = URI.parse('foo://bar/bang');
|
||||
let a1 = URI.parse('foo://bar/BANG');
|
||||
let b = URI.parse('bar://bar/bang');
|
||||
let b1 = URI.parse('bar://bar/BANG');
|
||||
|
||||
assert.equal(_service.extUri.isEqual(a, a1), true);
|
||||
assert.equal(_service.extUri.isEqual(a1, a), true);
|
||||
|
||||
assert.equal(_service.extUri.isEqual(b, b1), false);
|
||||
assert.equal(_service.extUri.isEqual(b1, b), false);
|
||||
});
|
||||
|
||||
test('asCanonicalUri (casing)', function () {
|
||||
|
||||
let a = URI.parse('foo://bar/bang');
|
||||
let a1 = URI.parse('foo://bar/BANG');
|
||||
let b = URI.parse('bar://bar/bang');
|
||||
let b1 = URI.parse('bar://bar/BANG');
|
||||
|
||||
assertCanonical(a, a);
|
||||
assertCanonical(a1, a);
|
||||
|
||||
assertCanonical(b, b);
|
||||
assertCanonical(b1, b1); // case sensitive
|
||||
});
|
||||
|
||||
test('asCanonicalUri (normalization)', function () {
|
||||
let a = URI.parse('foo://bar/bang');
|
||||
assertCanonical(a, a);
|
||||
assertCanonical(URI.parse('foo://bar/./bang'), a);
|
||||
assertCanonical(URI.parse('foo://bar/./bang'), a);
|
||||
assertCanonical(URI.parse('foo://bar/./foo/../bang'), a);
|
||||
});
|
||||
|
||||
test('asCanonicalUri (keep fragement)', function () {
|
||||
|
||||
let a = URI.parse('foo://bar/bang');
|
||||
|
||||
assertCanonical(a, a);
|
||||
assertCanonical(URI.parse('foo://bar/./bang#frag'), a.with({ fragment: 'frag' }));
|
||||
assertCanonical(URI.parse('foo://bar/./bang#frag'), a.with({ fragment: 'frag' }));
|
||||
assertCanonical(URI.parse('foo://bar/./bang#frag'), a.with({ fragment: 'frag' }));
|
||||
assertCanonical(URI.parse('foo://bar/./foo/../bang#frag'), a.with({ fragment: 'frag' }));
|
||||
|
||||
let b = URI.parse('foo://bar/bazz#frag');
|
||||
assertCanonical(b, b);
|
||||
assertCanonical(URI.parse('foo://bar/bazz'), b.with({ fragment: '' }));
|
||||
assertCanonical(URI.parse('foo://bar/BAZZ#DDD'), b.with({ fragment: 'DDD' })); // lower-case path, but fragment is kept
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user