Skip to content

Commit fe991d4

Browse files
committed
feat(gui): implement file search popup for workspace panel #358
- Add createAddButton() method to generate the add button- Implement showFileSearchPopup() method to display file search popup - Create FileSearchPopup class to handle file search functionality - Update UI and functionality of the WorkspacePanel to use the new file search feature
1 parent f338788 commit fe991d4

File tree

1 file changed

+176
-22
lines changed

1 file changed

+176
-22
lines changed

core/src/main/kotlin/cc/unitmesh/devti/gui/chat/ui/WorkspacePanel.kt

Lines changed: 176 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,22 @@ import com.intellij.util.ui.JBUI
1111
import java.awt.*
1212
import java.awt.event.MouseAdapter
1313
import java.awt.event.MouseEvent
14-
import javax.swing.BorderFactory
1514
import javax.swing.JPanel
15+
import javax.swing.JList
16+
import javax.swing.ListCellRenderer
17+
import javax.swing.UIManager
18+
import javax.swing.JTextField
19+
import javax.swing.JScrollPane
20+
import javax.swing.DefaultListModel
21+
import javax.swing.BorderFactory
22+
import javax.swing.event.DocumentEvent
23+
import javax.swing.event.DocumentListener
24+
import com.intellij.openapi.ui.popup.JBPopupFactory
25+
import com.intellij.openapi.ui.popup.JBPopup
26+
import com.intellij.openapi.roots.ProjectRootManager
27+
import com.intellij.openapi.vfs.VfsUtil
28+
import com.intellij.util.ui.UIUtil
29+
import org.jetbrains.annotations.NotNull
1630

