Skip to content

Commit e6083d3

Browse files
committed
Merge branch 'feature/q-lsp-chat' into samgst/q-lsp-telemetry
# Conflicts: # plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt # plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt
2 parents f2507f2 + 9b4d638 commit e6083d3

File tree

219 files changed

+5910
-12742
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

219 files changed

+5910
-12742
lines changed

plugins/amazonq/chat/jetbrains-community/resources/META-INF/plugin-chat.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
<projectListeners>
99
<listener class="software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowListener"
1010
topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
11-
<listener class="software.aws.toolkits.jetbrains.services.amazonq.project.ProjectContextEditorListener"
12-
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener"/>
1311
</projectListeners>
1412

1513
<extensions defaultExtensionNs="com.intellij">

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import com.intellij.ui.components.panels.Wrapper
1818
import com.intellij.ui.dsl.builder.Align
1919
import com.intellij.ui.dsl.builder.panel
2020
import com.intellij.ui.jcef.JBCefJSQuery
21+
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.flow.distinctUntilChanged
23+
import kotlinx.coroutines.flow.launchIn
24+
import kotlinx.coroutines.flow.onEach
2125
import org.cef.CefApp
2226
import software.aws.toolkits.core.utils.debug
2327
import software.aws.toolkits.core.utils.error
@@ -40,6 +44,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.profile.QProfileSwitchIn
4044
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
4145
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
4246
import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser
47+
import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter
48+
import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter
4349
import software.aws.toolkits.jetbrains.utils.isQConnected
4450
import software.aws.toolkits.jetbrains.utils.isQExpired
4551
import software.aws.toolkits.jetbrains.utils.isQWebviewsAvailable
@@ -54,7 +60,7 @@ import javax.swing.JButton
5460
import javax.swing.JComponent
5561

