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

Add support for dynamic text document content #1532

Merged
merged 3 commits into from
Aug 7, 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
21 changes: 21 additions & 0 deletions client-node-tests/src/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ suite('Client integration', () => {
},
willDelete: { filters: [{ scheme: fsProvider.scheme, pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] },
},
textDocumentContent: {
scheme: 'content-test'
}
},
linkedEditingRangeProvider: true,
diagnosticProvider: {
Expand Down Expand Up @@ -1536,6 +1539,24 @@ suite('Client integration', () => {
rangeEqual(symbol.location.range, 1, 2, 3, 4);
});

test('Text Document Content', async () => {
const providers = client.getFeature(lsclient.TextDocumentContentRequest.method)?.getProviders();
isDefined(providers);
assert.strictEqual(providers.length, 1);
const provider = providers[0].provider;
const result = await provider.provideTextDocumentContent(vscode.Uri.parse('content-test:///test.txt'), tokenSource.token);
assert.strictEqual(result, 'Some test content');

let middlewareCalled: boolean = false;
middleware.provideTextDocumentContent = (uri, token, next) => {
middlewareCalled = true;
return next(uri, token);
};
await provider.provideTextDocumentContent(vscode.Uri.parse('content-test:///test.txt'), tokenSource.token);
middleware.provideTextDocumentContent = undefined;
assert.strictEqual(middlewareCalled, true);
});

test('General middleware', async () => {
let middlewareCallCount = 0;

Expand Down
7 changes: 7 additions & 0 deletions client-node-tests/src/servers/testServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ connection.onInitialize((params: InitializeParams): any => {
filters: [{ scheme: 'file-test', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }]
},
},
textDocumentContent: {
scheme: 'content-test'
}
},
linkedEditingRangeProvider: true,
diagnosticProvider: {
Expand Down Expand Up @@ -522,6 +525,10 @@ connection.languages.inlineCompletion.on((_params) => {
];
});

connection.workspace.textDocumentContent.on((_params) => {
return 'Some test content';
});

connection.onRequest(
new ProtocolRequestType<null, null, never, any, any>('testing/sendSampleProgress'),
async (_, __) => {
Expand Down
10 changes: 7 additions & 3 deletions client/src/common/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
ConnectionOptions, PositionEncodingKind, DocumentDiagnosticRequest, NotebookDocumentSyncRegistrationType, NotebookDocumentSyncRegistrationOptions, ErrorCodes,
MessageStrategy, DidOpenTextDocumentParams, CodeLensResolveRequest, CompletionResolveRequest, CodeActionResolveRequest, InlayHintResolveRequest, DocumentLinkResolveRequest, WorkspaceSymbolResolveRequest,
CancellationToken as ProtocolCancellationToken, InlineCompletionRequest, InlineCompletionRegistrationOptions, ExecuteCommandRequest, ExecuteCommandOptions, HandlerResult,
type DidCloseTextDocumentParams
type DidCloseTextDocumentParams, type TextDocumentContentRequest
} from 'vscode-languageserver-protocol';

import * as c2p from './codeConverter';
Expand Down Expand Up @@ -94,6 +94,7 @@ import { InlayHintsFeature, InlayHintsMiddleware, InlayHintsProviderShape } from
import { WorkspaceFoldersFeature, WorkspaceFolderMiddleware } from './workspaceFolder';
import { DidCreateFilesFeature, DidDeleteFilesFeature, DidRenameFilesFeature, WillCreateFilesFeature, WillDeleteFilesFeature, WillRenameFilesFeature, FileOperationsMiddleware } from './fileOperations';
import { InlineCompletionItemFeature, InlineCompletionMiddleware } from './inlineCompletion';
import { TextDocumentContentFeature, type TextDocumentContentMiddleware, type TextDocumentContentProviderShape } from './textDocumentContent';
import { FileSystemWatcherFeature } from './fileSystemWatcher';
import { ProgressFeature } from './progress';

Expand Down Expand Up @@ -342,7 +343,8 @@ export type Middleware = _Middleware & TextDocumentSynchronizationMiddleware & C
DocumentHighlightMiddleware & DocumentSymbolMiddleware & WorkspaceSymbolMiddleware & ReferencesMiddleware & TypeDefinitionMiddleware & ImplementationMiddleware &
ColorProviderMiddleware & CodeActionMiddleware & CodeLensMiddleware & FormattingMiddleware & RenameMiddleware & DocumentLinkMiddleware & ExecuteCommandMiddleware &
FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware &
LinkedEditingRangeMiddleware & TypeHierarchyMiddleware & InlineValueMiddleware & InlayHintsMiddleware & NotebookDocumentMiddleware & DiagnosticProviderMiddleware & InlineCompletionMiddleware & GeneralMiddleware;
LinkedEditingRangeMiddleware & TypeHierarchyMiddleware & InlineValueMiddleware & InlayHintsMiddleware & NotebookDocumentMiddleware & DiagnosticProviderMiddleware &
InlineCompletionMiddleware & TextDocumentContentMiddleware & GeneralMiddleware;

export type LanguageClientOptions = {
documentSelector?: DocumentSelector | string[];
Expand Down Expand Up @@ -1933,6 +1935,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
getFeature(request: typeof DocumentDiagnosticRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DiagnosticProviderShape> & DiagnosticFeatureShape;
getFeature(request: typeof NotebookDocumentSyncRegistrationType.method): DynamicFeature<NotebookDocumentSyncRegistrationOptions> & NotebookDocumentProviderShape;
getFeature(request: typeof InlineCompletionRequest.method): (DynamicFeature<InlineCompletionRegistrationOptions> & TextDocumentProviderFeature<InlineCompletionItemProvider>) | undefined;
getFeature(request: typeof TextDocumentContentRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<TextDocumentContentProviderShape> | undefined;
getFeature(request: typeof ExecuteCommandRequest.method): DynamicFeature<ExecuteCommandOptions>;
public getFeature(request: string): DynamicFeature<any> | undefined {
return this._dynamicFeatures.get(request);
Expand Down Expand Up @@ -2462,7 +2465,8 @@ function createConnection(input: MessageReader, output: MessageWriter, errorHand
export namespace ProposedFeatures {
export function createAll(_client: FeatureClient<Middleware, LanguageClientOptions>): (StaticFeature | DynamicFeature<any>)[] {
const result: (StaticFeature | DynamicFeature<any>)[] = [
new InlineCompletionItemFeature(_client)
new InlineCompletionItemFeature(_client),
new TextDocumentContentFeature(_client)
];
return result;
}
Expand Down
84 changes: 84 additions & 0 deletions client/src/common/textDocumentContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

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

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


export interface ProvideTextDocumentContentSignature {
(this: void, uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult<string>;
}

export interface TextDocumentContentMiddleware {
provideTextDocumentContent?: (this: void, uri: vscode.Uri, token: vscode.CancellationToken, next: ProvideTextDocumentContentSignature) => vscode.ProviderResult<string>;
}

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

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

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

public fillClientCapabilities(capabilities: ClientCapabilities): void {
const textDocumentContent = ensure(ensure(capabilities, 'workspace')!, 'textDocumentContent')!;
textDocumentContent.dynamicRegistration = true;
}

public initialize(capabilities: ServerCapabilities): void {
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);
}
});

if (!capabilities?.workspace?.textDocumentContent) {
return;
}
const capability = capabilities.workspace.textDocumentContent;
const id = StaticRegistrationOptions.hasId(capability) ? capability.id : UUID.generateUuid();
this.register({
id: id,
registerOptions: capability
});
}

protected registerLanguageProvider(options: TextDocumentContentRegistrationOptions): [vscode.Disposable, TextDocumentContentProviderShape] {
const eventEmitter: vscode.EventEmitter<vscode.Uri> = new vscode.EventEmitter<vscode.Uri>();
const provider: vscode.TextDocumentContentProvider = {
onDidChange: eventEmitter.event,
provideTextDocumentContent: (uri, token) => {
const client = this._client;
const provideTextDocumentContent: ProvideTextDocumentContentSignature = (uri, token) => {
const params: TextDocumentContentParams = {
uri: client.code2ProtocolConverter.asUri(uri)
};
return client.sendRequest(TextDocumentContentRequest.type, params, token).then((result) => {
if (token.isCancellationRequested) {
return null;
}
return result;
}, (error) => {
return client.handleFailedRequest(TextDocumentContentRequest.type, token, error, null);
});
};
const middleware = client.middleware;
return middleware.provideTextDocumentContent
? middleware.provideTextDocumentContent(uri, token, provideTextDocumentContent)
: provideTextDocumentContent(uri, token);
}
};
return [vscode.workspace.registerTextDocumentContentProvider(options.scheme, provider), { provider, onDidChangeEmitter: eventEmitter }];
}
}
98 changes: 98 additions & 0 deletions protocol/src/common/protocol.textDocumentContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import type { DocumentUri } from 'vscode-languageserver-types';
import type { RequestHandler } from 'vscode-jsonrpc';

import { MessageDirection, ProtocolRequestType } from './messages';
import type { StaticRegistrationOptions } from './protocol';

/**
* Client capabilities for a text document content provider.
*
* @since 3.18.0
* @proposed
*/
export type TextDocumentContentClientCapabilities = {
/**
* Text document content provider supports dynamic registration.
*/
dynamicRegistration?: boolean;
};

/**
* Text document content provider options.
*
* @since 3.18.0
* @proposed
*/
export type TextDocumentContentOptions = {
/**
* The scheme for which the server provides content.
*/
scheme: string;
};

/**
* Text document content provider registration options.
*
* @since 3.18.0
* @proposed
*/
export type TextDocumentContentRegistrationOptions = TextDocumentContentOptions & StaticRegistrationOptions;

/**
* Parameters for the `workspace/textDocumentContent` request.
*
* @since 3.18.0
* @proposed
*/
export interface TextDocumentContentParams {
/**
* The uri of the text document.
*/
uri: DocumentUri;
}

/**
* The `workspace/textDocumentContent` request is sent from the client to the
* server to request the content of a text document.
*
* @since 3.18.0
* @proposed
*/
export namespace TextDocumentContentRequest {
export const method: 'workspace/textDocumentContent' = 'workspace/textDocumentContent';
export const messageDirection: MessageDirection = MessageDirection.clientToServer;
export const type = new ProtocolRequestType<TextDocumentContentParams, string, void, void, TextDocumentContentRegistrationOptions>(method);
export type HandlerSignature = RequestHandler<TextDocumentContentParams, string, void>;
}

/**
* Parameters for the `workspace/textDocumentContent/refresh` request.
*
* @since 3.18.0
* @proposed
*/
export interface TextDocumentContentRefreshParams {
/**
* The uri of the text document to refresh.
*/
uri: DocumentUri;
}

/**
* The `workspace/textDocumentContent` request is sent from the server to the client to refresh
* the content of a specific text document.
*
* @since 3.18.0
* @proposed
*/
export namespace TextDocumentContentRefreshRequest {
export const method: `workspace/textDocumentContent/refresh` = `workspace/textDocumentContent/refresh`;
export const messageDirection: MessageDirection = MessageDirection.serverToClient;
export const type = new ProtocolRequestType<TextDocumentContentRefreshParams, void, void, void, void>(method);
export type HandlerSignature = RequestHandler<TextDocumentContentRefreshParams, void, void>;
}
28 changes: 27 additions & 1 deletion protocol/src/common/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,14 @@ import {
DidCloseNotebookDocumentNotification, NotebookDocumentFilterWithCells, NotebookDocumentFilterWithNotebook
} from './protocol.notebook';

import { InlineCompletionClientCapabilities, InlineCompletionOptions, InlineCompletionParams, InlineCompletionRegistrationOptions, InlineCompletionRequest } from './protocol.inlineCompletion';
import {
InlineCompletionClientCapabilities, InlineCompletionOptions, InlineCompletionParams, InlineCompletionRegistrationOptions, InlineCompletionRequest
} from './protocol.inlineCompletion';

import {
TextDocumentContentClientCapabilities, TextDocumentContentOptions, TextDocumentContentRegistrationOptions, TextDocumentContentParams,
TextDocumentContentRequest, TextDocumentContentRefreshParams, TextDocumentContentRefreshRequest
} from './protocol.textDocumentContent';

// @ts-ignore: to avoid inlining LocationLink as dynamic import
let __noDynamicImport: LocationLink | undefined;
Expand Down Expand Up @@ -654,6 +661,14 @@ export interface WorkspaceClientCapabilities {
* @proposed
*/
foldingRange?: FoldingRangeWorkspaceClientCapabilities;

/**
* Capabilities specific to the `workspace/textDocumentContent` request.
*
* @since 3.18.0
* @proposed
*/
textDocumentContent?: TextDocumentContentClientCapabilities;
}

/**
Expand Down Expand Up @@ -1186,6 +1201,14 @@ export type WorkspaceOptions = {
* @since 3.16.0
*/
fileOperations?: FileOperationOptions;

/**
* The server supports the `workspace/textDocumentContent` request.
*
* @since 3.18.0
* @proposed
*/
textDocumentContent?: TextDocumentContentOptions | TextDocumentContentRegistrationOptions;
};

/**
Expand Down Expand Up @@ -4286,6 +4309,9 @@ export {
DidCloseNotebookDocumentNotification, NotebookDocumentFilterWithCells, NotebookDocumentFilterWithNotebook,
// Inline Completions
InlineCompletionClientCapabilities, InlineCompletionOptions, InlineCompletionParams, InlineCompletionRegistrationOptions, InlineCompletionRequest,
// Text Document Content
TextDocumentContentClientCapabilities, TextDocumentContentOptions, TextDocumentContentRegistrationOptions, TextDocumentContentParams, TextDocumentContentRequest,
TextDocumentContentRefreshParams, TextDocumentContentRefreshRequest
};

// To be backwards compatible
Expand Down
6 changes: 4 additions & 2 deletions server/src/common/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SemanticTokensBuilder } from './semanticTokens';
import type { WorkDoneProgressReporter, WorkDoneProgressServerReporter, ResultProgressReporter } from './progress';

import * as ic from './inlineCompletion.proposed';
import * as tdc from './textDocumentContent';

export * from 'vscode-languageserver-protocol';
export { WorkDoneProgressReporter, WorkDoneProgressServerReporter, ResultProgressReporter };
Expand All @@ -19,10 +20,11 @@ export { NotebookDocuments };
export * from './server';

export namespace ProposedFeatures {
export const all: Features<_, _, _, _, _, _, ic.InlineCompletionFeatureShape, _> = {
export const all: Features<_, _, _, _, _, tdc.TextDocumentContentFeatureShape, ic.InlineCompletionFeatureShape, _> = {
__brand: 'features',
workspace: tdc.TextDocumentContentFeature,
languages: ic.InlineCompletionFeature
};

export type Connection = _Connection<_, _, _, _, _, _, ic.InlineCompletionFeatureShape, _>;
export type Connection = _Connection<_, _, _, _, _, tdc.TextDocumentContentFeatureShape, ic.InlineCompletionFeatureShape, _>;
}
Loading