1731
class WorkspacePanel(
1832
private val project: Project,
@@ -24,6 +38,14 @@ class WorkspacePanel(
2438
init {
2539
border = JBUI.Borders.empty()
2640

41+
filesPanel.isOpaque = false
42+
filesPanel.add(createAddButton())
43+
44+
add(filesPanel, BorderLayout.NORTH)
45+
isOpaque = false
46+
}
47+
48+
private fun createAddButton(): JBLabel {
2749
val addButton = JBLabel(AllIcons.General.Add)
2850
addButton.cursor = Cursor(Cursor.HAND_CURSOR)
2951
addButton.toolTipText = "Add files to workspace"
@@ -32,15 +54,19 @@ class WorkspacePanel(
3254
addButton.isOpaque = true
3355
addButton.addMouseListener(object : MouseAdapter() {
3456
override fun mouseClicked(e: MouseEvent) {
35-
addFile()
57+
showFileSearchPopup(e.component)
3658
}
3759
})
38-
39-
filesPanel.isOpaque = false
40-
filesPanel.add(addButton)
41-
42-
add(filesPanel, BorderLayout.NORTH)
43-
isOpaque = false
60+
return addButton
61+
}
62+
63+
private fun showFileSearchPopup(component: Component) {
64+
val popup = FileSearchPopup(project) { files ->
65+
for (file in files) {
66+
addFileToWorkspace(file)
67+
}
68+
}
69+
popup.show(component)
4470
}
4571

4672
private fun addFile() {
@@ -65,19 +91,7 @@ class WorkspacePanel(
6591

6692
private fun updateFilesPanel() {
6793
filesPanel.removeAll()
68-
69-
val addButton = JBLabel(AllIcons.General.Add)
70-
addButton.cursor = Cursor(Cursor.HAND_CURSOR)
71-
addButton.toolTipText = "Add files to workspace"
72-
addButton.border = JBUI.Borders.empty(2, 4)
73-
addButton.background = JBColor(0xEDF4FE, 0x313741)
74-
addButton.isOpaque = true
75-
addButton.addMouseListener(object : MouseAdapter() {
76-
override fun mouseClicked(e: MouseEvent) {
77-
addFile()
78-
}
79-
})
80-
filesPanel.add(addButton)
94+
filesPanel.add(createAddButton())
8195

8296
for (filePresentation in workspaceFiles) {
8397
val fileLabel = FileItemPanel(project, filePresentation) {
@@ -111,6 +125,147 @@ class WorkspacePanel(
111125
}
112126
}
113127

128+
class FileSearchPopup(
129+
private val project: Project,
130+
private val onFilesSelected: (List<VirtualFile>) -> Unit
131+
) {
132+
private var popup: JBPopup? = null
133+
private val fileListModel = DefaultListModel<FileItem>()
134+
private val fileList = JList(fileListModel)
135+
private val searchField = JTextField()
136+
private val contentPanel = JPanel(BorderLayout())
137+
private val allProjectFiles = mutableListOf<FileItem>()
138+
139+
init {
140+
loadProjectFiles()
141+
setupUI()
142+
}
143+
144+
private fun loadProjectFiles() {
145+
val projectRootManager = ProjectRootManager.getInstance(project)
146+
val roots = projectRootManager.contentRoots
147+
148+
roots.forEach { root ->
149+
VfsUtil.collectChildrenRecursively(root).forEach { file ->
150+
if (!file.isDirectory) {
151+
allProjectFiles.add(FileItem(file))
152+
}
153+
}
154+
}
155+
156+
updateFileList("")
157+
}
158+
159+
private fun setupUI() {
160+
// Setup search field
161+
searchField.document.addDocumentListener(object : DocumentListener {
162+
override fun insertUpdate(e: DocumentEvent) = updateSearch()
163+
override fun removeUpdate(e: DocumentEvent) = updateSearch()
164+
override fun changedUpdate(e: DocumentEvent) = updateSearch()
165+
166+
private fun updateSearch() {
167+
updateFileList(searchField.text)
168+
}
169+
})
170+
171+
// Setup file list
172+
fileList.cellRenderer = FileListCellRenderer()
173+
fileList.addMouseListener(object : MouseAdapter() {
174+
override fun mouseClicked(e: MouseEvent) {
175+
if (e.clickCount == 2) {
176+
val selectedFiles = fileList.selectedValuesList.map { it.file }
177+
if (selectedFiles.isNotEmpty()) {
178+
onFilesSelected(selectedFiles)
179+
popup?.cancel()
180+
}
181+
}
182+
}
183+
})
184+
185+
// Layout components
186+
contentPanel.add(searchField, BorderLayout.NORTH)
187+
contentPanel.add(JScrollPane(fileList), BorderLayout.CENTER)
188+
contentPanel.preferredSize = Dimension(400, 300)
189+
}
190+
191+
private fun updateFileList(searchText: String) {
192+
fileListModel.clear()
193+
194+
val filteredFiles = if (searchText.isBlank()) {
195+
allProjectFiles
196+
} else {
197+
allProjectFiles.filter { item ->
198+
item.file.name.contains(searchText, ignoreCase = true) ||
199+
item.file.path.contains(searchText, ignoreCase = true)
200+
}
201+
}
202+
203+
filteredFiles.forEach { fileListModel.addElement(it) }
204+
}
205+
206+
fun show(component: Component) {
207+
popup = JBPopupFactory.getInstance()
208+
.createComponentPopupBuilder(contentPanel, searchField)
209+
.setTitle("Search Files")
210+
.setMovable(true)
211+
.setResizable(true)
212+
.setRequestFocus(true)
213+
.createPopup()
214+
215+
popup?.showUnderneathOf(component)
216+
}
217+
218+
data class FileItem(val file: VirtualFile) {
219+
val icon = file.fileType.icon
220+
val name = file.name
221+
val path = file.path
222+
}
223+
224+
class FileListCellRenderer : ListCellRenderer<FileItem> {
225+
private val noBorderFocus = BorderFactory.createEmptyBorder(1, 1, 1, 1)
226+
227+
@NotNull
228+
override fun getListCellRendererComponent(
229+
list: JList<out FileItem>?,
230+
value: FileItem?,
231+
index: Int,
232+
isSelected: Boolean,
233+
cellHasFocus: Boolean
234+
): Component {
235+
val panel = JPanel(BorderLayout())
236+
value?.let {
237+
val fileLabel = JBLabel(it.name, it.icon, JBLabel.LEFT)
238+
val pathLabel = JBLabel(it.path, JBLabel.LEFT)
239+
pathLabel.font = UIUtil.getFont(UIUtil.FontSize.SMALL, pathLabel.font)
240+
pathLabel.foreground = UIUtil.getContextHelpForeground()
241+
242+
val infoPanel = JPanel(BorderLayout())
243+
infoPanel.add(fileLabel, BorderLayout.NORTH)
244+
infoPanel.add(pathLabel, BorderLayout.SOUTH)
245+
infoPanel.isOpaque = false
246+
247+
panel.add(infoPanel, BorderLayout.CENTER)
248+
249+
if (isSelected) {
250+
panel.background = list?.selectionBackground
251+
panel.foreground = list?.selectionForeground
252+
} else {
253+
panel.background = list?.background
254+
panel.foreground = list?.foreground
255+
}
256+
257+
panel.border = if (cellHasFocus) {
258+
UIManager.getBorder("List.focusCellHighlightBorder") ?: noBorderFocus
259+
} else {
260+
noBorderFocus
261+
}
262+
}
263+
264+
return panel
265+
}
266+
}
267+
}
268+
114269
class FileItemPanel(
115270
private val project: Project,
116271
private val filePresentation: FilePresentation,
@@ -227,4 +382,3 @@ class WrapLayout : FlowLayout {
227382
}
228383
}
229384
}
230-

0 commit comments

Comments
 (0)