Skip to content

Commit 8b05b45

Browse files
committed
feat(gui): implement workspace panel for file management #358
- Add WorkspacePanel component for managing workspace files - Integrate workspace functionality into existing components - Update AutoDevInput and AutoDevInputSection to support workspace - Modify InputFileToolbar to use workspace for file operations - Add new string resource for workspace files label JBColor.border() - Replace UIUtil
1 parent d48aedd commit 8b05b45

File tree

6 files changed

+210
-18
lines changed

6 files changed

+210
-18
lines changed

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ class AutoDevInput(
6060

6161
DumbAwareAction.create {
6262
val editor = editor ?: return@create
63-
// Insert a new line
6463
CommandProcessor.getInstance().executeCommand(project, {
6564
val eol = "\n"
6665
val document = editor.document
@@ -115,7 +114,6 @@ class AutoDevInput(
115114
editor.caretModel.moveToOffset(0)
116115
editor.scrollPane.setBorder(border)
117116
editor.contentComponent.setOpaque(false)
118-
119117
return editor
120118
}
121119

@@ -160,14 +158,10 @@ class AutoDevInput(
160158
}
161159

162160
fun appendText(text: String) {
163-
WriteCommandAction.runWriteCommandAction(
164-
project,
165-
"Append text",
166-
"intentions.write.action",
167-
{
168-
val document = this.editor?.document ?: return@runWriteCommandAction
169-
InsertUtil.insertStringAndSaveChange(project, text, document, document.textLength, false)
170-
})
161+
WriteCommandAction.runWriteCommandAction(project, "Append text", "intentions.write.action", {
162+
val document = this.editor?.document ?: return@runWriteCommandAction
163+
InsertUtil.insertStringAndSaveChange(project, text, document, document.textLength, false)
164+
})
171165
}
172166
}
173167

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
7070

7171
private val fileListViewModel = FileListViewModel(project)
7272
private val elementsList = JBList(fileListViewModel.getListModel())
73+
74+
private val workspacePanel: WorkspacePanel
7375

7476
private val defaultRag: CustomAgentConfig = CustomAgentConfig("<Select Custom Agent>", "Normal")
7577
private var customAgent: ComboBox<CustomAgentConfig> = ComboBox(MutableCollectionComboBoxModel(listOf()))
@@ -95,6 +97,8 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
9597

9698
init {
9799
input = AutoDevInput(project, listOf(), disposable, this)
100+
// Initialize WorkspacePanel
101+
workspacePanel = WorkspacePanel(project, input)
98102

99103
setupElementsList()
100104
val sendButtonPresentation = Presentation(AutoDevBundle.message("chat.panel.send"))
@@ -164,6 +168,9 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
164168

165169
inputPanel.add(input, BorderLayout.CENTER)
166170
inputPanel.addToBottom(layoutPanel)
171+
172+
// Add workspace panel to the input panel
173+
inputPanel.addToTop(workspacePanel)
167174

168175
val scrollPane = JBScrollPane(elementsList)
169176
scrollPane.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
@@ -248,7 +255,8 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
248255
val actionType = fileListViewModel.determineFileAction(wrapper, e.point, cellBounds)
249256
val actionPerformed = fileListViewModel.handleFileAction(wrapper, actionType) { vfile, relativePath ->
250257
if (relativePath != null) {
251-
input.appendText("\n/" + "file" + ":${relativePath}")
258+
workspacePanel.addFileToWorkspace(vfile)
259+
252260
ApplicationManager.getApplication().invokeLater {
253261
if (!vfile.isValid) return@invokeLater
254262
val psiFile = PsiManager.getInstance(project).findFile(vfile) ?: return@invokeLater
@@ -361,13 +369,15 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
361369
return customAgent.selectedItem as CustomAgentConfig
362370
}
363371

372+
// Reset workspace panel when resetting agent
364373
fun resetAgent() {
365374
(customAgent.selectedItem as? CustomAgentConfig)?.let {
366375
it.state = CustomAgentState.START
367376
}
368377

369378
customAgent.selectedItem = defaultRag
370379
text = ""
380+
workspacePanel.clear()
371381
}
372382

373383
fun moveCursorToStart() {

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

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import com.intellij.util.ui.JBUI
1010
import com.intellij.util.ui.UIUtil
1111
import javax.swing.Box
1212
import javax.swing.JToolBar
13+
import java.awt.Component
14+
import java.awt.Container
1315

1416
object InputFileToolbar {
1517
fun createToolbar(project: Project, viewModel: FileListViewModel, input: AutoDevInput): JToolBar {
@@ -23,15 +25,39 @@ object InputFileToolbar {
2325

2426
toolbar.add(Box.createHorizontalGlue())
2527

28+
val findWorkspacePanel: () -> WorkspacePanel? = lambda@{
29+
var component: Component? = input.parent
30+
while (component != null) {
31+
if (component is AutoDevInputSection) {
32+
val workspace = component.findComponentOfType(WorkspacePanel::class.java)
33+
if (workspace != null) {
34+
return@lambda workspace
35+
}
36+
}
37+
38+
component = component.parent
39+
}
40+
null
41+
}
42+
2643
val recentFiles = LinkLabel(AutoDevBundle.message("chat.panel.add.openFiles"), null) { _: LinkLabel<Unit>, _: Unit? ->
2744
val addedFiles = viewModel.addRecentlyOpenedFiles()
28-
val fileReferences = StringBuilder()
29-
addedFiles.forEach { vfile ->
30-
fileReferences.append("\n/file:${vfile.presentablePath}")
31-
}
3245

33-
if (fileReferences.isNotEmpty()) {
34-
input.appendText(fileReferences.toString())
46+
val workspace = findWorkspacePanel()
47+
if (workspace != null) {
48+
addedFiles.forEach { file ->
49+
workspace.addFileToWorkspace(file.virtualFile)
50+
}
51+
} else {
52+
// Fallback to original behavior
53+
val fileReferences = StringBuilder()
54+
addedFiles.forEach { vfile ->
55+
fileReferences.append("\n/file:${vfile.presentablePath}")
56+
}
57+
58+
if (fileReferences.isNotEmpty()) {
59+
input.appendText(fileReferences.toString())
60+
}
3561
}
3662
}
3763

@@ -41,6 +67,7 @@ object InputFileToolbar {
4167

4268
val clearAll = LinkLabel(AutoDevBundle.message("chat.panel.clear.all"), null) { _: LinkLabel<Unit>, _: Unit? ->
4369
viewModel.clearAllFiles()
70+
findWorkspacePanel()?.clear()
4471
}
4572

4673
clearAll.mediumFontFunction()
@@ -51,3 +78,22 @@ object InputFileToolbar {
5178
return toolbar
5279
}
5380
}
81+
82+
fun <T> Component.findComponentOfType(clazz: Class<T>): T? {
83+
if (clazz.isInstance(this)) {
84+
@Suppress("UNCHECKED_CAST")
85+
return this as T
86+
}
87+
88+
if (this is Container) {
89+
for (i in 0 until componentCount) {
90+
val component = getComponent(i)
91+
val result = component.findComponentOfType(clazz)
92+
if (result != null) {
93+
return result
94+
}
95+
}
96+
}
97+
98+
return null
99+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package cc.unitmesh.devti.gui.chat.ui
2+
3+
import cc.unitmesh.devti.AutoDevBundle
4+
import com.intellij.icons.AllIcons
5+
import com.intellij.openapi.fileChooser.FileChooser
6+
import com.intellij.openapi.fileChooser.FileChooserDescriptor
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.vfs.VirtualFile
9+
import com.intellij.ui.JBColor
10+
import com.intellij.ui.components.JBLabel
11+
import com.intellij.util.ui.JBUI
12+
import com.intellij.util.ui.UIUtil
13+
import java.awt.BorderLayout
14+
import java.awt.FlowLayout
15+
import java.awt.Font
16+
import java.awt.event.MouseAdapter
17+
import java.awt.event.MouseEvent
18+
import javax.swing.BorderFactory
19+
import javax.swing.BoxLayout
20+
import javax.swing.JPanel
21+
22+
class WorkspacePanel(
23+
private val project: Project,
24+
private val input: AutoDevInput
25+
) : JPanel(BorderLayout()) {
26+
private val workspaceFiles = mutableListOf<VirtualFile>()
27+
private val filesPanel = JPanel()
28+
29+
init {
30+
border = BorderFactory.createCompoundBorder(
31+
BorderFactory.createMatteBorder(1, 0, 0, 0, JBColor.border()),
32+
JBUI.Borders.empty(10)
33+
)
34+
35+
val workspaceLabel = JBLabel(AutoDevBundle.message("chat.panel.workspace.files", "Edit files in your workspace"))
36+
workspaceLabel.foreground = UIUtil.getContextHelpForeground()
37+
workspaceLabel.font = Font(workspaceLabel.font.family, Font.PLAIN, 12)
38+
39+
val addButton = JBLabel(AllIcons.General.Add)
40+
addButton.cursor = java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)
41+
addButton.toolTipText = "Add files to workspace"
42+
addButton.addMouseListener(object : MouseAdapter() {
43+
override fun mouseClicked(e: MouseEvent) {
44+
addFile()
45+
}
46+
})
47+
48+
filesPanel.layout = BoxLayout(filesPanel, BoxLayout.Y_AXIS)
49+
50+
val topPanel = JPanel(BorderLayout())
51+
topPanel.isOpaque = false
52+
topPanel.add(workspaceLabel, BorderLayout.WEST)
53+
topPanel.add(addButton, BorderLayout.EAST)
54+
55+
add(topPanel, BorderLayout.NORTH)
56+
add(filesPanel, BorderLayout.CENTER)
57+
isOpaque = false
58+
filesPanel.isOpaque = false
59+
}
60+
61+
private fun addFile() {
62+
val descriptor = FileChooserDescriptor(true, true, false, false, false, true)
63+
.withTitle("Select Files for Workspace")
64+
.withDescription("Choose files to add to your workspace")
65+
66+
FileChooser.chooseFiles(descriptor, project, null) { files ->
67+
for (file in files) {
68+
addFileToWorkspace(file)
69+
}
70+
}
71+
}
72+
73+
fun addFileToWorkspace(file: VirtualFile) {
74+
if (!workspaceFiles.contains(file)) {
75+
workspaceFiles.add(file)
76+
updateFilesPanel()
77+
78+
// Add file reference to the input (will be visible on submit)
79+
val relativePath = try {
80+
project.basePath?.let { basePath ->
81+
file.path.substringAfter(basePath).removePrefix("/")
82+
} ?: file.path
83+
} catch (e: Exception) {
84+
file.path
85+
}
86+
87+
input.appendText("\n/file:$relativePath")
88+
}
89+
}
90+
91+
private fun updateFilesPanel() {
92+
filesPanel.removeAll()
93+
94+
for (file in workspaceFiles) {
95+
val fileLabel = FileItemPanel(project, file) {
96+
removeFile(file)
97+
}
98+
filesPanel.add(fileLabel)
99+
}
100+
101+
filesPanel.revalidate()
102+
filesPanel.repaint()
103+
}
104+
105+
private fun removeFile(file: VirtualFile) {
106+
workspaceFiles.remove(file)
107+
updateFilesPanel()
108+
}
109+
110+
fun clear() {
111+
workspaceFiles.clear()
112+
updateFilesPanel()
113+
}
114+
}
115+
116+
class FileItemPanel(
117+
private val project: Project,
118+
private val file: VirtualFile,
119+
private val onRemove: () -> Unit
120+
) : JPanel(FlowLayout(FlowLayout.LEFT, 5, 2)) {
121+
122+
init {
123+
border = JBUI.Borders.empty(2)
124+
isOpaque = false
125+
126+
val icon = file.fileType.icon
127+
val fileLabel = JBLabel(file.name, icon, JBLabel.LEFT)
128+
129+
val removeLabel = JBLabel(AllIcons.Actions.Close)
130+
removeLabel.cursor = java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)
131+
removeLabel.addMouseListener(object : MouseAdapter() {
132+
override fun mouseClicked(e: MouseEvent) {
133+
onRemove()
134+
}
135+
})
136+
137+
add(fileLabel)
138+
add(removeLabel)
139+
}
140+
}

core/src/main/resources/messages/AutoDevBundle_en.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,4 @@ planner.task.status.in_progress=In Progress
216216
planner.task.status.todo=To Do
217217
planner.task.execute=Execute
218218
chat.panel.add.openFiles=Add all open files
219+
chat.panel.workspace.files=Workspace File

core/src/main/resources/messages/AutoDevBundle_zh.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,5 @@ planner.task.status.failed=已失败
207207
planner.task.status.in_progress=进行中
208208
planner.task.status.todo=待办
209209
planner.task.execute=执行
210-
chat.panel.add.openFiles=添加所有打开的文件
210+
chat.panel.add.openFiles=添加所有打开的文件
211+
chat.panel.workspace.files=Workspace File

0 commit comments

Comments
 (0)