mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-29 18:52:45 +08:00
fix(jetbrains): align worktree editor titles
This commit is contained in:
+11
-6
@@ -11,6 +11,7 @@ import ai.kilocode.client.agentManager.worktree.WorktreeNameCache
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeEditorMatchers
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeSessionEditorMatcher
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeSessionEditorKind
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeTitle
|
||||
import ai.kilocode.client.agentManager.worktree.ensureWorktreeSessionEditorKind
|
||||
import ai.kilocode.client.agentManager.worktree.prTooltip
|
||||
import ai.kilocode.client.agentManager.worktree.normalizeWorktreePath
|
||||
@@ -123,7 +124,10 @@ class AgentManagerPanel(
|
||||
bindStatus()
|
||||
bindEditorSelection()
|
||||
// Reflect names adopted or renamed in a worktree session editor tab in the list live.
|
||||
service<WorktreeNameCache>().addListener(this) { path, name -> controller.applyName(path, name) }
|
||||
service<WorktreeNameCache>().addListener(this) { path, name ->
|
||||
controller.applyName(path, name)
|
||||
project?.service<KiloVfsManager>()?.updatePresentation(WorktreeSessionEditorKind.ID, mapOf("path" to path))
|
||||
}
|
||||
ActionManager.getInstance().getAction("RenameElement")?.shortcutSet?.let { set ->
|
||||
edit.registerCustomShortcutSet(set, list, this)
|
||||
}
|
||||
@@ -331,7 +335,9 @@ class AgentManagerPanel(
|
||||
(0 until controller.model.size).map {
|
||||
val item = controller.model.getElementAt(it)
|
||||
val key = normalizeWorktreePath(item.path)
|
||||
WorktreeRow(item, controller.isPending(item.id), controller.isDeleting(item.id), controller.kind(item.path), stats[key], prs[key])
|
||||
val pull = prs[key]
|
||||
service<WorktreeNameCache>().putPr(item.path, pull)
|
||||
WorktreeRow(item, controller.isPending(item.id), controller.isDeleting(item.id), controller.kind(item.path), stats[key], pull)
|
||||
},
|
||||
ActiveListSelection.PreserveNoScroll,
|
||||
)
|
||||
@@ -442,13 +448,12 @@ class AgentManagerPanel(
|
||||
val pr: WorktreePrDto?,
|
||||
) : ActiveListItem {
|
||||
override val key: String get() = dto.id
|
||||
override val title: String get() = pr?.title?.trim()?.takeIf { it.isNotBlank() } ?: dto.name
|
||||
override val description: String get() = defaultName
|
||||
override val title: String get() = WorktreeTitle.text(dto.name, dto.path, pr)
|
||||
override val description: String get() = WorktreeTitle.fallback(dto.path)
|
||||
override val tooltip: String? get() = null
|
||||
override val icon = WorktreeIcons.forRow(dto.locked, pending)
|
||||
override val search: String get() = listOfNotNull(dto.name, dto.branch, dto.path, dto.lockReason).joinToString(" ")
|
||||
private val defaultName: String get() = dto.path.trimEnd('/').substringAfterLast('/')
|
||||
private val customName: String? get() = dto.name.takeIf { it != defaultName }
|
||||
private val customName: String? get() = WorktreeTitle.custom(dto.name, dto.path)
|
||||
override val badges: List<ActiveListBadge>
|
||||
get() {
|
||||
if (pending || deleting) return emptyList()
|
||||
|
||||
+22
-1
@@ -1,6 +1,7 @@
|
||||
package ai.kilocode.client.agentManager.worktree
|
||||
|
||||
import ai.kilocode.rpc.dto.WorktreeDto
|
||||
import ai.kilocode.rpc.dto.WorktreePrDto
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.util.Disposer
|
||||
@@ -14,11 +15,15 @@ import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
@Service(Service.Level.APP)
|
||||
class WorktreeNameCache {
|
||||
private val names = linkedMapOf<String, String>()
|
||||
private val pulls = linkedMapOf<String, WorktreePrDto>()
|
||||
private val listeners = mutableListOf<(String, String?) -> Unit>()
|
||||
|
||||
@RequiresEdt
|
||||
fun get(path: String): String? = names[path]
|
||||
|
||||
@RequiresEdt
|
||||
fun title(path: String): String = WorktreeTitle.text(names[path], path, pulls[path])
|
||||
|
||||
@RequiresEdt
|
||||
fun put(path: String, name: String) {
|
||||
if (names[path] == name) return
|
||||
@@ -29,14 +34,30 @@ class WorktreeNameCache {
|
||||
@RequiresEdt
|
||||
fun put(item: WorktreeDto) = put(item.path, item.name)
|
||||
|
||||
@RequiresEdt
|
||||
fun putPr(path: String, pull: WorktreePrDto?) {
|
||||
val prev = pulls[path]
|
||||
if (pull == null) {
|
||||
if (prev == null) return
|
||||
pulls.remove(path)
|
||||
fire(path, names[path])
|
||||
return
|
||||
}
|
||||
if (prev == pull) return
|
||||
pulls[path] = pull
|
||||
fire(path, names[path])
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
fun remove(path: String) {
|
||||
if (names.remove(path) != null) fire(path, null)
|
||||
val changed = names.remove(path) != null || pulls.remove(path) != null
|
||||
if (changed) fire(path, null)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
fun clear() {
|
||||
names.clear()
|
||||
pulls.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-14
@@ -30,22 +30,24 @@ internal class WorktreePrHeaderView(
|
||||
private val changes = WorktreeStatsView(openDiff)
|
||||
private val statusPane = status.align(HAlign.LEFT, VAlign.CENTER)
|
||||
private val changesPane = changes.align(HAlign.RIGHT, VAlign.CENTER) as JComponent
|
||||
private val actions = BorderLayoutPanel()
|
||||
private var pull: WorktreePrDto? = null
|
||||
private var state: GhState? = null
|
||||
private var text: String? = null
|
||||
private var tip: String? = null
|
||||
private var url: String? = null
|
||||
private var changesLeft = false
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
actions.isOpaque = false
|
||||
status.border = JBUI.Borders.empty(0, UiStyle.Gap.md(), 0, UiStyle.Gap.xs())
|
||||
title.border = JBUI.Borders.empty(0, UiStyle.Gap.sm())
|
||||
title.isOpaque = false
|
||||
changesPane.border = JBUI.Borders.emptyRight(UiStyle.Gap.pad())
|
||||
actions.addToRight(changesPane)
|
||||
addToLeft(statusPane)
|
||||
addToCenter(title)
|
||||
addToRight(changesPane)
|
||||
addToRight(actions)
|
||||
val listener = object : MouseAdapter() {
|
||||
override fun mouseClicked(event: MouseEvent) {
|
||||
url?.let(BrowserUtil::browse)
|
||||
@@ -93,23 +95,11 @@ internal class WorktreePrHeaderView(
|
||||
if (value) {
|
||||
if (statusPane.parent !== this) add(statusPane, BorderLayout.WEST)
|
||||
if (title.parent !== this) add(title, BorderLayout.CENTER)
|
||||
placeChanges(left = false)
|
||||
title.isVisible = true
|
||||
return
|
||||
}
|
||||
if (statusPane.parent === this) remove(statusPane)
|
||||
placeChanges(left = true)
|
||||
title.isVisible = false
|
||||
}
|
||||
|
||||
private fun placeChanges(left: Boolean) {
|
||||
val constraint = if (left) BorderLayout.WEST else BorderLayout.EAST
|
||||
val border = if (left) JBUI.Borders.emptyLeft(UiStyle.Gap.md()) else JBUI.Borders.emptyRight(UiStyle.Gap.pad())
|
||||
if (changesPane.parent === this && changesLeft == left) return
|
||||
if (changesPane.parent === this) remove(changesPane)
|
||||
changesLeft = left
|
||||
changesPane.border = border
|
||||
add(changesPane, constraint)
|
||||
changed()
|
||||
}
|
||||
|
||||
|
||||
+1
-6
@@ -25,7 +25,7 @@ object WorktreeSessionEditorKind : KiloEditorKind {
|
||||
override val id: String = ID
|
||||
|
||||
override fun title(params: Map<String, String>): String = params[PATH]?.let { path ->
|
||||
service<WorktreeNameCache>().get(path) ?: name(path)
|
||||
service<WorktreeNameCache>().title(path)
|
||||
} ?: KiloBundle.message("worktree.session.title")
|
||||
override fun icon(params: Map<String, String>): Icon = WorktreeIcons.branch
|
||||
override fun fileType(params: Map<String, String>): FileType = WorktreeSessionFileType
|
||||
@@ -46,11 +46,6 @@ object WorktreeSessionEditorKind : KiloEditorKind {
|
||||
return WorktreeSessionEditorPanel(parent, manager, controller, worktree, project)
|
||||
}
|
||||
|
||||
private fun name(path: String): String {
|
||||
val value = path.trimEnd('/', '\\')
|
||||
return value.substringAfterLast('/').substringAfterLast('\\').takeIf { it.isNotBlank() } ?: value
|
||||
}
|
||||
|
||||
private const val PATH = "path"
|
||||
}
|
||||
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package ai.kilocode.client.agentManager.worktree
|
||||
|
||||
import ai.kilocode.rpc.dto.WorktreePrDto
|
||||
|
||||
internal object WorktreeTitle {
|
||||
fun text(name: String?, path: String, pull: WorktreePrDto? = null): String {
|
||||
return pull?.title?.trim()?.takeIf { it.isNotBlank() }
|
||||
?: name?.takeIf { it.isNotBlank() }
|
||||
?: fallback(path)
|
||||
}
|
||||
|
||||
fun custom(name: String?, path: String): String? {
|
||||
return name?.takeIf { it != fallback(path) }
|
||||
}
|
||||
|
||||
fun fallback(path: String): String {
|
||||
val value = path.trimEnd('/', '\\')
|
||||
return value.substringAfterLast('/').substringAfterLast('\\').takeIf { it.isNotBlank() } ?: value
|
||||
}
|
||||
}
|
||||
+10
@@ -10,6 +10,8 @@ import ai.kilocode.client.vfs.KiloEditorKindRegistry
|
||||
import ai.kilocode.client.vfs.KiloPath
|
||||
import ai.kilocode.client.vfs.KiloVirtualFileKindRegistry
|
||||
import ai.kilocode.client.vfs.KiloVirtualFileSystem
|
||||
import ai.kilocode.rpc.dto.GhState
|
||||
import ai.kilocode.rpc.dto.WorktreePrDto
|
||||
import ai.kilocode.rpc.dto.WorktreeDto
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.vfs.VirtualFilePathWrapper
|
||||
@@ -58,4 +60,12 @@ class WorktreeSessionEditorKindTest : BasePlatformTestCase() {
|
||||
|
||||
assertEquals("Feature Label", WorktreeSessionEditorKind.title(mapOf("path" to path)))
|
||||
}
|
||||
|
||||
fun `test worktree session title uses same winning label as worktree list`() {
|
||||
val path = "/repo/.kilo/worktrees/feature-x"
|
||||
service<WorktreeNameCache>().put(path, "Feature Label")
|
||||
service<WorktreeNameCache>().putPr(path, WorktreePrDto(path, 3, GhState.OPEN, "https://example.test/pr/3", "PR Label"))
|
||||
|
||||
assertEquals("PR Label", WorktreeSessionEditorKind.title(mapOf("path" to path)))
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -1,7 +1,9 @@
|
||||
package ai.kilocode.client.agentManager.worktree
|
||||
|
||||
import ai.kilocode.client.util.edtWait
|
||||
import ai.kilocode.rpc.dto.GhState
|
||||
import ai.kilocode.rpc.dto.WorktreeDto
|
||||
import ai.kilocode.rpc.dto.WorktreePrDto
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.util.Disposer
|
||||
@@ -43,6 +45,19 @@ class WorktreeNameCacheTest : BasePlatformTestCase() {
|
||||
assertTrue(events.isEmpty())
|
||||
}
|
||||
|
||||
fun `test title prefers PR title then worktree name`() = edt {
|
||||
val path = "/repo/wt"
|
||||
cache().put(path, "Feature")
|
||||
|
||||
assertEquals("Feature", cache().title(path))
|
||||
|
||||
cache().putPr(path, WorktreePrDto(path, 12, GhState.OPEN, "https://example.test/pr/12", "PR Title"))
|
||||
assertEquals("PR Title", cache().title(path))
|
||||
|
||||
cache().putPr(path, WorktreePrDto(path, 12, GhState.OPEN, "https://example.test/pr/12", " "))
|
||||
assertEquals("Feature", cache().title(path))
|
||||
}
|
||||
|
||||
fun `test listener is removed with parent disposable`() = edt {
|
||||
val parent = Disposer.newDisposable("worktree-cache-listener")
|
||||
Disposer.register(testRootDisposable, parent)
|
||||
|
||||
+3
-2
@@ -16,6 +16,7 @@ import java.awt.Point
|
||||
import java.awt.event.InputEvent
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
class WorktreePrHeaderViewTest : BasePlatformTestCase() {
|
||||
fun `test PR state title and colors render`() {
|
||||
@@ -61,7 +62,7 @@ class WorktreePrHeaderViewTest : BasePlatformTestCase() {
|
||||
assertEquals(Cursor.DEFAULT_CURSOR, title.cursor.type)
|
||||
}
|
||||
|
||||
fun `test no PR aligns changes badge to the left`() {
|
||||
fun `test no PR keeps changes badge on the right`() {
|
||||
val view = edt { WorktreePrHeaderView {} }
|
||||
val changes = edt { UIUtil.findComponentOfType(view, WorktreeStatsView::class.java)!! }
|
||||
|
||||
@@ -71,7 +72,7 @@ class WorktreePrHeaderViewTest : BasePlatformTestCase() {
|
||||
view.doLayout()
|
||||
}
|
||||
|
||||
assertTrue(edt { changes.parent.x < 20 })
|
||||
assertTrue(edt { SwingUtilities.convertPoint(changes.parent, Point(0, 0), view).x > 300 })
|
||||
}
|
||||
|
||||
fun `test changes view visibility follows stats`() {
|
||||
|
||||
Reference in New Issue
Block a user