Skip to content

Commit c7bfd16

Browse files
committed
feat(mcp): add search functionality to McpToolListPanel
1 parent 202ead6 commit c7bfd16

File tree

1 file changed

+112
-27
lines changed

1 file changed

+112
-27
lines changed

core/src/main/kotlin/cc/unitmesh/devti/mcp/ui/McpToolListPanel.kt

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package cc.unitmesh.devti.mcp.ui
22

33
import cc.unitmesh.devti.AutoDevIcons
44
import cc.unitmesh.devti.mcp.client.CustomMcpServerManager
5+
import com.intellij.icons.AllIcons
56
import com.intellij.openapi.project.Project
67
import com.intellij.ui.JBColor
8+
import com.intellij.ui.SearchTextField
79
import com.intellij.ui.components.JBLabel
810
import com.intellij.util.ui.JBUI
911
import com.intellij.util.ui.UIUtil
@@ -14,38 +16,99 @@ import kotlinx.coroutines.Job
1416
import kotlinx.coroutines.launch
1517
import java.awt.BorderLayout
1618
import java.awt.GridLayout
17-
import javax.swing.BorderFactory
18-
import javax.swing.BoxLayout
19-
import javax.swing.JPanel
19+
import javax.swing.*
2020
import javax.swing.SwingConstants
2121
import javax.swing.SwingUtilities
22+
import javax.swing.event.DocumentEvent
23+
import javax.swing.event.DocumentListener
2224

