Skip to content

Commit 1bb2822

Browse files
committed
refactor(devti): introduce FileListViewModel to manage file presentations #358
- Create FileListViewModel class to encapsulate file list management logic - Replace DefaultListModel with FileListViewModel in AutoDevInputSection and InputFileToolbar - Implement methods in FileListViewModel for adding/removing files and clearing the list - Add support for recently opened files in FileListViewModel - Update UI components to interact with FileListViewModel instead of DefaultListModel
1 parent 82b2987 commit 1bb2822

File tree

3 files changed

+113
-44
lines changed

3 files changed

+113
-44
lines changed

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

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cc.unitmesh.devti.AutoDevIcons
55
import cc.unitmesh.devti.settings.customize.customizeSetting
66
import cc.unitmesh.devti.agent.custom.model.CustomAgentConfig
77
import cc.unitmesh.devti.agent.custom.model.CustomAgentState
8+
import cc.unitmesh.devti.gui.chat.ui.viewmodel.FileListViewModel
89
import cc.unitmesh.devti.llms.tokenizer.Tokenizer
910
import cc.unitmesh.devti.llms.tokenizer.TokenizerFactory
1011
import cc.unitmesh.devti.provider.RelatedClassesProvider
@@ -72,8 +73,8 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
7273
private val buttonPanel = JPanel(CardLayout())
7374
private val inputPanel = BorderLayoutPanel()
7475

75-
private val listModel = DefaultListModel<FilePresentation>()
76-
private val elementsList = JBList(listModel)
76+
private val fileListViewModel = FileListViewModel(project)
77+
private val elementsList = JBList(fileListViewModel.getListModel())
7778

7879
private val defaultRag: CustomAgentConfig = CustomAgentConfig("<Select Custom Agent>", "Normal")
7980
private var customRag: ComboBox<CustomAgentConfig> = ComboBox(MutableCollectionComboBoxModel(listOf()))
@@ -173,7 +174,7 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
173174
scrollPane.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
174175
scrollPane.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
175176

176-
val toolbar = InputFileToolbar.createToolbar(project, this@AutoDevInputSection.listModel)
177+
val toolbar = InputFileToolbar.createToolbar(project, fileListViewModel)
177178

178179
val headerPanel = JPanel(BorderLayout())
179180
headerPanel.add(toolbar, BorderLayout.NORTH)
@@ -199,12 +200,7 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
199200

200201
val currentFile = FileEditorManager.getInstance(project).selectedFiles.firstOrNull()
201202
currentFile?.let {
202-
listModel.addIfAbsent(currentFile, first = true)
203-
val index = listModel.indexOf(currentFile)
204-
if (index != -1) {
205-
listModel.remove(index)
206-
listModel.insertElementAt(listModel.getElementAt(index), 0)
207-
}
203+
fileListViewModel.addFileIfAbsent(currentFile, first = true)
208204
}
209205
}
210206

@@ -215,7 +211,7 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
215211
override fun selectionChanged(event: FileEditorManagerEvent) {
216212
val file = event.newFile ?: return
217213
ApplicationManager.getApplication().invokeLater {
218-
listModel.addIfAbsent(file, true)
214+
fileListViewModel.addFileIfAbsent(file, true)
219215
}
220216
}
221217
}
@@ -251,20 +247,19 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
251247
val index = list.locationToIndex(e.point)
252248
if (index == -1) return
253249

