@@ -4,13 +4,20 @@ import cc.unitmesh.devti.devin.InsCommand
4
4
import cc.unitmesh.devti.devin.dataprovider.BuiltinCommand
5
5
import cc.unitmesh.devti.language.utils.lookupFile
6
6
import com.intellij.openapi.application.runReadAction
7
+ import com.intellij.openapi.progress.ProgressIndicator
8
+ import com.intellij.openapi.progress.ProgressManager
9
+ import com.intellij.openapi.progress.Task
10
+ import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator
7
11
import com.intellij.openapi.project.Project
8
12
import com.intellij.openapi.util.text.StringUtilRt
9
13
import com.intellij.openapi.vcs.FileStatus
10
14
import com.intellij.openapi.vcs.FileStatusManager
15
+ import com.intellij.openapi.vfs.VirtualFile
11
16
import com.intellij.psi.PsiDirectory
12
17
import com.intellij.psi.PsiFile
13
18
import com.intellij.psi.PsiManager
19
+ import java.util.concurrent.CompletableFuture
20
+ import java.util.regex.Pattern
14
21
15
22
16
23
/* *
@@ -50,6 +57,80 @@ import com.intellij.psi.PsiManager
50
57
*/
51
58
class DirInsCommand (private val myProject : Project , private val dir : String ) : InsCommand {
52
59
override val commandName: BuiltinCommand = BuiltinCommand .DIR
60
+ private val HASH_FILE_PATTERN : Pattern = Pattern .compile(
61
+ " ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:\\ .json|@[0-9a-f]+\\ .json)$" ,
62
+ Pattern .CASE_INSENSITIVE
63
+ )
64
+
65
+ fun isHashJson (file : VirtualFile ? ): Boolean {
66
+ return file != null && HASH_FILE_PATTERN .matcher(file.name).matches()
67
+ }
68
+
69
+ private val output = StringBuilder ()
70
+
71
+ override suspend fun execute (): String? {
72
+ val virtualFile = myProject.lookupFile(dir) ? : return " File not found: $dir "
73
+ val future = CompletableFuture <String >()
74
+ val task = object : Task .Backgroundable (myProject, " Processing context" , false ) {
75
+ override fun run (indicator : ProgressIndicator ) {
76
+ val psiDirectory = runReadAction {
77
+ PsiManager .getInstance(myProject!! ).findDirectory(virtualFile)
78
+ }
79
+
80
+ if (psiDirectory == null ) {
81
+ future.complete(" Directory not found: $dir " )
82
+ return
83
+ }
84
+
85
+ output.appendLine(" $dir /" )
86
+ runReadAction { listDirectory(myProject!! , psiDirectory, 1 ) }
87
+ future.complete(output.toString())
88
+ }
89
+ }
90
+
91
+ ProgressManager .getInstance()
92
+ .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator (task))
93
+
94
+ return future.get()
95
+ }
96
+
97
+ private fun listDirectory (project : Project , directory : PsiDirectory , depth : Int ) {
98
+ if (isExclude(project, directory)) return
99
+
100
+ val files = directory.files
101
+ val subdirectories = directory.subdirectories
102
+
103
+ for ((index, file) in files.withIndex()) {
104
+ // / skip binary files? ignore hashed file names, like `f5086740-a1a1-491b-82c9-ab065a9d1754.json`
105
+ if (file.fileType.isBinary) continue
106
+ if (isHashJson(file.virtualFile)) continue
107
+
108
+ if (index == files.size - 1 ) {
109
+ output.appendLine(" ${" " .repeat(depth)} └── ${file.name} " )
110
+ } else {
111
+ output.appendLine(" ${" " .repeat(depth)} ├── ${file.name} " )
112
+ }
113
+ }
114
+
115
+ for ((index, subdirectory) in subdirectories.withIndex()) {
116
+ if (isExclude(project, directory)) continue
117
+
118
+ if (index == subdirectories.size - 1 ) {
119
+ output.appendLine(" ${" " .repeat(depth)} └── ${subdirectory.name} /" )
120
+ } else {
121
+ output.appendLine(" ${" " .repeat(depth)} ├── ${subdirectory.name} /" )
122
+ }
123
+ listDirectory(project, subdirectory, depth + 1 )
124
+ }
125
+ }
126
+
127
+ private fun isExclude (project : Project , directory : PsiDirectory ): Boolean {
128
+ if (directory.name == " .idea" ) return true
129
+
130
+ val status = FileStatusManager .getInstance(project).getStatus(directory.virtualFile)
131
+ return status == FileStatus .IGNORED
132
+ }
133
+
53
134
private val defaultMaxDepth = 2
54
135
55
136
// 定义表示目录树节点的数据模型
@@ -71,16 +152,16 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
71
152
72
153
// 压缩目录节点,用于显示多个同层次目录
73
154
data class CompressedNode (override val name : String , val subdirNames : List <String >) : TreeNode()
74
-
155
+
75
156
// 并列简单目录节点,用于像 component/{col,row,tag}/src 这样的结构
76
157
data class ParallelDirsNode (
77
- override val name : String ,
78
- val dirNames : List <String >,
158
+ override val name : String ,
159
+ val dirNames : List <String >,
79
160
val commonChildName : String
80
161
) : TreeNode()
81
162
}
82
163
83
- override suspend fun execute (): String? {
164
+ suspend fun executeDepth (): String? {
84
165
val virtualFile = myProject.lookupFile(dir) ? : return " File not found: $dir "
85
166
val psiDirectory = PsiManager .getInstance(myProject).findDirectory(virtualFile) ? : return null
86
167
@@ -121,10 +202,10 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
121
202
val parallelDirsNode = detectParallelSimpleDirs(project, subdirectories)
122
203
if (parallelDirsNode != null ) {
123
204
dirNode.addChild(parallelDirsNode)
124
-
205
+
125
206
// 添加那些不符合并列模式的其他子目录
126
207
processRemainingDirs(project, subdirectories, parallelDirsNode.dirNames, dirNode, depth)
127
-
208
+
128
209
return dirNode
129
210
}
130
211
@@ -146,7 +227,7 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
146
227
147
228
return dirNode
148
229
}
149
-
230
+
150
231
/* *
151
232
* 处理剩余的不符合并列目录模式的子目录
152
233
*/
@@ -170,10 +251,10 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
170
251
*/
171
252
private fun detectParallelSimpleDirs (project : Project , subdirs : List <PsiDirectory >): TreeNode .ParallelDirsNode ? {
172
253
if (subdirs.size < 2 ) return null
173
-
254
+
174
255
// 收集有相同子目录结构的目录组
175
256
val dirGroups = mutableMapOf<String , MutableList <PsiDirectory >>()
176
-
257
+
177
258
// 对每个目录,检查它是否有单一子目录,如果有,记录子目录名
178
259
subdirs.forEach { dir ->
179
260
val nonExcludedChildren = dir.subdirectories.filter { ! isExcluded(project, it) }
@@ -182,26 +263,27 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
182
263
dirGroups.getOrPut(childName) { mutableListOf () }.add(dir)
183
264
}
184
265
}
185
-
266
+
186
267
// 找出最大的组(具有相同子目录名的父目录组)
187
268
val largestGroup = dirGroups.maxByOrNull { it.value.size }
188
-
269
+
189
270
// 如果最大组至少有2个目录且子目录名不为空,则创建并列目录节点
190
271
if (largestGroup != null && largestGroup.value.size >= 2 && largestGroup.key.isNotEmpty()) {
191
272
val commonChildName = largestGroup.key
192
273
val parentDirNames = largestGroup.value.map { it.name }
193
-
274
+
194
275
return TreeNode .ParallelDirsNode (" parallelDirs" , parentDirNames, commonChildName)
195
276
}
196
-
277
+
197
278
return null
198
279
}
199
280
200
281
/* *
201
282
* 判断是否应该压缩显示子目录
202
283
*/
203
284
private fun shouldCompressSubdirectories (
204
- project : Project , directory : PsiDirectory , subdirectories : List <PsiDirectory >, depth : Int ): Boolean {
285
+ project : Project , directory : PsiDirectory , subdirectories : List <PsiDirectory >, depth : Int
286
+ ): Boolean {
205
287
// 深度超过阈值且有多个子目录时考虑压缩
206
288
return depth > defaultMaxDepth + 1 && subdirectories.size > 1 &&
207
289
// 确保这些子目录大多是叶子节点或近似叶子节点
@@ -246,7 +328,7 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
246
328
is TreeNode .CompressedNode -> {
247
329
output.appendLine(" $indent$prefix ── {${child.subdirNames.joinToString(" ," )} }/" )
248
330
}
249
-
331
+
250
332
is TreeNode .ParallelDirsNode -> {
251
333
// 以更紧凑的格式显示并列目录结构
252
334
val dirs = child.dirNames.sorted().joinToString(" ," )
0 commit comments