Skip to content

Commit 0e5d415

Browse files
authored
Reuse diagnostic collections and introduce a provider (#1814)
* Reuse the push diagnostics collection if it has the same name * Introduce a DiagnosticCollectionProvider
1 parent 44e65e9 commit 0e5d415

3 files changed

Lines changed: 78 additions & 8 deletions

File tree

client/src/common/client.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
* ------------------------------------------------------------------------------------------ */
55
import {
6-
workspace as Workspace, window as Window, languages as Languages, LogLevel, version as VSCodeVersion, TextDocument, Disposable,
6+
workspace as Workspace, window as Window, LogLevel, version as VSCodeVersion, TextDocument, Disposable,
77
FileSystemWatcher as VFileSystemWatcher, DiagnosticCollection, Diagnostic as VDiagnostic, Uri, CancellationToken, WorkspaceEdit as VWorkspaceEdit,
88
MessageItem, WorkspaceFolder as VWorkspaceFolder, env as Env, TextDocumentShowOptions, CancellationError, CancellationTokenSource, FileCreateEvent,
99
FileRenameEvent, FileDeleteEvent, FileWillCreateEvent, FileWillRenameEvent, FileWillDeleteEvent, CompletionItemProvider, HoverProvider, SignatureHelpProvider,
@@ -49,7 +49,7 @@ import * as UUID from './utils/uuid';
4949
import { ProgressPart } from './progressPart';
5050
import {
5151
DynamicFeature, ensure, FeatureClient, LSPCancellationError, TextDocumentSendFeature, RegistrationData, StaticFeature,
52-
TextDocumentProviderFeature, WorkspaceProviderFeature, type VisibleDocuments
52+
TextDocumentProviderFeature, WorkspaceProviderFeature, DefaultDiagnosticCollectionProvider, DiagnosticCollectionSource, type VisibleDocuments
5353
} from './features';
5454

5555
import { DiagnosticFeature, DiagnosticProviderMiddleware, DiagnosticProviderShape, $DiagnosticPullOptions, DiagnosticFeatureShape } from './diagnostic';
@@ -735,6 +735,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
735735
// interval: clientOptions.suspend?.interval ? Math.max(clientOptions.suspend.interval, defaultInterval) : defaultInterval
736736
// },
737737
diagnosticPullOptions: clientOptions.diagnosticPullOptions ?? { onChange: true, onSave: false },
738+
diagnosticCollectionProvider: clientOptions.diagnosticCollectionProvider ?? new DefaultDiagnosticCollectionProvider(),
738739
notebookDocumentOptions: clientOptions.notebookDocumentOptions ?? { },
739740
textSynchronization: this.createTextSynchronizationOptions(clientOptions.textSynchronization)
740741
};
@@ -866,7 +867,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
866867
return undefined;
867868
}
868869
if (this._diagnostics === undefined) {
869-
this._diagnostics = Languages.createDiagnosticCollection(this._clientOptions.diagnosticCollectionName ?? this._id);
870+
this._diagnostics = this._clientOptions.diagnosticCollectionProvider.create(this._clientOptions.diagnosticCollectionName ?? this._id, DiagnosticCollectionSource.push);
870871
}
871872
return this._diagnostics;
872873
}
@@ -1672,7 +1673,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
16721673
this._diagnostics = null;
16731674
}
16741675
if (this._diagnostics !== null) {
1675-
this._diagnostics.dispose();
1676+
this._clientOptions.diagnosticCollectionProvider.dispose(this._diagnostics, DiagnosticCollectionSource.push);
16761677
this._diagnostics = null;
16771678
}
16781679
}

client/src/common/diagnostic.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { generateUuid } from './utils/uuid';
2020
import { matchGlobPattern } from './utils/globPattern';
2121

2222
import {
23-
TextDocumentLanguageFeature, FeatureClient, LSPCancellationError, type VisibleDocuments
23+
TextDocumentLanguageFeature, FeatureClient, LSPCancellationError, DiagnosticCollectionSource, type DiagnosticCollectionProvider, type VisibleDocuments
2424
} from './features';
2525