5662
@Service(Service.Level.PROJECT)
57-
class QWebviewPanel private constructor(val project: Project) : Disposable {
63+
class QWebviewPanel private constructor(val project: Project, private val scope: CoroutineScope) : Disposable {
5864
private val webviewContainer = Wrapper()
5965
var browser: QWebviewBrowser? = null
6066
private set
@@ -102,6 +108,14 @@ class QWebviewPanel private constructor(val project: Project) : Disposable {
102108
} else {
103109
browser = QWebviewBrowser(project, this).also {
104110
webviewContainer.add(it.component())
111+
112+
val themeBrowserAdapter = ThemeBrowserAdapter()
113+
EditorThemeAdapter().onThemeChange()
114+
.distinctUntilChanged()
115+
.onEach { theme ->
116+
themeBrowserAdapter.updateLoginThemeInBrowser(it.jcefBrowser.cefBrowser, theme)
117+
}
118+
.launchIn(scope)
105119
}
106120
}
107121
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import com.fasterxml.jackson.databind.MapperFeature
1212
import com.fasterxml.jackson.databind.SerializationFeature
1313
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
1414
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
15+
import com.fasterxml.jackson.module.kotlin.treeToValue
1516
import org.jetbrains.annotations.VisibleForTesting
1617
import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
1718
import software.aws.toolkits.jetbrains.services.amazonq.messages.UnknownMessageType
1819
import software.aws.toolkits.jetbrains.services.amazonq.util.command
1920

2021
class MessageSerializer @VisibleForTesting constructor() {
2122

22-
private val objectMapper = jacksonObjectMapper()
23+
val objectMapper = jacksonObjectMapper()
2324
.registerModule(JavaTimeModule())
2425
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
2526
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
@@ -36,6 +37,9 @@ class MessageSerializer @VisibleForTesting constructor() {
3637

3738
fun serialize(value: Any): String = objectMapper.writeValueAsString(value)
3839

40+
inline fun <reified T> deserializeChatMessages(value: JsonNode): T =
41+
objectMapper.treeToValue<T>(value)
42+
3943
// Provide singleton global access
4044
companion object {
4145
private val instance = MessageSerializer()

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/startup/AmazonQStartupActivity.kt

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,20 @@ package software.aws.toolkits.jetbrains.services.amazonq.startup
66
import com.intellij.openapi.application.ApplicationManager
77
import com.intellij.openapi.application.runInEdt
88
import com.intellij.openapi.project.Project
9-
import com.intellij.openapi.project.waitForSmartMode
109
import com.intellij.openapi.startup.ProjectActivity
1110
import com.intellij.openapi.wm.ToolWindowManager
12-
import kotlinx.coroutines.TimeoutCancellationException
13-
import kotlinx.coroutines.delay
14-
import kotlinx.coroutines.time.withTimeout
15-
import software.aws.toolkits.core.utils.getLogger
16-
import software.aws.toolkits.core.utils.warn
1711
import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
1812
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
1913
import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
2014
import software.aws.toolkits.jetbrains.core.gettingstarted.emitUserState
2115
import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService
2216
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
2317
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
24-
import software.aws.toolkits.jetbrains.services.amazonq.project.ProjectContextController
2518
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindow
2619
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
2720
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
2821
import software.aws.toolkits.jetbrains.services.cwc.inline.InlineChatController
2922
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
30-
import java.lang.management.ManagementFactory
31-
import java.time.Duration
3223
import java.util.concurrent.atomic.AtomicBoolean
3324

3425
class AmazonQStartupActivity : ProjectActivity {
@@ -58,39 +49,8 @@ class AmazonQStartupActivity : ProjectActivity {
5849
QRegionProfileManager.getInstance().validateProfile(project)
5950

6051
AmazonQLspService.getInstance(project)
61-
startLsp(project)
6252
if (runOnce.get()) return
6353
emitUserState(project)
6454
runOnce.set(true)
6555
}
66-
67-
private suspend fun startLsp(project: Project) {
68-
// Automatically start the project context LSP after some delay when average CPU load is below 30%.
69-
// The CPU load requirement is to avoid competing with native JetBrains indexing and other CPU expensive OS processes
70-
// In the future we will decouple LSP start and indexing start to let LSP perform other tasks.
71-
val startLspIndexingDuration = Duration.ofMinutes(30)
72-
project.waitForSmartMode()
73-
delay(30_000) // Wait for 30 seconds for systemLoadAverage to be more accurate
74-
try {
75-
withTimeout(startLspIndexingDuration) {
76-
while (true) {
77-
val cpuUsage = ManagementFactory.getOperatingSystemMXBean().systemLoadAverage
78-
if (cpuUsage > 0 && cpuUsage < 30) {
79-
ProjectContextController.getInstance(project = project)
80-
break
81-
} else {
82-
delay(60_000) // Wait for 60 seconds
83-
}
84-
}
85-
}
86-
} catch (e: TimeoutCancellationException) {
87-
LOG.warn { "Failed to start LSP server due to time out" }
88-
} catch (e: Exception) {
89-
LOG.warn { "Failed to start LSP server" }
90-
}
91-
}
92-
93-
companion object {
94-
private val LOG = getLogger<AmazonQStartupActivity>()
95-
}
9656
}

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

Lines changed: 153 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,67 @@ 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
10+
import com.intellij.openapi.components.service
11+
import com.intellij.openapi.project.Project
812
import com.intellij.openapi.util.Disposer
13+
import com.intellij.ui.components.JBLoadingPanel
914
import com.intellij.ui.components.JBTextArea
1015
import com.intellij.ui.components.panels.Wrapper
1116
import com.intellij.ui.dsl.builder.Align
1217
import com.intellij.ui.dsl.builder.AlignX
1318
import com.intellij.ui.dsl.builder.AlignY
1419
import com.intellij.ui.dsl.builder.panel
1520
import com.intellij.ui.jcef.JBCefApp
21+
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.launch
23+
import kotlinx.coroutines.runBlocking
1624
import software.aws.toolkits.jetbrains.isDeveloperMode
25+
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
26+
import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection
27+
import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry
28+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager
29+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener
30+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
31+
import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
32+
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector
33+
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
34+
import software.aws.toolkits.jetbrains.services.amazonq.util.highlightCommand
1735
import software.aws.toolkits.jetbrains.services.amazonq.webview.Browser
18-
import java.awt.event.ActionListener
36+
import software.aws.toolkits.jetbrains.services.amazonq.webview.BrowserConnector
37+
import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
38+
import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter
39+
import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable
40+
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.auth.isCodeTestAvailable
41+
import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable
42+
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable
43+
import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
44+
import java.util.concurrent.CompletableFuture
1945
import javax.swing.JButton
2046

21-
class AmazonQPanel(private val parent: Disposable) {
47+
class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Disposable {
48+
private val browser = CompletableFuture<Browser>()
2249
private val webviewContainer = Wrapper()
23-
var browser: Browser? = null
24-
private set
50+
private val appSource = AppSource()
51+
private val browserConnector = BrowserConnector(project = project)
52+
private val editorThemeAdapter = EditorThemeAdapter()
53+
private val appConnections = mutableListOf<AppConnection>()
54+
55+
init {
56+
project.messageBus.connect().subscribe(
57+
AsyncChatUiListener.TOPIC,
58+
object : AsyncChatUiListener {
59+
override fun onChange(command: String) {
60+
browser.get()?.postChat(command)
61+
}
62+
63+
override fun onChange(command: FlareUiMessage) {
64+
browser.get()?.postChat(command)
65+
}
66+
}
67+
)
68+
}
2569

2670
val component = panel {
2771
row {
@@ -34,14 +78,12 @@ class AmazonQPanel(private val parent: Disposable) {
3478
row {
3579
cell(
3680
JButton("Show Web Debugger").apply {
37-
addActionListener(
38-
ActionListener {
39-
// Code to be executed when the button is clicked
40-
// Add your logic here
41-
42-
browser?.jcefBrowser?.openDevtools()
43-
},
44-
)
81+
addActionListener {
82+
// Code to be executed when the button is clicked
83+
// Add your logic here
84+
85+
browser.get().jcefBrowser.openDevtools()
86+
}
4587
},
4688
)
4789
.align(AlignX.CENTER)
@@ -51,31 +93,114 @@ class AmazonQPanel(private val parent: Disposable) {
5193
}
5294

5395
init {
54-
init()
55-
}
56-
57-
fun disposeAndRecreate() {
58-
webviewContainer.removeAll()
59-
val toDispose = browser
60-
init()
61-
if (toDispose != null) {
62-
Disposer.dispose(toDispose)
63-
}
64-
}
65-
66-
private fun init() {
6796
if (!JBCefApp.isSupported()) {
6897
// Fallback to an alternative browser-less solution
6998
if (AppMode.isRemoteDevHost()) {
7099
webviewContainer.add(JBTextArea("Amazon Q chat is not supported in remote dev environment."))
71100
} else {
72101
webviewContainer.add(JBTextArea("JCEF not supported"))
73102
}
74-
browser = null
103+
browser.complete(null)
75104
} else {
76-
browser = Browser(parent).also {
77-
webviewContainer.add(it.component())
105+
val loadingPanel = JBLoadingPanel(null, this)
106+
val wrapper = Wrapper()
107+
loadingPanel.startLoading()
108+
109+
webviewContainer.add(wrapper)
110+
wrapper.setContent(loadingPanel)
111+
112+
ApplicationManager.getApplication().executeOnPooledThread {
113+
val webUri = runBlocking { service<ArtifactManager>().fetchArtifact(project).resolve("amazonq-ui.js").toUri() }
114+
loadingPanel.stopLoading()
115+
runInEdt {
116+
browser.complete(
117+
Browser(this, webUri, project).also {
118+
wrapper.setContent(it.component())
119+
120+
initConnections()
121+
connectUi(it)
122+
connectApps(it)
123+
}
124+
)
125+
}
78126
}
79127
}
80128
}
129+
130+
fun sendMessage(message: AmazonQMessage, tabType: String) {
131+
appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach {
132+
scope.launch {
133+
it.messagesFromUiToApp.publish(message)
134+
}
135+
}
136+
}
137+
138+
fun sendMessageAppToUi(message: AmazonQMessage, tabType: String) {
139+
appConnections.filter { it.app.tabTypes.contains(tabType) }.forEach {
140+
scope.launch {
141+
it.messagesFromAppToUi.publish(message)
142+
}
143+
}
144+
}
145+
146+
private fun initConnections() {
147+
val apps = appSource.getApps(project)
148+
apps.forEach { app ->
149+
appConnections += AppConnection(
150+
app = app,
151+
messagesFromAppToUi = MessageConnector(),
152+
messagesFromUiToApp = MessageConnector(),
153+
messageTypeRegistry = MessageTypeRegistry(),
154+
)
155+
}
156+
}
157+
158+
private fun connectApps(browser: Browser) {
159+
val fqnWebviewAdapter = FqnWebviewAdapter(browser.jcefBrowser, browserConnector)
160+
161+
appConnections.forEach { connection ->
162+
val initContext = AmazonQAppInitContext(
163+
project = project,
164+
messagesFromAppToUi = connection.messagesFromAppToUi,
165+
messagesFromUiToApp = connection.messagesFromUiToApp,
166+
messageTypeRegistry = connection.messageTypeRegistry,
167+
fqnWebviewAdapter = fqnWebviewAdapter,
168+
)
169+
// Connect the app to the UI
170+
connection.app.init(initContext)
171+
// Dispose of the app when the tool window is disposed.
172+
Disposer.register(this, connection.app)
173+
}
174+
}
175+
176+
private fun connectUi(browser: Browser) {
177+
browser.init(
178+
isCodeTransformAvailable = isCodeTransformAvailable(project),
179+
isFeatureDevAvailable = isFeatureDevAvailable(project),
180+
isCodeScanAvailable = isCodeScanAvailable(project),
181+
isCodeTestAvailable = isCodeTestAvailable(project),
182+
isDocAvailable = isDocAvailable(project),
183+
highlightCommand = highlightCommand(),
184+
activeProfile = QRegionProfileManager.getInstance().takeIf { it.shouldDisplayProfileInfo(project) }?.activeProfile(project)
185+
)
186+
187+
scope.launch {
188+
// Pipe messages from the UI to the relevant apps and vice versa
189+
browserConnector.connect(
190+
browser = browser,
191+
connections = appConnections,
192+
)
193+
}
194+
195+
scope.launch {
196+
// Update the theme in the UI when the IDE theme changes
197+
browserConnector.connectTheme(
198+
chatBrowser = browser.jcefBrowser.cefBrowser,
199+
themeSource = editorThemeAdapter.onThemeChange(),
200+
)
201+
}
202+
}
203+
204+
override fun dispose() {
205+
}
81206
}

0 commit comments

Comments
 (0)