Skip to content
This repository was archived by the owner on Aug 1, 2025. It is now read-only.

Commit 3b847cb

Browse files
authored
Improve Cody Console in JetBrains (#8192)
# PR Summary: Improve Cody Console in JetBrains ## Overview Enhanced the JetBrains Cody Console with multi-channel support, filtering capabilities, and improved UI. ## Key Changes ### JetBrains Console Improvements - **Multi-channel support**: Console now creates separate tabs for different channels - **Message filtering**: Added toggle to show only errors and warnings - **Custom toolbar**: Added clear and filter actions to each console tab - **Message storage**: Console now stores messages for filtering and refreshing - **Better error handling**: Improved message categorisation and display ### VSCode/Core Logging Fixes - **Log level fix**: Upgraded VSCode output channels to use proper log levels - **Telemetry logging fix**: Added `NotAuthenticated` tier to telemetry; previously serialisation failing when user was not authenticated and `tier` was `undefined` ## Test plan 1. Run `/gradlew :customRunIDE` and wait for Cody to start. 2. Open `Problems` view. You should notice two Cody tabs: <img width="1792" height="598" alt="image" src="https://github.com/user-attachments/assets/f3ff1d46-4485-454d-b82f-8a10641a2896" /> 3. You can try to generate some errors (e.g. network errors, byt disabling WiFi and asking Cody some questions) and try how the error filtering works.
1 parent 1f6b485 commit 3b847cb

3 files changed

Lines changed: 102 additions & 31 deletions

File tree

jetbrains/src/main/kotlin/com/sourcegraph/cody/error/CodyConsole.kt

Lines changed: 90 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,122 @@
11
package com.sourcegraph.cody.error
22

3+
import com.intellij.execution.actions.ClearConsoleAction
34
import com.intellij.execution.filters.TextConsoleBuilderFactory
45
import com.intellij.execution.ui.ConsoleViewContentType
6+
import com.intellij.openapi.actionSystem.*
57
import com.intellij.openapi.application.runInEdt
68
import com.intellij.openapi.components.Service
79
import com.intellij.openapi.components.service
810
import com.intellij.openapi.diagnostic.Logger
911
import com.intellij.openapi.project.Project
12+
import com.intellij.openapi.ui.SimpleToolWindowPanel
1013
import com.intellij.openapi.wm.ToolWindowManager
11-
import com.intellij.ui.content.Content
1214
import com.sourcegraph.cody.agent.protocol_generated.DebugMessage
1315
import com.sourcegraph.config.ConfigUtil
1416

1517
@Service(Service.Level.PROJECT)
16-
class CodyConsole(project: Project) {
18+
class CodyConsole(val project: Project) {
1719
private val logger = Logger.getInstance(CodyConsole::class.java)
18-
private val consoleView = TextConsoleBuilderFactory.getInstance().createBuilder(project).console
1920
private val toolWindow = ToolWindowManager.getInstance(project).getToolWindow("Problems View")
20-
var content: Content? = null
21+
private val consoleViews = mutableMapOf<String, com.intellij.execution.ui.ConsoleView>()
22+
23+
private val storedMessages = mutableMapOf<String, MutableList<DebugMessage>>()
24+
private val showErrorsAndWarnsOnly = mutableMapOf<String, Boolean>()
2125

2226
fun addMessage(message: DebugMessage) {
2327
if (toolWindow?.isDisposed != false) return
28+
storedMessages.getOrPut(message.channel) { mutableListOf() }.add(message)
29+
printMessage(message, useLogger = true)
30+
}
31+
32+
private fun printMessage(message: DebugMessage, useLogger: Boolean = false) {
33+
val channel = message.channel
34+
val consoleView = getOrCreateConsoleForChannel(channel)
35+
36+
val isErrorOrWarn = message.level == "error" || message.level == "warn"
37+
if (showErrorsAndWarnsOnly.getOrPut(channel) { false } && !isErrorOrWarn) {
38+
return
39+
}
2440

25-
val messageText = "${message.channel}: ${message.message}\n"
26-
if (message.level == "error" || message.level == "warn") {
27-
runInEdt { content?.let { toolWindow.contentManager.setSelectedContent(it) } }
41+
val messageText = "${message.message}\n"
42+
if (isErrorOrWarn) {
2843
consoleView.print(messageText, ConsoleViewContentType.ERROR_OUTPUT)
29-
logger.warn(messageText)
30-
} else if (ConfigUtil.isCodyDebugEnabled()) {
44+
if (useLogger) logger.warn("$channel: ${message.message}")
45+
} else if (ConfigUtil.isCodyDebugEnabled() || ConfigUtil.isDevMode()) {
3146
consoleView.print(messageText, ConsoleViewContentType.NORMAL_OUTPUT)
32-
logger.info(messageText)
47+
if (useLogger) logger.info("$channel: ${message.message}")
3348
}
49+
}
50+
51+
private fun getOrCreateConsoleForChannel(channel: String): com.intellij.execution.ui.ConsoleView {
52+
return consoleViews.getOrPut(channel) {
53+
val consoleView = TextConsoleBuilderFactory.getInstance().createBuilder(project).console
54+
55+
val toggleAction =
56+
object :
57+
ToggleAction(
58+
{ "Only Show Errors And Warnings" }, com.intellij.icons.AllIcons.General.Error) {
59+
override fun isSelected(e: AnActionEvent): Boolean {
60+
return showErrorsAndWarnsOnly[channel] ?: false
61+
}
62+
63+
override fun getActionUpdateThread(): ActionUpdateThread {
64+
return ActionUpdateThread.EDT
65+
}
66+
67+
override fun setSelected(e: AnActionEvent, state: Boolean) {
68+
showErrorsAndWarnsOnly[channel] = state
69+
refreshConsole(channel)
70+
}
71+
}
3472

35-
if (ConfigUtil.isCodyDebugEnabled() && ConfigUtil.isDevMode()) {
36-
toolWindow.contentManager.getReady(this).doWhenDone {
37-
if (!toolWindow.isVisible) {
38-
runInEdt { toolWindow.show() }
39-
}
73+
val clearAction =
74+
object : ClearConsoleAction() {
75+
override fun actionPerformed(e: AnActionEvent) {
76+
consoleView.clear()
77+
storedMessages[channel]?.clear()
78+
}
79+
80+
override fun getActionUpdateThread(): ActionUpdateThread {
81+
return ActionUpdateThread.EDT
82+
}
83+
}
84+
85+
runInEdt {
86+
if (toolWindow?.isDisposed != false) return@runInEdt
87+
88+
val component = consoleView.component
89+
val factory = toolWindow.contentManager.factory
90+
91+
val actions = consoleView.createConsoleActions().toMutableList()
92+
actions.removeIf({ it is ClearConsoleAction })
93+
actions.add(clearAction)
94+
actions.add(toggleAction)
95+
96+
val actionGroup = DefaultActionGroup(actions)
97+
val toolbar =
98+
ActionManager.getInstance().createActionToolbar("CodyConsole", actionGroup, false)
99+
toolbar.targetComponent = component
100+
101+
val panel = SimpleToolWindowPanel(false, true)
102+
panel.toolbar = toolbar.component
103+
panel.setContent(component)
104+
105+
val content = factory.createContent(panel, channel, true)
106+
toolWindow.contentManager.addContent(content)
40107
}
108+
109+
consoleView
41110
}
42111
}
43112

44-
init {
113+
private fun refreshConsole(channel: String) {
114+
val consoleView = consoleViews[channel] ?: return
115+
val messages = storedMessages[channel] ?: return
116+
45117
runInEdt {
46-
if (toolWindow?.isDisposed != false) return@runInEdt
47-
val factory = toolWindow.contentManager.factory
48-
content =
49-
factory
50-
.createContent(consoleView.component, "Cody Console", true)
51-
.also(toolWindow.contentManager::addContent)
118+
consoleView.clear()
119+
messages.forEach { message -> printMessage(message) }
52120
}
53121
}
54122

lib/shared/src/telemetry-v2/cody-tier.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ enum CodyTier {
66
Free = 0,
77
Pro = 1,
88
Enterprise = 2,
9+
NotAuthenticated = 3,
910
}
1011

1112
export function getTier(
1213
authStatus: AuthStatus,
1314
sub: UserProductSubscription | null
1415
): CodyTier | undefined {
1516
return !authStatus.authenticated
16-
? undefined
17+
? CodyTier.NotAuthenticated
1718
: !isDotCom(authStatus)
1819
? CodyTier.Enterprise
1920
: !sub || sub.userCanUpgrade

vscode/src/output-channel-logger.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,28 @@ export const CODY_OUTPUT_CHANNEL = 'Cody by Sourcegraph'
1010
* Provides a default output channel and creates per-feature output channels when needed.
1111
*/
1212
class OutputChannelManager {
13-
public defaultOutputChannel = vscode.window.createOutputChannel(CODY_OUTPUT_CHANNEL, 'json')
14-
private outputChannels: Map<string, vscode.OutputChannel> = new Map()
13+
public defaultOutputChannel = vscode.window.createOutputChannel(CODY_OUTPUT_CHANNEL, { log: true })
14+
private outputChannels: Map<string, vscode.LogOutputChannel> = new Map()
1515

16-
getOutputChannel(feature: string): vscode.OutputChannel | undefined {
16+
getOutputChannel(feature: string): vscode.LogOutputChannel | undefined {
1717
if (!this.outputChannels.has(feature) && process.env.NODE_ENV === 'development') {
18-
const channel = vscode.window.createOutputChannel(`Cody ${feature}`, 'json')
18+
const channel = vscode.window.createOutputChannel(`Cody ${feature}`, { log: true })
1919
this.outputChannels.set(feature, channel)
2020
}
2121

2222
return this.outputChannels.get(feature)
2323
}
2424

25-
appendLine(text: string, feature?: string): void {
25+
appendLine(level: 'debug' | 'error', text: string, feature?: string): void {
2626
// Always log to the default output channel
27-
this.defaultOutputChannel?.appendLine(text)
27+
level === 'error'
28+
? this.defaultOutputChannel?.error(text)
29+
: this.defaultOutputChannel?.debug(text)
2830

2931
// Also log to the feature-specific output channel if available
3032
if (feature) {
3133
const channel = this.getOutputChannel(feature)
32-
channel?.appendLine(text)
34+
level === 'error' ? channel?.error(text) : channel?.debug(text)
3335
}
3436

3537
// Write to log file if needed
@@ -97,7 +99,7 @@ export class Logger {
9799
debugVerbose,
98100
})
99101

100-
outputChannelManager.appendLine(message, feature)
102+
outputChannelManager.appendLine(level, message, feature)
101103
}
102104
}
103105

0 commit comments

Comments
 (0)