diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt index 3ee88706f4f..a6dac68fb4c 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt @@ -12,7 +12,6 @@ import com.intellij.execution.actions.RunConfigurationProducer import com.intellij.execution.configurations.ModuleBasedConfiguration import com.intellij.execution.junit.* import com.intellij.openapi.project.DumbService -import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.util.Ref import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement @@ -21,13 +20,11 @@ import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.util.ProjectRootsUtil -import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.psiUtil.getParentOfType -import org.jetbrains.kotlin.resolve.TargetPlatform import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer(JUnitConfigurationType.getInstance()) { @@ -86,20 +83,21 @@ class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt.181 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt.181 new file mode 100644 index 00000000000..3ee88706f4f --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt.181 @@ -0,0 +1,180 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.run + +import com.intellij.execution.* +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.actions.ConfigurationFromContext +import com.intellij.execution.actions.RunConfigurationProducer +import com.intellij.execution.configurations.ModuleBasedConfiguration +import com.intellij.execution.junit.* +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.idea.project.TargetPlatformDetector +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.idea.util.module +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.TargetPlatform +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform + +class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer(JUnitConfigurationType.getInstance()) { + override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean { + return other.isProducedBy(JUnitConfigurationProducer::class.java) || other.isProducedBy(PatternConfigurationProducer::class.java) + } + + override fun isConfigurationFromContext( + configuration: JUnitConfiguration, + context: ConfigurationContext + ): Boolean { + if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) { + return false + } + + val leaf = context.location?.psiElement ?: return false + val methodLocation = getTestMethodLocation(leaf) + val testClass = getTestClass(leaf) + val testObject = configuration.testObject + + if (!testObject.isConfiguredByElement(configuration, testClass, methodLocation?.psiElement, null, null)) { + return false + } + + return settingsMatchTemplate(configuration, context) + } + + // copied from JUnitConfigurationProducer in IDEA + private fun settingsMatchTemplate(configuration: JUnitConfiguration, context: ConfigurationContext): Boolean { + val predefinedConfiguration = context.getOriginalConfiguration(JUnitConfigurationType.getInstance()) + + val vmParameters = (predefinedConfiguration as? CommonJavaRunConfigurationParameters)?.vmParameters + if (vmParameters != null && configuration.vmParameters != vmParameters) return false + + val template = RunManager.getInstance(configuration.project).getConfigurationTemplate(configurationFactory) + val predefinedModule = (template.configuration as ModuleBasedConfiguration<*>).configurationModule.module + val configurationModule = configuration.configurationModule.module + return configurationModule == context.location?.module || configurationModule == predefinedModule + } + + override fun setupConfigurationFromContext( + configuration: JUnitConfiguration, + context: ConfigurationContext, + sourceElement: Ref + ): Boolean { + if (DumbService.getInstance(context.project).isDumb) return false + + val location = context.location ?: return false + val leaf = location.psiElement + + if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) { + return false + } + + if (leaf.containingFile !is KtFile) { + return false + } + + val ktFile = leaf.containingFile as KtFile + + val targetPlatform = TargetPlatformDetector.getPlatform(ktFile) + if (targetPlatform != JvmPlatform && targetPlatform != TargetPlatform.Common) { + return false + } + + val methodLocation = getTestMethodLocation(leaf) + if (methodLocation != null) { + val originalModule = configuration.configurationModule.module + configuration.beMethodConfiguration(methodLocation) + configuration.restoreOriginalModule(originalModule) + JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location) + fixJdkForCommonModule(ktFile, configuration) + return true + } + + val testClass = getTestClass(leaf) + if (testClass != null) { + val originalModule = configuration.configurationModule.module + configuration.beClassConfiguration(testClass) + configuration.restoreOriginalModule(originalModule) + JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location) + fixJdkForCommonModule(ktFile, configuration) + return true + } + + return false + } + + private fun fixJdkForCommonModule(ktFile: KtFile, configuration: JUnitConfiguration) { + val implModule = ktFile.module?.findJvmImplementationModule() ?: return + val sdk = ModuleRootManager.getInstance(implModule).sdk + if (sdk != null) { + configuration.setModule(implModule) + configuration.isAlternativeJrePathEnabled = true + configuration.alternativeJrePath = sdk.homePath + } + } + + override fun onFirstRun(fromContext: ConfigurationFromContext, context: ConfigurationContext, performRunnable: Runnable) { + val leaf = fromContext.sourceElement + getTestClass(leaf)?.let { testClass -> + val fromContextSubstitute = object : ConfigurationFromContext() { + override fun getConfigurationSettings() = fromContext.configurationSettings + + override fun setConfigurationSettings(configurationSettings: RunnerAndConfigurationSettings) { + fromContext.configurationSettings = configurationSettings + } + + override fun getSourceElement() = testClass + } + // TODO: use TestClassConfigurationProducer when constructor becomes public + return object : AbstractTestClassConfigurationProducer(JUnitConfigurationType.getInstance()){} + .onFirstRun(fromContextSubstitute, context, performRunnable) + } + + super.onFirstRun(fromContext, context, performRunnable) + } + + companion object { + fun getTestClass(leaf: PsiElement): PsiClass? { + val containingFile = leaf.containingFile as? KtFile ?: return null + var ktClass = leaf.getParentOfType(false) + if (!ktClass.isJUnitTestClass()) { + ktClass = getTestClassInFile(containingFile) + } + return ktClass?.toLightClass() + } + + fun getTestMethodLocation(leaf: PsiElement): Location? { + val function = leaf.getParentOfType(false) ?: return null + val owner = PsiTreeUtil.getParentOfType(function, KtFunction::class.java, KtClass::class.java) + + if (owner is KtClass) { + val delegate = owner.toLightClass() ?: return null + val method = delegate.methods.firstOrNull() { it.navigationElement == function } ?: return null + val methodLocation = PsiLocation.fromPsiElement(method) + if (JUnitUtil.isTestMethod(methodLocation, false)) { + return methodLocation + } + } + return null + } + + private fun KtClass?.isJUnitTestClass() = + this?.toLightClass()?.let { JUnitUtil.isTestClass(it, false, true) } ?: false + + private fun getTestClassInFile(ktFile: KtFile) = + ktFile.declarations.filterIsInstance().singleOrNull { it.isJUnitTestClass() } + } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt.as33 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt.as33 new file mode 100644 index 00000000000..3ee88706f4f --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinJUnitRunConfigurationProducer.kt.as33 @@ -0,0 +1,180 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.run + +import com.intellij.execution.* +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.actions.ConfigurationFromContext +import com.intellij.execution.actions.RunConfigurationProducer +import com.intellij.execution.configurations.ModuleBasedConfiguration +import com.intellij.execution.junit.* +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.idea.project.TargetPlatformDetector +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.idea.util.module +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.TargetPlatform +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform + +class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer(JUnitConfigurationType.getInstance()) { + override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean { + return other.isProducedBy(JUnitConfigurationProducer::class.java) || other.isProducedBy(PatternConfigurationProducer::class.java) + } + + override fun isConfigurationFromContext( + configuration: JUnitConfiguration, + context: ConfigurationContext + ): Boolean { + if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) { + return false + } + + val leaf = context.location?.psiElement ?: return false + val methodLocation = getTestMethodLocation(leaf) + val testClass = getTestClass(leaf) + val testObject = configuration.testObject + + if (!testObject.isConfiguredByElement(configuration, testClass, methodLocation?.psiElement, null, null)) { + return false + } + + return settingsMatchTemplate(configuration, context) + } + + // copied from JUnitConfigurationProducer in IDEA + private fun settingsMatchTemplate(configuration: JUnitConfiguration, context: ConfigurationContext): Boolean { + val predefinedConfiguration = context.getOriginalConfiguration(JUnitConfigurationType.getInstance()) + + val vmParameters = (predefinedConfiguration as? CommonJavaRunConfigurationParameters)?.vmParameters + if (vmParameters != null && configuration.vmParameters != vmParameters) return false + + val template = RunManager.getInstance(configuration.project).getConfigurationTemplate(configurationFactory) + val predefinedModule = (template.configuration as ModuleBasedConfiguration<*>).configurationModule.module + val configurationModule = configuration.configurationModule.module + return configurationModule == context.location?.module || configurationModule == predefinedModule + } + + override fun setupConfigurationFromContext( + configuration: JUnitConfiguration, + context: ConfigurationContext, + sourceElement: Ref + ): Boolean { + if (DumbService.getInstance(context.project).isDumb) return false + + val location = context.location ?: return false + val leaf = location.psiElement + + if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) { + return false + } + + if (leaf.containingFile !is KtFile) { + return false + } + + val ktFile = leaf.containingFile as KtFile + + val targetPlatform = TargetPlatformDetector.getPlatform(ktFile) + if (targetPlatform != JvmPlatform && targetPlatform != TargetPlatform.Common) { + return false + } + + val methodLocation = getTestMethodLocation(leaf) + if (methodLocation != null) { + val originalModule = configuration.configurationModule.module + configuration.beMethodConfiguration(methodLocation) + configuration.restoreOriginalModule(originalModule) + JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location) + fixJdkForCommonModule(ktFile, configuration) + return true + } + + val testClass = getTestClass(leaf) + if (testClass != null) { + val originalModule = configuration.configurationModule.module + configuration.beClassConfiguration(testClass) + configuration.restoreOriginalModule(originalModule) + JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location) + fixJdkForCommonModule(ktFile, configuration) + return true + } + + return false + } + + private fun fixJdkForCommonModule(ktFile: KtFile, configuration: JUnitConfiguration) { + val implModule = ktFile.module?.findJvmImplementationModule() ?: return + val sdk = ModuleRootManager.getInstance(implModule).sdk + if (sdk != null) { + configuration.setModule(implModule) + configuration.isAlternativeJrePathEnabled = true + configuration.alternativeJrePath = sdk.homePath + } + } + + override fun onFirstRun(fromContext: ConfigurationFromContext, context: ConfigurationContext, performRunnable: Runnable) { + val leaf = fromContext.sourceElement + getTestClass(leaf)?.let { testClass -> + val fromContextSubstitute = object : ConfigurationFromContext() { + override fun getConfigurationSettings() = fromContext.configurationSettings + + override fun setConfigurationSettings(configurationSettings: RunnerAndConfigurationSettings) { + fromContext.configurationSettings = configurationSettings + } + + override fun getSourceElement() = testClass + } + // TODO: use TestClassConfigurationProducer when constructor becomes public + return object : AbstractTestClassConfigurationProducer(JUnitConfigurationType.getInstance()){} + .onFirstRun(fromContextSubstitute, context, performRunnable) + } + + super.onFirstRun(fromContext, context, performRunnable) + } + + companion object { + fun getTestClass(leaf: PsiElement): PsiClass? { + val containingFile = leaf.containingFile as? KtFile ?: return null + var ktClass = leaf.getParentOfType(false) + if (!ktClass.isJUnitTestClass()) { + ktClass = getTestClassInFile(containingFile) + } + return ktClass?.toLightClass() + } + + fun getTestMethodLocation(leaf: PsiElement): Location? { + val function = leaf.getParentOfType(false) ?: return null + val owner = PsiTreeUtil.getParentOfType(function, KtFunction::class.java, KtClass::class.java) + + if (owner is KtClass) { + val delegate = owner.toLightClass() ?: return null + val method = delegate.methods.firstOrNull() { it.navigationElement == function } ?: return null + val methodLocation = PsiLocation.fromPsiElement(method) + if (JUnitUtil.isTestMethod(methodLocation, false)) { + return methodLocation + } + } + return null + } + + private fun KtClass?.isJUnitTestClass() = + this?.toLightClass()?.let { JUnitUtil.isTestClass(it, false, true) } ?: false + + private fun getTestClassInFile(ktFile: KtFile) = + ktFile.declarations.filterIsInstance().singleOrNull { it.isJUnitTestClass() } + } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfigurationProducer.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfigurationProducer.kt index 2c1ea6bb116..6a047276ddc 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfigurationProducer.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfigurationProducer.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.idea.run import com.intellij.execution.Location import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.RunConfigurationProducer -import com.intellij.openapi.module.Module import com.intellij.openapi.project.DumbService import com.intellij.openapi.util.Ref import com.intellij.psi.PsiElement @@ -28,13 +27,9 @@ import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName import org.jetbrains.kotlin.idea.MainFunctionDetector -import org.jetbrains.kotlin.idea.caches.project.implementingModules import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.project.TargetPlatformDetector -import org.jetbrains.kotlin.idea.project.platform import org.jetbrains.kotlin.idea.util.ProjectRootsUtil -import org.jetbrains.kotlin.platform.impl.isCommon -import org.jetbrains.kotlin.platform.impl.isJvm import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform @@ -47,7 +42,7 @@ class KotlinRunConfigurationProducer : RunConfigurationProducer): Boolean { val location = context.location ?: return false val module = location.module ?: return false - val container = getEntryPointContainer(location) + val container = getEntryPointContainer(location) ?: return false val startClassFQName = getStartClassFqName(container) ?: return false configuration.setModule(module) @@ -63,7 +58,7 @@ class KotlinRunConfigurationProducer : RunConfigurationProducer null + fun getStartClassFqName(container: KtDeclarationContainer): String? = when(container) { is KtFile -> container.javaFileFacadeFqName.asString() is KtClassOrObject -> { if (!container.isValid) { @@ -126,8 +121,3 @@ class KotlinRunConfigurationProducer : RunConfigurationProducer(KotlinRunConfigurationType.getInstance()) { + + override fun setupConfigurationFromContext(configuration: KotlinRunConfiguration, + context: ConfigurationContext, + sourceElement: Ref): Boolean { + val location = context.location ?: return false + val module = location.module ?: return false + val container = getEntryPointContainer(location) + val startClassFQName = getStartClassFqName(container) ?: return false + + configuration.setModule(module) + configuration.runClass = startClassFQName + configuration.setGeneratedName() + + return true + } + + private fun getEntryPointContainer(location: Location<*>?): KtDeclarationContainer? { + if (location == null) return null + if (DumbService.getInstance(location.project).isDumb) return null + + val module = location.module ?: return null + + if (TargetPlatformDetector.getPlatform(module) !is JvmPlatform && module.findJvmImplementationModule() == null) { + return null + } + val locationElement = location.psiElement + + return getEntryPointContainer(locationElement) + } + + override fun isConfigurationFromContext(configuration: KotlinRunConfiguration, context: ConfigurationContext): Boolean { + val startClassFQName = getStartClassFqName(getEntryPointContainer(context.location)) ?: return false + + return configuration.runClass == startClassFQName && + context.module == configuration.configurationModule.module + } + + companion object { + fun getEntryPointContainer(locationElement: PsiElement): KtDeclarationContainer? { + val psiFile = locationElement.containingFile + if (!(psiFile is KtFile && ProjectRootsUtil.isInProjectOrLibSource(psiFile))) return null + + val mainFunctionDetector = MainFunctionDetector { it.resolveToDescriptorIfAny(BodyResolveMode.FULL) } + + var currentElement = locationElement.declarationContainer(false) + while (currentElement != null) { + var entryPointContainer = currentElement + if (entryPointContainer is KtClass) { + entryPointContainer = entryPointContainer.companionObjects.singleOrNull() + } + if (entryPointContainer != null && mainFunctionDetector.hasMain(entryPointContainer.declarations)) return entryPointContainer + currentElement = (currentElement as PsiElement).declarationContainer(true) + } + + return null + } + + fun getStartClassFqName(container: KtDeclarationContainer?): String? = when(container) { + null -> null + is KtFile -> container.javaFileFacadeFqName.asString() + is KtClassOrObject -> { + if (!container.isValid) { + null + } + else if (container is KtObjectDeclaration && container.isCompanion()) { + val containerClass = container.getParentOfType(true) + containerClass?.toLightClass()?.let { ClassUtil.getJVMClassName(it) } + } + else { + container.toLightClass()?.let { ClassUtil.getJVMClassName(it) } + } + } + else -> throw IllegalArgumentException("Invalid entry-point container: " + (container as PsiElement).text) + } + + private fun PsiElement.declarationContainer(strict: Boolean): KtDeclarationContainer? { + val element = if (strict) + PsiTreeUtil.getParentOfType(this, KtClassOrObject::class.java, KtFile::class.java) + else + PsiTreeUtil.getNonStrictParentOfType(this, KtClassOrObject::class.java, KtFile::class.java) + return element as KtDeclarationContainer? + } + + } +} + +fun Module.findJvmImplementationModule(): Module? { + if (!platform.isCommon) return null + return implementingModules.firstOrNull { it.platform.isJvm } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfigurationProducer.kt.as33 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfigurationProducer.kt.as33 new file mode 100644 index 00000000000..2c1ea6bb116 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/run/KotlinRunConfigurationProducer.kt.as33 @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.run + +import com.intellij.execution.Location +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.actions.RunConfigurationProducer +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiElement +import com.intellij.psi.util.ClassUtil +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName +import org.jetbrains.kotlin.idea.MainFunctionDetector +import org.jetbrains.kotlin.idea.caches.project.implementingModules +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.project.TargetPlatformDetector +import org.jetbrains.kotlin.idea.project.platform +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.platform.impl.isCommon +import org.jetbrains.kotlin.platform.impl.isJvm +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class KotlinRunConfigurationProducer : RunConfigurationProducer(KotlinRunConfigurationType.getInstance()) { + + override fun setupConfigurationFromContext(configuration: KotlinRunConfiguration, + context: ConfigurationContext, + sourceElement: Ref): Boolean { + val location = context.location ?: return false + val module = location.module ?: return false + val container = getEntryPointContainer(location) + val startClassFQName = getStartClassFqName(container) ?: return false + + configuration.setModule(module) + configuration.runClass = startClassFQName + configuration.setGeneratedName() + + return true + } + + private fun getEntryPointContainer(location: Location<*>?): KtDeclarationContainer? { + if (location == null) return null + if (DumbService.getInstance(location.project).isDumb) return null + + val module = location.module ?: return null + + if (TargetPlatformDetector.getPlatform(module) !is JvmPlatform && module.findJvmImplementationModule() == null) { + return null + } + val locationElement = location.psiElement + + return getEntryPointContainer(locationElement) + } + + override fun isConfigurationFromContext(configuration: KotlinRunConfiguration, context: ConfigurationContext): Boolean { + val startClassFQName = getStartClassFqName(getEntryPointContainer(context.location)) ?: return false + + return configuration.runClass == startClassFQName && + context.module == configuration.configurationModule.module + } + + companion object { + fun getEntryPointContainer(locationElement: PsiElement): KtDeclarationContainer? { + val psiFile = locationElement.containingFile + if (!(psiFile is KtFile && ProjectRootsUtil.isInProjectOrLibSource(psiFile))) return null + + val mainFunctionDetector = MainFunctionDetector { it.resolveToDescriptorIfAny(BodyResolveMode.FULL) } + + var currentElement = locationElement.declarationContainer(false) + while (currentElement != null) { + var entryPointContainer = currentElement + if (entryPointContainer is KtClass) { + entryPointContainer = entryPointContainer.companionObjects.singleOrNull() + } + if (entryPointContainer != null && mainFunctionDetector.hasMain(entryPointContainer.declarations)) return entryPointContainer + currentElement = (currentElement as PsiElement).declarationContainer(true) + } + + return null + } + + fun getStartClassFqName(container: KtDeclarationContainer?): String? = when(container) { + null -> null + is KtFile -> container.javaFileFacadeFqName.asString() + is KtClassOrObject -> { + if (!container.isValid) { + null + } + else if (container is KtObjectDeclaration && container.isCompanion()) { + val containerClass = container.getParentOfType(true) + containerClass?.toLightClass()?.let { ClassUtil.getJVMClassName(it) } + } + else { + container.toLightClass()?.let { ClassUtil.getJVMClassName(it) } + } + } + else -> throw IllegalArgumentException("Invalid entry-point container: " + (container as PsiElement).text) + } + + private fun PsiElement.declarationContainer(strict: Boolean): KtDeclarationContainer? { + val element = if (strict) + PsiTreeUtil.getParentOfType(this, KtClassOrObject::class.java, KtFile::class.java) + else + PsiTreeUtil.getNonStrictParentOfType(this, KtClassOrObject::class.java, KtFile::class.java) + return element as KtDeclarationContainer? + } + + } +} + +fun Module.findJvmImplementationModule(): Module? { + if (!platform.isCommon) return null + return implementingModules.firstOrNull { it.platform.isJvm } +}