2626
function ensure<T, K extends keyof T>(target: T, key: K): T[K] {
@@ -167,6 +167,14 @@ export type DiagnosticPullOptions = {
167167

168168
export type $DiagnosticPullOptions = {
169169
diagnosticPullOptions?: DiagnosticPullOptions;
170+
171+
/**
172+
* The provider used to create and dispose the diagnostic collections for the
173+
* push (`textDocument/publishDiagnostics`) and the pull (`textDocument/diagnostic`)
174+
* model. If omitted a default provider is used that doesn't share a diagnostic
175+
* collection between the two models.
176+
*/
177+
diagnosticCollectionProvider?: DiagnosticCollectionProvider;
170178
};
171179

172180

@@ -314,12 +322,20 @@ class DiagnosticRequestor implements Disposable {
314322
this.onDidChangeDiagnosticsEmitter = new EventEmitter<void>();
315323
this.provider = this.createProvider();
316324

317-
this.diagnostics = Languages.createDiagnosticCollection(options.identifier);
325+
this.diagnostics = this.createDiagnosticCollection();
318326
this.openRequests = new Map();
319327
this.documentStates = new DocumentPullStateTracker();
320328
this.workspaceErrorCounter = 0;
321329
}
322330

331+
private createDiagnosticCollection(): DiagnosticCollection {
332+
if (this.client.clientOptions.diagnosticCollectionProvider === undefined) {
333+
return Languages.createDiagnosticCollection(this.options.identifier);
334+
} else {
335+
return this.client.clientOptions.diagnosticCollectionProvider.create(this.options.identifier, DiagnosticCollectionSource.pull);
336+
}
337+
}
338+
323339
public knows(kind: PullState, document: TextDocument | Uri): boolean {
324340
const uri = document instanceof Uri ? document : document.uri;
325341
return this.documentStates.tracks(kind, document) || this.openRequests.has(uri.toString());
@@ -650,7 +666,11 @@ class DiagnosticRequestor implements Disposable {
650666
}
651667

652668
// cleanup old diagnostics
653-
this.diagnostics.dispose();
669+
if (this.client.clientOptions.diagnosticCollectionProvider !== undefined) {
670+
this.client.clientOptions.diagnosticCollectionProvider.dispose(this.diagnostics, DiagnosticCollectionSource.pull);
671+
} else {
672+
this.diagnostics.dispose();
673+
}
654674
}
655675
}
656676

client/src/common/features.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
DocumentRangeFormattingEditProvider, OnTypeFormattingEditProvider, RenameProvider, DocumentSymbolProvider, DocumentLinkProvider, DocumentColorProvider,
1111
DeclarationProvider, ImplementationProvider, SelectionRangeProvider, TypeDefinitionProvider, CallHierarchyProvider,
1212
LinkedEditingRangeProvider, TypeHierarchyProvider, FileCreateEvent, FileRenameEvent, FileDeleteEvent, FileWillCreateEvent, FileWillRenameEvent,
13-
FileWillDeleteEvent, CancellationError, InlineCompletionItemProvider, type Uri
13+
FileWillDeleteEvent, CancellationError, InlineCompletionItemProvider, type Uri, type DiagnosticCollection
1414
} from 'vscode';
1515

1616
import {
@@ -689,6 +689,55 @@ import type { DiagnosticFeatureShape, DiagnosticProviderShape } from './diagnost
689689
import type { NotebookDocumentProviderShape } from './notebook';
690690
import { FoldingRangeProviderShape } from './foldingRange';
691691

692+
export enum DiagnosticCollectionSource {
693+
push = 'push',
694+
pull = 'pull'
695+
}
696+
697+
/**
698+
* A provider that creates and disposes the diagnostic collections used by the
699+
* push (`textDocument/publishDiagnostics`) and the pull (`textDocument/diagnostic`)
700+
* model. The same provider instance is shared between the client and the
701+
* diagnostic pull implementation so that an implementation can decide whether the
702+
* two models share the same underlying collection.
703+
*/
704+
export interface DiagnosticCollectionProvider {
705+
/**
706+
* Creates or returns a diagnostic collection for the given name and source.
707+
*
708+
* @param name the name of the diagnostic collection or `undefined` if no name
709+
* is available.
710+
* @param source whether the collection is requested by the `push` or the
711+
* `pull` model.
712+
*/
713+
create(name: string | undefined, source: DiagnosticCollectionSource): DiagnosticCollection;
714+
715+
/**
716+
* Disposes the given diagnostic collection on behalf of the given source.
717+
*
718+
* @param collection the collection to dispose.
719+
* @param source whether the `push` or the `pull` model releases the collection.
720+
*/
721+
dispose(collection: DiagnosticCollection, source: DiagnosticCollectionSource): void;
722+
}
723+
724+
/**
725+
* The default {@link DiagnosticCollectionProvider}. It never shares a diagnostic
726+
* collection between the push and the pull model. Every call to {@link create}
727+
* returns a new collection and {@link dispose} disposes it directly.
728+
*/
729+
export class DefaultDiagnosticCollectionProvider implements DiagnosticCollectionProvider {
730+
public create(name: string | undefined, _source: DiagnosticCollectionSource): DiagnosticCollection {
731+
return name !== undefined
732+
? Languages.createDiagnosticCollection(name)
733+
: Languages.createDiagnosticCollection();
734+
}
735+
736+
public dispose(collection: DiagnosticCollection, _source: DiagnosticCollectionSource): void {
737+
collection.dispose();
738+
}
739+
}
740+
692741
export interface FeatureClient<M, CO = object> {
693742

694743
protocol2CodeConverter: p2c.Converter;

0 commit comments

Comments
 (0)