Skip to content

Commit dcb4f48

Browse files
committed
feat(compiler): add background task for directory processing
- Introduce `ProgressManager` and `Task.Backgroundable` for async directory processing. - Add `isHashJson` method to filter hashed JSON files. - Refactor `execute` method to use `CompletableFuture` for async execution.
1 parent 6098ee7 commit dcb4f48

File tree

1 file changed

+97
-15
lines changed
  • exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/exec

1 file changed

+97
-15
lines changed

exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/exec/DirInsCommand.kt

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import cc.unitmesh.devti.devin.InsCommand
44
import cc.unitmesh.devti.devin.dataprovider.BuiltinCommand
55
import cc.unitmesh.devti.language.utils.lookupFile
66
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
711
import com.intellij.openapi.project.Project
812
import com.intellij.openapi.util.text.StringUtilRt
913
import com.intellij.openapi.vcs.FileStatus
1014
import com.intellij.openapi.vcs.FileStatusManager
15+
import com.intellij.openapi.vfs.VirtualFile
1116
import com.intellij.psi.PsiDirectory
1217
import com.intellij.psi.PsiFile
1318
import com.intellij.psi.PsiManager
19+
import java.util.concurrent.CompletableFuture
20+
import java.util.regex.Pattern
1421

1522

1623
/**
@@ -50,6 +57,80 @@ import com.intellij.psi.PsiManager
5057
*/
5158
class DirInsCommand(private val myProject: Project, private val dir: String) : InsCommand {
5259
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+
53134
private val defaultMaxDepth = 2
54135

55136
// 定义表示目录树节点的数据模型
@@ -71,16 +152,16 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
71152

72153
// 压缩目录节点,用于显示多个同层次目录
73154
data class CompressedNode(override val name: String, val subdirNames: List<String>) : TreeNode()
74-
155+
75156
// 并列简单目录节点,用于像 component/{col,row,tag}/src 这样的结构
76157
data class ParallelDirsNode(
77-
override val name: String,
78-
val dirNames: List<String>,
158+
override val name: String,
159+
val dirNames: List<String>,
79160
val commonChildName: String
80161
) : TreeNode()
81162
}
82163

83-
override suspend fun execute(): String? {
164+
suspend fun executeDepth(): String? {
84165
val virtualFile = myProject.lookupFile(dir) ?: return "File not found: $dir"
85166
val psiDirectory = PsiManager.getInstance(myProject).findDirectory(virtualFile) ?: return null
86167

@@ -121,10 +202,10 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
121202
val parallelDirsNode = detectParallelSimpleDirs(project, subdirectories)
122203
if (parallelDirsNode != null) {
123204
dirNode.addChild(parallelDirsNode)
124-
205+
125206
// 添加那些不符合并列模式的其他子目录
126207
processRemainingDirs(project, subdirectories, parallelDirsNode.dirNames, dirNode, depth)
127-
208+
128209
return dirNode
129210
}
130211

@@ -146,7 +227,7 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
146227

147228
return dirNode
148229
}
149-
230+
150231
/**
151232
* 处理剩余的不符合并列目录模式的子目录
152233
*/
@@ -170,10 +251,10 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
170251
*/
171252
private fun detectParallelSimpleDirs(project: Project, subdirs: List<PsiDirectory>): TreeNode.ParallelDirsNode? {
172253
if (subdirs.size < 2) return null
173-
254+
174255
// 收集有相同子目录结构的目录组
175256
val dirGroups = mutableMapOf<String, MutableList<PsiDirectory>>()
176-
257+
177258
// 对每个目录,检查它是否有单一子目录,如果有,记录子目录名
178259
subdirs.forEach { dir ->
179260
val nonExcludedChildren = dir.subdirectories.filter { !isExcluded(project, it) }
@@ -182,26 +263,27 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
182263
dirGroups.getOrPut(childName) { mutableListOf() }.add(dir)
183264
}
184265
}
185-
266+
186267
// 找出最大的组(具有相同子目录名的父目录组)
187268
val largestGroup = dirGroups.maxByOrNull { it.value.size }
188-
269+
189270
// 如果最大组至少有2个目录且子目录名不为空,则创建并列目录节点
190271
if (largestGroup != null && largestGroup.value.size >= 2 && largestGroup.key.isNotEmpty()) {
191272
val commonChildName = largestGroup.key
192273
val parentDirNames = largestGroup.value.map { it.name }
193-
274+
194275
return TreeNode.ParallelDirsNode("parallelDirs", parentDirNames, commonChildName)
195276
}
196-
277+
197278
return null
198279
}
199280

200281
/**
201282
* 判断是否应该压缩显示子目录
202283
*/
203284
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 {
205287
// 深度超过阈值且有多个子目录时考虑压缩
206288
return depth > defaultMaxDepth + 1 && subdirectories.size > 1 &&
207289
// 确保这些子目录大多是叶子节点或近似叶子节点
@@ -246,7 +328,7 @@ class DirInsCommand(private val myProject: Project, private val dir: String) : I
246328
is TreeNode.CompressedNode -> {
247329
output.appendLine("$indent$prefix── {${child.subdirNames.joinToString(",")}}/")
248330
}
249-
331+
250332
is TreeNode.ParallelDirsNode -> {
251333
// 以更紧凑的格式显示并列目录结构
252334
val dirs = child.dirNames.sorted().joinToString(",")

0 commit comments

Comments
 (0)