@@ -3,38 +3,48 @@ package cc.unitmesh.devti.gui.chat.ui.file
3
3
import cc.unitmesh.devti.util.canBeAdded
4
4
import com.intellij.openapi.fileEditor.impl.EditorHistoryManager
5
5
import com.intellij.openapi.project.Project
6
+ import com.intellij.openapi.project.guessProjectDir
6
7
import com.intellij.openapi.roots.ProjectFileIndex
7
8
import com.intellij.openapi.ui.popup.JBPopup
8
9
import com.intellij.openapi.ui.popup.JBPopupFactory
10
+ import com.intellij.openapi.ui.popup.JBPopupListener
11
+ import com.intellij.openapi.ui.popup.LightweightWindowEvent
9
12
import com.intellij.openapi.vfs.VirtualFile
13
+ import com.intellij.openapi.wm.IdeFocusManager
10
14
import com.intellij.ui.JBColor
15
+ import com.intellij.ui.SearchTextField
16
+ import com.intellij.ui.SpeedSearchComparator
11
17
import com.intellij.ui.awt.RelativePoint
12
18
import com.intellij.ui.components.JBLabel
13
19
import com.intellij.ui.components.JBList
20
+ import com.intellij.ui.components.JBScrollPane
14
21
import com.intellij.util.ui.JBUI
15
22
import com.intellij.util.ui.UIUtil
16
23
import org.jetbrains.annotations.NotNull
17
24
import java.awt.BorderLayout
18
25
import java.awt.Component
19
26
import java.awt.Dimension
20
27
import java.awt.Point
28
+ import java.awt.event.KeyAdapter
29
+ import java.awt.event.KeyEvent
21
30
import java.awt.event.MouseAdapter
22
31
import java.awt.event.MouseEvent
23
32
import javax.swing.*
24
- import javax.swing.event.DocumentEvent
25
- import javax.swing.event.DocumentListener
26
33
27
34
class WorkspaceFileSearchPopup (
28
35
private val project : Project ,
29
36
private val onFilesSelected : (List <VirtualFile >) -> Unit
30
37
) {
31
38
private var popup: JBPopup ? = null
32
39
private val fileListModel = DefaultListModel <FilePresentation >()
33
- private val fileList = JBList (fileListModel)
34
- private val searchField = JTextField ()
40
+ private val fileList = JBList (fileListModel).apply {
41
+ cellRenderer = FileListCellRenderer ()
42
+ selectionMode = ListSelectionModel .MULTIPLE_INTERVAL_SELECTION
43
+ }
44
+ private val searchField = SearchTextField ()
35
45
private val contentPanel = JPanel (BorderLayout ())
36
46
private val allProjectFiles = mutableListOf<FilePresentation >()
37
- private val minPopupSize = Dimension (435 , 300 )
47
+ private val minPopupSize = Dimension (500 , 400 )
38
48
39
49
init {
40
50
loadProjectFiles()
@@ -43,15 +53,18 @@ class WorkspaceFileSearchPopup(
43
53
44
54
private fun loadProjectFiles () {
45
55
allProjectFiles.clear()
56
+
57
+ // Add recent files with higher priority
46
58
val fileList = EditorHistoryManager .getInstance(project).fileList
47
- fileList.forEach { file ->
59
+ fileList.take( 20 ). forEach { file ->
48
60
if (file.canBeAdded(project)) {
49
61
val presentation = FilePresentation .from(project, file)
50
62
presentation.isRecentFile = true
51
63
allProjectFiles.add(presentation)
52
64
}
53
65
}
54
66
67
+ // Add all project files
55
68
ProjectFileIndex .getInstance(project).iterateContent { file ->
56
69
if (file.canBeAdded(project) &&
57
70
! ProjectFileIndex .getInstance(project).isUnderIgnored(file) &&
@@ -63,76 +76,125 @@ class WorkspaceFileSearchPopup(
63
76
true
64
77
}
65
78
66
- updateFileList (" " )
79
+ filterFiles (" " )
67
80
}
68
81
69
82
private fun setupUI () {
70
- searchField.document.addDocumentListener(object : DocumentListener {
71
- override fun insertUpdate (e : DocumentEvent ) = updateSearch()
72
- override fun removeUpdate (e : DocumentEvent ) = updateSearch()
73
- override fun changedUpdate (e : DocumentEvent ) = updateSearch()
74
-
75
- private fun updateSearch () {
76
- updateFileList(searchField.text)
83
+ // Configure search field
84
+ searchField.textEditor.addKeyListener(object : KeyAdapter () {
85
+ override fun keyReleased (e : KeyEvent ) {
86
+ filterFiles(searchField.text.trim())
87
+ if (e.keyCode == KeyEvent .VK_DOWN && fileListModel.size > 0 ) {
88
+ fileList.requestFocus()
89
+ fileList.selectedIndex = 0
90
+ }
91
+ }
92
+ })
93
+
94
+ // Configure file list
95
+ fileList.addKeyListener(object : KeyAdapter () {
96
+ override fun keyPressed (e : KeyEvent ) {
97
+ when (e.keyCode) {
98
+ KeyEvent .VK_ENTER -> {
99
+ selectFiles()
100
+ e.consume()
101
+ }
102
+ KeyEvent .VK_ESCAPE -> {
103
+ popup?.cancel()
104
+ e.consume()
105
+ }
106
+ KeyEvent .VK_UP -> {
107
+ if (fileList.selectedIndex == 0 ) {
108
+ searchField.requestFocus()
109
+ e.consume()
110
+ }
111
+ }
112
+ }
77
113
}
78
114
})
79
115
80
- fileList.cellRenderer = FileListCellRenderer ()
81
116
fileList.addMouseListener(object : MouseAdapter () {
82
117
override fun mouseClicked (e : MouseEvent ) {
83
118
if (e.clickCount == 2 ) {
84
- val selectedFiles = fileList.selectedValuesList.map { it.virtualFile }
85
- if (selectedFiles.isNotEmpty()) {
86
- onFilesSelected(selectedFiles)
87
- popup?.cancel()
88
- }
119
+ selectFiles()
89
120
}
90
121
}
91
122
})
92
123
124
+ // Setup layout with proper borders and spacing
125
+ contentPanel.border = JBUI .Borders .empty()
93
126
contentPanel.add(searchField, BorderLayout .NORTH )
94
- contentPanel.add(JScrollPane (fileList), BorderLayout .CENTER )
127
+ contentPanel.add(JBScrollPane (fileList), BorderLayout .CENTER )
95
128
contentPanel.preferredSize = minPopupSize
96
129
}
97
130
98
- private fun updateFileList ( searchText : String ) {
131
+ private fun filterFiles ( query : String ) {
99
132
fileListModel.clear()
100
-
101
- val filteredFiles = if (searchText.isBlank()) {
102
- allProjectFiles
133
+ val filteredFiles = if (query.isBlank()) {
134
+ allProjectFiles.take(50 ) // Show first 50 files when no query
103
135
} else {
104
- allProjectFiles.filter { item ->
105
- item.name.contains(searchText, ignoreCase = true ) ||
106
- item.path.contains(searchText, ignoreCase = true )
107
- }
136
+ allProjectFiles.filter { file ->
137
+ file.name.contains(query, ignoreCase = true ) ||
138
+ file.path.contains(query, ignoreCase = true )
139
+ }.take(50 )
140
+ }
141
+
142
+ // Sort files: recent files first, then by name
143
+ val sortedFiles = filteredFiles.sortedWith(compareBy<FilePresentation > { ! it.isRecentFile }.thenBy { it.name })
144
+ sortedFiles.forEach { fileListModel.addElement(it) }
145
+
146
+ // Auto-select first item if available
147
+ if (fileListModel.size > 0 ) {
148
+ fileList.selectedIndex = 0
149
+ }
150
+ }
151
+
152
+ private fun selectFiles () {
153
+ val selectedFiles = fileList.selectedValuesList.map { it.virtualFile }
154
+ if (selectedFiles.isNotEmpty()) {
155
+ onFilesSelected(selectedFiles)
156
+ popup?.cancel()
108
157
}
109
-
110
- filteredFiles.forEach { fileListModel.addElement(it) }
111
158
}
112
159
113
160
fun show (component : JComponent ) {
114
161
popup = JBPopupFactory .getInstance()
115
- .createComponentPopupBuilder(contentPanel, searchField)
116
- .setTitle(" Search Files" )
162
+ .createComponentPopupBuilder(contentPanel, searchField.textEditor )
163
+ .setTitle(" Add Files to Workspace " )
117
164
.setMovable(true )
118
165
.setResizable(true )
119
166
.setRequestFocus(true )
120
167
.setFocusable(true )
168
+ .setCancelOnClickOutside(true )
169
+ .setCancelOnOtherWindowOpen(true )
121
170
.setMinSize(minPopupSize)
122
171
.createPopup()
172
+
173
+ popup?.addListener(object : JBPopupListener {
174
+ override fun onClosed (event : LightweightWindowEvent ) {
175
+ // Clean up resources when popup is closed
176
+ allProjectFiles.clear()
177
+ fileListModel.clear()
178
+ }
179
+ })
123
180
124
- val topOffset = (component.border?.getBorderInsets(component)?.top ? : 0 )
125
- val leftOffset = (component.border?.getBorderInsets(component)?.left ? : 0 )
126
-
127
- popup?.show(RelativePoint (component, Point (leftOffset, - minPopupSize.height + topOffset)))
181
+ // Show popup in best position
182
+ // popup?.showInBestPositionFor(component)
183
+ popup?.showInCenterOf(component)
184
+
185
+ // Request focus for search field after popup is shown
186
+ SwingUtilities .invokeLater {
187
+ IdeFocusManager .findInstance().requestFocus(searchField.textEditor, false )
188
+ }
128
189
}
129
190
130
- class FileListCellRenderer () : ListCellRenderer<FilePresentation> {
191
+ private inner class FileListCellRenderer : ListCellRenderer <FilePresentation > {
131
192
private val noBorderFocus = BorderFactory .createEmptyBorder(1 , 1 , 1 , 1 )
193
+ private val speedSearchComparator = SpeedSearchComparator (false )
132
194
133
195
@NotNull
134
196
override fun getListCellRendererComponent (
135
- list : JList <out FilePresentation >? ,
197
+ list : JList <out FilePresentation >,
136
198
value : FilePresentation ,
137
199
index : Int ,
138
200
isSelected : Boolean ,
@@ -161,11 +223,11 @@ class WorkspaceFileSearchPopup(
161
223
mainPanel.add(infoPanel, BorderLayout .CENTER )
162
224
163
225
if (isSelected) {
164
- mainPanel.background = list? .selectionBackground
165
- mainPanel.foreground = list? .selectionForeground
226
+ mainPanel.background = list.selectionBackground
227
+ mainPanel.foreground = list.selectionForeground
166
228
} else {
167
- mainPanel.background = list? .background
168
- mainPanel.foreground = list? .foreground
229
+ mainPanel.background = list.background
230
+ mainPanel.foreground = list.foreground
169
231
}
170
232
171
233
mainPanel.border = if (cellHasFocus) {
@@ -177,4 +239,4 @@ class WorkspaceFileSearchPopup(
177
239
return mainPanel
178
240
}
179
241
}
180
- }
242
+ }
0 commit comments