@@ -9,6 +9,7 @@ import com.intellij.openapi.util.text.StringUtilRt
9
9
import com.intellij.openapi.vcs.FileStatus
10
10
import com.intellij.openapi.vcs.FileStatusManager
11
11
import com.intellij.psi.PsiDirectory
12
+ import com.intellij.psi.PsiFile
12
13
import com.intellij.psi.PsiManager
13
14
14
15
@@ -50,101 +51,147 @@ import com.intellij.psi.PsiManager
50
51
class DirInsCommand (private val myProject : Project , private val dir : String ) : InsCommand {
51
52
override val commandName: BuiltinCommand = BuiltinCommand .DIR
52
53
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
+ }
54
75
55
76
override suspend fun execute (): String? {
56
77
val virtualFile = myProject.lookupFile(dir) ? : return " File not found: $dir "
57
78
val psiDirectory = PsiManager .getInstance(myProject).findDirectory(virtualFile) ? : return null
58
79
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
+ }
61
90
62
91
return output.toString()
63
92
}
64
93
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)
70
101
71
- // 只在深度不超过默认最大深度时显示文件
102
+ // 添加文件节点(受深度限制)
72
103
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))
78
107
}
79
108
}
80
109
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 }))
89
119
}
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)
105
125
}
106
126
}
107
127
}
108
128
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
122
130
}
123
131
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
+ }
131
144
}
132
145
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
+ }
140
187
}
141
188
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
148
195
149
196
val status = FileStatusManager .getInstance(project).getStatus(directory.virtualFile)
150
197
return status == FileStatus .IGNORED
0 commit comments