@@ -2,8 +2,10 @@ package cc.unitmesh.devti.mcp.ui
2
2
3
3
import cc.unitmesh.devti.AutoDevIcons
4
4
import cc.unitmesh.devti.mcp.client.CustomMcpServerManager
5
+ import com.intellij.icons.AllIcons
5
6
import com.intellij.openapi.project.Project
6
7
import com.intellij.ui.JBColor
8
+ import com.intellij.ui.SearchTextField
7
9
import com.intellij.ui.components.JBLabel
8
10
import com.intellij.util.ui.JBUI
9
11
import com.intellij.util.ui.UIUtil
@@ -14,38 +16,99 @@ import kotlinx.coroutines.Job
14
16
import kotlinx.coroutines.launch
15
17
import java.awt.BorderLayout
16
18
import java.awt.GridLayout
17
- import javax.swing.BorderFactory
18
- import javax.swing.BoxLayout
19
- import javax.swing.JPanel
19
+ import javax.swing.*
20
20
import javax.swing.SwingConstants
21
21
import javax.swing.SwingUtilities
22
+ import javax.swing.event.DocumentEvent
23
+ import javax.swing.event.DocumentListener
22
24
23
25
class McpToolListPanel (private val project : Project ) : JPanel() {
24
26
private val mcpServerManager = CustomMcpServerManager .instance(project)
25
27
private val allTools = mutableMapOf<String , List <Tool >>()
26
28
private var loadingJob: Job ? = null
27
29
private val serverLoadingStatus = mutableMapOf<String , Boolean >()
28
30
private val serverPanels = mutableMapOf<String , JPanel >()
31
+ private val searchField = SearchTextField ()
32
+ private var currentFilterText = " "
29
33
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 )
33
37
34
38
init {
35
- layout = BoxLayout ( this , BoxLayout . Y_AXIS )
39
+ layout = BorderLayout ( )
36
40
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
+ }
37
97
}
38
98
39
99
fun loadTools (content : String , onToolsLoaded : (MutableMap <String , List <Tool >>) -> Unit = {}) {
40
100
loadingJob?.cancel()
41
101
serverLoadingStatus.clear()
42
102
serverPanels.clear()
43
103
allTools.clear()
104
+ currentFilterText = " "
105
+ searchField.text = " "
44
106
45
107
SwingUtilities .invokeLater {
46
- removeAll()
47
- revalidate()
48
- repaint()
108
+ val contentPanel = getContentPanel()
109
+ contentPanel.removeAll()
110
+ contentPanel.revalidate()
111
+ contentPanel.repaint()
49
112
}
50
113
51
114
loadingJob = CoroutineScope (Dispatchers .IO ).launch {
@@ -72,7 +135,7 @@ class McpToolListPanel(private val project: Project) : JPanel() {
72
135
onToolsLoaded(allTools)
73
136
74
137
SwingUtilities .invokeLater {
75
- updateServerSection(serverName, tools)
138
+ updateServerSection(serverName, tools, currentFilterText )
76
139
serverLoadingStatus[serverName] = false
77
140
}
78
141
} catch (e: Exception ) {
@@ -85,6 +148,10 @@ class McpToolListPanel(private val project: Project) : JPanel() {
85
148
}
86
149
}
87
150
151
+ private fun getContentPanel (): JPanel {
152
+ return (components.find { it is JScrollPane } as JScrollPane ).viewport.view as JPanel
153
+ }
154
+
88
155
private fun createServerSection (serverName : String ) {
89
156
val serverPanel = JPanel (BorderLayout ()).apply {
90
157
background = UIUtil .getPanelBackground()
@@ -125,30 +192,48 @@ class McpToolListPanel(private val project: Project) : JPanel() {
125
192
126
193
serverPanels[serverName] = toolsPanel
127
194
128
- add(serverPanel)
129
- revalidate()
130
- repaint()
195
+ getContentPanel(). add(serverPanel)
196
+ getContentPanel(). revalidate()
197
+ getContentPanel(). repaint()
131
198
}
132
199
133
- private fun updateServerSection (serverName : String , tools : List <Tool >) {
200
+ private fun updateServerSection (serverName : String , tools : List <Tool >, filterText : String = "" ) {
134
201
val toolsPanel = serverPanels[serverName] ? : return
135
202
toolsPanel.removeAll()
136
203
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
141
210
}
142
- toolsPanel.add(noToolsLabel)
211
+ }
212
+
213
+ val parentPanel = toolsPanel.parent
214
+ if (filteredTools.isEmpty() && filterText.isNotEmpty()) {
215
+ parentPanel.isVisible = false
143
216
} 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
+ }
147
230
}
148
231
}
149
232
150
233
toolsPanel.revalidate()
151
234
toolsPanel.repaint()
235
+ getContentPanel().revalidate()
236
+ getContentPanel().repaint()
152
237
}
153
238
154
239
private fun showServerError (serverName : String , errorMessage : String ) {
@@ -166,7 +251,7 @@ class McpToolListPanel(private val project: Project) : JPanel() {
166
251
}
167
252
168
253
private fun showNoServersMessage () {
169
- removeAll()
254
+ getContentPanel(). removeAll()
170
255
171
256
val noServersPanel = JPanel (BorderLayout ()).apply {
172
257
background = UIUtil .getPanelBackground()
@@ -179,9 +264,9 @@ class McpToolListPanel(private val project: Project) : JPanel() {
179
264
}
180
265
181
266
noServersPanel.add(noServersLabel, BorderLayout .CENTER )
182
- add(noServersPanel)
183
- revalidate()
184
- repaint()
267
+ getContentPanel(). add(noServersPanel)
268
+ getContentPanel(). revalidate()
269
+ getContentPanel(). repaint()
185
270
}
186
271
187
272
fun dispose () {
0 commit comments