diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/FileLinkText.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/FileLinkText.kt
new file mode 100644
index 00000000000..bcc402ef85e
--- /dev/null
+++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/FileLinkText.kt
@@ -0,0 +1,14 @@
+package ai.kilocode.client.session.ui
+
+import com.intellij.xml.util.XmlStringUtil
+
+internal fun fileLinkText(value: String): String = value.lineSequence()
+ .map { it.trim() }
+ .filter { it.isNotEmpty() }
+ .joinToString(" ")
+
+internal fun fileLinkHtml(value: String): String {
+ val text = fileLinkText(value)
+ if (text.isBlank()) return ""
+ return XmlStringUtil.wrapInHtml("${XmlStringUtil.escapeString(text)}")
+}
diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/attachment/AttachmentCard.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/attachment/AttachmentCard.kt
index a54e683355a..b5b3d3ebc3b 100644
--- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/attachment/AttachmentCard.kt
+++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/attachment/AttachmentCard.kt
@@ -1,6 +1,7 @@
package ai.kilocode.client.session.ui.attachment
import ai.kilocode.client.plugin.KiloBundle
+import ai.kilocode.client.session.ui.fileLinkHtml
import ai.kilocode.client.session.ui.style.SessionUiStyle
import ai.kilocode.client.ui.UiStyle
import ai.kilocode.client.ui.iconButton
@@ -68,7 +69,7 @@ class AttachmentChip(
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
toolTipText = tip
accessibleContext?.accessibleName = KiloBundle.message("prompt.attachment.open", item.name)
- val label = JBLabel(label()).apply {
+ val label = JBLabel(fileLinkHtml(label())).apply {
icon = attachmentIcon(item.mime, item.name)
iconTextGap = JBUI.scale(SessionUiStyle.View.Attachment.CHIP_ICON_GAP)
toolTipText = tip
diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptAttachmentView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptAttachmentView.kt
index 919b114fe5d..331499655e3 100644
--- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptAttachmentView.kt
+++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PromptAttachmentView.kt
@@ -27,8 +27,9 @@ class PromptAttachmentView(
private val cards = LinkedHashMap()
private val row = Stack.horizontal(gap = UiStyle.Gap.sm())
private val scroll = JBScrollPane(row).apply {
- border = null
- viewportBorder = null
+ // Empty borders remove the visible scroll pane frame; null can be replaced by the current UI.
+ border = JBUI.Borders.empty()
+ viewportBorder = JBUI.Borders.empty()
isOpaque = false
viewport.isOpaque = false
horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ToolSupport.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ToolSupport.kt
index a17b164e31d..3eabce5da94 100644
--- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ToolSupport.kt
+++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ToolSupport.kt
@@ -7,6 +7,8 @@ import ai.kilocode.client.session.SessionFileOpener
import ai.kilocode.client.session.model.Tool
import ai.kilocode.client.session.model.ToolExecState
import ai.kilocode.client.session.model.ToolKind
+import ai.kilocode.client.session.ui.fileLinkHtml
+import ai.kilocode.client.session.ui.fileLinkText
import ai.kilocode.client.session.ui.selection.SessionSelection
import ai.kilocode.client.session.ui.selection.SessionCopyTarget
import ai.kilocode.client.session.ui.style.SessionEditorStyle
@@ -135,8 +137,8 @@ class FileLinkLabel(
@RequiresEdt
fun setTarget(path: String?, text: String): Boolean {
- val next = single(text.ifBlank { path.orEmpty() })
- val value = if (next.isBlank()) "" else XmlStringUtil.wrapInHtml("${XmlStringUtil.escapeString(next)}")
+ val next = fileLinkText(text.ifBlank { path.orEmpty() })
+ val value = fileLinkHtml(next)
var changed = false
if (href != path) {
href = path
@@ -489,7 +491,7 @@ internal fun setText(label: JBLabel, text: String): Boolean {
@RequiresEdt
internal fun setTargetText(label: JBLabel, text: String): Boolean {
- val value = single(text)
+ val value = fileLinkText(text)
if (label.text == value) return false
label.text = value
return true
@@ -511,16 +513,11 @@ private fun clip(label: T): T = label.apply {
}
private fun html(text: String): String {
- val value = single(text)
+ val value = fileLinkText(text)
if (value.isBlank()) return ""
return XmlStringUtil.wrapInHtml("${XmlStringUtil.escapeString(value)}")
}
-private fun single(text: String): String = text.lineSequence()
- .map { it.trim() }
- .filter { it.isNotEmpty() }
- .joinToString(" ")
-
@RequiresEdt
internal fun show(parts: ToolParts, link: Boolean): Boolean {
var changed = false
diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionUiUpdateTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionUiUpdateTest.kt
index 49ff27f5838..e85eb69e4cf 100644
--- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionUiUpdateTest.kt
+++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionUiUpdateTest.kt
@@ -383,7 +383,7 @@ class SessionUiUpdateTest : BasePlatformTestCase() {
val chip = find(view, AttachmentChip::class.java)
assertNotNull(chip)
- assertTrue(findAll(chip!!, JBLabel::class.java).any { it.text == "HvJwtFilter.java:12-40" })
+ assertTrue(findAll(chip!!, JBLabel::class.java).any { it.text.contains("HvJwtFilter.java:12-40") })
}
fun `test source backed image attachment still renders in prompt strip`() {
diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PromptAttachmentViewTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PromptAttachmentViewTest.kt
index 457a8cdfd8e..27ad1470af7 100644
--- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PromptAttachmentViewTest.kt
+++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PromptAttachmentViewTest.kt
@@ -5,6 +5,8 @@ import ai.kilocode.client.session.ui.attachment.AttachmentCardItem
import ai.kilocode.client.session.ui.attachment.AttachmentChip
import ai.kilocode.client.ui.UiStyle
import com.intellij.testFramework.fixtures.BasePlatformTestCase
+import com.intellij.ui.components.JBLabel
+import java.awt.Container
class PromptAttachmentViewTest : BasePlatformTestCase() {
// The attachment strip should line up with the prompt text horizontally and keep only a
@@ -22,8 +24,14 @@ class PromptAttachmentViewTest : BasePlatformTestCase() {
fun `test attachment scroll pane has no border line`() {
val scroll = PromptAttachmentView("m1") {}.scrollPane()
- assertNull(scroll.border)
- assertNull(scroll.viewportBorder)
+ assertEquals(0, scroll.border.getBorderInsets(scroll).top)
+ assertEquals(0, scroll.border.getBorderInsets(scroll).left)
+ assertEquals(0, scroll.border.getBorderInsets(scroll).bottom)
+ assertEquals(0, scroll.border.getBorderInsets(scroll).right)
+ assertEquals(0, scroll.viewportBorder.getBorderInsets(scroll).top)
+ assertEquals(0, scroll.viewportBorder.getBorderInsets(scroll).left)
+ assertEquals(0, scroll.viewportBorder.getBorderInsets(scroll).bottom)
+ assertEquals(0, scroll.viewportBorder.getBorderInsets(scroll).right)
}
// With the outline removed, the chip owns no internal padding; alignment comes from the
@@ -41,4 +49,24 @@ class PromptAttachmentViewTest : BasePlatformTestCase() {
assertEquals(0, chip.top)
assertEquals(0, chip.bottom)
}
+
+ fun `test attachment chip uses file link underline style`() {
+ val chip = AttachmentChip(
+ AttachmentCardItem("HvJwtFilter.java", "text/plain", "file:///HvJwtFilter.java"),
+ file = true,
+ startLine = 40,
+ endLine = 42,
+ )
+ val label = components(chip).filterIsInstance().single()
+
+ assertTrue(label.text.contains("HvJwtFilter.java:40-42"))
+ }
+
+ private fun components(root: Container): List = buildList {
+ fun visit(comp: java.awt.Component) {
+ add(comp)
+ if (comp is Container) comp.components.forEach { visit(it) }
+ }
+ visit(root)
+ }
}