Skip to content

Commit 72a3df2

Browse files
committed
fix(amazonq): fix chat refresh button
Previously, the browser waits for the server initialization to succeed before unlocking the loading process. When reloading the webview, since the server has already been initialized, it does not receive an initialization event and is stuck loading forever. Fix by moving to a hot flow that always returns the latest instance of AmazonQLanguageServer. While this could be any value, the idea is to bring us closer to the state where we can tie the lifetime of ChatCommunicationManager to the webview instance so that we can delete AsyncChatUiListener. Additionally this should also address the rare race condition where the server finishes startup before the webview finishes drawing.
1 parent 7bc1baa commit 72a3df2

File tree

3 files changed

+33
-25
lines changed
  • plugins/amazonq

3 files changed

+33
-25
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ package software.aws.toolkits.jetbrains.services.amazonq.toolwindow
55

66
import com.intellij.idea.AppMode
77
import com.intellij.openapi.Disposable
8-
import com.intellij.openapi.application.ApplicationManager
9-
import com.intellij.openapi.application.runInEdt
108
import com.intellij.openapi.components.service
119
import com.intellij.openapi.project.Project
1210
import com.intellij.openapi.util.Disposer
@@ -19,12 +17,16 @@ import com.intellij.ui.dsl.builder.AlignY
1917
import com.intellij.ui.dsl.builder.panel
2018
import com.intellij.ui.jcef.JBCefApp
2119
import kotlinx.coroutines.CoroutineScope
20+
import kotlinx.coroutines.flow.first
21+
import kotlinx.coroutines.flow.last
2222
import kotlinx.coroutines.launch
23-
import kotlinx.coroutines.runBlocking
23+
import kotlinx.coroutines.withContext
24+
import software.aws.toolkits.jetbrains.core.coroutines.EDT
2425
import software.aws.toolkits.jetbrains.isDeveloperMode
2526
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
2627
import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection
2728
import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry
29+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
2830
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager
2931
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener
3032
import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
@@ -104,17 +106,21 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di
104106
webviewContainer.add(wrapper)
105107
wrapper.setContent(loadingPanel)
106108

107-
ApplicationManager.getApplication().executeOnPooledThread {
108-
val webUri = runBlocking { service<ArtifactManager>().fetchArtifact(project).resolve("amazonq-ui.js").toUri() }
109-
loadingPanel.stopLoading()
110-
runInEdt {
109+
scope.launch {
110+
val webUri = service<ArtifactManager>().fetchArtifact(project).resolve("amazonq-ui.js").toUri()
111+
// wait for server to be running
112+
AmazonQLspService.getInstance(project).instanceFlow.first()
113+
114+
withContext(EDT) {
111115
browser.complete(
112-
Browser(this, webUri, project).also {
116+
Browser(this@AmazonQPanel, webUri, project).also {
113117
wrapper.setContent(it.component())
114118

115119
initConnections()
116120
connectUi(it)
117121
connectApps(it)
122+
123+
loadingPanel.stopLoading()
118124
}
119125
)
120126
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.intellij.openapi.Disposable
88
import com.intellij.openapi.project.Project
99
import com.intellij.openapi.util.Disposer
1010
import com.intellij.ui.jcef.JBCefJSQuery
11+
import kotlinx.coroutines.flow.last
1112
import org.cef.CefApp
1213
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
1314
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
@@ -44,17 +45,16 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
4445
"mynah",
4546
AssetResourceHandler.AssetResourceHandlerFactory(),
4647
)
47-
AmazonQLspService.getInstance(project).addLspInitializeMessageListener {
48-
loadWebView(
49-
isCodeTransformAvailable,
50-
isFeatureDevAvailable,
51-
isDocAvailable,
52-
isCodeScanAvailable,
53-
isCodeTestAvailable,
54-
highlightCommand,
55-
activeProfile
56-
)
57-
}
48+
49+
loadWebView(
50+
isCodeTransformAvailable,
51+
isFeatureDevAvailable,
52+
isDocAvailable,
53+
isCodeScanAvailable,
54+
isCodeTestAvailable,
55+
highlightCommand,
56+
activeProfile
57+
)
5858
}
5959

6060
override fun dispose() {

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import com.intellij.util.net.JdkProxyProvider
2525
import kotlinx.coroutines.CoroutineScope
2626
import kotlinx.coroutines.Deferred
2727
import kotlinx.coroutines.async
28+
import kotlinx.coroutines.channels.BufferOverflow
29+
import kotlinx.coroutines.flow.MutableSharedFlow
30+
import kotlinx.coroutines.flow.map
2831
import kotlinx.coroutines.future.asCompletableFuture
2932
import kotlinx.coroutines.runBlocking
3033
import kotlinx.coroutines.sync.Mutex
@@ -75,7 +78,6 @@ import java.net.Proxy
7578
import java.net.URI
7679
import java.nio.charset.StandardCharsets
7780
import java.nio.file.Files
78-
import java.util.Collections
7981
import java.util.concurrent.Future
8082
import kotlin.time.Duration.Companion.seconds
8183

@@ -114,9 +116,8 @@ internal class LSPProcessListener : ProcessListener {
114116

115117
@Service(Service.Level.PROJECT)
116118
class AmazonQLspService(private val project: Project, private val cs: CoroutineScope) : Disposable {
117-
private val lspInitializedMessageReceivedListener = Collections.synchronizedList(mutableListOf<AmazonQInitializeMessageReceivedListener>())
118-
fun addLspInitializeMessageListener(listener: AmazonQInitializeMessageReceivedListener) = lspInitializedMessageReceivedListener.add(listener)
119-
fun notifyInitializeMessageReceived() = lspInitializedMessageReceivedListener.forEach { it() }
119+
private val _flowInstance = MutableSharedFlow<AmazonQServerInstance>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
120+
val instanceFlow = _flowInstance.map { it.languageServer }
120121

121122
private var instance: Deferred<AmazonQServerInstance>
122123
val capabilities
@@ -140,7 +141,9 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS
140141
// wait for handshake to complete
141142
instance.initializeResult.join()
142143

143-
instance
144+
instance.also {
145+
_flowInstance.emit(it)
146+
}
144147
}
145148
} catch (e: Exception) {
146149
LOG.warn(e) { "Failed to start LSP server" }
@@ -324,7 +327,6 @@ private class AmazonQServerInstance(private val project: Project, private val cs
324327
if (message is ResponseMessage && message.result is AwsExtendedInitializeResult) {
325328
val result = message.result as AwsExtendedInitializeResult
326329
AwsServerCapabilitiesProvider.getInstance(project).setAwsServerCapabilities(result.getAwsServerCapabilities())
327-
AmazonQLspService.getInstance(project).notifyInitializeMessageReceived()
328330
}
329331
consumer?.consume(message)
330332
}

0 commit comments

Comments
 (0)