254-
val wrapper = listModel.getElementAt(index)
250+
val wrapper = fileListViewModel.getListModel().getElementAt(index)
255251
val cellBounds = list.getCellBounds(index, index)
256252
wrapper.panel?.components?.firstOrNull { it.contains(e.x - cellBounds.x - it.x, it.height - 1) }
257253
?.let { component ->
258254
when {
259255
component is JPanel -> {
260-
listModel.removeElement(wrapper)
256+
fileListViewModel.removeFile(wrapper)
261257
val vfile = wrapper.virtualFile
262258
val relativePath = vfile.path.substringAfter(project.basePath!!).removePrefix("/")
263-
listModel.addIfAbsent(vfile)
259+
fileListViewModel.addFileIfAbsent(vfile)
264260

265261
input.appendText("\n/" + "file" + ":${relativePath}")
266-
listModel.indexOf(wrapper.virtualFile).takeIf { it != -1 }
267-
?.let { listModel.remove(it) }
262+
fileListViewModel.removeFileByVirtualFile(wrapper.virtualFile)
268263

269264
ApplicationManager.getApplication().invokeLater {
270265
if (!vfile.isValid) return@invokeLater
@@ -275,17 +270,15 @@ class AutoDevInputSection(private val project: Project, val disposable: Disposab
275270
}
276271
}
277272

278-
component is JLabel && component.icon == AllIcons.Actions.Close -> listModel.removeElement(
279-
wrapper
280-
)
273+
component is JLabel && component.icon == AllIcons.Actions.Close -> fileListViewModel.removeFile(wrapper)
281274

282275
else -> list.clearSelection()
283276
}
284277
} ?: list.clearSelection()
285278
}
286279

287280
private fun updateElements(elements: List<PsiElement>?) {
288-
elements?.forEach { listModel.addIfAbsent(it.containingFile.virtualFile) }
281+
elements?.forEach { fileListViewModel.addFileIfAbsent(it.containingFile.virtualFile) }
289282
}
290283

291284
fun showStopButton() {
@@ -432,5 +425,3 @@ fun JComponent.mediumFontFunction() {
432425
}
433426
putClientProperty(FONT_KEY, f)
434427
}
435-
436-
Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package cc.unitmesh.devti.gui.chat.ui
22

33
import cc.unitmesh.devti.AutoDevBundle
4-
import com.intellij.openapi.fileEditor.FileEditorManager
4+
import cc.unitmesh.devti.gui.chat.ui.viewmodel.FileListViewModel
55
import com.intellij.openapi.project.Project
66
import com.intellij.ui.components.JBLabel
77
import com.intellij.ui.components.labels.LinkLabel
88
import com.intellij.util.ui.JBUI
99
import com.intellij.util.ui.UIUtil
1010
import javax.swing.Box
11-
import javax.swing.DefaultListModel
1211
import javax.swing.JToolBar
1312

