Skip to content

Commit 2f01323

Browse files
committed
refactor(ui): improve session list rendering and interaction in ViewHistoryActionui): improve session history popup layout and UX
- Replace ColoredListCellRenderer with custom ListCellRenderer for better control - Add scroll pane with fixed dimensions and proper sizing - Implement text truncation with tooltips for long session names - Enhance delete button with hover effects and proper event handling - Set fixed cell heights and improved spacing for consistent appearance
1 parent 16006c4 commit 2f01323

File tree

1 file changed

+123
-60
lines changed

1 file changed

+123
-60
lines changed

core/src/main/kotlin/cc/unitmesh/devti/gui/toolbar/ViewHistoryAction.kt

Lines changed: 123 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ import com.intellij.openapi.actionSystem.AnActionEvent
1414
import com.intellij.openapi.project.Project
1515
import com.intellij.openapi.ui.Messages
1616
import com.intellij.openapi.ui.popup.JBPopupFactory
17+
import com.intellij.openapi.ui.popup.JBPopup
1718
import com.intellij.openapi.wm.ToolWindowManager
18-
import com.intellij.ui.ColoredListCellRenderer
19-
import com.intellij.ui.SimpleTextAttributes
2019
import com.intellij.ui.components.JBList
20+
import com.intellij.ui.components.JBScrollPane
2121
import com.intellij.util.ui.JBUI
22-
import java.awt.BorderLayout
22+
import java.awt.*
23+
import java.awt.event.ComponentAdapter
24+
import java.awt.event.ComponentEvent
2325
import java.awt.event.MouseAdapter
2426
import java.awt.event.MouseEvent
25-
import javax.swing.JButton
26-
import javax.swing.JLabel
27-
import javax.swing.JPanel
28-
import javax.swing.ListSelectionModel
27+
import javax.swing.*
28+
import javax.swing.border.EmptyBorder
2929

