From ad933a31dfa9c71a714e3675ba513118accc900a Mon Sep 17 00:00:00 2001 From: kirillk Date: Mon, 10 Aug 2026 13:34:46 -0400 Subject: [PATCH] feat(jetbrains): align prompt background with code fragments Give the prompt input and the transcript user-prompt bubble a dedicated background that defaults to the editor's code-fragment (code block) color, via a new Kilo.Session.Prompt.Background theme key so it can be restyled independently. Attachment strip is transparent so the prompt surface shows through, and the prompt-bar mode/model/effort pickers now blend into that surface when idle while keeping the standard hover fill. The transcript bubble drops its outline unless the prompt shares the session background, in which case it keeps the original outline for contrast. --- .../ui/prompt/PromptAttachmentStrip.kt | 3 +++ .../client/session/ui/prompt/PromptPanel.kt | 16 ++++++----- .../session/ui/style/SessionEditorStyle.kt | 12 ++++----- .../client/session/ui/style/SessionUiStyle.kt | 10 +++++++ .../client/session/views/MessageView.kt | 23 +++++++++------- .../client/session/views/PromptView.kt | 2 +- .../ai/kilocode/client/ui/PickerButton.kt | 27 +++++++++++++------ .../kotlin/ai/kilocode/client/ui/UiStyle.kt | 10 +++++++ .../ai/kilocode/client/ui/md/MdCommon.kt | 2 +- .../client/session/ui/PromptPanelTest.kt | 19 ++++++++----- .../client/session/views/TextViewTest.kt | 4 +-- 11 files changed, 87 insertions(+), 41 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptAttachmentStrip.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptAttachmentStrip.kt index 9efa44aabf1..f4d850bdc68 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptAttachmentStrip.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptAttachmentStrip.kt @@ -23,6 +23,9 @@ class PromptAttachmentStrip( private val chips = LinkedHashMap() init { + // Transparent so the prompt shell background shows through instead of the strip + // painting its own panel background above the input surface. + isOpaque = false border = JBUI.Borders.emptyBottom(UiStyle.Gap.sm()) isVisible = false } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt index 1b5b656b85c..7a4fcd32974 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt @@ -127,11 +127,14 @@ class PromptPanel( private val INVALID_KEY = CodeInsightColors.WRONG_REFERENCES_ATTRIBUTES } - val mode = ModePicker() + // Prompt-bar pickers blend into the prompt background when idle and only show the standard + // hover fill on pointer-over (idleFill = null paints nothing behind the label). + val mode = ModePicker().apply { idleFill = null } val model = ModelPicker().apply { placement = ModelPicker.Placement.ABOVE + idleFill = null } - val reasoning = ReasoningPicker() + val reasoning = ReasoningPicker().apply { idleFill = null } var onReset: () -> Unit = {} var onChange: () -> Unit = {} var onAutoApproveToggle: (Boolean) -> Unit = {} @@ -411,7 +414,7 @@ class PromptPanel( @RequiresEdt private fun chrome(ed: EditorEx) { if (ed.isDisposed) return - style.applyPromptToEditor(ed) + style.applyPromptToEditor(ed, SessionUiStyle.View.Prompt.bgColor(style)) if (ed.isDisposed) return } @@ -497,11 +500,12 @@ class PromptPanel( @RequiresEdt override fun applyStyle(style: SessionEditorStyle) { this.style = style - background = style.editorScheme.defaultBackground - shell.background = style.editorScheme.defaultBackground + val bg = SessionUiStyle.View.Prompt.bgColor(style) + background = bg + shell.background = bg style.applyTranscriptToField(editor) editor.getEditor(false)?.let(::chrome) - editor.background = style.editorBackground + editor.background = bg syncEditorHeight() syncAutoApprove() syncHighlights() diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionEditorStyle.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionEditorStyle.kt index 1a71b24c65a..66d4c18fef0 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionEditorStyle.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionEditorStyle.kt @@ -80,7 +80,7 @@ data class SessionEditorStyle( } /** Apply the visible prompt-input text styling to embedded session editor components. */ - fun applyPromptToEditor(editor: EditorEx) { + fun applyPromptToEditor(editor: EditorEx, background: Color = editorBackground) { if (editor.isDisposed) return applyTranscriptToEditor(editor) if (editor.isDisposed) return @@ -92,11 +92,11 @@ data class SessionEditorStyle( 0, JBUI.scale(SessionUiStyle.View.Prompt.EDITOR_HORIZONTAL_INSET), ) - editor.backgroundColor = editorBackground - editor.component.background = editorBackground - editor.contentComponent.background = editorBackground - editor.scrollPane.background = editorBackground - editor.scrollPane.viewport.background = editorBackground + editor.backgroundColor = background + editor.component.background = background + editor.contentComponent.background = background + editor.scrollPane.background = background + editor.scrollPane.viewport.background = background editor.scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER editor.scrollPane.revalidate() editor.scrollPane.repaint() diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt index f610298b9b3..9a3af597cf7 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt @@ -96,6 +96,16 @@ object SessionUiStyle { /** Prompt input dimensions and chrome inside the session view. */ object Prompt { + /** + * Background of the prompt input and the transcript user-prompt bubble. Uses a dedicated + * theme key so the prompt surface can be restyled independently, defaulting to the + * code-fragment background so the prompt matches rendered code blocks. + */ + fun bgColor(style: SessionEditorStyle): Color = JBColor.namedColor( + "Kilo.Session.Prompt.Background", + UiStyle.Colors.codeBlockBackground(style.editorScheme), + ) + const val EDITOR_LINES = 1 const val EDITOR_CHROME = 16 const val SEND_BUTTON_SIZE = 24 diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/MessageView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/MessageView.kt index 7a55bc0d08a..710c0f93351 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/MessageView.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/MessageView.kt @@ -99,7 +99,7 @@ class MessageView( init { isOpaque = false - if (msg.info.role == SessionUiStyle.View.Message.USER_ROLE) background = style.editorScheme.defaultBackground + if (msg.info.role == SessionUiStyle.View.Message.USER_ROLE) background = SessionUiStyle.View.Prompt.bgColor(style) border = assistantBorder() // Populate content that already exists (e.g. after loadHistory) @@ -439,7 +439,7 @@ class MessageView( @RequiresEdt override fun applyStyle(style: SessionEditorStyle) { this.style = style - if (msg.info.role == SessionUiStyle.View.Message.USER_ROLE) background = style.editorScheme.defaultBackground + if (msg.info.role == SessionUiStyle.View.Message.USER_ROLE) background = SessionUiStyle.View.Prompt.bgColor(style) for (view in parts.values) view.applyStyle(style) refresh() } @@ -481,14 +481,17 @@ class MessageView( g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) val arc = JBUI.scale(JBUI.getInt("Button.arc", SessionUiStyle.View.Prompt.CORNER_ARC)) val pt = if (box === this) Point() else SwingUtilities.convertPoint(box, Point(), this) - val x = pt.x - val y = pt.y - val w = box.width - 1 - val h = box.height - 1 - g2.color = style.editorScheme.defaultBackground - g2.fillRoundRect(x, y, box.width, box.height, arc, arc) - g2.color = SessionUiStyle.View.Outline.color() - if (w > 0 && h > 0) g2.drawRoundRect(x, y, w, h, arc, arc) + val bg = SessionUiStyle.View.Prompt.bgColor(style) + g2.color = bg + g2.fillRoundRect(pt.x, pt.y, box.width, box.height, arc, arc) + // When the prompt shares the session background there is no fill contrast, so draw the + // outline to keep the bubble visible. + if (bg.rgb == style.editorBackground.rgb) { + val w = box.width - 1 + val h = box.height - 1 + g2.color = SessionUiStyle.View.Outline.color() + if (w > 0 && h > 0) g2.drawRoundRect(pt.x, pt.y, w, h, arc, arc) + } } finally { g2.dispose() } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptView.kt index 7d3f7e267e9..32ddf7c21ec 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptView.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptView.kt @@ -74,7 +74,7 @@ class PromptView( override fun styleFont(style: SessionEditorStyle) = style.transcriptFont - override fun styleBackground(style: SessionEditorStyle) = style.editorBackground + override fun styleBackground(style: SessionEditorStyle) = SessionUiStyle.View.Prompt.bgColor(style) private fun sync() { md.set(linkifyMentions(buffer.toString(), mentions)) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/PickerButton.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/PickerButton.kt index 86b1b6763ef..f2ccbdd8564 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/PickerButton.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/PickerButton.kt @@ -2,6 +2,7 @@ package ai.kilocode.client.ui import com.intellij.ui.components.JBLabel import com.intellij.util.ui.JBUI +import java.awt.Color import java.awt.Graphics import java.awt.Graphics2D import java.awt.RenderingHints @@ -11,6 +12,13 @@ import java.awt.event.MouseEvent open class PickerButton : JBLabel() { private var over = false + /** + * Idle (unhovered) fill. Defaults to the standard picker surface; set to `null` to paint + * nothing so the picker blends into its container (e.g. the prompt background). The hover + * fill is unaffected. + */ + var idleFill: Color? = UiStyle.Colors.picker() + init { border = pickerBorder() background = UiStyle.Colors.picker() @@ -34,14 +42,17 @@ open class PickerButton : JBLabel() { } override fun paintComponent(g: Graphics) { - val g2 = g.create() as Graphics2D - try { - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) - g2.color = if (isEnabled && over) JBUI.CurrentTheme.ActionButton.hoverBackground() else UiStyle.Colors.picker() - val arc = JBUI.scale(JBUI.getInt("Button.arc", 6)) - g2.fillRoundRect(0, 0, width, height, arc, arc) - } finally { - g2.dispose() + val fill = if (isEnabled && over) JBUI.CurrentTheme.ActionButton.hoverBackground() else idleFill + if (fill != null) { + val g2 = g.create() as Graphics2D + try { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + g2.color = fill + val arc = JBUI.scale(JBUI.getInt("Button.arc", 6)) + g2.fillRoundRect(0, 0, width, height, arc, arc) + } finally { + g2.dispose() + } } super.paintComponent(g) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt index 7e711204661..b874ed74910 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt @@ -1,6 +1,8 @@ package ai.kilocode.client.ui +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.colors.EditorColorsScheme import com.intellij.ui.JBColor import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI @@ -118,6 +120,14 @@ object UiStyle { /** Uses the editor background so chat cards feel native beside editor content. */ fun editorBackground(): Color = JBColor.lazy { EditorColorsManager.getInstance().globalScheme.defaultBackground } + /** + * Background for rendered code fragments (markdown code blocks). Uses the editor's doc + * code-block attribute background and falls back to the editor background when the theme + * leaves it unset. + */ + fun codeBlockBackground(scheme: EditorColorsScheme): Color = + scheme.getAttributes(DefaultLanguageHighlighterColors.DOC_CODE_BLOCK)?.backgroundColor ?: scheme.defaultBackground + /** * Contained panel background: follows the active theme's text-field/input surface. * Falls back to the panel background when unavailable. diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdCommon.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdCommon.kt index ac73c1400a5..d95db32ee09 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdCommon.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdCommon.kt @@ -94,7 +94,7 @@ internal object MdCommon { ?: fg(style, DefaultLanguageHighlighterColors.DOC_COMMENT) ?: UIUtil.getContextHelpForeground() val border = color(style, EditorColors.PREVIEW_BORDER_COLOR) ?: UiStyle.Colors.contentBorder() - val blockBg = bg(style, DefaultLanguageHighlighterColors.DOC_CODE_BLOCK) ?: style.editorBackground + val blockBg = UiStyle.Colors.codeBlockBackground(style.editorScheme) return MdStyle( font = style.transcriptFont, foreground = style.editorForeground, diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt index 63d41c27f21..3f9c0e22fa5 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt @@ -138,11 +138,11 @@ class PromptPanelTest : BasePlatformTestCase() { assertEquals(style.transcriptFont.size, font.size) } - fun `test prompt input uses editor background`() { + fun `test prompt input uses prompt background`() { val style = SessionEditorStyle.current() val panel = PromptPanel(project = project, onSend = { _, _ -> }, onAbort = {}, onEnhance = { _, _ -> }) - assertEquals(style.editorScheme.defaultBackground, panel.defaultFocusedComponent.background) + assertEquals(SessionUiStyle.View.Prompt.bgColor(style), panel.defaultFocusedComponent.background) } fun `test prompt editor hides floating toolbar`() { @@ -249,7 +249,12 @@ class PromptPanelTest : BasePlatformTestCase() { HighlighterColors.TEXT, TextAttributes(Color(0xEA, 0xEA, 0xEA), bg, null, null, Font.PLAIN), ) + scheme.setAttributes( + DefaultLanguageHighlighterColors.DOC_CODE_BLOCK, + TextAttributes(null, bg, null, null, Font.PLAIN), + ) val style = SessionEditorStyle.create(scheme = scheme) + val promptBg = SessionUiStyle.View.Prompt.bgColor(style) realize(panel, 260, 400) val editor = (panel.defaultFocusedComponent as EditorTextField).getEditor(false)!! @@ -259,11 +264,11 @@ class PromptPanelTest : BasePlatformTestCase() { panel.applyStyle(style) - assertEquals(bg, panel.defaultFocusedComponent.background) - assertEquals(bg, editor.backgroundColor) - assertEquals(bg, editor.scrollPane.background) - assertEquals(bg, editor.scrollPane.viewport.background) - assertEquals(bg, editor.contentComponent.background) + assertEquals(promptBg, panel.defaultFocusedComponent.background) + assertEquals(promptBg, editor.backgroundColor) + assertEquals(promptBg, editor.scrollPane.background) + assertEquals(promptBg, editor.scrollPane.viewport.background) + assertEquals(promptBg, editor.contentComponent.background) } fun `test prompt editor grows when lines are added`() { diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/TextViewTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/TextViewTest.kt index 14bfaea5269..4f6d118dfec 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/TextViewTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/TextViewTest.kt @@ -229,14 +229,14 @@ class TextViewTest : BasePlatformTestCase() { assertEquals(style.editorForeground, view.md.foreground) } - fun `test prompt view uses transcript font and editor background`() { + fun `test prompt view uses transcript font and prompt background`() { val style = SessionEditorStyle.create(family = "Courier New", size = 23) val view = PromptView(Text("p1")) view.applyStyle(style) assertEquals(style.transcriptFont, view.md.font) - assertEquals(style.editorBackground, view.md.background) + assertEquals(SessionUiStyle.View.Prompt.bgColor(style), view.md.background) assertFalse(view.contentOpaque()) }