1413
object InputFileToolbar {
15-
fun createToolbar(project: Project, model: DefaultListModel<FilePresentation>): JToolBar {
14+
fun createToolbar(project: Project, viewModel: FileListViewModel): JToolBar {
1615
val toolbar = JToolBar()
1716
toolbar.isFloatable = false
1817

@@ -24,20 +23,15 @@ object InputFileToolbar {
2423
toolbar.add(Box.createHorizontalGlue())
2524

2625
val recentFiles = LinkLabel(AutoDevBundle.message("chat.panel.add.openFiles"), null) { _: LinkLabel<Unit>, _: Unit? ->
27-
val files = getRecentlyOpenedFiles(project)
28-
files.forEach { file ->
29-
if (model.elements().asSequence().none { it.virtualFile == file.virtualFile }) {
30-
model.addElement(file)
31-
}
32-
}
26+
viewModel.addRecentlyOpenedFiles()
3327
}
3428

3529
recentFiles.mediumFontFunction()
3630
recentFiles.border = JBUI.Borders.emptyRight(10)
3731
toolbar.add(recentFiles)
3832

3933
val clearAll = LinkLabel(AutoDevBundle.message("chat.panel.clear.all"), null) { _: LinkLabel<Unit>, _: Unit? ->
40-
model.removeAllElements()
34+
viewModel.clearAllFiles()
4135
}
4236

4337
clearAll.mediumFontFunction()
@@ -47,17 +41,4 @@ object InputFileToolbar {
4741

4842
return toolbar
4943
}
50-
51-
private const val DEFAULT_FILE_LIMIT = 12
52-
53-
fun getRecentlyOpenedFiles(
54-
project: Project,
55-
maxFiles: Int = DEFAULT_FILE_LIMIT
56-
): List<FilePresentation> {
57-
val fileEditorManager = FileEditorManager.getInstance(project)
58-
return fileEditorManager.openFiles
59-
.take(maxFiles)
60-
.map { FilePresentation.from(project, it) }
61-
.toMutableList()
62-
}
6344
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package cc.unitmesh.devti.gui.chat.ui.viewmodel
2+
3+
import cc.unitmesh.devti.gui.chat.ui.FilePresentation
4+
import cc.unitmesh.devti.util.isInProject
5+
import com.intellij.diff.editor.DiffVirtualFileBase
6+
import com.intellij.openapi.Disposable
7+
import com.intellij.openapi.fileEditor.FileEditorManager
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.vfs.VirtualFile
10+
import java.util.EventListener
11+
import javax.swing.DefaultListModel
12+
13+
/**
14+
* ViewModel for managing the list of file presentations.
15+
*/
16+
class FileListViewModel(private val project: Project) : Disposable {
17+
private val listModel = DefaultListModel<FilePresentation>()
18+
19+
interface FileListChangeListener : EventListener {
20+
fun onFileAdded(file: FilePresentation)
21+
fun onFileRemoved(file: FilePresentation)
22+
fun onListCleared()
23+
}
24+
25+
private val listeners = mutableListOf<FileListChangeListener>()
26+
27+
fun addChangeListener(listener: FileListChangeListener) {
28+
listeners.add(listener)
29+
}
30+
31+
fun removeChangeListener(listener: FileListChangeListener) {
32+
listeners.remove(listener)
33+
}
34+
35+
fun getListModel(): DefaultListModel<FilePresentation> = listModel
36+
37+
fun addFileIfAbsent(vfile: VirtualFile, first: Boolean = false) {
38+
if (!vfile.isValid || vfile.fileType.isBinary) return
39+
if (!isInProject(vfile, project)) return
40+
if (vfile is DiffVirtualFileBase) return
41+
42+
if (listModel.elements().asSequence().none { it.virtualFile == vfile }) {
43+
val filePresentation = FilePresentation(vfile)
44+
if (first) {
45+
listModel.insertElementAt(filePresentation, 0)
46+
} else {
47+
listModel.addElement(filePresentation)
48+
}
49+
50+
listeners.forEach { it.onFileAdded(filePresentation) }
51+
}
52+
}
53+
54+
fun removeFile(file: FilePresentation) {
55+
if (listModel.contains(file)) {
56+
listModel.removeElement(file)
57+
listeners.forEach { it.onFileRemoved(file) }
58+
}
59+
}
60+
61+
fun removeFileByVirtualFile(vfile: VirtualFile) {
62+
val filePresentation = listModel.elements().asSequence()
63+
.find { it.virtualFile == vfile }
64+
65+
filePresentation?.let {
66+
listModel.removeElement(it)
67+
listeners.forEach { listener -> listener.onFileRemoved(it) }
68+
}
69+
}
70+
71+
fun clearAllFiles() {
72+
listModel.removeAllElements()
73+
listeners.forEach { it.onListCleared() }
74+
}
75+
76+
fun getRecentlyOpenedFiles(maxFiles: Int = 12): List<FilePresentation> {
77+
val fileEditorManager = FileEditorManager.getInstance(project)
78+
return fileEditorManager.openFiles
79+
.take(maxFiles)
80+
.map { FilePresentation.from(project, it) }
81+
.toMutableList()
82+
}
83+
84+
fun addRecentlyOpenedFiles() {
85+
val files = getRecentlyOpenedFiles()
86+
files.forEach { file ->
87+
if (listModel.elements().asSequence().none { it.virtualFile == file.virtualFile }) {
88+
listModel.addElement(file)
89+
listeners.forEach { it.onFileAdded(file) }
90+
}
91+
}
92+
}
93+
94+
override fun dispose() {
95+
listeners.clear()
96+
}
97+
}

0 commit comments

Comments
 (0)