diff --git a/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml b/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml
index bd363667b776..7d135e180aa6 100644
--- a/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml
+++ b/plugins/gradle/java/resources/META-INF/gradle-groovy-integration.xml
@@ -47,6 +47,14 @@
implementationClass="org.jetbrains.plugins.gradle.execution.GradleGroovyRunLineMarkerProvider"/>
+
+
+
diff --git a/plugins/gradle/java/src/service/resolve/GradleProjectReference.kt b/plugins/gradle/java/src/service/resolve/GradleProjectReference.kt
new file mode 100644
index 000000000000..31b72cb1913a
--- /dev/null
+++ b/plugins/gradle/java/src/service/resolve/GradleProjectReference.kt
@@ -0,0 +1,62 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.plugins.gradle.service.resolve
+
+import com.intellij.model.SingleTargetReference
+import com.intellij.model.Symbol
+import com.intellij.model.psi.PsiCompletableReference
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiElement
+import org.jetbrains.annotations.ApiStatus.Internal
+import org.jetbrains.plugins.gradle.settings.GradleExtensionsSettings
+import org.jetbrains.plugins.gradle.settings.GradleExtensionsSettings.GradleProject
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
+
+@Internal
+class GradleProjectReference(
+ private val literal: GrLiteral,
+ private val range: TextRange,
+ private val projectPath: List
+) : SingleTargetReference(), PsiCompletableReference {
+
+ init {
+ require(projectPath.isNotEmpty())
+ }
+
+ private val projectPathString = projectPath.joinToString(":")
+
+ override fun getElement(): PsiElement = literal
+
+ override fun getRangeInElement(): TextRange = range
+
+ override fun resolveSingleTarget(): Symbol? {
+ val gradleProject = GradleExtensionsSettings.getRootProject(literal) ?: return null
+ if (projectPathString in gradleProject.extensions) {
+ return GradleProjectSymbol(projectPath)
+ }
+ return null
+ }
+
+ /**
+ * This could be much easier if we could query list of sub-projects by project fqn.
+ */
+ override fun getCompletionVariants(): Collection {
+ val gradleProject: GradleProject = GradleExtensionsSettings.getRootProject(literal) ?: return emptyList()
+ val allProjectFqns: Set = gradleProject.extensions.keys
+ val parentProjectFqn: List = projectPath // ["com", "IntellijIdeaRulezzz"]
+ .run { take(size - 1) } // ["com"]
+ val parentProjectPrefix: String = parentProjectFqn.joinToString("") { "$it:" } // "com:"
+ val result = LinkedHashSet()
+ for (projectFqn in allProjectFqns) { // "com:foo:bar"
+ if (!projectFqn.startsWith(parentProjectPrefix)) {
+ continue
+ }
+ val relativeFqn = projectFqn.removePrefix(parentProjectPrefix) // "foo:bar"
+ val childProjectName = relativeFqn.split(':').firstOrNull() // "foo"
+ if (childProjectName.isNullOrEmpty()) {
+ continue
+ }
+ result += childProjectName
+ }
+ return result
+ }
+}
diff --git a/plugins/gradle/java/src/service/resolve/GradleProjectReferenceProvider.kt b/plugins/gradle/java/src/service/resolve/GradleProjectReferenceProvider.kt
new file mode 100644
index 000000000000..5f71e6f646bb
--- /dev/null
+++ b/plugins/gradle/java/src/service/resolve/GradleProjectReferenceProvider.kt
@@ -0,0 +1,67 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.plugins.gradle.service.resolve
+
+import com.intellij.model.Symbol
+import com.intellij.model.psi.PsiExternalReferenceHost
+import com.intellij.model.psi.PsiSymbolReference
+import com.intellij.model.psi.PsiSymbolReferenceHints
+import com.intellij.model.psi.PsiSymbolReferenceProvider
+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
+import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyMethodCallPattern
+import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns
+import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyElement
+import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod
+
+@Internal
+class GradleProjectReferenceProvider : PsiSymbolReferenceProvider {
+
+ private val myPattern = GroovyPatterns.stringLiteral().withParent(
+ groovyElement().withParent(
+ GroovyMethodCallPattern.resolvesTo(psiMethod(GradleCommonClassNames.GRADLE_API_PROJECT, "project"))
+ )
+ )
+
+ /**
+ * Handled by [GradleProjectReferenceSearcher].
+ */
+ override fun getSearchRequests(project: Project, target: Symbol): Collection = emptyList()
+
+ override fun getReferences(element: PsiExternalReferenceHost, hints: PsiSymbolReferenceHints): Collection {
+ if (element !is GrLiteral || !myPattern.accepts(element)) {
+ return emptyList()
+ }
+ val escaper = element.createLiteralTextEscaper()
+ val manipulator = ElementManipulators.getManipulator(element)
+ val value = StringBuilder()
+ val rangeInHost = manipulator.getRangeInElement(element)
+ if (!escaper.decode(rangeInHost, value)) {
+ return emptyList()
+ }
+ if (!value.startsWith(":")) {
+ return emptyList()
+ }
+ val path = value.split(":")
+ val result = SmartList()
+ val subPath = ArrayList()
+ var currentOffsetInPath = 0
+ for (part in path) {
+ subPath.add(part)
+ val partStart = escaper.getOffsetInHost(currentOffsetInPath, rangeInHost)
+ currentOffsetInPath += part.length
+ val partEnd = escaper.getOffsetInHost(currentOffsetInPath, rangeInHost)
+ currentOffsetInPath += 1 // ":"
+ if (partStart < 0 || partEnd < 0) {
+ continue
+ }
+ result += GradleProjectReference(element, TextRange(partStart, partEnd), ArrayList(subPath))
+ }
+ return result
+ }
+}
diff --git a/plugins/gradle/java/src/service/resolve/GradleProjectReferenceSearcher.kt b/plugins/gradle/java/src/service/resolve/GradleProjectReferenceSearcher.kt
new file mode 100644
index 000000000000..41d42fcfe734
--- /dev/null
+++ b/plugins/gradle/java/src/service/resolve/GradleProjectReferenceSearcher.kt
@@ -0,0 +1,26 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.plugins.gradle.service.resolve
+
+import com.intellij.model.psi.PsiSymbolReference
+import com.intellij.model.search.PsiSymbolReferenceSearchParameters
+import com.intellij.model.search.PsiSymbolReferenceSearcher
+import com.intellij.model.search.SearchContext
+import com.intellij.model.search.SearchService
+import com.intellij.model.search.impl.ExternalReferenceMapper
+import com.intellij.util.Query
+import org.jetbrains.plugins.groovy.GroovyLanguage
+
+class GradleProjectReferenceSearcher : PsiSymbolReferenceSearcher {
+
+ override fun collectSearchRequests(parameters: PsiSymbolReferenceSearchParameters): Collection> {
+ val projectSymbol = parameters.symbol as? GradleProjectSymbol ?: return emptyList()
+ val projectName = projectSymbol.projectName.takeIf(String::isNotEmpty) ?: return emptyList()
+ val query = SearchService.getInstance()
+ .searchWord(parameters.project, projectName)
+ .inScope(parameters.searchScope)
+ .inFilesWithLanguage(GroovyLanguage)
+ .inContexts(SearchContext.IN_STRINGS)
+ .buildQuery(ExternalReferenceMapper(projectSymbol.createPointer()))
+ return listOf(query)
+ }
+}
diff --git a/plugins/gradle/java/src/service/resolve/GradleProjectSymbol.kt b/plugins/gradle/java/src/service/resolve/GradleProjectSymbol.kt
new file mode 100644
index 000000000000..6255c11bc2ae
--- /dev/null
+++ b/plugins/gradle/java/src/service/resolve/GradleProjectSymbol.kt
@@ -0,0 +1,47 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.plugins.gradle.service.resolve
+
+import com.intellij.model.Pointer
+import com.intellij.model.presentation.PresentableSymbol
+import com.intellij.model.presentation.SymbolPresentation
+import icons.GradleIcons
+import org.jetbrains.annotations.ApiStatus.Internal
+import org.jetbrains.plugins.gradle.util.GradleBundle
+import javax.swing.Icon
+
+@Internal
+class GradleProjectSymbol(private val myQualifiedName: List) : PresentableSymbol {
+
+ init {
+ require(myQualifiedName.isNotEmpty())
+ }
+
+ override fun createPointer(): Pointer = Pointer.hardPointer(this)
+
+ val projectName: String get() = myQualifiedName.last()
+ val qualifiedName: String get() = myQualifiedName.joinToString(separator = ":")
+
+ private val myPresentation = object : SymbolPresentation {
+ override fun getIcon(): Icon? = GradleIcons.Gradle
+ override fun getShortNameString(): String = projectName
+ override fun getShortDescription(): String = GradleBundle.message("gradle.project.0", projectName)
+ override fun getLongDescription(): String = GradleBundle.message("gradle.project.0", qualifiedName)
+ }
+
+ override fun getSymbolPresentation(): SymbolPresentation = myPresentation
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as GradleProjectSymbol
+
+ if (myQualifiedName != other.myQualifiedName) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return myQualifiedName.hashCode()
+ }
+}
diff --git a/plugins/gradle/java/src/service/resolve/GradleProjectTextSearcher.kt b/plugins/gradle/java/src/service/resolve/GradleProjectTextSearcher.kt
new file mode 100644
index 000000000000..c258b579c121
--- /dev/null
+++ b/plugins/gradle/java/src/service/resolve/GradleProjectTextSearcher.kt
@@ -0,0 +1,9 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.plugins.gradle.service.resolve
+
+import com.intellij.find.usages.SymbolTextSearcher
+
+class GradleProjectTextSearcher : SymbolTextSearcher {
+
+ override fun getStringsToSearch(symbol: GradleProjectSymbol): Collection = listOf(symbol.qualifiedName)
+}
diff --git a/plugins/gradle/resources/messages/GradleBundle.properties b/plugins/gradle/resources/messages/GradleBundle.properties
index b8a50d4de6d6..7ce2a7b8279c 100644
--- a/plugins/gradle/resources/messages/GradleBundle.properties
+++ b/plugins/gradle/resources/messages/GradleBundle.properties
@@ -165,3 +165,4 @@ compiler.build.messages.gradle.configuration.not.found=Unable to make ''{0}''.\n
gradle.project.generation.wrapper.progress.title=Generating Gradle Wrapper
gradle.commandline.description=Configures Gradle projects under the given folder
+gradle.project.0=Gradle project ''{0}''
\ No newline at end of file
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java
index a7ef900705d2..63f54e53bccf 100644
--- a/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java
@@ -12,10 +12,14 @@ import com.intellij.openapi.externalSystem.service.project.ProjectDataManager;
import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManager;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.psi.CommonClassNames;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter;
@@ -188,14 +192,20 @@ public class GradleExtensionsSettings {
*/
@Nullable
public GradleExtensionsData getExtensionsFor(@Nullable String rootProjectPath, @Nullable String gradlePath) {
- if (rootProjectPath == null || gradlePath == null) return null;
- GradleProject gradleProject = projects.get(rootProjectPath);
+ GradleProject gradleProject = getRootGradleProject(rootProjectPath);
if (gradleProject == null) return null;
return gradleProject.extensions.get(gradlePath);
}
+
+ @Contract("null -> null")
+ @Nullable
+ public GradleProject getRootGradleProject(@Nullable String rootProjectPath) {
+ if (rootProjectPath == null) return null;
+ return projects.get(rootProjectPath);
+ }
}
- static class GradleProject {
+ public static class GradleProject {
public Map extensions = new HashMap<>();
}
@@ -219,6 +229,7 @@ public class GradleExtensionsSettings {
public final Map configurations = new HashMap<>();
@NotNull
public final Map buildScriptConfigurations = new HashMap<>();
+
@Nullable
public GradleExtensionsData getParent() {
if (myGradleProject == null) return null;
@@ -315,4 +326,13 @@ public class GradleExtensionsSettings {
public boolean scriptClasspath;
public String description;
}
+
+ @Nullable
+ public static GradleProject getRootProject(@NotNull PsiElement element) {
+ final PsiFile containingFile = element.getContainingFile().getOriginalFile();
+ final Project project = containingFile.getProject();
+ final Module module = ModuleUtilCore.findModuleForFile(containingFile);
+ final String rootProjectPath = ExternalSystemApiUtil.getExternalRootProjectPath(module);
+ return getInstance(project).getRootGradleProject(rootProjectPath);
+ }
}