Skip to content

Commit 1bcbc79

Browse files
committed
refactor(compiler): restructure directory tree rendering logic #308
- Introduce a sealed class `TreeNode` to model directory tree nodes. - Split directory listing logic into separate methods for building and rendering the tree. - Add support for compressed directory display and improve exclusion handling.
1 parent 0bdb5e6 commit 1bcbc79

File tree

1 file changed

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

1 file changed

+117
-70
lines changed

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

Lines changed: 117 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.text.StringUtilRt
99
import com.intellij.openapi.vcs.FileStatus
1010
import com.intellij.openapi.vcs.FileStatusManager
1111
import com.intellij.psi.PsiDirectory
12+
import com.intellij.psi.PsiFile
1213
import com.intellij.psi.PsiManager
1314

1415

@@ -50,101 +51,147 @@ import com.intellij.psi.PsiManager
5051
class DirInsCommand(private val myProject: Project, private val dir: String) : InsCommand {
5152
override val commandName: BuiltinCommand = BuiltinCommand.DIR
5253
private val defaultMaxDepth = 2
53-
private val output = StringBuilder()
54+
55+
// 定义表示目录树节点的数据模型
56+
private sealed class TreeNode {
57+
abstract val name: String
58+
59+
// 文件节点,包含文件大小信息
60+
data class FileNode(override val name: String, val size: String?) : TreeNode()
61+
62+
// 目录节点,包含子节点列表
63+
data class DirectoryNode(
64+
override val name: String, val children: MutableList<TreeNode> = mutableListOf()
65+
) : TreeNode() {
66+
// 添加子节点的便捷方法
67+
fun addChild(child: TreeNode) {
68+
children.add(child)
69+
}
70+
}
71+
72+
// 压缩目录节点,用于显示多个同层次目录
73+
data class CompressedNode(override val name: String, val subdirNames: List<String>) : TreeNode()
74+
}
5475

5576
override suspend fun execute(): String? {
5677
val virtualFile = myProject.lookupFile(dir) ?: return "File not found: $dir"
5778
val psiDirectory = PsiManager.getInstance(myProject).findDirectory(virtualFile) ?: return null
5879

59-
output.appendLine("$dir/")
60-
runReadAction { listDirectory(myProject, psiDirectory, 1) }
80+
// 第一步:构建目录树模型
81+
val rootNode = runReadAction {
82+
buildDirectoryTree(myProject, psiDirectory, 1)
83+
} ?: return null
84+
85+
// 第二步:将树模型转换为文本表示
86+
val output = StringBuilder().apply {
87+
appendLine("$dir/")
88+
renderTree(rootNode, 1, this)
89+
}
6190

6291
return output.toString()
6392
}
6493

65-
private fun listDirectory(project: Project, directory: PsiDirectory, depth: Int) {
66-
if(isExclude(project, directory)) return
67-
68-
val files = directory.files
69-
val subdirectories = directory.subdirectories.filter { !isExclude(project, it) }.toList()
94+
/**
95+
* 构建目录树的数据模型
96+
*/
97+
private fun buildDirectoryTree(project: Project, directory: PsiDirectory, depth: Int): TreeNode.DirectoryNode? {
98+
if (isExcluded(project, directory)) return null
99+
100+
val dirNode = TreeNode.DirectoryNode(directory.name)
70101

71-
// 只在深度不超过默认最大深度时显示文件
102+
// 添加文件节点(受深度限制)
72103
if (depth <= defaultMaxDepth) {
73-
files.forEachIndexed { index, file ->
74-
val isLast = index == files.lastIndex && subdirectories.isEmpty()
75-
val prefix = if (isLast) "" else ""
76-
val size = StringUtilRt.formatFileSize(file.virtualFile.length)
77-
output.appendLine("${" ".repeat(depth)}$prefix── ${file.name}${size?.let { " ($it)" } ?: ""}")
104+
directory.files.forEach { file ->
105+
val fileSize = StringUtilRt.formatFileSize(file.virtualFile.length)
106+
dirNode.addChild(TreeNode.FileNode(file.name, fileSize))
78107
}
79108
}
80109

81-
// 如果子目录深度超过一定值,考虑压缩显示
82-
if (depth > defaultMaxDepth + 1) {
83-
// 检查是否所有子目录都已经达到最大深度可压缩显示
84-
val canCompressAllSubdirs = subdirectories.all {
85-
it.subdirectories.isNotEmpty() &&
86-
it.subdirectories.all { subdir ->
87-
!isExclude(project, subdir) && subdir.subdirectories.isEmpty()
88-
}
110+
// 添加目录节点
111+
val subdirectories = directory.subdirectories.filter { !isExcluded(project, it) }
112+
113+
// 检查是否应该压缩显示子目录
114+
if (shouldCompressSubdirectories(project, directory, subdirectories, depth)) {
115+
// 获取可以压缩的子目录列表
116+
val compressableSubdirs = getCompressableSubdirectories(subdirectories)
117+
if (compressableSubdirs.isNotEmpty()) {
118+
dirNode.addChild(TreeNode.CompressedNode("compressed", compressableSubdirs.map { it.name }))
89119
}
90-
91-
if (canCompressAllSubdirs && subdirectories.isNotEmpty()) {
92-
// 收集所有叶节点目录名
93-
val compressedNames = mutableListOf<String>()
94-
subdirectories.forEach { subdir ->
95-
val leafDirs = subdir.subdirectories.filter { !isExclude(project, it) }
96-
if (leafDirs.isNotEmpty()) {
97-
compressedNames.add(subdir.name)
98-
}
99-
}
100-
101-
if (compressedNames.isNotEmpty()) {
102-
val prefix = "" // 这里可以根据实际情况决定是否是最后一项
103-
output.appendLine("${" ".repeat(depth)}$prefix── {${compressedNames.joinToString(",")}}/")
104-
return // 不再递归显示更深层次
120+
} else {
121+
// 常规递归处理子目录
122+
subdirectories.forEach { subdir ->
123+
buildDirectoryTree(project, subdir, depth + 1)?.let { subdirNode ->
124+
dirNode.addChild(subdirNode)
105125
}
106126
}
107127
}
108128

109-
// 常规目录显示逻辑
110-
subdirectories.forEachIndexed { index, subdir ->
111-
val prefix = if (index == subdirectories.lastIndex) "" else ""
112-
output.appendLine("${" ".repeat(depth)}$prefix── ${subdir.name}/")
113-
114-
// 判断是否需要压缩显示子目录
115-
if (shouldCompressChildren(project, subdir, depth + 1)) {
116-
compressAndDisplayChildren(project, subdir, depth + 1)
117-
} else {
118-
// 继续递归,文件显示将受深度限制
119-
listDirectory(project, subdir, depth + 1)
120-
}
121-
}
129+
return dirNode
122130
}
123131

124-
private fun shouldCompressChildren(project: Project, directory: PsiDirectory, depth: Int): Boolean {
125-
// 当深度超过阈值且子目录结构符合压缩条件时
126-
if (depth > defaultMaxDepth + 1) {
127-
val subdirs = directory.subdirectories.filter { !isExclude(project, it) }
128-
return subdirs.size > 1 && subdirs.all { it.subdirectories.isEmpty() }
129-
}
130-
return false
132+
/**
133+
* 判断是否应该压缩显示子目录
134+
*/
135+
private fun shouldCompressSubdirectories(
136+
project: Project, directory: PsiDirectory, subdirectories: List<PsiDirectory>, depth: Int): Boolean {
137+
// 深度超过阈值且有多个子目录时考虑压缩
138+
return depth > defaultMaxDepth + 1 && subdirectories.size > 1 &&
139+
// 确保这些子目录大多是叶子节点或近似叶子节点
140+
subdirectories.all { subdir ->
141+
val childDirs = subdir.subdirectories.filter { !isExcluded(project, it) }
142+
childDirs.isEmpty() || childDirs.all { it.subdirectories.isEmpty() }
143+
}
131144
}
132145

133-
private fun compressAndDisplayChildren(project: Project, directory: PsiDirectory, depth: Int) {
134-
val subdirs = directory.subdirectories.filter { !isExclude(project, it) }
135-
if (subdirs.isEmpty()) return
136-
137-
val subdirNames = subdirs.map { it.name }
138-
val prefix = ""
139-
output.appendLine("${" ".repeat(depth)}$prefix── {${subdirNames.joinToString(",")}}/")
146+
/**
147+
* 获取可以压缩显示的子目录
148+
*/
149+
private fun getCompressableSubdirectories(subdirectories: List<PsiDirectory>): List<PsiDirectory> {
150+
// 这里可以添加更复杂的逻辑来决定哪些目录可以压缩
151+
return subdirectories
152+
}
153+
154+
/**
155+
* 将目录树渲染为文本输出
156+
*/
157+
private fun renderTree(node: TreeNode, depth: Int, output: StringBuilder) {
158+
val indent = " ".repeat(depth)
159+
160+
when (node) {
161+
is TreeNode.DirectoryNode -> {
162+
// 目录节点的子节点渲染
163+
node.children.forEachIndexed { index, child ->
164+
val isLast = index == node.children.lastIndex
165+
val prefix = if (isLast) "" else ""
166+
167+
when (child) {
168+
is TreeNode.FileNode -> {
169+
val sizeInfo = child.size?.let { " ($it)" } ?: ""
170+
output.appendLine("$indent$prefix── ${child.name}$sizeInfo")
171+
}
172+
173+
is TreeNode.DirectoryNode -> {
174+
output.appendLine("$indent$prefix── ${child.name}/")
175+
renderTree(child, depth + 1, output)
176+
}
177+
178+
is TreeNode.CompressedNode -> {
179+
output.appendLine("$indent$prefix── {${child.subdirNames.joinToString(",")}}/")
180+
}
181+
}
182+
}
183+
}
184+
185+
else -> {} // 其他类型节点在这里不需要单独处理
186+
}
140187
}
141188

142-
private fun isExclude(project: Project, directory: PsiDirectory): Boolean {
143-
if (directory.name == ".idea" ||
144-
directory.name == "build" ||
145-
directory.name == "target" ||
146-
directory.name == ".gradle" ||
147-
directory.name == "node_modules") return true
189+
/**
190+
* 判断目录是否应被排除
191+
*/
192+
private fun isExcluded(project: Project, directory: PsiDirectory): Boolean {
193+
val excludedDirs = setOf(".idea", "build", "target", ".gradle", "node_modules")
194+
if (directory.name in excludedDirs) return true
148195

149196
val status = FileStatusManager.getInstance(project).getStatus(directory.virtualFile)
150197
return status == FileStatus.IGNORED

0 commit comments

Comments
 (0)