Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support that servers can register a text content provider for multiple schemes #1539

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client-node-tests/src/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ suite('Client integration', () => {
willDelete: { filters: [{ scheme: fsProvider.scheme, pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] },
},
textDocumentContent: {
scheme: 'content-test'
schemes: ['content-test']
}
},
linkedEditingRangeProvider: true,
Expand Down
2 changes: 1 addition & 1 deletion client-node-tests/src/servers/testServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ connection.onInitialize((params: InitializeParams): any => {
},
},
textDocumentContent: {
scheme: 'content-test'
schemes: ['content-test']
}
},
linkedEditingRangeProvider: true,
Expand Down
69 changes: 60 additions & 9 deletions client/src/common/textDocumentContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* ------------------------------------------------------------------------------------------ */

import * as vscode from 'vscode';
import { StaticRegistrationOptions, TextDocumentContentRefreshRequest, TextDocumentContentRequest, type ClientCapabilities, type ServerCapabilities, type TextDocumentContentParams, type TextDocumentContentRegistrationOptions } from 'vscode-languageserver-protocol';
import { StaticRegistrationOptions, TextDocumentContentRefreshRequest, TextDocumentContentRequest, type ClientCapabilities, type RegistrationType, type ServerCapabilities, type TextDocumentContentParams, type TextDocumentContentRegistrationOptions } from 'vscode-languageserver-protocol';

import { WorkspaceFeature, ensure, type FeatureClient } from './features';
import { ensure, type DynamicFeature, type FeatureClient, type FeatureState, type RegistrationData } from './features';
import * as UUID from './utils/uuid';


Expand All @@ -19,14 +19,35 @@ export interface TextDocumentContentMiddleware {
}

export interface TextDocumentContentProviderShape {
provider: vscode.TextDocumentContentProvider;
scheme: string;
onDidChangeEmitter: vscode.EventEmitter<vscode.Uri>;
provider: vscode.TextDocumentContentProvider;
}

export class TextDocumentContentFeature extends WorkspaceFeature<TextDocumentContentRegistrationOptions, TextDocumentContentProviderShape, TextDocumentContentMiddleware> {
export class TextDocumentContentFeature implements DynamicFeature<TextDocumentContentRegistrationOptions> {

private readonly _client: FeatureClient<TextDocumentContentMiddleware>;
private readonly _registrations: Map<string, { disposable: vscode.Disposable; providers: TextDocumentContentProviderShape[] }> = new Map();

constructor(client: FeatureClient<TextDocumentContentMiddleware>) {
super(client, TextDocumentContentRequest.type);
this._client = client;
}

public getState(): FeatureState {
const registrations = this._registrations.size > 0;
return { kind: 'workspace', id: TextDocumentContentRequest.method, registrations };
}

public get registrationType(): RegistrationType<TextDocumentContentRegistrationOptions> {
return TextDocumentContentRequest.type;
}

public getProviders(): TextDocumentContentProviderShape[] {
const result: TextDocumentContentProviderShape[] = [];
for (const registration of this._registrations.values()) {
result.push(...registration.providers);
}
return result;
}

public fillClientCapabilities(capabilities: ClientCapabilities): void {
Expand All @@ -38,8 +59,12 @@ export class TextDocumentContentFeature extends WorkspaceFeature<TextDocumentCon
const client = this._client;
client.onRequest(TextDocumentContentRefreshRequest.type, async (params) => {
const uri = client.protocol2CodeConverter.asUri(params.uri);
for (const provider of this.getProviders()) {
provider.onDidChangeEmitter.fire(uri);
for (const registrations of this._registrations.values()) {
for (const provider of registrations.providers) {
if (provider.scheme !== uri.scheme) {
provider.onDidChangeEmitter.fire(uri);
}
}
}
});

Expand All @@ -54,7 +79,18 @@ export class TextDocumentContentFeature extends WorkspaceFeature<TextDocumentCon
});
}

protected registerLanguageProvider(options: TextDocumentContentRegistrationOptions): [vscode.Disposable, TextDocumentContentProviderShape] {
public register(data: RegistrationData<TextDocumentContentRegistrationOptions>): void {
const registrations: TextDocumentContentProviderShape[] = [];
const disposables: vscode.Disposable[] = [];
for (const scheme of data.registerOptions.schemes) {
const [disposable, registration] = this.registerTextDocumentContentProvider(scheme);
registrations.push(registration);
disposables.push(disposable);
}
this._registrations.set(data.id, { disposable: vscode.Disposable.from(...disposables), providers: registrations });
}

private registerTextDocumentContentProvider(scheme: string): [vscode.Disposable, TextDocumentContentProviderShape] {
const eventEmitter: vscode.EventEmitter<vscode.Uri> = new vscode.EventEmitter<vscode.Uri>();
const provider: vscode.TextDocumentContentProvider = {
onDidChange: eventEmitter.event,
Expand All @@ -79,6 +115,21 @@ export class TextDocumentContentFeature extends WorkspaceFeature<TextDocumentCon
: provideTextDocumentContent(uri, token);
}
};
return [vscode.workspace.registerTextDocumentContentProvider(options.scheme, provider), { provider, onDidChangeEmitter: eventEmitter }];
return [vscode.workspace.registerTextDocumentContentProvider(scheme, provider), { scheme, onDidChangeEmitter: eventEmitter, provider }];
}

public unregister(id: string): void {
const registration = this._registrations.get(id);
if (registration !== undefined) {
this._registrations.delete(id);
registration.disposable.dispose();
}
}

public clear(): void {
this._registrations.forEach((registration) => {
registration.disposable.dispose();
});
this._registrations.clear();
}
}
4 changes: 2 additions & 2 deletions protocol/src/common/protocol.textDocumentContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export type TextDocumentContentClientCapabilities = {
*/
export type TextDocumentContentOptions = {
/**
* The scheme for which the server provides content.
* The schemes for which the server provides content.
*/
scheme: string;
schemes: string[];
};

/**
Expand Down
2 changes: 1 addition & 1 deletion testbed/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ connection.onInitialize((params, cancel, progress): Thenable<InitializeResult> |
changeNotifications: true
},
textDocumentContent: {
scheme: 'test-content'
schemes: ['test-content']
}
},
implementationProvider: {
Expand Down