2325
class McpToolListPanel(private val project: Project) : JPanel() {
2426
private val mcpServerManager = CustomMcpServerManager.instance(project)
2527
private val allTools = mutableMapOf<String, List<Tool>>()
2628
private var loadingJob: Job? = null
2729
private val serverLoadingStatus = mutableMapOf<String, Boolean>()
2830
private val serverPanels = mutableMapOf<String, JPanel>()
31+
private val searchField = SearchTextField()
32+
private var currentFilterText = ""
2933

30-
private val borderColor = JBColor(0xE5E7EB, 0x3C3F41) // Equivalent to Tailwind gray-200
31-
private val textGray = JBColor(0x6B7280, 0x9DA0A8) // Equivalent to Tailwind gray-500
32-
private val headerColor = JBColor(0xF3F4F6, 0x2B2D30) // Light gray for section headers
34+
private val borderColor = JBColor(0xE5E7EB, 0x3C3F41)
35+
private val textGray = JBColor(0x6B7280, 0x9DA0A8)
36+
private val headerColor = JBColor(0xF3F4F6, 0x2B2D30)
3337

3438
init {
35-
layout = BoxLayout(this, BoxLayout.Y_AXIS)
39+
layout = BorderLayout()
3640
background = UIUtil.getPanelBackground()
41+
42+
val searchPanel = createSearchPanel()
43+
add(searchPanel, BorderLayout.NORTH)
44+
45+
val contentPanel = JPanel().apply {
46+
layout = BoxLayout(this, BoxLayout.Y_AXIS)
47+
background = UIUtil.getPanelBackground()
48+
}
49+
add(JScrollPane(contentPanel), BorderLayout.CENTER)
50+
}
51+
52+
private fun createSearchPanel(): JPanel {
53+
return JPanel(BorderLayout()).apply {
54+
background = UIUtil.getPanelBackground()
55+
border = BorderFactory.createCompoundBorder(
56+
BorderFactory.createMatteBorder(0, 0, 1, 0, borderColor),
57+
JBUI.Borders.empty()
58+
)
59+
60+
searchField.apply {
61+
textEditor.document.addDocumentListener(object : DocumentListener {
62+
override fun insertUpdate(e: DocumentEvent) = updateFilter()
63+
override fun removeUpdate(e: DocumentEvent) = updateFilter()
64+
override fun changedUpdate(e: DocumentEvent) = updateFilter()
65+
})
66+
}
67+
68+
val searchInnerPanel = JPanel(BorderLayout(JBUI.scale(4), 0)).apply {
69+
background = UIUtil.getPanelBackground()
70+
add(searchField, BorderLayout.CENTER)
71+
border = JBUI.Borders.empty(4)
72+
}
73+
74+
add(searchInnerPanel, BorderLayout.CENTER)
75+
}
76+
}
77+
78+
private fun updateFilter() {
79+
val filterText = searchField.text.trim().lowercase()
80+
81+
if (filterText == currentFilterText) return
82+
currentFilterText = filterText
83+
84+
SwingUtilities.invokeLater {
85+
allTools.forEach { (serverName, tools) ->
86+
updateServerSection(serverName, tools, filterText)
87+
}
88+
}
89+
}
90+
91+
fun resetSearch() {
92+
searchField.text = ""
93+
currentFilterText = ""
94+
allTools.forEach { (serverName, tools) ->
95+
updateServerSection(serverName, tools)
96+
}
3797
}
3898

3999
fun loadTools(content: String, onToolsLoaded: (MutableMap<String, List<Tool>>) -> Unit = {}) {
40100
loadingJob?.cancel()
41101
serverLoadingStatus.clear()
42102
serverPanels.clear()
43103
allTools.clear()
104+
currentFilterText = ""
105+
searchField.text = ""
44106

45107
SwingUtilities.invokeLater {
46-
removeAll()
47-
revalidate()
48-
repaint()
108+
val contentPanel = getContentPanel()
109+
contentPanel.removeAll()
110+
contentPanel.revalidate()
111+
contentPanel.repaint()
49112
}
50113

51114
loadingJob = CoroutineScope(Dispatchers.IO).launch {
@@ -72,7 +135,7 @@ class McpToolListPanel(private val project: Project) : JPanel() {
72135
onToolsLoaded(allTools)
73136

74137
SwingUtilities.invokeLater {
75-
updateServerSection(serverName, tools)
138+
updateServerSection(serverName, tools, currentFilterText)
76139
serverLoadingStatus[serverName] = false
77140
}
78141
} catch (e: Exception) {
@@ -85,6 +148,10 @@ class McpToolListPanel(private val project: Project) : JPanel() {
85148
}
86149
}
87150

151+
private fun getContentPanel(): JPanel {
152+
return (components.find { it is JScrollPane } as JScrollPane).viewport.view as JPanel
153+
}
154+
88155
private fun createServerSection(serverName: String) {
89156
val serverPanel = JPanel(BorderLayout()).apply {
90157
background = UIUtil.getPanelBackground()
@@ -125,30 +192,48 @@ class McpToolListPanel(private val project: Project) : JPanel() {
125192

126193
serverPanels[serverName] = toolsPanel
127194

128-
add(serverPanel)
129-
revalidate()
130-
repaint()
195+
getContentPanel().add(serverPanel)
196+
getContentPanel().revalidate()
197+
getContentPanel().repaint()
131198
}
132199

133-
private fun updateServerSection(serverName: String, tools: List<Tool>) {
200+
private fun updateServerSection(serverName: String, tools: List<Tool>, filterText: String = "") {
134201
val toolsPanel = serverPanels[serverName] ?: return
135202
toolsPanel.removeAll()
136203

137-
if (tools.isEmpty()) {
138-
val noToolsLabel = JBLabel("No tools available for $serverName").apply {
139-
foreground = textGray
140-
horizontalAlignment = SwingConstants.LEFT
204+
val filteredTools = if (filterText.isEmpty()) {
205+
tools
206+
} else {
207+
tools.filter {
208+
it.name.lowercase().contains(filterText) ||
209+
it.description?.lowercase()?.contains(filterText) == true
141210
}
142-
toolsPanel.add(noToolsLabel)
211+
}
212+
213+
val parentPanel = toolsPanel.parent
214+
if (filteredTools.isEmpty() && filterText.isNotEmpty()) {
215+
parentPanel.isVisible = false
143216
} else {
144-
tools.forEach { tool ->
145-
val panel = McpToolListCardPanel(project, serverName, tool)
146-
toolsPanel.add(panel)
217+
parentPanel.isVisible = true
218+
219+
if (filteredTools.isEmpty()) {
220+
val noToolsLabel = JBLabel("No tools available for $serverName").apply {
221+
foreground = textGray
222+
horizontalAlignment = SwingConstants.LEFT
223+
}
224+
toolsPanel.add(noToolsLabel)
225+
} else {
226+
filteredTools.forEach { tool ->
227+
val panel = McpToolListCardPanel(project, serverName, tool)
228+
toolsPanel.add(panel)
229+
}
147230
}
148231
}
149232

150233
toolsPanel.revalidate()
151234
toolsPanel.repaint()
235+
getContentPanel().revalidate()
236+
getContentPanel().repaint()
152237
}
153238

154239
private fun showServerError(serverName: String, errorMessage: String) {
@@ -166,7 +251,7 @@ class McpToolListPanel(private val project: Project) : JPanel() {
166251
}
167252

168253
private fun showNoServersMessage() {
169-
removeAll()
254+
getContentPanel().removeAll()
170255

171256
val noServersPanel = JPanel(BorderLayout()).apply {
172257
background = UIUtil.getPanelBackground()
@@ -179,9 +264,9 @@ class McpToolListPanel(private val project: Project) : JPanel() {
179264
}
180265

181266
noServersPanel.add(noServersLabel, BorderLayout.CENTER)
182-
add(noServersPanel)
183-
revalidate()
184-
repaint()
267+
getContentPanel().add(noServersPanel)
268+
getContentPanel().revalidate()
269+
getContentPanel().repaint()
185270
}
186271

187272
fun dispose() {

0 commit comments

Comments
 (0)