diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/ConfigureKotlinInProjectAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigureKotlinInProjectAction.kt index f061f4f4297..96647f7fb66 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/ConfigureKotlinInProjectAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/ConfigureKotlinInProjectAction.kt @@ -33,7 +33,7 @@ abstract class ConfigureKotlinInProjectAction : AnAction() { override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return - val modules = getModulesWithKotlinFiles(project).ifEmpty { project.allModules() } + val modules = getConfigurableModulesWithKotlinFiles(project).ifEmpty { project.allModules() } if (modules.all(::isModuleConfigured)) { Messages.showInfoMessage("All modules with Kotlin files are configured", e.presentation.text!!) return diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt index 36e3c98d377..318ec0ce90f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt @@ -18,7 +18,9 @@ package org.jetbrains.kotlin.idea.configuration import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.util.Computable @@ -71,8 +73,20 @@ fun getModulesWithKotlinFiles(project: Project): Collection { return emptyList() } - return project.allModules().filter { module -> - FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, module.getModuleScope(true)) + return project.allModules() + .filter { module -> + FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, module.getModuleScope(true)) + } +} + +fun getConfigurableModulesWithKotlinFiles(project: Project): Collection { + val modules = getModulesWithKotlinFiles(project) + if (modules.isEmpty()) return modules + + val pathMap = ModuleManager.getInstance(project).modules.asList().buildExternalPathMap() + return modules.mapTo(HashSet()) { module -> + val externalPath = module.externalProjectPath + if (externalPath == null) module else (pathMap[externalPath] ?: module) } } @@ -85,7 +99,7 @@ fun showConfigureKotlinNotificationIfNeeded(module: Module) { fun showConfigureKotlinNotificationIfNeeded(project: Project, excludeModules: List = emptyList()) { ApplicationManager.getApplication().executeOnPooledThread { val notificationString = DumbService.getInstance(project).runReadActionInSmartMode(Computable { - val modules = getModulesWithKotlinFiles(project) - excludeModules + val modules = getConfigurableModulesWithKotlinFiles(project) - excludeModules if (modules.all(::isModuleConfigured)) null else ConfigureKotlinNotification.getNotificationString(project, excludeModules) }) if (notificationString != null) { @@ -97,7 +111,7 @@ fun showConfigureKotlinNotificationIfNeeded(project: Project, excludeModules: Li } fun getAbleToRunConfigurators(project: Project): Collection { - val modules = getModulesWithKotlinFiles(project).ifEmpty { project.allModules() } + val modules = getConfigurableModulesWithKotlinFiles(project).ifEmpty { project.allModules() } return Extensions.getExtensions(KotlinProjectConfigurator.EP_NAME).filter { configurator -> modules.any { module -> configurator.getStatus(module) == ConfigureKotlinStatus.CAN_BE_CONFIGURED } @@ -113,16 +127,48 @@ fun getConfiguratorByName(name: String): KotlinProjectConfigurator? { } fun getNonConfiguredModules(project: Project, configurator: KotlinProjectConfigurator): List { - return project.allModules().filter { module -> configurator.getStatus(module) == ConfigureKotlinStatus.CAN_BE_CONFIGURED } + return project.allModules() + .filter { module -> configurator.getStatus(module) == ConfigureKotlinStatus.CAN_BE_CONFIGURED } + .excludeSourceRootModules() } +fun Collection.excludeSourceRootModules(): List { + val pathMap = buildExternalPathMap() + return filter { it.externalProjectId == null || it.externalProjectPath == null } + pathMap.values +} + +fun Collection.buildExternalPathMap(): Map { + val pathMap = mutableMapOf() + for (module in this) { + val externalId = module.externalProjectId + val externalPath = module.externalProjectPath + if (externalId != null && externalPath != null) { + val previousModule = pathMap[externalPath] + // the module without the source root suffix will have the shortest name + if (previousModule == null || isSourceRootPrefix(externalId, previousModule.externalProjectId!!)) { + pathMap[externalPath] = module + } + } + } + return pathMap +} + +private fun isSourceRootPrefix(externalId: String, previousModuleExternalId: String) + = externalId.length < previousModuleExternalId.length && previousModuleExternalId.startsWith(externalId) + +val Module.externalProjectId: String? + get() = ExternalSystemApiUtil.getExternalProjectId(this) + +val Module.externalProjectPath: String? + get() = ExternalSystemApiUtil.getExternalProjectPath(this) + fun getNonConfiguredModulesWithKotlinFiles(project: Project, configurator: KotlinProjectConfigurator): List { - val modules = getModulesWithKotlinFiles(project) + val modules = getConfigurableModulesWithKotlinFiles(project) return modules.filter { module -> configurator.getStatus(module) == ConfigureKotlinStatus.CAN_BE_CONFIGURED } } fun getNonConfiguredModules(project: Project, excludeModules: Collection = emptyList()): Collection { - val modulesWithKotlinFiles = getModulesWithKotlinFiles(project) - excludeModules + val modulesWithKotlinFiles = getConfigurableModulesWithKotlinFiles(project) - excludeModules val ableToRunConfigurators = getAbleToRunConfigurators(project) return modulesWithKotlinFiles.filter { module -> ableToRunConfigurators.any { it.getStatus(module) == ConfigureKotlinStatus.CAN_BE_CONFIGURED } diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt index 6124b39f89e..0394d7dc943 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt @@ -20,15 +20,14 @@ import com.intellij.openapi.extensions.Extensions import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.impl.LoadTextUtil import com.intellij.openapi.module.ModuleManager -import org.jetbrains.kotlin.idea.configuration.ConfigureKotlinStatus -import org.jetbrains.kotlin.idea.configuration.KotlinGradleModuleConfigurator -import org.jetbrains.kotlin.idea.configuration.KotlinProjectConfigurator -import org.jetbrains.kotlin.idea.configuration.createConfigureKotlinNotificationCollector +import org.jetbrains.kotlin.idea.configuration.* +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.test.testFramework.runInEdtAndWait import org.jetbrains.kotlin.test.testFramework.runWriteAction import org.junit.Test class GradleConfiguratorTest : GradleImportingTestCase() { + @Test fun testProjectWithModule() { createProjectSubFile("settings.gradle", "include ':app'") @@ -58,8 +57,7 @@ class GradleConfiguratorTest : GradleImportingTestCase() { myProject.baseDir.createChildData(null, "build.gradle") val module = ModuleManager.getInstance(myProject).findModuleByName("app")!! - val configurator = Extensions.findExtension(KotlinProjectConfigurator.EP_NAME, - KotlinGradleModuleConfigurator::class.java) + val configurator = findGradleModuleConfigurator() // We have a Kotlin runtime in build.gradle but not in the classpath, so it doesn't make sense // to suggest configuring it assertEquals(ConfigureKotlinStatus.BROKEN, configurator.getStatus(module)) @@ -84,8 +82,7 @@ class GradleConfiguratorTest : GradleImportingTestCase() { runInEdtAndWait { runWriteAction { val module = ModuleManager.getInstance(myProject).findModuleByName("app")!! - val configurator = Extensions.findExtension(KotlinProjectConfigurator.EP_NAME, - KotlinGradleModuleConfigurator::class.java) + val configurator = findGradleModuleConfigurator() val collector = createConfigureKotlinNotificationCollector(myProject) configurator.configureWithVersion(myProject, listOf(module), "1.0.6", collector) @@ -113,4 +110,34 @@ class GradleConfiguratorTest : GradleImportingTestCase() { } } } + + private fun findGradleModuleConfigurator() = Extensions.findExtension(KotlinProjectConfigurator.EP_NAME, + KotlinGradleModuleConfigurator::class.java) + + @Test + fun testListNonConfiguredModules() { + createProjectSubFile("settings.gradle", "include ':app'") + createProjectSubFile("app/build.gradle", """ + buildscript { + repositories { + jcenter() + mavenCentral() + } + } + + apply plugin: 'java' + """.trimIndent()) + createProjectSubFile("app/src/main/java/foo.kt", "") + + importProject() + + runReadAction { + val configurator = findGradleModuleConfigurator() + val moduleNames = getNonConfiguredModules(myProject, configurator).map { it.name } + assertSameElements(moduleNames, "app", "project") + + val moduleNamesWithKotlinFiles = getNonConfiguredModulesWithKotlinFiles(myProject, configurator).map { it.name } + assertSameElements(moduleNamesWithKotlinFiles, "app") + } + } }