mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 05:52:35 +08:00
feat(jetbrains): hover rename/delete + popover rename in session history
Mirror the worktree list in the JetBrains session history: reveal rename and delete buttons on hover, rename via the shared inline popover instead of a modal dialog, and advance the selection to the neighbouring session when the selected one is deleted so the highlight moves naturally. Extract the shared rename/delete action cells into ui/list/ActiveListActions and adopt them across the history, worktree, and worktree-session lists to avoid duplicating the cell ids and construction.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": minor
|
||||
---
|
||||
|
||||
Reveal rename and delete buttons on hover in JetBrains session history, rename sessions through an inline popover instead of a modal dialog, and move the selection to the neighbouring session after deleting the selected one.
|
||||
+2
-36
@@ -1,28 +1,12 @@
|
||||
package ai.kilocode.client.actions
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.history.HistoryDataKeys
|
||||
import ai.kilocode.client.session.history.title
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
|
||||
class RenameSessionAction : AnAction() {
|
||||
/** Overridable in tests to avoid showing a real modal dialog. */
|
||||
internal var input: (project: Project?, current: String) -> String? = { project, current ->
|
||||
Messages.showInputDialog(
|
||||
project,
|
||||
KiloBundle.message("history.rename.prompt"),
|
||||
KiloBundle.message("history.rename.title"),
|
||||
null,
|
||||
current,
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread() = ActionUpdateThread.EDT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
@@ -35,26 +19,8 @@ class RenameSessionAction : AnAction() {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val selection = e.getData(HistoryDataKeys.SELECTION) ?: return
|
||||
val controller = e.getData(HistoryDataKeys.CONTROLLER) ?: return
|
||||
val rename = e.getData(HistoryDataKeys.RENAME) ?: return
|
||||
val item = selection.selectedLocal.singleOrNull() ?: return
|
||||
|
||||
val current = title(item)
|
||||
controller.requestRename()
|
||||
val raw = input(e.project, current)
|
||||
if (raw == null) {
|
||||
controller.cancelRename("cancelled")
|
||||
return
|
||||
}
|
||||
val newTitle = raw.trim()
|
||||
|
||||
if (newTitle.isBlank()) {
|
||||
controller.cancelRename("blank")
|
||||
return
|
||||
}
|
||||
if (newTitle == current) {
|
||||
controller.cancelRename("unchanged")
|
||||
return
|
||||
}
|
||||
controller.rename(item, newTitle)
|
||||
rename(item)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-19
@@ -14,6 +14,8 @@ import ai.kilocode.client.agentManager.worktree.worktreeSessionParams
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionActivityKind
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_DELETE_CELL
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_RENAME_CELL
|
||||
import ai.kilocode.client.ui.list.ActiveList
|
||||
import ai.kilocode.client.ui.list.ActiveListBadge
|
||||
import ai.kilocode.client.ui.list.ActiveListCell
|
||||
@@ -22,6 +24,8 @@ import ai.kilocode.client.ui.list.ActiveListDeleteOptions
|
||||
import ai.kilocode.client.ui.list.ActiveListItem
|
||||
import ai.kilocode.client.ui.list.ActiveListSelection
|
||||
import ai.kilocode.client.ui.list.ActiveListSurface
|
||||
import ai.kilocode.client.ui.list.activeListDeleteCell
|
||||
import ai.kilocode.client.ui.list.activeListRenameCell
|
||||
import ai.kilocode.client.ui.list.activeListToolWindowBackground
|
||||
import ai.kilocode.client.vfs.KiloVfsManager
|
||||
import ai.kilocode.rpc.dto.RemoveWorktreeResultDto
|
||||
@@ -73,8 +77,8 @@ class AgentManagerPanel(
|
||||
showSearch = false,
|
||||
onCell = { key, id ->
|
||||
val item = item(key) ?: return@ActiveList
|
||||
if (id == RENAME_CELL && renameable(item)) beginRename(item, id)
|
||||
if (id == DELETE_CELL && deletable(item)) showDeletePopup(item, id)
|
||||
if (id == ACTIVE_LIST_RENAME_CELL && renameable(item)) beginRename(item, id)
|
||||
if (id == ACTIVE_LIST_DELETE_CELL && deletable(item)) showDeletePopup(item, id)
|
||||
},
|
||||
onOpen = { row, focus ->
|
||||
val item = (row as? WorktreeRow)?.dto ?: return@ActiveList
|
||||
@@ -392,25 +396,10 @@ class AgentManagerPanel(
|
||||
}
|
||||
override val cells: List<ActiveListCell>
|
||||
get() = if (dto.main || pending) emptyList() else listOf(
|
||||
ActiveListCell(
|
||||
RENAME_CELL,
|
||||
KiloBundle.message("worktree.rename.action"),
|
||||
icon = AllIcons.Actions.Edit,
|
||||
iconOnly = true,
|
||||
),
|
||||
ActiveListCell(
|
||||
DELETE_CELL,
|
||||
KiloBundle.message("worktree.delete.action"),
|
||||
icon = AllIcons.Actions.GC,
|
||||
iconOnly = true,
|
||||
),
|
||||
activeListRenameCell(KiloBundle.message("worktree.rename.action")),
|
||||
activeListDeleteCell(KiloBundle.message("worktree.delete.action")),
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RENAME_CELL = "rename"
|
||||
const val DELETE_CELL = "delete"
|
||||
}
|
||||
}
|
||||
|
||||
internal fun worktreeDeletable(item: WorktreeDto?, pending: Boolean): Boolean = item != null && !item.main && !pending
|
||||
|
||||
+8
-19
@@ -8,6 +8,8 @@ import ai.kilocode.client.session.SessionRef
|
||||
import ai.kilocode.client.session.history.HistorySection
|
||||
import ai.kilocode.client.session.history.HistoryTime
|
||||
import ai.kilocode.client.session.history.LocalHistoryItem
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_DELETE_CELL
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_RENAME_CELL
|
||||
import ai.kilocode.client.ui.list.ActiveList
|
||||
import ai.kilocode.client.ui.list.ActiveListBadge
|
||||
import ai.kilocode.client.ui.list.ActiveListCell
|
||||
@@ -18,6 +20,8 @@ import ai.kilocode.client.ui.list.ActiveListItem
|
||||
import ai.kilocode.client.ui.list.ActiveListRowHeight
|
||||
import ai.kilocode.client.ui.list.ActiveListSelection
|
||||
import ai.kilocode.client.ui.list.ActiveListSurface
|
||||
import ai.kilocode.client.ui.list.activeListDeleteCell
|
||||
import ai.kilocode.client.ui.list.activeListRenameCell
|
||||
import ai.kilocode.client.ui.list.activeListToolWindowBackground
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import com.intellij.icons.AllIcons
|
||||
@@ -72,8 +76,8 @@ class WorktreeSessionEditorPanel(
|
||||
showSearch = false,
|
||||
enter = { true },
|
||||
onCell = { key, id ->
|
||||
if (id == RENAME_CELL) beginRename(key, RENAME_CELL)
|
||||
if (id == DELETE_CELL) confirmDelete(listOf(key), DELETE_CELL)
|
||||
if (id == ACTIVE_LIST_RENAME_CELL) beginRename(key, ACTIVE_LIST_RENAME_CELL)
|
||||
if (id == ACTIVE_LIST_DELETE_CELL) confirmDelete(listOf(key), ACTIVE_LIST_DELETE_CELL)
|
||||
},
|
||||
onOpen = { row, focus -> open(row, focus) },
|
||||
)
|
||||
@@ -341,24 +345,9 @@ class WorktreeSessionEditorPanel(
|
||||
get() {
|
||||
if (selectedKeys().size != 1) return emptyList()
|
||||
return listOf(
|
||||
ActiveListCell(
|
||||
RENAME_CELL,
|
||||
KiloBundle.message("worktree.session.rename.action"),
|
||||
icon = AllIcons.Actions.Edit,
|
||||
iconOnly = true,
|
||||
),
|
||||
ActiveListCell(
|
||||
DELETE_CELL,
|
||||
KiloBundle.message("worktree.session.delete.action"),
|
||||
icon = AllIcons.Actions.GC,
|
||||
iconOnly = true,
|
||||
),
|
||||
activeListRenameCell(KiloBundle.message("worktree.session.rename.action")),
|
||||
activeListDeleteCell(KiloBundle.message("worktree.session.delete.action")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RENAME_CELL = "rename"
|
||||
const val DELETE_CELL = "delete"
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -13,4 +13,11 @@ data class HistorySelection(
|
||||
object HistoryDataKeys {
|
||||
val SELECTION: DataKey<HistorySelection> = DataKey.create("ai.kilocode.client.session.history.HistorySelection")
|
||||
val CONTROLLER: DataKey<HistoryController> = DataKey.create("ai.kilocode.client.session.history.HistoryController")
|
||||
|
||||
/**
|
||||
* Opens the inline rename popover anchored to a local session row. Provided by [HistoryPanel] so
|
||||
* the context-menu and RenameElement-shortcut rename share the same balloon as the hover pencil
|
||||
* instead of a modal dialog.
|
||||
*/
|
||||
val RENAME: DataKey<(LocalHistoryItem) -> Unit> = DataKey.create("ai.kilocode.client.session.history.HistoryRename")
|
||||
}
|
||||
|
||||
+37
-5
@@ -9,6 +9,8 @@ import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.layout.HAlign
|
||||
import ai.kilocode.client.ui.layout.VAlign
|
||||
import ai.kilocode.client.ui.layout.align
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_DELETE_CELL
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_RENAME_CELL
|
||||
import ai.kilocode.client.ui.list.ActiveList
|
||||
import ai.kilocode.client.ui.list.ActiveListConfig
|
||||
import ai.kilocode.client.ui.list.ActiveListDeleteOptions
|
||||
@@ -221,9 +223,11 @@ class HistoryPanel(
|
||||
showSearch = false,
|
||||
openOnClick = false,
|
||||
onCell = { key, id ->
|
||||
if (id != HISTORY_DELETE_CELL) return@ActiveList
|
||||
val item = localRows.firstOrNull { it.key == key }?.item ?: return@ActiveList
|
||||
showDeletePopup(listOf(item), HISTORY_DELETE_CELL)
|
||||
when (id) {
|
||||
ACTIVE_LIST_RENAME_CELL -> beginRename(item, id)
|
||||
ACTIVE_LIST_DELETE_CELL -> showDeletePopup(listOf(item), id)
|
||||
}
|
||||
},
|
||||
onOpen = { row, _ -> activate(row) },
|
||||
).apply {
|
||||
@@ -274,15 +278,27 @@ class HistoryPanel(
|
||||
restore(cloudList, cloudRows)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds [list] while keeping selection stable. Surviving selected rows stay selected; when a
|
||||
* selected row disappears (e.g. it was just deleted) selection slides to the row that took its
|
||||
* slot — the following row, or the last row when the removed one was last — so the highlight
|
||||
* moves naturally instead of vanishing. Selection clears only when nothing was selected or the
|
||||
* list is now empty.
|
||||
*/
|
||||
private fun restore(list: ActiveList, rows: List<ActiveListItem>) {
|
||||
val keys = list.selectedKeys()
|
||||
val anchor = list.selectedIndex()
|
||||
list.update(rows, ActiveListSelection.PreserveNoScroll)
|
||||
val indices = keys.mapNotNull { key -> rows.indexOfFirst { it.key == key }.takeIf { it >= 0 } }.toIntArray()
|
||||
if (indices.isEmpty()) {
|
||||
list.clearSelection()
|
||||
if (indices.isNotEmpty()) {
|
||||
list.setSelectionIndices(indices)
|
||||
return
|
||||
}
|
||||
list.setSelectionIndices(indices)
|
||||
if (keys.isNotEmpty() && rows.isNotEmpty()) {
|
||||
list.selectIndex(anchor.coerceIn(0, rows.size - 1))
|
||||
return
|
||||
}
|
||||
list.clearSelection()
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
@@ -320,6 +336,7 @@ class HistoryPanel(
|
||||
override fun getData(dataId: String): Any? {
|
||||
if (SessionManager.KEY.`is`(dataId)) return manager
|
||||
if (HistoryDataKeys.CONTROLLER.`is`(dataId)) return controller
|
||||
if (HistoryDataKeys.RENAME.`is`(dataId)) return { item: LocalHistoryItem -> beginRename(item) }
|
||||
if (HistoryDataKeys.SELECTION.`is`(dataId)) {
|
||||
val source = selectedSource()
|
||||
val local = if (source == HistorySource.LOCAL) {
|
||||
@@ -361,6 +378,21 @@ class HistoryPanel(
|
||||
showDeletePopup(items)
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the inline rename popover anchored to [item]'s row (or its pencil cell), matching the
|
||||
* worktree list. Committing sends the new title through the controller; the popover itself gates
|
||||
* out blank and unchanged names, so no modal dialog is involved.
|
||||
*/
|
||||
private fun beginRename(item: LocalHistoryItem, cell: String? = null) {
|
||||
controller.requestRename()
|
||||
localList.rename(
|
||||
item.id,
|
||||
cell,
|
||||
current = { title(item) },
|
||||
commit = { _, name -> controller.rename(item, name) },
|
||||
)
|
||||
}
|
||||
|
||||
internal fun itemCount() = activeRows().size
|
||||
|
||||
internal fun selectedSource() = if (tabs.selectedInfo === cloudInfo) HistorySource.CLOUD else HistorySource.LOCAL
|
||||
|
||||
+3
-12
@@ -1,13 +1,11 @@
|
||||
package ai.kilocode.client.session.history
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionActivityKind
|
||||
import ai.kilocode.client.ui.list.ActiveListBadge
|
||||
import ai.kilocode.client.ui.list.ActiveListCell
|
||||
import ai.kilocode.client.ui.list.ActiveListItem
|
||||
import com.intellij.icons.AllIcons
|
||||
|
||||
internal const val HISTORY_DELETE_CELL = "delete"
|
||||
import ai.kilocode.client.ui.list.activeListDeleteCell
|
||||
import ai.kilocode.client.ui.list.activeListRenameCell
|
||||
|
||||
internal data class LocalHistoryRow(
|
||||
val item: LocalHistoryItem,
|
||||
@@ -28,14 +26,7 @@ internal data class LocalHistoryRow(
|
||||
override val cells: List<ActiveListCell>
|
||||
get() {
|
||||
if (deleting) return emptyList()
|
||||
return listOf(
|
||||
ActiveListCell(
|
||||
HISTORY_DELETE_CELL,
|
||||
KiloBundle.message("common.delete"),
|
||||
icon = AllIcons.Actions.GC,
|
||||
iconOnly = true,
|
||||
),
|
||||
)
|
||||
return listOf(activeListRenameCell(), activeListDeleteCell())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package ai.kilocode.client.ui.list
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import com.intellij.icons.AllIcons
|
||||
|
||||
/**
|
||||
* Standard hover action-cell ids and factories shared by the reveal-on-hover lists (the worktree
|
||||
* list, the worktree-session editor list, and session history). Centralising them keeps the
|
||||
* pencil/trash buttons — their ids, icons, and `iconOnly` treatment — identical across every list
|
||||
* instead of each panel re-declaring `"rename"`/`"delete"` and rebuilding the same [ActiveListCell].
|
||||
*/
|
||||
internal const val ACTIVE_LIST_RENAME_CELL = "rename"
|
||||
internal const val ACTIVE_LIST_DELETE_CELL = "delete"
|
||||
|
||||
internal fun activeListRenameCell(label: String = KiloBundle.message("common.rename")) = ActiveListCell(
|
||||
ACTIVE_LIST_RENAME_CELL,
|
||||
label,
|
||||
icon = AllIcons.Actions.Edit,
|
||||
iconOnly = true,
|
||||
)
|
||||
|
||||
internal fun activeListDeleteCell(label: String = KiloBundle.message("common.delete")) = ActiveListCell(
|
||||
ACTIVE_LIST_DELETE_CELL,
|
||||
label,
|
||||
icon = AllIcons.Actions.GC,
|
||||
iconOnly = true,
|
||||
)
|
||||
+17
-87
@@ -3,7 +3,6 @@ package ai.kilocode.client.actions
|
||||
import ai.kilocode.client.app.KiloSessionService
|
||||
import ai.kilocode.client.app.KiloWorkspaceService
|
||||
import ai.kilocode.client.app.Workspace
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import ai.kilocode.client.session.SessionRef
|
||||
import ai.kilocode.client.session.history.CloudHistoryItem
|
||||
@@ -298,72 +297,20 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
|
||||
// ------ RenameSessionAction.actionPerformed ------
|
||||
|
||||
fun `test rename action calls controller with trimmed changed title`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> " Renamed " } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
fun `test rename action opens rename popover for single local selection`() {
|
||||
val item = localItem("ses_1")
|
||||
val renamed = mutableListOf<String>()
|
||||
val action = RenameSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller) { renamed += it.id }
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf(Triple("ses_1", "/test", "Renamed")), rpc.renames)
|
||||
assertEquals(listOf("ses_1"), renamed)
|
||||
}
|
||||
|
||||
fun `test rename action passes displayed current title to input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val prompts = mutableListOf<String>()
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply {
|
||||
input = { _, current ->
|
||||
prompts.add(current)
|
||||
null
|
||||
}
|
||||
}
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf("Original"), prompts)
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action passes untitled fallback to input`() {
|
||||
rpc.listed += sessionDto("ses_1", "")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val prompts = mutableListOf<String>()
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply {
|
||||
input = { _, current ->
|
||||
prompts.add(current)
|
||||
null
|
||||
}
|
||||
}
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf(KiloBundle.message("history.untitled")), prompts)
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action ignores blank input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> " " } }
|
||||
fun `test rename action does nothing without a rename provider`() {
|
||||
val item = localItem("ses_1")
|
||||
val action = RenameSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
@@ -372,34 +319,15 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action ignores unchanged input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> "Original" } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
fun `test rename action does nothing for multiple local selection`() {
|
||||
val items = listOf(localItem("ses_1"), localItem("ses_2"))
|
||||
val renamed = mutableListOf<String>()
|
||||
val action = RenameSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, items), controller) { renamed += it.id }
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action ignores null input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> null } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
assertTrue(renamed.isEmpty())
|
||||
}
|
||||
|
||||
fun `test frontend descriptor registers history actions`() {
|
||||
@@ -426,6 +354,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
manager: SessionManager?,
|
||||
selection: HistorySelection,
|
||||
ctrl: HistoryController,
|
||||
rename: ((LocalHistoryItem) -> Unit)? = null,
|
||||
): AnActionEvent {
|
||||
val presentation = Presentation().apply { copyFrom(action.templatePresentation) }
|
||||
val context = DataContext { id ->
|
||||
@@ -434,6 +363,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
SessionManager.KEY.`is`(id) -> manager
|
||||
HistoryDataKeys.SELECTION.`is`(id) -> selection
|
||||
HistoryDataKeys.CONTROLLER.`is`(id) -> ctrl
|
||||
HistoryDataKeys.RENAME.`is`(id) -> rename
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
+54
-4
@@ -12,6 +12,8 @@ import ai.kilocode.client.testing.FakeWorkspaceRpcApi
|
||||
import ai.kilocode.client.testing.TestCoroutines
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.HoverIcon
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_DELETE_CELL
|
||||
import ai.kilocode.client.ui.list.ACTIVE_LIST_RENAME_CELL
|
||||
import ai.kilocode.client.ui.layout.Align
|
||||
import ai.kilocode.rpc.dto.CloudSessionDto
|
||||
import ai.kilocode.rpc.dto.KiloWorkspaceStateDto
|
||||
@@ -199,14 +201,13 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
assertTrue(row.badges.isEmpty())
|
||||
}
|
||||
|
||||
fun `test local history rows use trailing time and delete cell`() {
|
||||
fun `test local history rows use trailing time and rename and delete cells`() {
|
||||
val item = LocalHistoryItem(session("ses_1", "Long ".repeat(80)))
|
||||
val row = localHistoryRows(listOf(item), HistoryActivitySnapshot()) { false }[0]
|
||||
val cell = row.cells.single()
|
||||
|
||||
assertEquals(HistoryTime.relative(item), row.trailing)
|
||||
assertEquals(HISTORY_DELETE_CELL, cell.id)
|
||||
assertTrue(cell.iconOnly)
|
||||
assertEquals(listOf(ACTIVE_LIST_RENAME_CELL, ACTIVE_LIST_DELETE_CELL), row.cells.map { it.id })
|
||||
assertTrue(row.cells.all { it.iconOnly })
|
||||
}
|
||||
|
||||
fun `test deleting local history rows hide actions and badges`() {
|
||||
@@ -631,6 +632,55 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
assertEquals(javax.swing.ListSelectionModel.SINGLE_SELECTION, panel.listSelectionMode())
|
||||
}
|
||||
|
||||
// ------ Selection advances naturally on delete ------
|
||||
|
||||
fun `test deleting the selected session advances selection to the next row`() {
|
||||
rpc.listed += session("ses_a", "A", 3.0)
|
||||
rpc.listed += session("ses_b", "B", 2.0)
|
||||
rpc.listed += session("ses_c", "C", 1.0)
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
panel.select(1)
|
||||
|
||||
controller.delete(controller.local.items[1])
|
||||
flush()
|
||||
|
||||
assertEquals(1, panel.selectedIndex())
|
||||
assertEquals("C", panel.titleText(1))
|
||||
}
|
||||
|
||||
fun `test deleting the last selected session falls back to the previous row`() {
|
||||
rpc.listed += session("ses_a", "A", 3.0)
|
||||
rpc.listed += session("ses_b", "B", 2.0)
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
panel.select(1)
|
||||
|
||||
controller.delete(controller.local.items[1])
|
||||
flush()
|
||||
|
||||
assertEquals(0, panel.selectedIndex())
|
||||
assertEquals("A", panel.titleText(0))
|
||||
}
|
||||
|
||||
fun `test deleting a background session keeps the current selection`() {
|
||||
rpc.listed += session("ses_a", "A", 3.0)
|
||||
rpc.listed += session("ses_b", "B", 2.0)
|
||||
rpc.listed += session("ses_c", "C", 1.0)
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
panel.select(0)
|
||||
|
||||
controller.delete(controller.local.items[2])
|
||||
flush()
|
||||
|
||||
assertEquals(0, panel.selectedIndex())
|
||||
assertEquals("A", panel.titleText(0))
|
||||
}
|
||||
|
||||
// ------ Rename failure and directory selection ------
|
||||
|
||||
fun `test rename failure keeps original title and sets error`() {
|
||||
|
||||
Reference in New Issue
Block a user