[gradle] IDEA-197590: make root project reference span over the colon

- in GradleProjectSymbol empty qualified name means root project;
- update equals/hashCode in GradleProjectSymbol;
- make naming consistent.

GitOrigin-RevId: ce249833d126e3fbc353e7d1df17eff71117b250
This commit is contained in:
Daniil Ovchinnikov
2020-06-16 22:54:48 +03:00
committed by intellij-monorepo-bot
parent cfb368a902
commit fa8e231994
3 changed files with 63 additions and 45 deletions
@@ -13,46 +13,38 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals
@Internal
class GradleProjectReference(
private val literal: GrLiteral,
private val range: TextRange,
private val projectPath: List<String>
private val myElement: GrLiteral,
private val myRange: TextRange,
private val myQualifiedName: List<String>
) : SingleTargetReference(), PsiCompletableReference {
init {
require(projectPath.isNotEmpty())
}
override fun getElement(): PsiElement = myElement
private val projectPathString = projectPath.joinToString(":")
override fun getElement(): PsiElement = literal
override fun getRangeInElement(): TextRange = range
override fun getRangeInElement(): TextRange = myRange
override fun resolveSingleTarget(): Symbol? {
val gradleProject = GradleExtensionsSettings.getRootProject(literal) ?: return null
val rootProjectPath = GradleExtensionsSettings.getRootProjectPath(literal) ?: return null
if (projectPathString in gradleProject.extensions) {
return GradleProjectSymbol(projectPath, rootProjectPath)
val gradleProject = GradleExtensionsSettings.getRootProject(myElement) ?: return null
val rootProjectPath = GradleExtensionsSettings.getRootProjectPath(myElement) ?: return null
if (GradleProjectSymbol.qualifiedName(myQualifiedName) in gradleProject.extensions) {
return GradleProjectSymbol(myQualifiedName, rootProjectPath)
}
return null
}
/**
* This could be much easier if we could query list of sub-projects by project fqn.
* This could've been much easier if we could query list of sub-projects by project fqn.
*/
override fun getCompletionVariants(): Collection<Any> {
val gradleProject: GradleProject = GradleExtensionsSettings.getRootProject(literal) ?: return emptyList()
val allProjectFqns: Set<String> = gradleProject.extensions.keys
val parentProjectFqn: List<String> = projectPath // ["com", "IntellijIdeaRulezzz"]
.run { take(size - 1) } // ["com"]
val parentProjectPrefix: String = parentProjectFqn.joinToString("") { "$it:" } // "com:"
val gradleProject: GradleProject = GradleExtensionsSettings.getRootProject(myElement) ?: return emptyList()
val parentProjectFqn: List<String> = myQualifiedName.dropLast(1) // ["com", "foo", "IntellijIdeaRulezzz "] -> ["com", "foo"]
val parentProjectPrefix: String = parentProjectFqn.joinToString(separator = "", postfix = ":") { ":$it" } // ":com:foo:"
val result = LinkedHashSet<String>()
for (projectFqn in allProjectFqns) { // "com:foo:bar"
for (projectFqn in gradleProject.extensions.keys) { // let's say there is ":com:foo:bar:baz" among keys
if (!projectFqn.startsWith(parentProjectPrefix)) {
continue
}
val relativeFqn = projectFqn.removePrefix(parentProjectPrefix) // "foo:bar"
val childProjectName = relativeFqn.split(':').firstOrNull() // "foo"
val relativeFqn = projectFqn.removePrefix(parentProjectPrefix) // "bar:baz"
val childProjectName = relativeFqn.split(':').firstOrNull() // "bar"
if (childProjectName.isNullOrEmpty()) {
continue
}
@@ -10,7 +10,6 @@ import com.intellij.model.search.SearchRequest
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.ElementManipulators
import com.intellij.util.SmartList
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
@@ -47,10 +46,22 @@ class GradleProjectReferenceProvider : PsiSymbolReferenceProvider {
if (!value.startsWith(":")) {
return emptyList()
}
val path = value.split(":")
val result = SmartList<PsiSymbolReference>()
if (value.toString() == ":") {
// in this special case we want the root project reference to span over the colon symbol
return listOf(GradleProjectReference(element, rangeInHost, emptyList()))
}
val path = value.split(":").drop(1) // drop first empty string
val result = ArrayList<PsiSymbolReference>(path.size + 1)
val rootOffsetInHost = escaper.getOffsetInHost(0, rangeInHost)
if (rootOffsetInHost >= 0) {
// add a root project reference just before the first colon, i.e. it has an empty range
result += GradleProjectReference(element, TextRange(rootOffsetInHost, rootOffsetInHost), emptyList())
}
val subPath = ArrayList<String>()
var currentOffsetInPath = 0
var currentOffsetInPath = 1 // skip first ":"
for (part in path) {
subPath.add(part)
val partStart = escaper.getOffsetInHost(currentOffsetInPath, rangeInHost)
@@ -17,18 +17,23 @@ import org.jetbrains.plugins.gradle.model.ExternalProject
import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataCache
import org.jetbrains.plugins.gradle.util.GradleBundle
/**
* Empty [myQualifiedName] means root project.
*/
@Internal
class GradleProjectSymbol(private val myQualifiedName: List<String>, private val myRootProjectPath: String) : PresentableSymbol, NavigatableSymbol {
class GradleProjectSymbol(
private val myQualifiedName: List<String>,
private val myRootProjectPath: String
) : PresentableSymbol, NavigatableSymbol {
init {
require(myQualifiedName.isNotEmpty())
require(myRootProjectPath.isNotBlank())
}
override fun createPointer(): Pointer<GradleProjectSymbol> = Pointer.hardPointer(this)
val projectName: String get() = myQualifiedName.last()
val qualifiedName: String get() = myQualifiedName.joinToString(separator = ":")
val projectName: String get() = if (myQualifiedName.isEmpty()) ":" else myQualifiedName.last()
val qualifiedName: String get() = qualifiedName(myQualifiedName)
private val myPresentation = SymbolPresentation.create(
GradleIcons.Gradle,
@@ -48,23 +53,18 @@ class GradleProjectSymbol(private val myQualifiedName: List<String>, private val
}
private fun findBuildFile(project: Project): PsiElement? {
val rootProject = ExternalProjectDataCache.getInstance(project)
.getRootExternalProject(myRootProjectPath)
?: return null
val externalProject = if (":" == qualifiedName) {
val rootProject = ExternalProjectDataCache.getInstance(project).getRootExternalProject(myRootProjectPath) ?: return null
val externalProject = if (myQualifiedName.isEmpty()) {
rootProject
} else {
myQualifiedName
.drop(1)
.fold(rootProject as ExternalProject?) { extProject, name ->
extProject?.childProjects?.get(name)
} ?: return null
}
else {
myQualifiedName.fold(rootProject as ExternalProject?) { extProject, name ->
extProject?.childProjects?.get(name)
} ?: return null
}
val buildFile = externalProject.buildFile ?: return null
val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(buildFile) ?: return null
return PsiManager.getInstance(project).findFile(virtualFile)
}
@@ -75,11 +75,26 @@ class GradleProjectSymbol(private val myQualifiedName: List<String>, private val
other as GradleProjectSymbol
if (myQualifiedName != other.myQualifiedName) return false
if (myRootProjectPath != other.myRootProjectPath) return false
return true
}
override fun hashCode(): Int {
return myQualifiedName.hashCode()
var result = myQualifiedName.hashCode()
result = 31 * result + myRootProjectPath.hashCode()
return result
}
companion object {
fun qualifiedName(qualifiedNameParts: List<String>): String {
return if (qualifiedNameParts.isEmpty()) {
":"
}
else {
qualifiedNameParts.joinToString("") { ":$it" }
}
}
}
}