From 295aaa393fda8ecce110c38880a00466b9320e63 Mon Sep 17 00:00:00 2001 From: Andrey Kuzmin Date: Wed, 31 Jan 2024 10:20:50 +0100 Subject: [PATCH] Round progress percentage according to the spec (#1413) Round progress to match the spec --- client-node-tests/src/integration.test.ts | 34 +++++++++++++++++++++ client-node-tests/src/servers/testServer.ts | 8 +++++ server/src/common/progress.ts | 10 ++++-- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/client-node-tests/src/integration.test.ts b/client-node-tests/src/integration.test.ts index dd89f86f..70356e7a 100644 --- a/client-node-tests/src/integration.test.ts +++ b/client-node-tests/src/integration.test.ts @@ -566,6 +566,40 @@ suite('Client integration', () => { ); }); + test('Progress percentage is an integer', async () => { + const progressToken = 'TEST-PROGRESS-PERCENTAGE'; + const percentages: Array = []; + let currentProgressResolver: (value: unknown) => void | undefined; + + // Set up middleware that calls the current resolve function when it gets its 'end' progress event. + middleware.handleWorkDoneProgress = (token: lsclient.ProgressToken, params: lsclient.WorkDoneProgressBegin | lsclient.WorkDoneProgressReport | lsclient.WorkDoneProgressEnd, next) => { + if (token === progressToken) { + const percentage = params.kind === 'report' || params.kind === 'begin' ? params.percentage : undefined; + percentages.push(percentage); + + if (params.kind === 'end') { + setImmediate(currentProgressResolver); + } + } + return next(token, params); + }; + + // Trigger a progress event. + await new Promise((resolve) => { + currentProgressResolver = resolve; + void client.sendRequest( + new lsclient.ProtocolRequestType('testing/sendSampleProgress'), + {}, + tokenSource.token, + ); + }); + + middleware.handleWorkDoneProgress = undefined; + + // Ensure percentages are rounded according to the spec + assert.deepStrictEqual(percentages, [0, 50, undefined]); + }); + test('Document Formatting', async () => { const provider = client.getFeature(lsclient.DocumentFormattingRequest.method).getProvider(document); isDefined(provider); diff --git a/client-node-tests/src/servers/testServer.ts b/client-node-tests/src/servers/testServer.ts index bd7226df..f0302609 100644 --- a/client-node-tests/src/servers/testServer.ts +++ b/client-node-tests/src/servers/testServer.ts @@ -530,6 +530,14 @@ connection.onRequest( void connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'begin', title: 'Test Progress' }); void connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'report', percentage: 50, message: 'Halfway!' }); void connection.sendProgress(WorkDoneProgress.type, progressToken, { kind: 'end', message: 'Completed!' }); + + // According to the spec, the reported percentage has to be an integer. + // Because JS doesn't have integer support, we have rounding code in place. + const progressToken2 = 'TEST-PROGRESS-PERCENTAGE'; + const progress = connection.window.attachWorkDoneProgress(progressToken2); + progress.begin('Test Progress', 0.1); + progress.report(49.9, 'Halfway!'); + progress.done(); }, ); diff --git a/server/src/common/progress.ts b/server/src/common/progress.ts index 8f1ab5a7..4abff060 100644 --- a/server/src/common/progress.ts +++ b/server/src/common/progress.ts @@ -47,10 +47,14 @@ class WorkDoneProgressReporterImpl implements WorkDoneProgressReporter { const param: WorkDoneProgressBegin = { kind: 'begin', title, - percentage, message, cancellable }; + if (typeof percentage === 'number') { + // Round to the nearest integer, because the percentage + // is a uinteger according to the specification + param.percentage = Math.round(percentage); + } this._connection.sendProgress(WorkDoneProgress.type, this._token, param); } @@ -59,7 +63,9 @@ class WorkDoneProgressReporterImpl implements WorkDoneProgressReporter { kind: 'report' }; if (typeof arg0 === 'number') { - param.percentage = arg0; + // Round to the nearest integer, because the percentage + // is a uinteger according to the specification + param.percentage = Math.round(arg0); if (arg1 !== undefined) { param.message = arg1; }