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()) }