diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt index cf724d971aa..f72b77e6030 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt @@ -15,16 +15,15 @@ import com.intellij.ui.EditorNotifications import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.jetbrains.kotlin.idea.core.script.* -import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationSnapshot -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache +import org.jetbrains.kotlin.idea.core.script.configuration.cache.* import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache -import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache import org.jetbrains.kotlin.idea.core.script.configuration.listener.DefaultScriptChangeListener import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener -import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier -import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater import org.jetbrains.kotlin.idea.core.script.configuration.loader.* +import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader +import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptOutsiderFileConfigurationLoader import org.jetbrains.kotlin.idea.core.script.configuration.utils.BackgroundExecutor +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.psi.KtFile @@ -34,6 +33,7 @@ import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock import kotlin.script.experimental.api.ScriptDiagnostic +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater /** * Standard implementation of scripts configuration loading and caching @@ -100,10 +100,12 @@ internal class DefaultScriptConfigurationManager(project: Project) : private val loaders: List = listOf( ScriptOutsiderFileConfigurationLoader(project), ScriptConfigurationFileAttributeCache(project), + GradleScriptConfigurationLoader(project), DefaultScriptConfigurationLoader(project) ) private val listeners: List = listOf( + GradleScriptListener(), DefaultScriptChangeListener() ) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/gradle.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/gradle.kt new file mode 100644 index 00000000000..6502ab2d785 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/gradle.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.core.script.configuration + +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiRecursiveElementVisitor +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.impl.source.tree.LeafPsiElement +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs +import org.jetbrains.kotlin.idea.core.script.configuration.listener.DefaultScriptChangeListener +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater +import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader +import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext +import org.jetbrains.kotlin.idea.core.util.EDT +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtScriptInitializer +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition + +class GradleScriptListener : DefaultScriptChangeListener() { + private val listenGradleRelatedFiles = false // todo + + override fun editorActivated(file: KtFile, updater: ScriptConfigurationUpdater): Boolean { + if (!isGradleKotlinScript(file.virtualFile)) return false + if (listenGradleRelatedFiles) { + updater.ensureUpToDatedConfigurationSuggested(file) + // todo: force reload if related files changed + } else { + // configuration will be reloaded after editor activation, even it is already up-to-date + // this is required for Gradle scripts, since it's classpath may depend on other files (`.properties` for example) + updater.forceConfigurationReload(file) + } + + return true + } +} + +class GradleScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) { + private val useProjectImport: Boolean + get() = Registry.`is`("kotlin.gradle.scripts.useIdeaProjectImport", false) + + override fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean { + return if (useProjectImport) false else super.shouldRunInBackground(scriptDefinition) + } + + override fun loadDependencies( + isFirstLoad: Boolean, + virtualFile: VirtualFile, + scriptDefinition: ScriptDefinition, + context: ScriptConfigurationLoadingContext + ): Boolean { + if (!isGradleKotlinScript(virtualFile)) return false + + if (useProjectImport) { + // do nothing, project import notification will be already showed + // and configuration for gradle build scripts will be saved at the end of import + // todo: use default configuration loader for out-of-project scripts? + + return true + } else { + // Gradle read files from FS + GlobalScope.launch(EDT(project)) { + runWriteAction { + FileDocumentManager.getInstance().saveAllDocuments() + } + } + + return super.loadDependencies(isFirstLoad, virtualFile, scriptDefinition, context) + } + } + + override fun getInputsStamp(file: KtFile): CachedConfigurationInputs { + return getGradleScriptInputsStamp(project, file.virtualFile, file) ?: super.getInputsStamp(file) + } +} + +data class GradleKotlinScriptConfigurationInputs( + val buildScriptAndPluginsSections: String +) : CachedConfigurationInputs { + override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean { + val actualStamp = getGradleScriptInputsStamp(project, file, ktFile) + return actualStamp == this + } +} + +fun isGradleKotlinScript(virtualFile: VirtualFile) = virtualFile.name.endsWith(".gradle.kts") + +fun getGradleScriptInputsStamp( + project: Project, + file: VirtualFile, + givenKtFile: KtFile? = null +): GradleKotlinScriptConfigurationInputs? { + if (!isGradleKotlinScript(file)) return null + + return runReadAction { + val ktFile = givenKtFile ?: PsiManager.getInstance(project).findFile(file) as? KtFile + + if (ktFile != null) { + val result = StringBuilder() + ktFile.script?.blockExpression + ?.getChildrenOfType() + ?.forEach { + val call = it.children.singleOrNull() as? KtCallExpression + val callRef = call?.firstChild?.text + if (callRef == "buildscript" || callRef == "plugins") { + result.append(callRef) + val lambda = call.lambdaArguments.singleOrNull() + lambda?.accept(object : PsiRecursiveElementVisitor(false) { + override fun visitElement(element: PsiElement) { + super.visitElement(element) + when (element) { + is PsiWhiteSpace -> if (element.text.contains("\n")) result.append("\n") + is LeafPsiElement -> result.append(element.text) + } + } + }) + result.append("\n") + } + } + + GradleKotlinScriptConfigurationInputs(result.toString()) + } else null + } +} \ No newline at end of file diff --git a/idea/resources/META-INF/extensions/ide.xml b/idea/resources/META-INF/extensions/ide.xml index e1e07ff592f..f0ed001b820 100644 --- a/idea/resources/META-INF/extensions/ide.xml +++ b/idea/resources/META-INF/extensions/ide.xml @@ -78,11 +78,11 @@ implementation="org.jetbrains.kotlin.idea.script.ScriptTemplatesFromDependenciesProvider"/> + order="last" + implementation="org.jetbrains.kotlin.idea.core.script.StandardScriptDefinitionContributor"/> + implementation="org.jetbrains.kotlin.console.ConsoleScriptDefinitionContributor"/> @@ -93,7 +93,7 @@ - + +