diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3568792ff6a..b7d91ad95b3 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -156,7 +156,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData)); - const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); + const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService, extHostFileSystemInfo)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index e8be6415fca..5326da678e7 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -14,19 +14,24 @@ import { Event, Emitter, DebounceEmitter } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; import { ResourceMap } from 'vs/base/common/map'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { IExtUri } from 'vs/base/common/resources'; export class DiagnosticCollection implements vscode.DiagnosticCollection { + private readonly _data: ResourceMap; private _isDisposed = false; - private _data = new ResourceMap(); constructor( private readonly _name: string, private readonly _owner: string, private readonly _maxDiagnosticsPerFile: number, + extUri: IExtUri, private readonly _proxy: MainThreadDiagnosticsShape | undefined, private readonly _onDidChangeDiagnostics: Emitter - ) { } + ) { + this._data = new ResourceMap(uri => extUri.getComparisonKey(uri)); + } dispose(): void { if (!this._isDisposed) { @@ -34,7 +39,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { if (this._proxy) { this._proxy.$clear(this._owner); } - this._data = undefined!; + this._data.clear(); this._isDisposed = true; } } @@ -227,13 +232,17 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { readonly onDidChangeDiagnostics: Event = Event.map(this._onDidChangeDiagnostics.event, ExtHostDiagnostics._mapper); - constructor(mainContext: IMainContext, @ILogService private readonly _logService: ILogService) { + constructor( + mainContext: IMainContext, + @ILogService private readonly _logService: ILogService, + @IExtHostFileSystemInfo private readonly _fileSystemInfoService: IExtHostFileSystemInfo, + ) { this._proxy = mainContext.getProxy(MainContext.MainThreadDiagnostics); } createDiagnosticCollection(extensionId: ExtensionIdentifier, name?: string): vscode.DiagnosticCollection { - const { _collections, _proxy, _onDidChangeDiagnostics, _logService } = this; + const { _collections, _proxy, _onDidChangeDiagnostics, _logService, _fileSystemInfoService } = this; const loggingProxy = new class implements MainThreadDiagnosticsShape { $changeMany(owner: string, entries: [UriComponents, IMarkerData[] | undefined][]): void { @@ -265,7 +274,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { const result = new class extends DiagnosticCollection { constructor() { - super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, loggingProxy, _onDidChangeDiagnostics); + super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, _fileSystemInfoService.extUri, loggingProxy, _onDidChangeDiagnostics); _collections.set(owner, this); } override dispose() { @@ -317,7 +326,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { if (!this._mirrorCollection) { const name = '_generated_mirror'; - const collection = new DiagnosticCollection(name, name, ExtHostDiagnostics._maxDiagnosticsPerFile, undefined, this._onDidChangeDiagnostics); + const collection = new DiagnosticCollection(name, name, ExtHostDiagnostics._maxDiagnosticsPerFile, this._fileSystemInfoService.extUri, undefined, this._onDidChangeDiagnostics); this._collections.set(name, collection); this._mirrorCollection = collection; } diff --git a/src/vs/workbench/api/common/extHostFileSystemInfo.ts b/src/vs/workbench/api/common/extHostFileSystemInfo.ts index 500a9184cd5..d86fd1bf404 100644 --- a/src/vs/workbench/api/common/extHostFileSystemInfo.ts +++ b/src/vs/workbench/api/common/extHostFileSystemInfo.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; +import { ExtUri, IExtUri } from 'vs/base/common/resources'; +import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostFileSystemInfoShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -14,6 +16,23 @@ export class ExtHostFileSystemInfo implements ExtHostFileSystemInfoShape { private readonly _systemSchemes = new Set(Object.keys(Schemas)); private readonly _providerInfo = new Map(); + readonly extUri: IExtUri; + + constructor() { + this.extUri = new ExtUri(uri => { + const capabilities = this._providerInfo.get(uri.scheme); + if (capabilities === undefined) { + // default: not ignore + return false; + } + if (capabilities & FileSystemProviderCapabilities.PathCaseSensitive) { + // configured as case sensitive + return false; + } + return true; + }); + } + $acceptProviderInfos(scheme: string, capabilities: number | null): void { if (capabilities === null) { this._providerInfo.delete(scheme); @@ -31,5 +50,7 @@ export class ExtHostFileSystemInfo implements ExtHostFileSystemInfoShape { } } -export interface IExtHostFileSystemInfo extends ExtHostFileSystemInfo { } +export interface IExtHostFileSystemInfo extends ExtHostFileSystemInfo { + readonly extUri: IExtUri; +} export const IExtHostFileSystemInfo = createDecorator('IExtHostFileSystemInfo'); diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 4b22daaa1ee..1bdf51f286b 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -50,6 +50,7 @@ import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/suggest/suggest'; import 'vs/editor/contrib/rename/rename'; import 'vs/editor/contrib/inlayHints/inlayHintsController'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = createTextModel( @@ -144,7 +145,7 @@ suite('ExtHostLanguageFeatureCommands', function () { rpcProtocol.set(MainContext.MainThreadCommands, insta.createInstance(MainThreadCommands, rpcProtocol)); ExtHostApiCommands.register(commands); - const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); + const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock() { }); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService); diff --git a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts index 241e6413a23..d9d8c7c3303 100644 --- a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts @@ -14,6 +14,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { NullLogService } from 'vs/platform/log/common/log'; import type * as vscode from 'vscode'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtUri, extUri } from 'vs/base/common/resources'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; suite('ExtHostDiagnostics', () => { @@ -26,9 +28,13 @@ suite('ExtHostDiagnostics', () => { } } + const fileSystemInfoService = new class extends mock() { + override readonly extUri = extUri; + }; + test('disposeCheck', () => { - const collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter()); + const collection = new DiagnosticCollection('test', 'test', 100, extUri, new DiagnosticsShape(), new Emitter()); collection.dispose(); collection.dispose(); // that's OK @@ -44,13 +50,13 @@ suite('ExtHostDiagnostics', () => { test('diagnostic collection, forEach, clear, has', function () { - let collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter()); + let collection = new DiagnosticCollection('test', 'test', 100, extUri, new DiagnosticsShape(), new Emitter()); assert.strictEqual(collection.name, 'test'); collection.dispose(); assert.throws(() => collection.name); let c = 0; - collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter()); + collection = new DiagnosticCollection('test', 'test', 100, extUri, new DiagnosticsShape(), new Emitter()); collection.forEach(() => c++); assert.strictEqual(c, 0); @@ -87,7 +93,7 @@ suite('ExtHostDiagnostics', () => { }); test('diagnostic collection, immutable read', function () { - let collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter()); + let collection = new DiagnosticCollection('test', 'test', 100, extUri, new DiagnosticsShape(), new Emitter()); collection.set(URI.parse('foo:bar'), [ new Diagnostic(new Range(0, 0, 1, 1), 'message-1'), new Diagnostic(new Range(0, 0, 1, 1), 'message-2') @@ -112,7 +118,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostics collection, set with dupliclated tuples', function () { - let collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter()); + let collection = new DiagnosticCollection('test', 'test', 100, extUri, new DiagnosticsShape(), new Emitter()); let uri = URI.parse('sc:hightower'); collection.set([ [uri, [new Diagnostic(new Range(0, 0, 0, 1), 'message-1')]], @@ -163,7 +169,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostics collection, set tuple overrides, #11547', function () { let lastEntries!: [UriComponents, IMarkerData[]][]; - let collection = new DiagnosticCollection('test', 'test', 100, new class extends DiagnosticsShape { + let collection = new DiagnosticCollection('test', 'test', 100, extUri, new class extends DiagnosticsShape { override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { lastEntries = entries; return super.$changeMany(owner, entries); @@ -197,7 +203,7 @@ suite('ExtHostDiagnostics', () => { const emitter = new Emitter(); emitter.event(_ => eventCount += 1); - const collection = new DiagnosticCollection('test', 'test', 100, new class extends DiagnosticsShape { + const collection = new DiagnosticCollection('test', 'test', 100, extUri, new class extends DiagnosticsShape { override $changeMany() { changeCount += 1; } @@ -217,7 +223,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostics collection, tuples and undefined (small array), #15585', function () { - const collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter()); + const collection = new DiagnosticCollection('test', 'test', 100, extUri, new DiagnosticsShape(), new Emitter()); let uri = URI.parse('sc:hightower'); let uri2 = URI.parse('sc:nomad'); let diag = new Diagnostic(new Range(0, 0, 0, 1), 'ffff'); @@ -238,7 +244,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostics collection, tuples and undefined (large array), #15585', function () { - const collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter()); + const collection = new DiagnosticCollection('test', 'test', 100, extUri, new DiagnosticsShape(), new Emitter()); const tuples: [URI, Diagnostic[]][] = []; for (let i = 0; i < 500; i++) { @@ -262,7 +268,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostic capping', function () { let lastEntries!: [UriComponents, IMarkerData[]][]; - let collection = new DiagnosticCollection('test', 'test', 250, new class extends DiagnosticsShape { + let collection = new DiagnosticCollection('test', 'test', 250, extUri, new class extends DiagnosticsShape { override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { lastEntries = entries; return super.$changeMany(owner, entries); @@ -288,7 +294,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostic eventing', async function () { let emitter = new Emitter>(); - let collection = new DiagnosticCollection('ddd', 'test', 100, new DiagnosticsShape(), emitter); + let collection = new DiagnosticCollection('ddd', 'test', 100, extUri, new DiagnosticsShape(), emitter); let diag1 = new Diagnostic(new Range(1, 1, 2, 3), 'diag1'); let diag2 = new Diagnostic(new Range(1, 1, 2, 3), 'diag2'); @@ -326,7 +332,7 @@ suite('ExtHostDiagnostics', () => { test('vscode.languages.onDidChangeDiagnostics Does Not Provide Document URI #49582', async function () { let emitter = new Emitter>(); - let collection = new DiagnosticCollection('ddd', 'test', 100, new DiagnosticsShape(), emitter); + let collection = new DiagnosticCollection('ddd', 'test', 100, extUri, new DiagnosticsShape(), emitter); let diag1 = new Diagnostic(new Range(1, 1, 2, 3), 'diag1'); @@ -349,7 +355,7 @@ suite('ExtHostDiagnostics', () => { test('diagnostics with related information', function (done) { - let collection = new DiagnosticCollection('ddd', 'test', 100, new class extends DiagnosticsShape { + let collection = new DiagnosticCollection('ddd', 'test', 100, extUri, new class extends DiagnosticsShape { override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) { let [[, data]] = entries; @@ -392,7 +398,7 @@ suite('ExtHostDiagnostics', () => { drain() { return undefined!; } - }, new NullLogService()); + }, new NullLogService(), fileSystemInfoService); let collection1 = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'foo'); let collection2 = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'foo'); // warns, uses a different owner @@ -407,7 +413,7 @@ suite('ExtHostDiagnostics', () => { test('Error updating diagnostics from extension #60394', function () { let callCount = 0; - let collection = new DiagnosticCollection('ddd', 'test', 100, new class extends DiagnosticsShape { + let collection = new DiagnosticCollection('ddd', 'test', 100, extUri, new class extends DiagnosticsShape { override $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) { callCount += 1; } @@ -444,7 +450,7 @@ suite('ExtHostDiagnostics', () => { drain() { return undefined!; } - }, new NullLogService()); + }, new NullLogService(), fileSystemInfoService); // @@ -468,4 +474,50 @@ suite('ExtHostDiagnostics', () => { await p2; assert.strictEqual(diags.getDiagnostics(uri).length, 0); }); + + test('languages.getDiagnostics doesn\'t handle case insensitivity correctly #128198', function () { + + const diags = new ExtHostDiagnostics(new class implements IMainContext { + getProxy(id: any): any { + return new DiagnosticsShape(); + } + set(): any { + return null; + } + assertRegistered(): void { + + } + drain() { + return undefined!; + } + }, new NullLogService(), new class extends mock() { + + override readonly extUri = new ExtUri(uri => uri.scheme === 'insensitive'); + }); + + const col = diags.createDiagnosticCollection(nullExtensionDescription.identifier); + + const uriSensitive = URI.from({ scheme: 'foo', path: '/some/path' }); + const uriSensitiveUpper = uriSensitive.with({ path: uriSensitive.path.toUpperCase() }); + + const uriInSensitive = URI.from({ scheme: 'insensitive', path: '/some/path' }); + const uriInSensitiveUpper = uriInSensitive.with({ path: uriInSensitive.path.toUpperCase() }); + + col.set(uriSensitive, [new Diagnostic(new Range(0, 0, 0, 0), 'sensitive')]); + col.set(uriInSensitive, [new Diagnostic(new Range(0, 0, 0, 0), 'insensitive')]); + + // collection itself honours casing + assert.strictEqual(col.get(uriSensitive)?.length, 1); + assert.strictEqual(col.get(uriSensitiveUpper)?.length, 0); + + assert.strictEqual(col.get(uriInSensitive)?.length, 1); + assert.strictEqual(col.get(uriInSensitiveUpper)?.length, 1); + + // languages.getDiagnostics honours casing + assert.strictEqual(diags.getDiagnostics(uriSensitive)?.length, 1); + assert.strictEqual(diags.getDiagnostics(uriSensitiveUpper)?.length, 0); + + assert.strictEqual(diags.getDiagnostics(uriInSensitive)?.length, 1); + assert.strictEqual(diags.getDiagnostics(uriInSensitiveUpper)?.length, 1); + }); }); diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index 2338d947e23..20b6ba1b858 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -49,6 +49,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { Progress } from 'vs/platform/progress/common/progress'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = createTextModel( @@ -104,7 +105,7 @@ suite('ExtHostLanguageFeatures', function () { rpcProtocol.set(ExtHostContext.ExtHostCommands, commands); rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); - const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); + const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock() { }); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService);