Skip to content

Commit 6276aa1

Browse files
authored
fix(amazonq): fix chat refresh button (#5701)
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 f72b7db commit 6276aa1

File tree

3 files changed

+32
-26
lines changed
  • plugins/amazonq

3 files changed

+32
-26
lines changed

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

Lines changed: 13 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,15 @@ 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
2221
import kotlinx.coroutines.launch
23-
import kotlinx.coroutines.runBlocking
22+
import kotlinx.coroutines.withContext
23+
import software.aws.toolkits.jetbrains.core.coroutines.EDT
2424
import software.aws.toolkits.jetbrains.isDeveloperMode
2525
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
2626
import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection
2727
import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry
28+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
2829
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager
2930
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener
3031
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
@@ -109,17 +110,21 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di
109110
webviewContainer.add(wrapper)
110111
wrapper.setContent(loadingPanel)
111112

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

120123
initConnections()
121124
connectUi(it)
122125
connectApps(it)
126+
127+
loadingPanel.stopLoading()
123128
}
124129
)
125130
}

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import com.intellij.openapi.util.Disposer
1111
import com.intellij.ui.jcef.JBCefJSQuery
1212
import org.cef.CefApp
1313
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
14-
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
1514
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AwsServerCapabilitiesProvider
1615
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
1716
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
@@ -46,17 +45,16 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
4645
"mynah",
4746
AssetResourceHandler.AssetResourceHandlerFactory(),
4847
)
49-
AmazonQLspService.getInstance(project).addLspInitializeMessageListener {
50-
loadWebView(
51-
isCodeTransformAvailable,
52-
isFeatureDevAvailable,
53-
isDocAvailable,
54-
isCodeScanAvailable,
55-
isCodeTestAvailable,
56-
highlightCommand,
57-
activeProfile
58-
)
59-
}
48+
49+
loadWebView(
50+
isCodeTransformAvailable,
51+
isFeatureDevAvailable,
52+
isDocAvailable,
53+
isCodeScanAvailable,
54+
isCodeTestAvailable,
55+
highlightCommand,
56+
activeProfile
57+
)
6058
}
6159

6260
override fun dispose() {

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ 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.asSharedFlow
31+
import kotlinx.coroutines.flow.map
2832
import kotlinx.coroutines.future.asCompletableFuture
2933
import kotlinx.coroutines.runBlocking
3034
import kotlinx.coroutines.sync.Mutex
@@ -75,7 +79,6 @@ import java.net.Proxy
7579
import java.net.URI
7680
import java.nio.charset.StandardCharsets
7781
import java.nio.file.Files
78-
import java.util.Collections
7982
import java.util.concurrent.Future
8083
import kotlin.time.Duration.Companion.seconds
8184

@@ -114,9 +117,8 @@ internal class LSPProcessListener : ProcessListener {
114117

115118
@Service(Service.Level.PROJECT)
116119
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() }
120+
private val _flowInstance = MutableSharedFlow<AmazonQServerInstance>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
121+
val instanceFlow = _flowInstance.asSharedFlow().map { it.languageServer }
120122

121123
private var instance: Deferred<AmazonQServerInstance>
122124
val capabilities
@@ -140,7 +142,9 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS
140142
// wait for handshake to complete
141143
instance.initializeResult.join()
142144

143-
instance
145+
instance.also {
146+
_flowInstance.emit(it)
147+
}
144148
}
145149
} catch (e: Exception) {
146150
LOG.warn(e) { "Failed to start LSP server" }
@@ -324,7 +328,6 @@ private class AmazonQServerInstance(private val project: Project, private val cs
324328
if (message is ResponseMessage && message.result is AwsExtendedInitializeResult) {
325329
val result = message.result as AwsExtendedInitializeResult
326330
AwsServerCapabilitiesProvider.getInstance(project).setAwsServerCapabilities(result.getAwsServerCapabilities())
327-
AmazonQLspService.getInstance(project).notifyInitializeMessageReceived()
328331
}
329332
consumer?.consume(message)
330333
}

0 commit comments

Comments
 (0)