From 8ca4e2c3d797b763516a409f7daf70608af5490f Mon Sep 17 00:00:00 2001 From: Ludwig Valda Vasquez Date: Thu, 6 Nov 2025 20:33:03 +0100 Subject: [PATCH] [analyzer] fix initialisation progress reporting Progress reporting during initialisation has a time-based debounce: the sending coroutine (working concurrently with the passed-in block) is waiting 100ms after sending a progress update, before proceeding with the next update. When the block returns, the sending coroutine is immediately cancelled, robbing it of a chance to wake up and deliver the end progress message, that the block requests at the very end. This caused the never-ending progress displaying in Air. The "withProgress" function has been fixed to always send begin/end message out of bound and only debounce intermediate progress updates. Since the end message may be a subject for customisation, depending on the execution path taken, to enforce the begin/end contract on progress, the signature was updated: the function now takes the begin progress title, and the block is now required to return "WorkDoneProgress.End" instance that will be sent to the client. To keep the code simple, the function no longer returns a generic T. It can be added back, if necessary, however, currently it only has one usage that expects Unit. GitOrigin-RevId: ea8b390cb7bca4c894c5c2b75e304b71eaf03481 --- .../jetbrains/lsp/implementation/progress.kt | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/fleet/lsp.protocol/srcCommonMain/com/jetbrains/lsp/implementation/progress.kt b/fleet/lsp.protocol/srcCommonMain/com/jetbrains/lsp/implementation/progress.kt index b3690ac20409..9e37f08e405c 100644 --- a/fleet/lsp.protocol/srcCommonMain/com/jetbrains/lsp/implementation/progress.kt +++ b/fleet/lsp.protocol/srcCommonMain/com/jetbrains/lsp/implementation/progress.kt @@ -1,7 +1,6 @@ package com.jetbrains.lsp.implementation import com.jetbrains.lsp.protocol.* -import com.jetbrains.lsp.implementation.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull @@ -11,19 +10,27 @@ fun interface ProgressReporter { companion object { val NOOP: ProgressReporter = ProgressReporter {} } + fun report(progress: WorkDoneProgress) } -suspend fun LspClient.withProgress( +suspend fun LspClient.withProgress( request: WorkDoneProgressParams, - body: suspend CoroutineScope.(ProgressReporter) -> T -): T = + beginTitle: String, + body: suspend CoroutineScope.(ProgressReporter) -> WorkDoneProgress.End, +) { coroutineScope { when (val token = request.workDoneToken) { null -> body(ProgressReporter.NOOP) else -> { + val beginProgress = WorkDoneProgress.Begin(title = beginTitle) + notify( + LSP.ProgressNotificationType, + ProgressParams(token, LSP.json.encodeToJsonElement(WorkDoneProgress.serializer(), beginProgress)), + ) + val state = MutableStateFlow(null) - launch { + val endProgress = launch { state.filterNotNull().collect { p -> notify(LSP.ProgressNotificationType, ProgressParams(token, LSP.json.encodeToJsonElement(WorkDoneProgress.serializer(), p))) delay(100.milliseconds) @@ -31,6 +38,12 @@ suspend fun LspClient.withProgress( }.use { body(ProgressReporter { p -> state.value = p }) } + + notify( + LSP.ProgressNotificationType, + ProgressParams(token, LSP.json.encodeToJsonElement(WorkDoneProgress.serializer(), endProgress)) + ) } } - } \ No newline at end of file + } +} \ No newline at end of file