@@ -2,16 +2,16 @@ package org.jetbrains.plugins.scala.projectView
22
33import com .intellij .ide .projectView .impl .ProjectRootsUtil
44import com .intellij .ide .projectView .impl .nodes .PsiDirectoryNode .canRealModuleNameBeHidden
5- import com .intellij .ide .projectView .impl .nodes .{PsiDirectoryNode , PsiFileSystemItemFilter }
5+ import com .intellij .ide .projectView .impl .nodes .{ProjectViewModuleGroupNode , ProjectViewProjectNode , PsiDirectoryNode , PsiFileSystemItemFilter }
66import com .intellij .ide .projectView .{PresentationData , TreeStructureProvider , ViewSettings }
77import com .intellij .openapi .externalSystem .util .ExternalSystemApiUtil
88import com .intellij .openapi .externalSystem .util .ExternalSystemApiUtil .isExternalSystemAwareModule
99import com .intellij .openapi .module .{Module , ModuleGrouper , ModuleManager }
1010import com .intellij .openapi .project .{DumbAware , Project }
11- import com .intellij .openapi .roots .ProjectRootManager
11+ import com .intellij .openapi .roots .{ ProjectFileIndex , ProjectRootManager }
1212import com .intellij .openapi .util .NlsSafe
1313import com .intellij .openapi .util .text .StringUtil
14- import com .intellij .openapi .vfs .VirtualFile
14+ import com .intellij .openapi .vfs .{ VfsUtilCore , VirtualFile }
1515import com .intellij .psi .PsiDirectory
1616import com .intellij .ui .SimpleTextAttributes .REGULAR_BOLD_ATTRIBUTES
1717import org .jetbrains .plugins .scala .lang .psi .api .ScalaFile
@@ -20,36 +20,120 @@ import org.jetbrains.sbt.project.SbtProjectSystem
2020
2121import java .util
2222import scala .jdk .CollectionConverters ._
23+ import scala .util .control .Breaks ._
24+
2325
2426final class ScalaTreeStructureProvider extends TreeStructureProvider with DumbAware {
2527
2628 import ScalaTreeStructureProvider ._
2729
2830
29- override def modify (parent : Node , children : util.Collection [Node ], settings : ViewSettings ): util.Collection [Node ] =
30- children.asScala.map { it =>
31- transform(it)(it.getProject, settings)
32- }.asJavaCollection
31+ override def modify (parent : Node , children : util.Collection [Node ], settings : ViewSettings ): util.Collection [Node ] = {
32+ val project = parent.getProject
33+ if (project == null ) return children
34+
35+ val childrenSeq = children.asScala.toSeq
36+ val modifiedChildren = parent match {
37+ // note: in GradleTreeStructureProvider they also implemented special handling for ProjectViewModuleGroupNode.
38+ // It is important in Gradle because they allow:
39+ // - modules to be created outside the project base directory,
40+ // - creation of modules with empty/non-existent/many content roots (we always have single content root for module)
41+ // In all this cases ProjectViewModuleGroupNode could be a parent.
42+ // In sbt I haven't been able to find any standard case in which we would need it. I was able to recreate such situation
43+ // by e.g. removing content root in the root module, but it is not our standard case, so I think we shouldn't care about it.
44+ // Handling ProjectViewModuleGroupNode will be probably needed for SCL-21157
45+ case _ : ProjectViewProjectNode =>
46+ getProjectViewProjectNodeChildren(childrenSeq)(project, settings)
47+ case _ =>
48+ childrenSeq.map { it =>
49+ transform(it)(it.getProject, settings)
50+ }
51+ }
52+ modifiedChildren.asJavaCollection
53+ }
3354}
3455
3556private object ScalaTreeStructureProvider {
3657
58+ // note: this logic is written based on like the case where the parent is ProjectViewProjectNode is implemented in
59+ // org.jetbrains.plugins.gradle.projectView.GradleTreeStructureProvider#getProjectNodeChildren
60+ private def getProjectViewProjectNodeChildren (children : Seq [Node ])
61+ (implicit project : Project , settings : ViewSettings ): Seq [Node ] = {
62+ val fileIndex = ProjectRootManager .getInstance(project).getFileIndex
63+ children.map {
64+ case projectViewModuleGroupNode : ProjectViewModuleGroupNode =>
65+ val directoryNode = getProjectViewModuleGroupNodeDirectoryNode(projectViewModuleGroupNode, fileIndex)
66+ directoryNode.getOrElse(projectViewModuleGroupNode)
67+ case psiDirectoryNode : PsiDirectoryNode if psiDirectoryNode.getParent == null && psiDirectoryNode.getValue != null =>
68+ val scalaModuleDirectoryNode = getScalaModuleDirectoryNode(psiDirectoryNode)
69+ scalaModuleDirectoryNode.getOrElse(psiDirectoryNode)
70+ case node =>
71+ transform(node)
72+ }
73+ }
74+
75+ // note: this logic is written based on like the case where the parent is ProjectViewProjectNode is implemented in
76+ // org.jetbrains.plugins.gradle.projectView.GradleTreeStructureProvider#getProjectNodeChildren
77+ private def getProjectViewModuleGroupNodeDirectoryNode (projectViewModuleGroupNode : ProjectViewModuleGroupNode , fileIndex : ProjectFileIndex ): Option [PsiDirectoryNode ] = {
78+ val children = projectViewModuleGroupNode.getChildren.asScala.toSeq
79+ val collectedChildren = children.collect {
80+ case child : PsiDirectoryNode if {
81+ val psiDirectory = child.getValue
82+ psiDirectory != null && {
83+ val module = fileIndex.getModuleForFile(psiDirectory.getVirtualFile)
84+ isExternalSystemAwareModule(SbtProjectSystem .Id , module)
85+ }
86+ } => (child.getValue.getVirtualFile, child)
87+ }
88+
89+ if (collectedChildren.length < children.length) return None
90+
91+ var parentNodePair : Option [(VirtualFile , PsiDirectoryNode )] = None
92+ breakable {
93+ collectedChildren.foreach { case (virtualFile, psiDirectoryNode) =>
94+ parentNodePair match {
95+ case None =>
96+ parentNodePair = Option (virtualFile, psiDirectoryNode)
97+ case Some ((file, _)) if VfsUtilCore .isAncestor(virtualFile, file, false ) =>
98+ parentNodePair = Option (virtualFile, psiDirectoryNode)
99+ case Some ((file, _)) if ! VfsUtilCore .isAncestor(file, virtualFile, false ) =>
100+ parentNodePair = None
101+ break()
102+ case _ =>
103+ }
104+ }
105+ }
106+ parentNodePair.map(_._2)
107+ }
108+
109+ private def getScalaModuleDirectoryNode (node : PsiDirectoryNode )
110+ (implicit project : Project , settings : ViewSettings ): Option [ScalaModuleDirectoryNode ] =
111+ getScalaModuleDirectoryNode(node.getValue, node.getFilter)
112+
113+ private def getScalaModuleDirectoryNode (
114+ psiDirectory : PsiDirectory ,
115+ filter : PsiFileSystemItemFilter
116+ )(implicit project : Project , settings : ViewSettings ) : Option [ScalaModuleDirectoryNode ] = {
117+ val virtualFile = psiDirectory.getVirtualFile
118+ // For now in the process of creating modules, a single content root for each module is created and its path is equal to project.base.path (it is the root of the module).
119+ // In ProjectRootsUtil#isModuleContentRoot it is checked whether the virtualFile is equal to the content root path associated with this virtualFile.
120+ // In a nutshell, with this we check whether virtualFile is the module root. If it is, there is some probability that we should create
121+ // ScalaModuleDirectoryNode for this node.
122+ if (! ProjectRootsUtil .isModuleContentRoot(virtualFile, project)) return None
123+ val fileIndex = ProjectRootManager .getInstance(project).getFileIndex
124+ val module = fileIndex.getModuleForFile(virtualFile)
125+ if (module == null ) return None
126+ val moduleShortName = getModuleShortName(module, project, virtualFile)
127+ moduleShortName
128+ .map(ScalaModuleDirectoryNode (project, psiDirectory, settings, _, filter, module))
129+ }
130+
37131 private def transform (node : Node )
38132 (implicit project : Project , settings : ViewSettings ): Node = {
39133 val nodeValue = node.getValue
40134 nodeValue match {
41135 case psiDirectory : PsiDirectory =>
42- val virtualFile = psiDirectory.getVirtualFile
43- val fileIndex = ProjectRootManager .getInstance(project).getFileIndex
44- val module = fileIndex.getModuleForFile(virtualFile)
45- // For now in the process of creating modules, a single content root for each module is created and its path is equal to project.base.path (it is the root of the module).
46- // In ProjectRootsUtil#isModuleContentRoot it is checked whether the virtualFile is equal to the content root path associated with this virtualFile.
47- // In a nutshell, with this we check whether virtualFile is the module root. If it is, there is some probability that we should create
48- // ScalaModuleDirectoryNode for this node.
49- if (! ProjectRootsUtil .isModuleContentRoot(virtualFile, project)) return node
50- val moduleShortName = getModuleShortName(module, project, virtualFile)
51- moduleShortName
52- .map(ScalaModuleDirectoryNode (project, psiDirectory, settings, _, node.asInstanceOf [PsiDirectoryNode ].getFilter, module))
136+ getScalaModuleDirectoryNode(psiDirectory, node.asInstanceOf [PsiDirectoryNode ].getFilter)
53137 .getOrElse(node)
54138 case file : ScalaFile =>
55139 Node (file)
0 commit comments