diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index d794dd4d4..865e14d89 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -6,8 +6,8 @@ import * as assert from 'assert'; import * as path from 'path'; -import * as lsclient from 'vscode-languageclient/node'; import * as vscode from 'vscode'; +import * as lsclient from 'vscode-languageclient/node'; suite('Client integration', () => { @@ -271,7 +271,7 @@ suite('Client integration', () => { isArray(result, vscode.Location, 2); for (let i = 0; i < result.length; i++) { const location = result[i]; - rangeEqual(location.range, i, i, i ,i); + rangeEqual(location.range, i, i, i, i); assert.strictEqual(location.uri.toString(), document.uri.toString()); } @@ -332,6 +332,42 @@ suite('Client integration', () => { assert.ok(middlewareCalled); }); + test('Progress', async () => { + const progressToken = 'TEST-PROGRESS-TOKEN'; + const middlewareEvents: Array = []; + let currentProgressResolver: () => void | undefined; + + // Set up middleware that calls the current resolve function when it gets its 'end' progress event. + middleware.handleWorkDoneProgress = (token: lsclient.ProgressToken, params, next) => { + if (token === progressToken) { + middlewareEvents.push(params); + if (params.kind === 'end') + setImmediate(currentProgressResolver); + } + return next(token, params); + }; + + // Trigger multiple sample progress events. + for (let i = 0; i < 2; i++) { + await new Promise((resolve) => { + currentProgressResolver = resolve; + client.sendRequest( + new lsclient.ProtocolRequestType('testing/sendSampleProgress'), + {}, + tokenSource.token, + ); + }); + } + + middleware.handleWorkDoneProgress = undefined; + + // Ensure all events were handled. + assert.deepStrictEqual( + middlewareEvents.map((p) => p.kind), + ['begin', 'report', 'end', 'begin', 'report', 'end'], + ); + }); + test('Document Formatting', async () => { const provider = client.getFeature(lsclient.DocumentFormattingRequest.method).getProvider(document); isDefined(provider); @@ -471,7 +507,7 @@ suite('Client integration', () => { await provider.provideDocumentColors(document, tokenSource.token); middleware.provideDocumentColors = undefined; - const presentations = await provider.provideColorPresentations(color.color, { document, range}, tokenSource.token); + const presentations = await provider.provideColorPresentations(color.color, { document, range }, tokenSource.token); isArray(presentations, vscode.ColorPresentation); const presentation = presentations[0]; @@ -481,7 +517,7 @@ suite('Client integration', () => { middlewareCalled++; return n(c, x, t); }; - await provider.provideColorPresentations(color.color, { document, range}, tokenSource.token); + await provider.provideColorPresentations(color.color, { document, range }, tokenSource.token); middleware.provideColorPresentations = undefined; assert.strictEqual(middlewareCalled, 2); }); @@ -562,7 +598,7 @@ suite('Client integration', () => { assert.strictEqual(middlewareCalled, true); }); - test('Type Definition', async() => { + test('Type Definition', async () => { const provider = client.getFeature(lsclient.TypeDefinitionRequest.method).getProvider(document); isDefined(provider); const result = (await provider.provideTypeDefinition(document, position, tokenSource.token)) as vscode.Location; @@ -677,4 +713,4 @@ suite('Client integration', () => { middleware.provideTypeDefinition = undefined; assert.strictEqual(middlewareCalled, true); }); -}); \ No newline at end of file +}); diff --git a/client-node-tests/src/servers/testServer.ts b/client-node-tests/src/servers/testServer.ts index 30cbd180c..7eeaa15ed 100644 --- a/client-node-tests/src/servers/testServer.ts +++ b/client-node-tests/src/servers/testServer.ts @@ -8,7 +8,7 @@ import { createConnection, Connection, InitializeParams, ServerCapabilities, CompletionItemKind, ResourceOperationKind, FailureHandlingKind, DiagnosticTag, CompletionItemTag, TextDocumentSyncKind, MarkupKind, SignatureHelp, SignatureInformation, ParameterInformation, Location, Range, DocumentHighlight, DocumentHighlightKind, CodeAction, Command, TextEdit, Position, DocumentLink, - ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind + ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress, WorkDoneProgressCreateRequest, } from '../../../server/node'; import { URI } from 'vscode-uri'; @@ -284,5 +284,16 @@ connection.languages.onOnTypeRename(() => { }; }); +connection.onRequest( + new ProtocolRequestType('testing/sendSampleProgress'), + async (_, __) => { + const progressToken = 'TEST-PROGRESS-TOKEN'; + await connection.sendRequest(WorkDoneProgressCreateRequest.type, { token: progressToken }); + connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'begin', title: 'Test Progress' }); + connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'report', percentage: 50, message: 'Halfway!' }); + connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'end', message: 'Completed!' }); + }, +); + // Listen on the connection connection.listen(); \ No newline at end of file diff --git a/client/src/common/client.ts b/client/src/common/client.ts index afaea8ce4..b84d265e2 100644 --- a/client/src/common/client.ts +++ b/client/src/common/client.ts @@ -49,7 +49,7 @@ import { DocumentRangeFormattingOptions, DocumentOnTypeFormattingOptions, RenameOptions, DocumentLinkOptions, CompletionItemTag, DiagnosticTag, DocumentColorRequest, DeclarationRequest, FoldingRangeRequest, ImplementationRequest, SelectionRangeRequest, TypeDefinitionRequest, SymbolTag, CallHierarchyPrepareRequest, CancellationStrategy, SaveOptions, LSPErrorCodes, CodeActionResolveRequest, RegistrationType, SemanticTokensRegistrationType, InsertTextMode, ShowDocumentRequest, - ShowDocumentParams, ShowDocumentResult, OnTypeRenameRequest + ShowDocumentParams, ShowDocumentResult, OnTypeRenameRequest, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, WorkDoneProgressReport } from 'vscode-languageserver-protocol'; import type { ColorProviderMiddleware } from './colorProvider'; @@ -347,6 +347,10 @@ export interface HandleDiagnosticsSignature { (this: void, uri: Uri, diagnostics: VDiagnostic[]): void; } +export interface HandleWorkDoneProgressSignature { + (this: void, token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd): void; +} + export interface ProvideCompletionItemsSignature { (this: void, document: TextDocument, position: VPosition, context: VCompletionContext, token: CancellationToken): ProviderResult; } @@ -469,6 +473,7 @@ export interface _Middleware { didClose?: NextSignature; handleDiagnostics?: (this: void, uri: Uri, diagnostics: VDiagnostic[], next: HandleDiagnosticsSignature) => void; + handleWorkDoneProgress?: (this: void, token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, next: HandleWorkDoneProgressSignature) => void; provideCompletionItem?: (this: void, document: TextDocument, position: VPosition, context: VCompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature) => ProviderResult; resolveCompletionItem?: (this: void, item: VCompletionItem, token: CancellationToken, next: ResolveCompletionItemSignature) => ProviderResult; provideHover?: (this: void, document: TextDocument, position: VPosition, token: CancellationToken, next: ProvideHoverSignature) => ProviderResult; @@ -2812,6 +2817,15 @@ export abstract class BaseLanguageClient { throw new Error('Language client is not ready yet'); } try { + if (WorkDoneProgress.is(type)) { + const handleWorkDoneProgress = this._clientOptions.middleware!.handleWorkDoneProgress; + if (handleWorkDoneProgress !== undefined) { + return this._resolvedConnection!.onProgress(type, token, (params) => { + handleWorkDoneProgress(token, params, () => handler(params as unknown as P)); + }); + } + } + return this._resolvedConnection!.onProgress(type, token, handler); } catch (error) { this.error(`Registering progress handler for token ${token} failed.`, error); diff --git a/protocol/src/common/protocol.progress.ts b/protocol/src/common/protocol.progress.ts index 2617c7cc6..a423dbb6e 100644 --- a/protocol/src/common/protocol.progress.ts +++ b/protocol/src/common/protocol.progress.ts @@ -107,6 +107,10 @@ export interface WorkDoneProgressEnd { export namespace WorkDoneProgress { export const type = new ProgressType(); + + export function is(value: ProgressType): value is typeof type { + return value === type; + } } export interface WorkDoneProgressCreateParams {