3030
class ViewHistoryAction : AnAction(
3131
AutoDevBundle.message("action.view.history.text"),
@@ -37,15 +37,15 @@ class ViewHistoryAction : AnAction(
3737
private fun formatRelativeTime(timestamp: Long): String {
3838
val now = System.currentTimeMillis()
3939
val diff = now - timestamp
40-
40+
4141
val seconds = diff / 1000
4242
val minutes = seconds / 60
4343
val hours = minutes / 60
4444
val days = hours / 24
4545
val weeks = days / 7
4646
val months = days / 30
4747
val years = days / 365
48-
48+
4949
return when {
5050
years > 0 -> "${years}年前"
5151
months > 0 -> "${months}个月前"
@@ -65,51 +65,95 @@ class ViewHistoryAction : AnAction(
6565
private inner class SessionListCellRenderer(
6666
private val project: Project,
6767
private val onDelete: (ChatSessionHistory) -> Unit
68-
) : ColoredListCellRenderer<SessionListItem>() {
68+
) : ListCellRenderer<SessionListItem> {
6969

70-
override fun customizeCellRenderer(
70+
override fun getListCellRendererComponent(
7171
list: javax.swing.JList<out SessionListItem>,
72-
value: SessionListItem?,
72+
value: SessionListItem,
7373
index: Int,
7474
selected: Boolean,
7575
hasFocus: Boolean
76-
) {
77-
if (value == null) return
78-
79-
// 创建主面板
76+
): Component {
77+
// 创建主面板,使用 BorderLayout
8078
val panel = JPanel(BorderLayout())
81-
panel.border = JBUI.Borders.empty(4, 8)
82-
83-
// 左侧标题
84-
val titleLabel = JLabel(value.session.name)
85-
titleLabel.font = titleLabel.font.deriveFont(14f)
86-
87-
// 右侧时间和删除按钮的面板
88-
val rightPanel = JPanel(BorderLayout())
79+
panel.border = JBUI.Borders.empty(8, 12)
80+
81+
// 设置背景颜色
82+
if (selected) {
83+
panel.background = list.selectionBackground
84+
panel.foreground = list.selectionForeground
85+
} else {
86+
panel.background = list.background
87+
panel.foreground = list.foreground
88+
}
89+
panel.isOpaque = true
90+
91+
// 左侧内容面板 - 包含会话名称和时间
92+
val contentPanel = JPanel()
93+
contentPanel.layout = BoxLayout(contentPanel, BoxLayout.Y_AXIS)
94+
contentPanel.isOpaque = false
95+
96+
// 会话名称标签 - 处理过长文本
97+
val sessionName = value.session.name
98+
val displayName = if (sessionName.length > 40) {
99+
sessionName.substring(0, 37) + "..."
100+
} else {
101+
sessionName
102+
}
89103

104+
val titleLabel = JLabel(displayName)
105+
titleLabel.font = titleLabel.font.deriveFont(Font.BOLD, 13f)
106+
titleLabel.toolTipText = sessionName // 完整名称作为工具提示
107+
if (selected) {
108+
titleLabel.foreground = list.selectionForeground
109+
} else {
110+
titleLabel.foreground = list.foreground
111+
}
112+
90113
// 时间标签
91114
val timeLabel = JLabel(value.relativeTime)
92-
timeLabel.font = timeLabel.font.deriveFont(12f)
93-
timeLabel.foreground = if (selected) list.selectionForeground else list.foreground.darker()
94-
95-
// 删除按钮
96-
val deleteButton = JButton(AllIcons.Actions.Close)
97-
deleteButton.isOpaque = false
98-
deleteButton.isBorderPainted = false
99-
deleteButton.preferredSize = JBUI.size(16, 16)
100-
deleteButton.addActionListener {
101-
onDelete(value.session)
115+
timeLabel.font = timeLabel.font.deriveFont(11f)
116+
if (selected) {
117+
timeLabel.foreground = list.selectionForeground.darker()
118+
} else {
119+
timeLabel.foreground = list.foreground.darker()
102120
}
103-
104-
rightPanel.add(timeLabel, BorderLayout.CENTER)
105-
rightPanel.add(deleteButton, BorderLayout.EAST)
106-
107-
panel.add(titleLabel, BorderLayout.WEST)
108-
panel.add(rightPanel, BorderLayout.EAST)
109-
110-
// 设置简单的文本渲染
111-
append(value.session.name, SimpleTextAttributes.REGULAR_ATTRIBUTES)
112-
append(" - ${value.relativeTime}", SimpleTextAttributes.GRAYED_ATTRIBUTES)
121+
122+
// 设置标签对齐
123+
titleLabel.alignmentX = Component.LEFT_ALIGNMENT
124+
timeLabel.alignmentX = Component.LEFT_ALIGNMENT
125+
126+
contentPanel.add(titleLabel)
127+
contentPanel.add(Box.createVerticalStrut(2))
128+
contentPanel.add(timeLabel)
129+
130+
// 右侧删除按钮
131+
val deleteButton = JLabel(AllIcons.Actions.Close)
132+
deleteButton.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
133+
deleteButton.border = JBUI.Borders.emptyLeft(8)
134+
deleteButton.addMouseListener(object : MouseAdapter() {
135+
override fun mouseClicked(e: MouseEvent) {
136+
e.consume() // 防止事件传播到列表
137+
onDelete(value.session)
138+
}
139+
140+
override fun mouseEntered(e: MouseEvent) {
141+
deleteButton.icon = AllIcons.Actions.CloseHovered
142+
}
143+
144+
override fun mouseExited(e: MouseEvent) {
145+
deleteButton.icon = AllIcons.Actions.Close
146+
}
147+
})
148+
149+
// 组装面板
150+
panel.add(contentPanel, BorderLayout.CENTER)
151+
panel.add(deleteButton, BorderLayout.EAST)
152+
153+
// 设置固定高度
154+
panel.preferredSize = Dimension(panel.preferredSize.width, 50)
155+
156+
return panel
113157
}
114158
}
115159

@@ -126,26 +170,36 @@ class ViewHistoryAction : AnAction(
126170
val listItems = sessions.map { SessionListItem(it, formatRelativeTime(it.createdAt)) }
127171
val jbList = JBList(listItems)
128172
jbList.selectionMode = ListSelectionModel.SINGLE_SELECTION
129-
173+
jbList.fixedCellHeight = 50 // 固定单元格高度
174+
130175
val onDeleteSession = { session: ChatSessionHistory ->
131176
val result = Messages.showYesNoDialog(
132177
project,
133178
"确定要删除会话 \"${session.name}\" 吗?",
134179
"删除会话",
135180
Messages.getQuestionIcon()
136181
)
137-
182+
138183
if (result == Messages.YES) {
139184
historyService.deleteSession(session.id)
140185
sessions = historyService.getAllSessions().sortedByDescending { it.createdAt }
141-
// 关闭当前popup并重新创建
142186
createAndShowPopup()
143187
}
144188
}
145-
189+
146190
jbList.cellRenderer = SessionListCellRenderer(project, onDeleteSession)
191+
192+
val scrollPane = JBScrollPane(jbList)
193+
scrollPane.border = null
194+
195+
// 设置 Popup 的固定宽度和自适应高度
196+
val popupWidth = 450
197+
val maxPopupHeight = 400
198+
val itemHeight = 50
199+
val calculatedHeight = (sessions.size * itemHeight + 20).coerceAtMost(maxPopupHeight)
147200

148-
// 添加右键菜单支持
201+
scrollPane.preferredSize = Dimension(popupWidth, calculatedHeight)
202+
149203
jbList.addMouseListener(object : MouseAdapter() {
150204
override fun mouseClicked(e: MouseEvent) {
151205
if (e.button == MouseEvent.BUTTON3) { // 右键
@@ -159,23 +213,32 @@ class ViewHistoryAction : AnAction(
159213
}
160214
})
161215

162-
JBPopupFactory.getInstance()
163-
.createListPopupBuilder(jbList)
216+
val popupBuilder = JBPopupFactory.getInstance()
217+
.createComponentPopupBuilder(scrollPane, jbList)
164218
.setTitle(AutoDevBundle.message("popup.title.session.history"))
165219
.setMovable(true)
166220
.setResizable(true)
167221
.setRequestFocus(true)
168-
.setItemChoosenCallback {
222+
.setCancelOnClickOutside(true)
223+
.setCancelOnOtherWindowOpen(true)
224+
225+
val popup: JBPopup = popupBuilder.createPopup()
226+
227+
// 设置 Popup 的最小和最大尺寸
228+
popup.setMinimumSize(Dimension(popupWidth, 150))
229+
230+
jbList.addListSelectionListener {
231+
if (!it.valueIsAdjusting && jbList.selectedIndex != -1) {
169232
val selectedIndex = jbList.selectedIndex
170-
if (selectedIndex != -1) {
171-
val selectedSession = listItems[selectedIndex].session
172-
loadSessionIntoSketch(project, selectedSession)
173-
}
233+
popup.closeOk(null)
234+
val selectedSession = listItems[selectedIndex].session
235+
loadSessionIntoSketch(project, selectedSession)
174236
}
175-
.createPopup()
176-
.showInBestPositionFor(e.dataContext)
237+
}
238+
239+
popup.showInBestPositionFor(e.dataContext)
177240
}
178-
241+
179242
createAndShowPopup()
180243
}
181244

@@ -196,7 +259,7 @@ class ViewHistoryAction : AnAction(
196259

197260
it.displayMessages(session.messages)
198261
session.messages.firstOrNull { msg -> msg.role == "user" }?.content?.let { intention ->
199-
agentStateService.state = agentStateService.state.copy(originIntention = intention)
262+
agentStateService.state = agentStateService.state.copy(originIntention = intention)
200263
}
201264

202265
toolWindow.activate(null)

0 commit comments

Comments
 (0)