Skip to content

Commit 78605bc

Browse files
authored
Handle runtime missing JCEF (#2451)
Resolves https://linear.app/sourcegraph/issue/CODY-3753/make-it-possible-to-automatically-switch-user-runtime-to-one-which. ## Test plan ### Dev run (`:customRunIde`) 1. Modify `build.gradle.kts`. Change ``` dependencies { intellijPlatform { jetbrainsRuntime() ... ``` to ``` dependencies { intellijPlatform { jetbrainsRuntimeExplicit("jbr-21.0.3-osx-aarch64-b446.1") ... ``` so that the IDE will use no jcef runtime at the start. 2. `:customRunIde` 3. Verify the notification appears 4. Verify the proper panel appears
1 parent a09e299 commit 78605bc

File tree

7 files changed

+181
-5
lines changed

7 files changed

+181
-5
lines changed

src/main/kotlin/com/sourcegraph/cody/CodyToolWindowContent.kt

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import com.intellij.util.concurrency.annotations.RequiresEdt
88
import com.intellij.util.ui.UIUtil
99
import com.sourcegraph.cody.chat.SignInWithSourcegraphPanel
1010
import com.sourcegraph.cody.chat.ui.CodyOnboardingGuidancePanel
11+
import com.sourcegraph.cody.chat.ui.ErrorPanel
12+
import com.sourcegraph.cody.chat.ui.MissingJcefPanel
1113
import com.sourcegraph.cody.config.CodyAccount
1214
import com.sourcegraph.cody.config.CodyApplicationSettings
1315
import com.sourcegraph.cody.config.CodyAuthenticationManager
16+
import com.sourcegraph.cody.initialization.VerifyJavaBootRuntimeVersion.Companion.isCurrentRuntimeMissingJcef
1417
import com.sourcegraph.cody.ui.web.CodyToolWindowContentWebviewHost
1518
import com.sourcegraph.cody.ui.web.WebUIService
1619
import java.awt.CardLayout
@@ -21,7 +24,7 @@ import javax.swing.JComponent
2124
import javax.swing.JPanel
2225

2326
@Service(Service.Level.PROJECT)
24-
class CodyToolWindowContent(project: Project) {
27+
class CodyToolWindowContent(val project: Project) {
2528
private val cardLayout = CardLayout()
2629
private val cardPanel = JPanel(cardLayout)
2730
val allContentPanel: JComponent = JPanel(GridLayout(1, 1))
@@ -37,11 +40,13 @@ class CodyToolWindowContent(project: Project) {
3740
cardPanel.add(codyOnboardingGuidancePanel, ONBOARDING_PANEL, ONBOARDING_PANEL_INDEX)
3841

3942
// Because the webview may be created lazily, populate a placeholder control.
40-
val placeholder = JPanel(GridBagLayout())
43+
val spinnerPlaceholder = JPanel(GridBagLayout())
4144
val spinnerLabel =
4245
JBLabel("Starting Cody...", Icons.StatusBar.CompletionInProgress, JBLabel.CENTER)
43-
placeholder.add(spinnerLabel, GridBagConstraints())
44-
cardPanel.add(placeholder, LOADING_PANEL, LOADING_PANEL_INDEX)
46+
spinnerPlaceholder.add(spinnerLabel, GridBagConstraints())
47+
cardPanel.add(spinnerPlaceholder, LOADING_PANEL, LOADING_PANEL_INDEX)
48+
cardPanel.add(MissingJcefPanel(), CHANGE_RUNTIME_PANEL, CHANGE_RUNTIME_PANEL_INDEX)
49+
cardPanel.add(ErrorPanel(), ERROR_PANEL, ERROR_INDEX)
4550

4651
WebUIService.getInstance(project).views.provideCodyToolWindowContent(this)
4752

@@ -67,6 +72,18 @@ class CodyToolWindowContent(project: Project) {
6772
showView(cardPanel)
6873
return
6974
}
75+
val errorOnProxyCreation = WebUIService.getInstance(project).proxyCreationException.get()
76+
if (errorOnProxyCreation != null) {
77+
if (isCurrentRuntimeMissingJcef()) {
78+
cardLayout.show(cardPanel, CHANGE_RUNTIME_PANEL)
79+
showView(cardPanel)
80+
} else {
81+
cardLayout.show(cardPanel, ERROR_PANEL)
82+
showView(cardPanel)
83+
logger.error(errorOnProxyCreation)
84+
}
85+
return
86+
}
7087
cardLayout.show(cardPanel, LOADING_PANEL)
7188
showView(webview?.proxy?.component ?: cardPanel)
7289
}
@@ -99,10 +116,14 @@ class CodyToolWindowContent(project: Project) {
99116
const val ONBOARDING_PANEL = "onboardingPanel"
100117
const val SIGN_IN_PANEL = "signInWithSourcegraphPanel"
101118
const val LOADING_PANEL = "loadingPanel"
119+
const val CHANGE_RUNTIME_PANEL = "changeRuntime"
120+
const val ERROR_PANEL = "error"
102121

103122
const val SIGN_IN_PANEL_INDEX = 0
104123
const val ONBOARDING_PANEL_INDEX = 1
105124
const val LOADING_PANEL_INDEX = 2
125+
const val CHANGE_RUNTIME_PANEL_INDEX = 3
126+
const val ERROR_INDEX = 4
106127

107128
var logger = Logger.getInstance(CodyToolWindowContent::class.java)
108129

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.sourcegraph.cody.chat.ui
2+
3+
import com.intellij.ui.SimpleTextAttributes
4+
import com.intellij.util.ui.JBUI
5+
import com.sourcegraph.Icons
6+
import com.sourcegraph.common.CodyBundle
7+
import java.awt.GridBagConstraints
8+
import java.awt.GridBagLayout
9+
import javax.swing.JLabel
10+
import javax.swing.JPanel
11+
import javax.swing.JTextPane
12+
import javax.swing.text.SimpleAttributeSet
13+
import javax.swing.text.StyleConstants
14+
15+
class ErrorPanel : JPanel(GridBagLayout()) {
16+
private val description =
17+
JTextPane().apply {
18+
text = CodyBundle.getString("ErrorPanel.content")
19+
foreground = SimpleTextAttributes.GRAY_ATTRIBUTES.fgColor
20+
val center = SimpleAttributeSet()
21+
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER)
22+
styledDocument.setParagraphAttributes(0, styledDocument.length, center, false)
23+
}
24+
25+
private val label =
26+
JLabel(CodyBundle.getString("ErrorPanel.label")).apply { icon = Icons.CodyLogoSlash }
27+
28+
init {
29+
val constraints =
30+
GridBagConstraints().apply {
31+
fill = GridBagConstraints.HORIZONTAL
32+
gridx = 0
33+
insets = JBUI.insets(20)
34+
}
35+
36+
add(label, constraints)
37+
add(description, constraints)
38+
}
39+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.sourcegraph.cody.chat.ui
2+
3+
import com.intellij.openapi.projectRoots.impl.jdkDownloader.RuntimeChooserUtil
4+
import com.intellij.ui.SimpleTextAttributes
5+
import com.intellij.util.ui.JBUI
6+
import com.sourcegraph.common.CodyBundle
7+
import java.awt.GridBagConstraints
8+
import java.awt.GridBagLayout
9+
import javax.swing.JButton
10+
import javax.swing.JPanel
11+
import javax.swing.JTextPane
12+
import javax.swing.text.SimpleAttributeSet
13+
import javax.swing.text.StyleConstants
14+
15+
class MissingJcefPanel : JPanel(GridBagLayout()) {
16+
private val jcefDescription =
17+
JTextPane().apply {
18+
text = CodyBundle.getString("MissingJcefPanel.content")
19+
foreground = SimpleTextAttributes.GRAY_ATTRIBUTES.fgColor
20+
val center = SimpleAttributeSet()
21+
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER)
22+
styledDocument.setParagraphAttributes(0, styledDocument.length, center, false)
23+
}
24+
25+
private val jcefButton =
26+
JButton(CodyBundle.getString("chooseRuntimeWithJcef.button")).apply {
27+
addActionListener { RuntimeChooserUtil.showRuntimeChooserPopup() }
28+
}
29+
30+
init {
31+
val constraints =
32+
GridBagConstraints().apply {
33+
fill = GridBagConstraints.HORIZONTAL
34+
gridx = 0
35+
insets = JBUI.insets(20)
36+
}
37+
38+
add(jcefDescription, constraints)
39+
add(jcefButton, constraints)
40+
}
41+
}

src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class PostStartupActivity : ProjectActivity {
2929
// doing something wrong, which may be slowing down agent startup. Not fixing it now but this
3030
// deserves more investigation.
3131
override suspend fun execute(project: Project) {
32+
VerifyJavaBootRuntimeVersion().runActivity(project)
3233
SettingsMigration().runActivity(project)
3334
CodyAuthNotificationActivity().runActivity(project)
3435
CodyAuthenticationManager.getInstance().addAuthChangeListener(project)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.sourcegraph.cody.initialization
2+
3+
import com.intellij.notification.Notification
4+
import com.intellij.notification.NotificationAction
5+
import com.intellij.notification.NotificationType
6+
import com.intellij.notification.impl.NotificationFullContent
7+
import com.intellij.openapi.actionSystem.AnActionEvent
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.projectRoots.impl.jdkDownloader.RuntimeChooserCurrentItem
10+
import com.intellij.openapi.projectRoots.impl.jdkDownloader.RuntimeChooserUtil
11+
import com.intellij.openapi.projectRoots.impl.jdkDownloader.currentRuntime
12+
import com.sourcegraph.Icons
13+
import com.sourcegraph.common.CodyBundle
14+
import com.sourcegraph.common.NotificationGroups
15+
16+
class VerifyJavaBootRuntimeVersion : Activity {
17+
18+
override fun runActivity(project: Project) {
19+
if (isCurrentRuntimeMissingJcef()) {
20+
JcefRuntimeNotification().notify(project)
21+
}
22+
}
23+
24+
companion object {
25+
fun isCurrentRuntimeMissingJcef(): Boolean {
26+
val model = RuntimeChooserCurrentItem.currentRuntime()
27+
val doesNameContainJcefSuffix = model.version?.endsWith("-jcef") ?: true
28+
return !doesNameContainJcefSuffix
29+
}
30+
}
31+
}
32+
33+
class JcefRuntimeNotification :
34+
Notification(
35+
NotificationGroups.SOURCEGRAPH_ERRORS,
36+
CodyBundle.getString("JcefRuntimeNotification.title"),
37+
CodyBundle.getString("JcefRuntimeNotification.content"),
38+
NotificationType.WARNING),
39+
NotificationFullContent {
40+
41+
init {
42+
icon = Icons.CodyLogoSlash
43+
44+
addAction(
45+
object : NotificationAction(CodyBundle.getString("chooseRuntimeWithJcef.button")) {
46+
override fun actionPerformed(anActionEvent: AnActionEvent, notification: Notification) {
47+
RuntimeChooserUtil.showRuntimeChooserPopup()
48+
}
49+
})
50+
}
51+
}

src/main/kotlin/com/sourcegraph/cody/ui/web/WebUIService.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import com.intellij.openapi.components.Service
66
import com.intellij.openapi.components.service
77
import com.intellij.openapi.diagnostic.Logger
88
import com.intellij.openapi.project.Project
9+
import com.jetbrains.rd.util.AtomicReference
910
import com.jetbrains.rd.util.ConcurrentHashMap
11+
import com.sourcegraph.cody.CodyToolWindowContent
1012
import com.sourcegraph.cody.agent.ConfigFeatures
1113
import com.sourcegraph.cody.agent.CurrentConfigFeatures
1214
import com.sourcegraph.cody.agent.protocol.WebviewCreateWebviewPanelParams
@@ -45,6 +47,8 @@ class WebUIService(private val project: Project) {
4547
return panels.reset()
4648
}
4749

50+
val proxyCreationException = AtomicReference<IllegalStateException?>(null)
51+
4852
private fun <T> withCreationGate(name: String, action: (gate: WebUIProxyCreationGate) -> T): T {
4953
val gate =
5054
proxies.computeIfAbsent(name) {
@@ -112,7 +116,8 @@ class WebUIService(private val project: Project) {
112116
portMapping = emptyList(),
113117
enableFindWidget = false,
114118
retainContextWhenHidden = false))
115-
val proxy = WebUIProxy.create(delegate)
119+
120+
val proxy = createWebUIProxy(delegate) ?: return
116121
delegate.view = createView(proxy)
117122
proxy.updateTheme(themeController.getTheme())
118123
withCreationGate(handle) {
@@ -122,6 +127,16 @@ class WebUIService(private val project: Project) {
122127
}
123128
}
124129

130+
private fun createWebUIProxy(delegate: WebUIHost): WebUIProxy? =
131+
try {
132+
proxyCreationException.getAndSet(null)
133+
WebUIProxy.create(delegate)
134+
} catch (e: IllegalStateException) {
135+
proxyCreationException.getAndSet(e)
136+
CodyToolWindowContent.executeOnInstanceIfNotDisposed(project) { refreshPanelsVisibility() }
137+
null
138+
}
139+
125140
internal fun createWebviewPanel(params: WebviewCreateWebviewPanelParams) {
126141
runInEdt {
127142
val delegate = WebUIHostImpl(project, params.handle, params.options)

src/main/resources/CodyBundle.properties

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ EndOfTrialNotification.do-not-show-again=Don't show again
9898
TrialEndedNotification.ignore=cody.ignore.notification.trial-ended
9999
TrialEndedNotification.ended.title=Your Cody Pro trial has ended
100100
TrialEndedNotification.ended.content=<html>Thank you for trying Cody Pro! Your trial period has ended. To keep enjoying all the features of Cody Pro, subscribe to our monthly plan. You can cancel anytime.</html>
101+
JcefRuntimeNotification.title=Cody requires a runtime with JCEF
102+
JcefRuntimeNotification.content=Your current runtime does not support Java Chromium Embedded Framework (JCEF), which is essential for Cody to work properly.
103+
MissingJcefPanel.content=Cody relies on Java Chromium Embedded Framework (JCEF) to function. To continue using Cody in your IDE, please switch to a runtime that includes JCEF.
104+
chooseRuntimeWithJcef.button=Choose a runtime with JCEF...
105+
106+
ErrorPanel.content=Check the IDE logs for errors
107+
ErrorPanel.label=Cody encountered an unexpected error
108+
101109
export.failed=Cody: Chat export failed. Please retry...
102110

103111
# GotItTooltip

0 commit comments

Comments
 (0)