diff --git a/idea/resources/META-INF/scripting-support.xml b/idea/resources/META-INF/scripting-support.xml
index 6b84786709f..e2c1e454ba1 100644
--- a/idea/resources/META-INF/scripting-support.xml
+++ b/idea/resources/META-INF/scripting-support.xml
@@ -9,5 +9,6 @@
+
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/script/AsyncScriptDefinitionsContributor.kt b/idea/src/org/jetbrains/kotlin/idea/script/AsyncScriptDefinitionsContributor.kt
deleted file mode 100644
index 5629e31c502..00000000000
--- a/idea/src/org/jetbrains/kotlin/idea/script/AsyncScriptDefinitionsContributor.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.script
-
-import com.intellij.openapi.progress.ProgressIndicator
-import com.intellij.openapi.progress.Task
-import com.intellij.openapi.project.Project
-import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionSourceAsContributor
-import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
-import org.jetbrains.kotlin.idea.util.application.executeOnPooledThread
-import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
-import java.util.concurrent.locks.ReentrantReadWriteLock
-import kotlin.concurrent.read
-import kotlin.concurrent.write
-
-abstract class AsyncScriptDefinitionsContributor(protected val project: Project) : ScriptDefinitionSourceAsContributor {
- abstract val progressMessage: String
-
- override fun isReady(): Boolean = _definitions != null
-
- override val definitions: Sequence
- get() {
- definitionsLock.read {
- if (_definitions != null) {
- return _definitions!!.asSequence()
- }
- }
-
- forceStartUpdate = false
- asyncRunUpdateScriptTemplates()
- return emptySequence()
- }
-
- protected fun asyncRunUpdateScriptTemplates() {
- val backgroundTask = inProgressLock.write {
- shouldStartNewUpdate = true
- if (!inProgress) {
- inProgress = true
- return@write DefinitionsCollectorBackgroundTask()
- }
- return@write null
- }
- // TODO: resolve actual reason for the exception below
- try {
- backgroundTask?.queue()
- } catch (e: IllegalStateException) {
- if (e.message?.contains("Calling invokeAndWait from read-action leads to possible deadlock") == false) throw e
- }
- }
-
- protected abstract fun loadScriptDefinitions(previous: List?): List
-
- private var _definitions: List? = null
- private val definitionsLock = ReentrantReadWriteLock()
-
- @Volatile
- protected var forceStartUpdate = false
-
- @Volatile
- protected var shouldStartNewUpdate = false
-
- private var inProgress = false
- private val inProgressLock = ReentrantReadWriteLock()
-
- private inner class DefinitionsCollectorBackgroundTask : Task.Backgroundable(project, progressMessage, true) {
- override fun onFinished() {
- inProgressLock.write {
- inProgress = false
- }
- }
-
- override fun run(indicator: ProgressIndicator) {
- while (true) {
- if (indicator.isCanceled || !shouldStartNewUpdate) {
- return
- }
-
- shouldStartNewUpdate = false
-
- val previousDefinitions = definitionsLock.read {
- if (!forceStartUpdate && _definitions != null) return
-
- _definitions
- }
-
- val newDefinitions = loadScriptDefinitions(previousDefinitions)
-
- val needReload = definitionsLock.write {
- if (newDefinitions != _definitions) {
- _definitions = newDefinitions
- return@write true
- }
- return@write false
- }
-
- if (needReload) {
- if (isHeadless) {
- // If new script definitions found, then ScriptDefinitionsManager.reloadDefinitionsBy should be called
- // This may cause deadlock because Task.Backgroundable.queue executes task synchronously in headless mode
- executeOnPooledThread {
- ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this@AsyncScriptDefinitionsContributor)
- }
- } else {
- ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this@AsyncScriptDefinitionsContributor)
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/script/ScriptTemplatesClassRootsIndex.kt b/idea/src/org/jetbrains/kotlin/idea/script/ScriptTemplatesClassRootsIndex.kt
new file mode 100644
index 00000000000..67b84a664f9
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/script/ScriptTemplatesClassRootsIndex.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2010-2020 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.script
+
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.indexing.*
+import com.intellij.util.io.IOUtil
+import com.intellij.util.io.KeyDescriptor
+import org.jetbrains.kotlin.scripting.definitions.SCRIPT_DEFINITION_MARKERS_PATH
+import java.io.DataInput
+import java.io.DataOutput
+import java.util.*
+
+class ScriptTemplatesClassRootsIndex :
+ ScalarIndexExtension(),
+ FileBasedIndex.InputFilter, KeyDescriptor,
+ DataIndexer {
+
+ companion object {
+ val KEY = ID.create(ScriptTemplatesClassRootsIndex::class.java.canonicalName)
+
+ private val suffix = SCRIPT_DEFINITION_MARKERS_PATH.removeSuffix("/")
+ }
+
+ override fun getName(): ID = KEY
+
+ override fun getIndexer(): DataIndexer = this
+
+ override fun getKeyDescriptor(): KeyDescriptor = this
+
+ override fun getInputFilter(): FileBasedIndex.InputFilter = this
+
+ override fun dependsOnFileContent() = false
+
+ override fun getVersion(): Int = 1
+
+ override fun indexDirectories(): Boolean = true
+
+ override fun acceptInput(file: VirtualFile): Boolean {
+ return file.isDirectory && file.path.endsWith(suffix)
+ }
+
+ override fun save(out: DataOutput, value: String) {
+ IOUtil.writeUTF(out, value)
+ }
+
+ override fun read(input: DataInput): String? {
+ return IOUtil.readUTF(input)
+ }
+
+ override fun getHashCode(value: String): Int {
+ return value.hashCode()
+ }
+
+ override fun isEqual(val1: String?, val2: String?): Boolean {
+ return val1 == val2
+ }
+
+ override fun map(inputData: FileContent): Map {
+ return Collections.singletonMap(inputData.file.url, null)
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/script/ScriptTemplatesFromDependenciesProvider.kt b/idea/src/org/jetbrains/kotlin/idea/script/ScriptTemplatesFromDependenciesProvider.kt
new file mode 100644
index 00000000000..28686c2a9f4
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/script/ScriptTemplatesFromDependenciesProvider.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.script
+
+import com.intellij.ProjectTopics
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ReadAction
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.ModuleRootEvent
+import com.intellij.openapi.roots.ModuleRootListener
+import com.intellij.openapi.roots.OrderEnumerator
+import com.intellij.openapi.roots.ProjectFileIndex
+import com.intellij.openapi.vfs.*
+import com.intellij.util.concurrency.AppExecutorUtil
+import com.intellij.util.indexing.FileBasedIndex
+import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionSourceAsContributor
+import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
+import org.jetbrains.kotlin.idea.core.script.loadDefinitionsFromTemplates
+import org.jetbrains.kotlin.scripting.definitions.SCRIPT_DEFINITION_MARKERS_EXTENSION_WITH_DOT
+import org.jetbrains.kotlin.scripting.definitions.SCRIPT_DEFINITION_MARKERS_PATH
+import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
+import org.jetbrains.kotlin.scripting.definitions.getEnvironment
+import java.io.File
+import java.net.URL
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+import kotlin.script.experimental.host.ScriptingHostConfiguration
+import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
+
+
+class ScriptTemplatesFromDependenciesProvider(private val project: Project) : ScriptDefinitionSourceAsContributor {
+ override val id = "ScriptTemplatesFromDependenciesProvider"
+
+ override fun isReady(): Boolean = _definitions != null
+
+ override val definitions: Sequence
+ get() {
+ definitionsLock.withLock {
+ if (_definitions != null) {
+ return _definitions!!.asSequence()
+ }
+ }
+
+ forceStartUpdate = false
+ asyncRunUpdateScriptTemplates()
+ return emptySequence()
+ }
+
+ init {
+ val connection = project.messageBus.connect()
+ connection.subscribe(
+ ProjectTopics.PROJECT_ROOTS,
+ object : ModuleRootListener {
+ override fun rootsChanged(event: ModuleRootEvent) {
+ if (project.isInitialized) {
+ forceStartUpdate = true
+ asyncRunUpdateScriptTemplates()
+ }
+ }
+ },
+ )
+ }
+
+ private fun asyncRunUpdateScriptTemplates() {
+ definitionsLock.withLock {
+ if (!forceStartUpdate && _definitions != null) return
+ }
+
+ inProgressLock.withLock {
+ if (!inProgress) {
+ inProgress = true
+
+ loadScriptDefinitions()
+ }
+ }
+ }
+
+ private var _definitions: List? = null
+ private val definitionsLock = ReentrantLock()
+
+ private var oldTemplates: TemplatesWithCp? = null
+
+ private data class TemplatesWithCp(
+ val templates: List,
+ val classpath: List,
+ )
+
+ private var inProgress = false
+ private val inProgressLock = ReentrantLock()
+
+ @Volatile
+ private var forceStartUpdate = false
+
+ private fun loadScriptDefinitions() {
+ if (ApplicationManager.getApplication().isUnitTestMode || project.isDefault) {
+ return onEarlyEnd()
+ }
+
+ val templates = LinkedHashSet()
+ val classpath = LinkedHashSet()
+
+ ReadAction
+ .nonBlocking> {
+ val fileManager = VirtualFileManager.getInstance()
+ FileBasedIndex.getInstance().getAllKeys(ScriptTemplatesClassRootsIndex.KEY, project).mapNotNull {
+ val vFile = fileManager.findFileByUrl(it)
+
+ // see SCRIPT_DEFINITION_MARKERS_PATH
+ vFile?.parent?.parent?.parent?.parent
+ }
+ }
+ .inSmartMode(project)
+ .expireWith(project)
+ .submit(AppExecutorUtil.getAppExecutorService())
+ .onSuccess { roots ->
+ val jarFS = JarFileSystem.getInstance()
+ roots.forEach { root ->
+ root.findFileByRelativePath(SCRIPT_DEFINITION_MARKERS_PATH)?.children?.forEach { resourceFile ->
+ if (resourceFile.isValid && !resourceFile.isDirectory) {
+ templates.add(resourceFile.name.removeSuffix(SCRIPT_DEFINITION_MARKERS_EXTENSION_WITH_DOT))
+ }
+ }
+
+ val templateSource = jarFS.getVirtualFileForJar(root) ?: root
+ val module = ProjectFileIndex.getInstance(project).getModuleForFile(templateSource) ?: return@forEach
+
+ // assuming that all libraries are placed into classes roots
+ // TODO: extract exact library dependencies instead of putting all module dependencies into classpath
+ // minimizing the classpath needed to use the template by taking cp only from modules with new templates found
+ // on the other hand the approach may fail if some module contains a template without proper classpath, while
+ // the other has properly configured classpath, so assuming that the dependencies are set correctly everywhere
+ classpath.addAll(
+ OrderEnumerator.orderEntries(module).withoutSdk().classesRoots.mapNotNull {
+ it.canonicalPath?.removeSuffix("!/").let(::File)
+ }
+ )
+ }
+ }
+ .onProcessed {
+ if (templates.isEmpty()) return@onProcessed onEarlyEnd()
+
+ val newTemplates = TemplatesWithCp(templates.toList(), classpath.toList())
+ if (newTemplates == oldTemplates) {
+ inProgressLock.withLock {
+ inProgress = false
+ }
+
+ return@onProcessed
+ }
+
+ oldTemplates = newTemplates
+
+ val hostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
+ getEnvironment {
+ mapOf(
+ "projectRoot" to (project.basePath ?: project.baseDir.canonicalPath)?.let(::File),
+ )
+ }
+ }
+
+ val newDefinitions = loadDefinitionsFromTemplates(
+ templateClassNames = newTemplates.templates,
+ templateClasspath = newTemplates.classpath,
+ baseHostConfiguration = hostConfiguration,
+ )
+
+ val needReload = definitionsLock.withLock {
+ if (newDefinitions != _definitions) {
+ _definitions = newDefinitions
+ return@withLock true
+ }
+ return@withLock false
+ }
+
+ if (needReload) {
+ ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this@ScriptTemplatesFromDependenciesProvider)
+ }
+
+ inProgressLock.withLock {
+ inProgress = false
+ }
+ }
+ }
+
+ private fun onEarlyEnd() {
+ definitionsLock.withLock {
+ _definitions = emptyList()
+ }
+ inProgressLock.withLock {
+ inProgress = false
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/script/scriptsTemplatesFromDependencies.kt b/idea/src/org/jetbrains/kotlin/idea/script/scriptsTemplatesFromDependencies.kt
deleted file mode 100644
index de529d346c2..00000000000
--- a/idea/src/org/jetbrains/kotlin/idea/script/scriptsTemplatesFromDependencies.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2000-2018 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.script
-
-import com.intellij.ProjectTopics
-import com.intellij.openapi.application.runReadAction
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.roots.ModuleRootEvent
-import com.intellij.openapi.roots.ModuleRootListener
-import com.intellij.openapi.roots.OrderEnumerator
-import com.intellij.openapi.vfs.JarFileSystem
-import com.intellij.openapi.vfs.VirtualFile
-import org.jetbrains.kotlin.idea.KotlinBundle
-import org.jetbrains.kotlin.idea.core.script.loadDefinitionsFromTemplates
-import org.jetbrains.kotlin.idea.util.projectStructure.allModules
-import org.jetbrains.kotlin.scripting.definitions.SCRIPT_DEFINITION_MARKERS_EXTENSION_WITH_DOT
-import org.jetbrains.kotlin.scripting.definitions.SCRIPT_DEFINITION_MARKERS_PATH
-import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
-import org.jetbrains.kotlin.scripting.definitions.getEnvironment
-import java.io.File
-import java.util.concurrent.locks.ReentrantReadWriteLock
-import kotlin.concurrent.write
-import kotlin.script.experimental.host.ScriptingHostConfiguration
-import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
-
-class ScriptTemplatesFromDependenciesProvider(project: Project) : AsyncScriptDefinitionsContributor(project) {
-
- private var templates: TemplatesWithCp? = null
- private val templatesLock = ReentrantReadWriteLock()
-
- init {
- val connection = project.messageBus.connect()
- connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
- override fun rootsChanged(event: ModuleRootEvent) {
- if (project.isInitialized) {
- forceStartUpdate = true
- asyncRunUpdateScriptTemplates()
- }
- }
- })
- }
-
- override val id = "ScriptTemplatesFromDependenciesProvider"
- override val progressMessage = KotlinBundle.message("script.progress.text.kotlin.scanning.dependencies.for.script.definitions")
-
- override fun loadScriptDefinitions(previous: List?): List {
- if (project.isDefault) return emptyList()
-
- val templatesCopy = templatesLock.write {
- val newTemplates = scriptDefinitionsFromDependencies(project)
- if (newTemplates != templates) {
- templates = newTemplates
- return@write newTemplates
- }
- return@write null
- }
- if (templatesCopy != null) {
- val hostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
- getEnvironment {
- mapOf(
- "projectRoot" to (project.basePath ?: project.baseDir.canonicalPath)?.let(::File)
- )
- }
- }
- return loadDefinitionsFromTemplates(
- templateClassNames = templatesCopy.templates,
- templateClasspath = templatesCopy.classpath,
- baseHostConfiguration = hostConfiguration
- )
- }
- return previous ?: emptyList()
- }
-}
-
-private data class TemplatesWithCp(
- val templates: List,
- val classpath: List
-)
-
-private fun scriptDefinitionsFromDependencies(project: Project): TemplatesWithCp {
- val templates = LinkedHashSet()
- val classpath = LinkedHashSet()
-
- fun addTemplatesFromRoot(vfile: VirtualFile): Boolean {
- var templatesFound = false
- val root = JarFileSystem.getInstance().getJarRootForLocalFile(vfile) ?: vfile
- if (root.isValid) {
- root.findFileByRelativePath(SCRIPT_DEFINITION_MARKERS_PATH)?.takeIf { it.isDirectory }?.children?.forEach {
- if (it.isValid && !it.isDirectory) {
- templates.add(it.name.removeSuffix(SCRIPT_DEFINITION_MARKERS_EXTENSION_WITH_DOT))
- templatesFound = true
- }
- }
- }
- return templatesFound
- }
-
- runReadAction {
- if (project.isDisposed) return@runReadAction
- // processing source roots from the same project first since the resources are copied to the classes roots only on compilation
- project.allModules().forEach { module ->
- OrderEnumerator.orderEntries(module).withoutDepModules().withoutLibraries().withoutSdk().sourceRoots.forEach { root ->
- if (addTemplatesFromRoot(root)) {
- classpath.addAll(OrderEnumerator.orderEntries(module).withoutSdk().classesRoots.mapNotNull {
- it.canonicalPath?.removeSuffix("!/").let(::File)
- })
- }
- }
- }
-
- project.allModules().forEach { module ->
- // assuming that all libraries are placed into classes roots
- // TODO: extract exact library dependencies instead of putting all module dependencies into classpath
- OrderEnumerator.orderEntries(module).withoutDepModules().withoutSdk().classesRoots.forEach { root ->
- if (addTemplatesFromRoot(root)) {
- // minimizing the classpath needed to use the template by taking cp only from modules with new templates found
- // on the other hand the approach may fail if some module contains a template without proper classpath, while
- // the other has properly configured classpath, so assuming that the dependencies are set correctly everywhere
- classpath.addAll(OrderEnumerator.orderEntries(module).withoutSdk().classesRoots.mapNotNull {
- it.canonicalPath?.removeSuffix("!/").let(::File)
- })
- }
- }
- }
- }
- return TemplatesWithCp(templates.toList(), classpath.toList())
-}
-