mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 05:52:35 +08:00
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.
This commit is contained in:
+3
@@ -23,6 +23,9 @@ class PromptAttachmentStrip(
|
||||
private val chips = LinkedHashMap<String, PromptAttachmentChip>()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
+10
-6
@@ -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()
|
||||
|
||||
+6
-6
@@ -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()
|
||||
|
||||
+10
@@ -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
|
||||
|
||||
+13
-10
@@ -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()
|
||||
}
|
||||
|
||||
+1
-1
@@ -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))
|
||||
|
||||
+19
-8
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+12
-7
@@ -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`() {
|
||||
|
||||
+2
-2
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user