Skip to content

Commit b3319f9

Browse files
author
builduser
committed
Merged branch idea241.x-support-ProjectViewProjectNode-in-TreeStructure into idea241.x
2 parents 810eade + 27bedbf commit b3319f9

File tree

1 file changed

+102
-18
lines changed

1 file changed

+102
-18
lines changed

scala/scala-impl/src/org/jetbrains/plugins/scala/projectView/ScalaTreeStructureProvider.scala

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ package org.jetbrains.plugins.scala.projectView
22

33
import com.intellij.ide.projectView.impl.ProjectRootsUtil
44
import 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}
66
import com.intellij.ide.projectView.{PresentationData, TreeStructureProvider, ViewSettings}
77
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
88
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.isExternalSystemAwareModule
99
import com.intellij.openapi.module.{Module, ModuleGrouper, ModuleManager}
1010
import com.intellij.openapi.project.{DumbAware, Project}
11-
import com.intellij.openapi.roots.ProjectRootManager
11+
import com.intellij.openapi.roots.{ProjectFileIndex, ProjectRootManager}
1212
import com.intellij.openapi.util.NlsSafe
1313
import com.intellij.openapi.util.text.StringUtil
14-
import com.intellij.openapi.vfs.VirtualFile
14+
import com.intellij.openapi.vfs.{VfsUtilCore, VirtualFile}
1515
import com.intellij.psi.PsiDirectory
1616
import com.intellij.ui.SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES
1717
import org.jetbrains.plugins.scala.lang.psi.api.ScalaFile
@@ -20,36 +20,120 @@ import org.jetbrains.sbt.project.SbtProjectSystem
2020

2121
import java.util
2222
import scala.jdk.CollectionConverters._
23+
import scala.util.control.Breaks._
24+
2325

2426
final 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

3556
private 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

Comments
 (0)