mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(jetbrains): polish session prompt chrome
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Polish the JetBrains session prompt background, editor-tab focus ring, and centered jump-to-bottom button alignment.
|
||||
+1
@@ -65,6 +65,7 @@ open class WorktreeSessionEditorManager(
|
||||
},
|
||||
) : SessionHost(project, worktree, create, resolve, status, timers, request) {
|
||||
override val showsBranchBadgeInHeader: Boolean get() = false
|
||||
override val hostedInEditorTab: Boolean get() = true
|
||||
private val right = JPanel(BorderLayout())
|
||||
private val deleting = linkedSetOf<String>()
|
||||
private var last: String? = null
|
||||
|
||||
+2
@@ -26,6 +26,8 @@ interface SessionManager {
|
||||
|
||||
val showsBranchBadgeInHeader: Boolean get() = true
|
||||
|
||||
val hostedInEditorTab: Boolean get() = false
|
||||
|
||||
fun openSession(session: SessionDto) {
|
||||
openSession(SessionRef.Local(session))
|
||||
}
|
||||
|
||||
+1
@@ -428,6 +428,7 @@ class SessionUi(
|
||||
onMentions = ::mentionParts,
|
||||
completion = completion,
|
||||
cs = cs,
|
||||
hostedInEditorTab = manager?.hostedInEditorTab == true,
|
||||
)
|
||||
connection = ConnectionPanel(this, controller)
|
||||
root.addOverlay(connection) { pane, child ->
|
||||
|
||||
+4
-1
@@ -5,6 +5,7 @@ import ai.kilocode.client.session.ui.SessionMessageListPanel
|
||||
import ai.kilocode.client.session.ui.SessionRootPanel
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.ui.components.JBLabel
|
||||
@@ -84,8 +85,10 @@ internal class SessionScroll(
|
||||
root.addOverlay(jump) { _, child ->
|
||||
val size = child.preferredSize
|
||||
val gap = UiStyle.Gap.pad()
|
||||
val lane = minOf(host.width, SessionUiStyle.SessionLayout.readableWidth(messages, style.transcriptFont))
|
||||
val right = host.x + (host.width + lane) / 2
|
||||
Rectangle(
|
||||
host.x + host.width - size.width - gap,
|
||||
right - size.width - gap,
|
||||
host.y + host.height - size.height - gap,
|
||||
size.width,
|
||||
size.height,
|
||||
|
||||
+50
-31
@@ -117,6 +117,7 @@ class PromptPanel(
|
||||
private val showSubmit: Boolean = true,
|
||||
private val approve: Boolean = true,
|
||||
private val showEnhance: Boolean = true,
|
||||
private val hostedInEditorTab: Boolean = false,
|
||||
) : BorderLayoutPanel(), SessionEditorStyleTarget, SendPromptContext, UiDataProvider {
|
||||
|
||||
companion object {
|
||||
@@ -146,7 +147,7 @@ class PromptPanel(
|
||||
private var style = SessionEditorStyle.current()
|
||||
private var focused = false
|
||||
private val shell = BorderLayoutPanel().apply {
|
||||
isOpaque = true
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.empty(
|
||||
JBUI.scale(SessionUiStyle.View.Prompt.SHELL_VERTICAL_PADDING),
|
||||
JBUI.scale(SessionUiStyle.View.Prompt.SHELL_HORIZONTAL_PADDING),
|
||||
@@ -274,6 +275,7 @@ class PromptPanel(
|
||||
|
||||
init {
|
||||
applyStyle(style)
|
||||
syncBorder()
|
||||
selection?.register(editor)
|
||||
editor.text = ""
|
||||
editor.addDocumentListener(object : DocumentListener {
|
||||
@@ -361,16 +363,31 @@ class PromptPanel(
|
||||
} else {
|
||||
JBUI.Borders.customLineTop(SessionUiStyle.View.Prompt.separator())
|
||||
},
|
||||
JBUI.Borders.empty(),
|
||||
JBUI.Borders.empty(0, focusInset(), focusInset(), focusInset()),
|
||||
)
|
||||
}
|
||||
|
||||
private fun focusInset() = if (hostedInEditorTab) JBUI.scale(SessionUiStyle.View.Prompt.FOCUS_WIDTH) else 0
|
||||
|
||||
private fun promptSize(size: Dimension): Dimension {
|
||||
val chrome = (shell.preferredSize.height - editor.preferredSize.height).coerceAtLeast(0)
|
||||
val ins = insets
|
||||
return Dimension(size.width, editor.preferredSize.height + chrome + ins.top + ins.bottom)
|
||||
}
|
||||
|
||||
override fun paintComponent(g: Graphics) {
|
||||
val g2 = g.create() as Graphics2D
|
||||
try {
|
||||
g2.color = SessionUiStyle.Colors.sessionBackground()
|
||||
g2.fillRect(0, 0, width, height)
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2.color = SessionUiStyle.View.Prompt.bgColor(style)
|
||||
g2.fill(surface(0f))
|
||||
} finally {
|
||||
g2.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
override fun paintChildren(g: Graphics) {
|
||||
super.paintChildren(g)
|
||||
if (!editorFocused()) return
|
||||
@@ -378,42 +395,44 @@ class PromptPanel(
|
||||
try {
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
val line = JBUI.scale(SessionUiStyle.View.Prompt.FOCUS_WIDTH)
|
||||
val half = line / 2f
|
||||
val top = half
|
||||
val left = half
|
||||
val right = width - half
|
||||
val bottom = height - half
|
||||
val arc = if (rounded && IslandsState.isEnabled()) {
|
||||
JBUI.scale(JBUI.getInt("Island.arc", SessionUiStyle.View.Prompt.CORNER_ARC)) / 2f
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val radius = arc
|
||||
.coerceAtMost((right - left) / 2f)
|
||||
.coerceAtMost(bottom - top)
|
||||
.coerceAtLeast(0f)
|
||||
val path = Path2D.Float().apply {
|
||||
moveTo(left, top)
|
||||
lineTo(right, top)
|
||||
lineTo(right, bottom - radius)
|
||||
if (radius > 0f) {
|
||||
quadTo(right, bottom, right - radius, bottom)
|
||||
lineTo(left + radius, bottom)
|
||||
quadTo(left, bottom, left, bottom - radius)
|
||||
} else {
|
||||
lineTo(right, bottom)
|
||||
lineTo(left, bottom)
|
||||
}
|
||||
closePath()
|
||||
}
|
||||
g2.color = JBUI.CurrentTheme.Focus.focusColor()
|
||||
g2.stroke = BasicStroke(line.toFloat(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)
|
||||
g2.draw(path)
|
||||
g2.draw(surface(line / 2f))
|
||||
} finally {
|
||||
g2.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private fun surface(inset: Float): Path2D.Float {
|
||||
val top = inset
|
||||
val left = inset + insets.left
|
||||
val right = width - inset - insets.right
|
||||
val bottom = height - inset - insets.bottom
|
||||
val arc = if (rounded && IslandsState.isEnabled()) {
|
||||
JBUI.scale(JBUI.getInt("Island.arc", SessionUiStyle.View.Prompt.CORNER_ARC)) / 2f
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val radius = arc
|
||||
.coerceAtMost((right - left) / 2f)
|
||||
.coerceAtMost(bottom - top)
|
||||
.coerceAtLeast(0f)
|
||||
return Path2D.Float().apply {
|
||||
moveTo(left, top)
|
||||
lineTo(right, top)
|
||||
lineTo(right, bottom - radius)
|
||||
if (radius > 0f) {
|
||||
quadTo(right, bottom, right - radius, bottom)
|
||||
lineTo(left + radius, bottom)
|
||||
quadTo(left, bottom, left, bottom - radius)
|
||||
} else {
|
||||
lineTo(right, bottom)
|
||||
lineTo(left, bottom)
|
||||
}
|
||||
closePath()
|
||||
}
|
||||
}
|
||||
|
||||
private fun editorFocused(): Boolean {
|
||||
val ed = editor.getEditor(false) ?: return editor.hasFocus()
|
||||
return editor.hasFocus() || ed.contentComponent.hasFocus()
|
||||
|
||||
+20
@@ -5,9 +5,11 @@ import ai.kilocode.client.session.ui.ModifiedFilesView
|
||||
import ai.kilocode.client.session.ui.SessionMessageListPanel
|
||||
import ai.kilocode.client.session.ui.prompt.PromptPanel
|
||||
import ai.kilocode.client.session.ui.selection.SessionCopyTarget
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.client.session.views.tool.ShellToolView
|
||||
import ai.kilocode.client.session.views.tool.ToolView
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.rpc.dto.ChatEventDto
|
||||
import ai.kilocode.rpc.dto.DiffFileDto
|
||||
import ai.kilocode.rpc.dto.MessageErrorDto
|
||||
@@ -647,6 +649,24 @@ class SessionScrollTest : SessionUiTestBase() {
|
||||
assertFalse(button.isVisible)
|
||||
}
|
||||
|
||||
fun `test scroll button aligns to centered readable lane`() {
|
||||
ui.setSize(1600, 600)
|
||||
showMessages()
|
||||
fillTranscript(24)
|
||||
val button = jumpButton()
|
||||
val bar = scrollBar()
|
||||
setValue(bar, bottom(bar) / 2)
|
||||
drainScroll()
|
||||
val host = scrollComponent().parent as JComponent
|
||||
val view = find<SessionMessageListPanel>(ui)
|
||||
val lane = minOf(host.width, SessionUiStyle.SessionLayout.readableWidth(view, SessionEditorStyle.current().transcriptFont))
|
||||
val right = host.x + (host.width + lane) / 2
|
||||
|
||||
assertTrue(button.isVisible)
|
||||
assertEquals(right, button.x + button.width + UiStyle.Gap.pad())
|
||||
assertTrue(right < host.x + host.width)
|
||||
}
|
||||
|
||||
fun `test scroll button scrolls transcript to bottom`() {
|
||||
showMessages()
|
||||
fillTranscript(24)
|
||||
|
||||
+47
@@ -230,6 +230,53 @@ class PromptPanelTest : BasePlatformTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
fun `test editor tab prompt reserves focus inset and paints session backdrop`() {
|
||||
val panel = PromptPanel(
|
||||
project = project,
|
||||
onSend = { _, _ -> },
|
||||
onAbort = {},
|
||||
onEnhance = { _, _ -> },
|
||||
hostedInEditorTab = true,
|
||||
)
|
||||
realize(panel, 260, 400)
|
||||
panel.setBounds(0, 0, 260, panel.preferredSize.height)
|
||||
panel.doLayout()
|
||||
val ins = panel.insets
|
||||
|
||||
assertEquals(JBUI.scale(SessionUiStyle.View.Prompt.FOCUS_WIDTH), ins.bottom)
|
||||
assertEquals(ins.bottom, ins.left)
|
||||
assertEquals(ins.bottom, ins.right)
|
||||
assertEquals(SessionUiStyle.Colors.sessionBackground().rgb, paint(panel, 0, panel.height / 2).rgb)
|
||||
assertEquals(SessionUiStyle.Colors.sessionBackground().rgb, paint(panel, panel.width - 1, panel.height / 2).rgb)
|
||||
assertEquals(SessionUiStyle.Colors.sessionBackground().rgb, paint(panel, panel.width / 2, panel.height - 1).rgb)
|
||||
|
||||
val editor = (panel.defaultFocusedComponent as EditorTextField).getEditor(false)!!
|
||||
val current = KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
val focus = TestFocusManager()
|
||||
KeyboardFocusManager.setCurrentKeyboardFocusManager(focus)
|
||||
try {
|
||||
focus.focus(editor.contentComponent)
|
||||
editor.contentComponent.focusListeners.forEach {
|
||||
it.focusGained(FocusEvent(editor.contentComponent, FocusEvent.FOCUS_GAINED))
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
JBUI.CurrentTheme.Focus.focusColor().rgb,
|
||||
paint(panel, panel.width / 2, panel.height - 1 - ins.bottom).rgb,
|
||||
)
|
||||
assertEquals(
|
||||
JBUI.CurrentTheme.Focus.focusColor().rgb,
|
||||
paint(panel, ins.left + 1, panel.height / 2).rgb,
|
||||
)
|
||||
assertEquals(
|
||||
JBUI.CurrentTheme.Focus.focusColor().rgb,
|
||||
paint(panel, panel.width - ins.right - 1, panel.height / 2).rgb,
|
||||
)
|
||||
} finally {
|
||||
KeyboardFocusManager.setCurrentKeyboardFocusManager(current)
|
||||
}
|
||||
}
|
||||
|
||||
fun `test applyStyle updates prompt input and height`() {
|
||||
val panel = PromptPanel(project = project, onSend = { _, _ -> }, onAbort = {}, onEnhance = { _, _ -> })
|
||||
val style = SessionEditorStyle.create(family = "Courier New", size = 26)
|
||||
|
||||
Reference in New Issue
Block a user