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); + } }