Refactor script definitions and resolving/refining infrastructure:

- implement wrappers to wrap old and new API providers and resolvers
- make old API deprecated (with error where possible)
- drop old internal classes related to the old API
- refactor usages accordingly
- fix and add missing features to the scripting API where necessary
This commit is contained in:
Ilya Chernikov
2019-05-12 22:26:37 +02:00
parent e5054f9648
commit e542c9ea84
94 changed files with 1554 additions and 764 deletions
@@ -39,11 +39,7 @@ import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.project.getScriptRelatedModuleInfo
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.script.ScriptTemplatesProvider
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinitionAdapterFromNewAPI
import org.jetbrains.kotlin.scripting.definitions.LazyScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.scripting.definitions.*
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
@@ -53,35 +49,33 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.dependencies.Environment
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.KotlinType
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.dependencies.asSuccess
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContextOrStdlib
import kotlin.script.templates.standard.ScriptTemplateWithArgs
class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinitionProvider() {
private var definitionsByContributor = mutableMapOf<ScriptDefinitionContributor, List<KotlinScriptDefinition>>()
private var definitions: List<KotlinScriptDefinition>? = null
private var definitionsBySource = mutableMapOf<ScriptDefinitionsSource, List<ScriptDefinition>>()
private var definitions: List<ScriptDefinition>? = null
private val failedContributorsHashes = HashSet<Int>()
private val scriptDefinitionsCacheLock = ReentrantReadWriteLock()
private val scriptDefinitionsCache = SLRUMap<String, KotlinScriptDefinition>(10, 10)
private val scriptDefinitionsCache = SLRUMap<String, ScriptDefinition>(10, 10)
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? {
override fun findDefinition(fileName: String): ScriptDefinition? {
if (nonScriptFileName(fileName)) return null
if (!isReady()) return null
val cached = scriptDefinitionsCacheLock.write { scriptDefinitionsCache.get(fileName) }
if (cached != null) return cached
val definition = super.findScriptDefinition(fileName) ?: return null
val definition = super.findDefinition(fileName) ?: return null
scriptDefinitionsCacheLock.write {
scriptDefinitionsCache.put(fileName, definition)
@@ -90,48 +84,43 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
return definition
}
fun reloadDefinitionsBy(contributor: ScriptDefinitionContributor) = lock.write {
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? = findDefinition(fileName)?.legacyDefinition
fun reloadDefinitionsBy(source: ScriptDefinitionsSource) = lock.write {
if (definitions == null) return // not loaded yet
if (contributor !in definitionsByContributor) error("Unknown contributor: ${contributor.id}")
if (source !in definitionsBySource) error("Unknown script definition source: $source")
definitionsByContributor[contributor] = contributor.safeGetDefinitions()
definitionsBySource[source] = source.safeGetDefinitions()
definitions = definitionsByContributor.values.flattenTo(mutableListOf())
definitions = definitionsBySource.values.flattenTo(mutableListOf())
updateDefinitions()
}
fun getDefinitionsBy(contributor: ScriptDefinitionContributor): List<KotlinScriptDefinition> = lock.write {
if (definitions == null) return emptyList() // not loaded yet
if (contributor !in definitionsByContributor) error("Unknown contributor: ${contributor.id}")
return definitionsByContributor[contributor] ?: emptyList()
}
override val currentDefinitions: Sequence<KotlinScriptDefinition>
override val currentDefinitions
get() =
(definitions ?: kotlin.run {
reloadScriptDefinitions()
definitions!!
}).asSequence().filter { KotlinScriptingSettings.getInstance(project).isScriptDefinitionEnabled(it) }
private fun getContributors(): List<ScriptDefinitionContributor> {
private fun getSources(): List<ScriptDefinitionsSource> {
@Suppress("DEPRECATION")
val fromDeprecatedEP = Extensions.getArea(project).getExtensionPoint(ScriptTemplatesProvider.EP_NAME).extensions.toList()
.map(::ScriptTemplatesProviderAdapter)
.map { ScriptTemplatesProviderAdapter(it).asSource() }
val fromNewEp = Extensions.getArea(project).getExtensionPoint(ScriptDefinitionContributor.EP_NAME).extensions.toList()
.map { it.asSource() }
return fromNewEp.dropLast(1) + fromDeprecatedEP + fromNewEp.last()
}
fun reloadScriptDefinitions() = lock.write {
for (contributor in getContributors()) {
val definitions = contributor.safeGetDefinitions()
definitionsByContributor[contributor] = definitions
for (source in getSources()) {
val definitions = source.safeGetDefinitions()
definitionsBySource[source] = definitions
}
definitions = definitionsByContributor.values.flattenTo(mutableListOf())
definitions = definitionsBySource.values.flattenTo(mutableListOf())
updateDefinitions()
}
@@ -140,7 +129,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
updateDefinitions()
}
fun getAllDefinitions(): List<KotlinScriptDefinition> {
fun getAllDefinitions(): List<ScriptDefinition> {
return definitions ?: kotlin.run {
reloadScriptDefinitions()
definitions!!
@@ -148,15 +137,16 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
}
fun isReady(): Boolean {
return definitionsByContributor.keys.all { contributor ->
contributor.isReady()
return definitionsBySource.keys.all { source ->
// TODO: implement another API for readiness checking
(source as? ScriptDefinitionContributor)?.isReady() != false
}
}
override fun getDefaultScriptDefinition(): KotlinScriptDefinition {
override fun getDefaultDefinition(): ScriptDefinition {
val standardScriptDefinitionContributor = ScriptDefinitionContributor.find<StandardScriptDefinitionContributor>(project)
?: error("StandardScriptDefinitionContributor should be registered is plugin.xml")
return standardScriptDefinitionContributor.getDefinitions().last()
return ScriptDefinition.FromLegacy(getScriptingHostConfiguration(), standardScriptDefinitionContributor.getDefinitions().last())
}
private fun updateDefinitions() {
@@ -187,7 +177,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
scriptDefinitionsCacheLock.write { scriptDefinitionsCache.clear() }
// TODO: clear by script type/definition
ServiceManager.getService(project, ScriptDependenciesCache::class.java).clear()
ServiceManager.getService(project, ScriptsCompilationConfigurationCache::class.java).clear()
ApplicationManager.getApplication().invokeLater {
if (!project.isDisposed) {
@@ -196,9 +186,9 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
}
}
private fun ScriptDefinitionContributor.safeGetDefinitions(): List<KotlinScriptDefinition> {
private fun ScriptDefinitionsSource.safeGetDefinitions(): List<ScriptDefinition> {
if (!failedContributorsHashes.contains(this@safeGetDefinitions.hashCode())) try {
return getDefinitions()
return definitions.toList()
} catch (t: Throwable) {
// reporting failed loading only once
LOG.error("[kts] cannot load script definitions using $this", t)
@@ -207,6 +197,15 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
return emptyList()
}
@Suppress("unused") // used in the 182/as33 bunches
fun getDefinitionsBy(source: ScriptDefinitionsSource): List<ScriptDefinition> = lock.write {
if (definitions == null) return emptyList() // not loaded yet
if (source !in definitionsBySource) error("Unknown source: ${source::class.java.name}")
return definitionsBySource[source] ?: emptyList()
}
companion object {
fun getInstance(project: Project): ScriptDefinitionsManager =
ServiceManager.getService(project, ScriptDefinitionProvider::class.java) as ScriptDefinitionsManager
@@ -216,10 +215,11 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
private val LOG = Logger.getInstance("ScriptTemplatesProviders")
// TODO: consider rewriting to return sequence
fun loadDefinitionsFromTemplates(
templateClassNames: List<String>,
templateClasspath: List<File>,
environment: Environment = emptyMap(),
baseHostConfiguration: ScriptingHostConfiguration,
// TODO: need to provide a way to specify this in compiler/repl .. etc
/*
* Allows to specify additional jars needed for DependenciesResolver (and not script template).
@@ -227,7 +227,7 @@ fun loadDefinitionsFromTemplates(
* i.e. gradle resolver may depend on some jars that 'built.gradle.kts' files should not depend on.
*/
additionalResolverClasspath: List<File> = emptyList()
): List<KotlinScriptDefinition> {
): List<ScriptDefinition> {
val classpath = templateClasspath + additionalResolverClasspath
LOG.info("[kts] loading script definitions $templateClassNames using cp: ${classpath.joinToString(File.pathSeparator)}")
val baseLoader = ScriptDefinitionContributor::class.java.classLoader
@@ -240,24 +240,13 @@ fun loadDefinitionsFromTemplates(
val template = loader.loadClass(templateClassName).kotlin
when {
template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>() != null -> {
KotlinScriptDefinitionFromAnnotatedTemplate(
template,
environment,
templateClasspath
)
ScriptDefinition.FromLegacyTemplate(baseHostConfiguration, template, templateClasspath)
}
template.annotations.firstIsInstanceOrNull<kotlin.script.experimental.annotations.KotlinScript>() != null -> {
val hostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
val hostConfiguration = ScriptingHostConfiguration(baseHostConfiguration) {
configurationDependencies(JvmDependency(classpath))
}
KotlinScriptDefinitionAdapterFromNewAPI(
createCompilationConfigurationFromTemplate(
KotlinType(
template
), hostConfiguration, KotlinScriptDefinition::class
),
hostConfiguration
)
ScriptDefinition.FromTemplate(hostConfiguration, template, ScriptDefinition::class)
}
else -> {
LOG.warn("[kts] cannot find a valid script definition annotation on the class $template")
@@ -276,10 +265,16 @@ fun loadDefinitionsFromTemplates(
}
}
@Deprecated("migrating to new configuration refinement: use ScriptDefinitionsSource internally and kotlin.script.experimental.intellij.ScriptDefinitionsProvider as a providing extension point")
interface ScriptDefinitionContributor {
@Deprecated("migrating to new configuration refinement: drop usages")
val id: String
@Deprecated("migrating to new configuration refinement: use ScriptDefinitionsSource instead")
fun getDefinitions(): List<KotlinScriptDefinition>
@Deprecated("migrating to new configuration refinement: drop usages")
fun isReady() = true
companion object {
@@ -289,9 +284,29 @@ interface ScriptDefinitionContributor {
inline fun <reified T> find(project: Project) =
Extensions.getArea(project).getExtensionPoint(ScriptDefinitionContributor.EP_NAME).extensions.filterIsInstance<T>().firstOrNull()
}
}
@Deprecated("migrating to new configuration refinement: use ScriptDefinitionsSource directly instead")
interface ScriptDefinitionSourceAsContributor : ScriptDefinitionContributor, ScriptDefinitionsSource {
override fun getDefinitions(): List<KotlinScriptDefinition> = definitions.map { it.legacyDefinition }.toList()
}
@Deprecated("migrating to new configuration refinement: convert all contributors to ScriptDefinitionsSource/ScriptDefinitionsProvider")
class ScriptDefinitionSourceFromContributor(
val contributor: ScriptDefinitionContributor,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration
) : ScriptDefinitionsSource {
override val definitions: Sequence<ScriptDefinition>
get() =
if (contributor is ScriptDefinitionsSource) contributor.definitions
else contributor.getDefinitions().asSequence().map { ScriptDefinition.FromLegacy(hostConfiguration, it) }
}
fun ScriptDefinitionContributor.asSource(): ScriptDefinitionsSource =
if (this is ScriptDefinitionsSource) this
else ScriptDefinitionSourceFromContributor(this)
class StandardScriptDefinitionContributor(project: Project) : ScriptDefinitionContributor {
private val standardIdeScriptDefinition = StandardIdeScriptDefinition(project)
@@ -18,8 +18,9 @@ package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.util.*
import java.io.DataInput
import java.io.DataOutput
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.*
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.dependencies.ScriptDependencies
var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute(
@@ -43,4 +44,27 @@ var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute(
writeFileList(sources)
}
}
)
)
var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute(
name = "kotlin-script-compilation-configuration",
version = 1,
read = {
val size = readInt()
val bytes = ByteArray(size)
read(bytes, 0, size)
val bis = ByteArrayInputStream(bytes)
ObjectInputStream(bis).use { ois ->
ois.readObject() as ScriptCompilationConfiguration
}
},
write = {
val os = ByteArrayOutputStream()
ObjectOutputStream(os).use { oos ->
oos.writeObject((it as? ScriptCompilationConfigurationWrapper.FromCompilationConfiguration)?.configuration)
}
val bytes = os.toByteArray()
writeInt(bytes.size)
write(bytes)
}
)
@@ -30,8 +30,10 @@ import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependenciesLoader
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.File
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.api.valueOrNull
// NOTE: this service exists exclusively because ScriptDependencyManager
@@ -39,18 +41,21 @@ import kotlin.script.experimental.dependencies.ScriptDependencies
class IdeScriptDependenciesProvider(
private val scriptDependenciesManager: ScriptDependenciesManager
) : ScriptDependenciesProvider {
override fun getScriptDependencies(file: VirtualFile): ScriptDependencies? {
return scriptDependenciesManager.getScriptDependencies(file)
}
override fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesManager.getRefinedCompilationConfiguration(file)
}
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class ScriptDependenciesManager internal constructor(
private val cacheUpdater: ScriptDependenciesUpdater,
private val cache: ScriptDependenciesCache
private val cacheUpdater: ScriptsCompilationConfigurationUpdater,
private val cache: ScriptsCompilationConfigurationCache
) {
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> = toVfsRoots(cacheUpdater.getCurrentDependencies(file).classpath)
fun getScriptDependencies(file: VirtualFile): ScriptDependencies = cacheUpdater.getCurrentDependencies(file)
fun getScriptSdk(file: VirtualFile): Sdk? = getScriptSdk(getScriptDependencies(file))
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
toVfsRoots(cacheUpdater.getCurrentCompilationConfiguration(file)?.valueOrNull()?.dependenciesClassPath.orEmpty())
fun getRefinedCompilationConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? =
cacheUpdater.getCurrentCompilationConfiguration(file)
fun getScriptSdk(file: VirtualFile): Sdk? = Companion.getScriptSdk(getRefinedCompilationConfiguration(file)?.valueOrNull())
fun getScriptDependenciesClassFilesScope(file: VirtualFile) = cache.scriptDependenciesClassFilesScope(file)
@@ -67,35 +72,29 @@ class ScriptDependenciesManager internal constructor(
fun getInstance(project: Project): ScriptDependenciesManager =
ServiceManager.getService(project, ScriptDependenciesManager::class.java)
fun getScriptSdk(dependencies: ScriptDependencies): Sdk? {
fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
// workaround for mismatched gradle wrapper and plugin version
try {
val javaHome = dependencies.javaHome
?.let { VfsUtil.findFileByIoFile(it, true) }
?: return null
return getAllProjectSdks().find { it.homeDirectory == javaHome }
val javaHome = try {
compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) }
} catch (e: Throwable) {
return null
}
null
} ?: return null
return getAllProjectSdks().find { it.homeDirectory == javaHome }
}
fun getScriptDefaultSdk(project: Project): Sdk? {
val projectSdk = getProjectSdk(project)
if (projectSdk != null) return projectSdk
val anyJavaSdk = getAllProjectSdks().find { it.canBeUsedForScript() }
if (anyJavaSdk != null) {
return anyJavaSdk
}
log.warn(
"Default Script SDK is null: " +
"projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " +
"all sdks = ${getAllProjectSdks().joinToString("\n")}"
)
return null
}
fun getScriptDefaultSdk(project: Project): Sdk? =
getProjectSdk(project)
?: getAllProjectSdks().find { it.canBeUsedForScript() }
?: run {
log.warn(
"Default Script SDK is null: " +
"projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " +
"all sdks = ${getAllProjectSdks().joinToString("; ")}"
)
null
}
fun getProjectSdk(project: Project) = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() }
@@ -16,7 +16,7 @@ import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.HyperlinkLabel
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.psi.UserDataProperty
import kotlin.script.experimental.dependencies.DependenciesResolver.ResolveResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
withSelectedEditor(project) { manager ->
@@ -28,14 +28,14 @@ fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
}
fun VirtualFile.addScriptDependenciesNotificationPanel(
resolveResult: ResolveResult,
compilationConfigurationResult: ScriptCompilationConfigurationResult?,
project: Project,
onClick: (ResolveResult) -> Unit
onClick: (ScriptCompilationConfigurationResult?) -> Unit
) {
withSelectedEditor(project) { manager ->
val existingPanel = notificationPanel
if (existingPanel != null) {
if (existingPanel.resolveResult.dependencies == resolveResult.dependencies) {
if (existingPanel.compilationConfigurationResult == compilationConfigurationResult) {
return@withSelectedEditor
}
notificationPanel?.let {
@@ -43,7 +43,7 @@ fun VirtualFile.addScriptDependenciesNotificationPanel(
}
}
val panel = NewScriptDependenciesNotificationPanel(onClick, resolveResult, project)
val panel = NewScriptDependenciesNotificationPanel(onClick, compilationConfigurationResult, project)
notificationPanel = panel
manager.addTopComponent(this, panel)
}
@@ -61,19 +61,19 @@ private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(File
private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty<FileEditor, NewScriptDependenciesNotificationPanel>(Key.create("script.dependencies.panel"))
private class NewScriptDependenciesNotificationPanel(
onClick: (ResolveResult) -> Unit,
val resolveResult: ResolveResult,
onClick: (ScriptCompilationConfigurationResult?) -> Unit,
val compilationConfigurationResult: ScriptCompilationConfigurationResult?,
project: Project
) : EditorNotificationPanel() {
init {
setText("There are new script dependencies available.")
createComponentActionLabel("Apply dependencies") {
onClick(resolveResult)
setText("There is a new script context available.")
createComponentActionLabel("Apply context") {
onClick(compilationConfigurationResult)
}
createComponentActionLabel("Enable auto-reload") {
onClick(resolveResult)
onClick(compilationConfigurationResult)
KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled = true
}
}
@@ -6,17 +6,25 @@
package org.jetbrains.kotlin.idea.core.script
import org.jetbrains.kotlin.script.ScriptTemplatesProvider
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.getEnvironment
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
class ScriptTemplatesProviderAdapter(private val templatesProvider: ScriptTemplatesProvider) : ScriptDefinitionSourceAsContributor {
class ScriptTemplatesProviderAdapter(private val templatesProvider: ScriptTemplatesProvider) :
ScriptDefinitionContributor {
override val id: String
get() = templatesProvider.id
override fun getDefinitions(): List<KotlinScriptDefinition> {
return loadDefinitionsFromTemplates(
templatesProvider.templateClassNames.toList(), templatesProvider.templateClasspath,
templatesProvider.environment.orEmpty(), templatesProvider.additionalResolverClasspath
)
}
override val definitions: Sequence<ScriptDefinition>
get() =
loadDefinitionsFromTemplates(
templatesProvider.templateClassNames.toList(), templatesProvider.templateClasspath,
ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
getEnvironment {
templatesProvider.environment
}
},
templatesProvider.additionalResolverClasspath
).asSequence()
}
@@ -29,15 +29,17 @@ import com.intellij.util.containers.SLRUMap
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.api.valueOrNull
class ScriptDependenciesCache(private val project: Project) {
class ScriptsCompilationConfigurationCache(private val project: Project) {
companion object {
const val MAX_SCRIPTS_CACHED = 50
@@ -45,10 +47,10 @@ class ScriptDependenciesCache(private val project: Project) {
private val cacheLock = ReentrantReadWriteLock()
private val scriptDependenciesCache = SLRUCacheWithLock<ScriptDependencies>()
private val scriptDependenciesCache = SLRUCacheWithLock<ScriptCompilationConfigurationResult>()
private val scriptsModificationStampsCache = SLRUCacheWithLock<Long>()
operator fun get(virtualFile: VirtualFile): ScriptDependencies? = scriptDependenciesCache.get(virtualFile)
operator fun get(virtualFile: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesCache.get(virtualFile)
fun shouldRunDependenciesUpdate(file: VirtualFile): Boolean {
return scriptsModificationStampsCache.replace(file, file.modificationStamp) != file.modificationStamp
@@ -58,10 +60,10 @@ class ScriptDependenciesCache(private val project: Project) {
fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
return scriptsDependenciesClasspathScopeCache.getOrPut(file) {
val dependencies = scriptDependenciesCache.get(file) ?: return@getOrPut GlobalSearchScope.EMPTY_SCOPE
val roots = dependencies.classpath
val compilationConfiguration = scriptDependenciesCache.get(file)?.valueOrNull() ?: return@getOrPut GlobalSearchScope.EMPTY_SCOPE
val roots = compilationConfiguration.dependenciesClassPath
val sdk = ScriptDependenciesManager.getScriptSdk(dependencies)
val sdk = ScriptDependenciesManager.getScriptSdk(compilationConfiguration)
@Suppress("FoldInitializerAndIfToElvis")
if (sdk == null) {
@@ -86,7 +88,8 @@ class ScriptDependenciesCache(private val project: Project) {
.filter { it != ScriptDependenciesManager.getProjectSdk(project) }
.flatMap { it.rootProvider.getFiles(OrderRootType.CLASSES).toList() }
val scriptDependenciesClasspath = scriptDependenciesCache.getAll().flatMap { it.value.classpath }.distinct()
val scriptDependenciesClasspath = scriptDependenciesCache.getAll()
.flatMap { it.value.valueOrNull()?.dependenciesClassPath ?: emptyList() }.distinct()
sdkFiles + ScriptDependenciesManager.toVfsRoots(scriptDependenciesClasspath)
}
@@ -96,7 +99,8 @@ class ScriptDependenciesCache(private val project: Project) {
.filter { it != ScriptDependenciesManager.getProjectSdk(project) }
.flatMap { it.rootProvider.getFiles(OrderRootType.SOURCES).toList() }
val scriptDependenciesSources = scriptDependenciesCache.getAll().flatMap { it.value.sources }.distinct()
val scriptDependenciesSources = scriptDependenciesCache.getAll()
.flatMap { it.value.valueOrNull()?.dependenciesSources ?: emptyList() }.distinct()
sdkSources + ScriptDependenciesManager.toVfsRoots(scriptDependenciesSources)
}
@@ -140,11 +144,10 @@ class ScriptDependenciesCache(private val project: Project) {
}
}
fun hasNotCachedRoots(scriptDependencies: ScriptDependencies): Boolean {
return !allSdks.contains(ScriptDependenciesManager.getScriptSdk(scriptDependencies)) ||
!allDependenciesClassFiles.containsAll(ScriptDependenciesManager.toVfsRoots(scriptDependencies.classpath)) ||
!allDependenciesSources.containsAll(ScriptDependenciesManager.toVfsRoots(scriptDependencies.sources))
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
return !allSdks.contains(ScriptDependenciesManager.getScriptSdk(compilationConfiguration)) ||
!allDependenciesClassFiles.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)) ||
!allDependenciesSources.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesSources))
}
fun clear() {
@@ -155,7 +158,7 @@ class ScriptDependenciesCache(private val project: Project) {
onChange(keys)
}
fun save(virtualFile: VirtualFile, new: ScriptDependencies): Boolean {
fun save(virtualFile: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean {
val old = scriptDependenciesCache.replace(virtualFile, new)
val changed = new != old
if (changed) {
@@ -207,8 +210,8 @@ private class SLRUCacheWithLock<T> {
private val lock = ReentrantReadWriteLock()
val cache = SLRUMap<VirtualFile, T>(
ScriptDependenciesCache.MAX_SCRIPTS_CACHED,
ScriptDependenciesCache.MAX_SCRIPTS_CACHED
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED,
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED
)
fun get(value: VirtualFile): T? = lock.write {
@@ -41,14 +41,15 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.LegacyResolverWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies
class ScriptDependenciesUpdater(
class ScriptsCompilationConfigurationUpdater(
private val project: Project,
private val cache: ScriptDependenciesCache
private val cache: ScriptsCompilationConfigurationCache
) {
private val scriptsQueue = Alarm(Alarm.ThreadToUse.SWING_THREAD, project)
private val scriptChangesListenerDelay = 1400
@@ -64,13 +65,13 @@ class ScriptDependenciesUpdater(
listenForChangesInScripts()
}
fun getCurrentDependencies(file: VirtualFile): ScriptDependencies {
fun getCurrentCompilationConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? {
cache[file]?.let { return it }
updateDependencies(file)
makeRootsChangeIfNeeded()
return cache[file] ?: ScriptDependencies.Empty
return cache[file]
}
fun updateDependenciesIfNeeded(files: List<VirtualFile>): Boolean {
@@ -174,15 +175,15 @@ class ScriptDependenciesUpdater(
return cache[file] != null || file.scriptDependencies != null
}
fun isAsyncDependencyResolver(scriptDef: KotlinScriptDefinition): Boolean {
val dependencyResolver = scriptDef.dependencyResolver
return dependencyResolver is AsyncDependenciesResolver || dependencyResolver is LegacyResolverWrapper
}
fun isAsyncDependencyResolver(scriptDef: ScriptDefinition): Boolean =
scriptDef.asLegacyOrNull<KotlinScriptDefinition>()?.dependencyResolver?.let {
it is AsyncDependenciesResolver || it is LegacyResolverWrapper
} ?: false
companion object {
@JvmStatic
fun getInstance(project: Project): ScriptDependenciesUpdater =
ServiceManager.getService(project, ScriptDependenciesUpdater::class.java)
fun getInstance(project: Project): ScriptsCompilationConfigurationUpdater =
ServiceManager.getService(project, ScriptsCompilationConfigurationUpdater::class.java)
fun areDependenciesCached(file: KtFile): Boolean {
return getInstance(file.project).areDependenciesCached(file.virtualFile)
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
@@ -13,18 +12,16 @@ import com.intellij.openapi.progress.util.BackgroundTaskUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesUpdater
import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.asResolveFailure
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
import kotlin.script.experimental.dependencies.DependenciesResolver
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class AsyncScriptDependenciesLoader internal constructor(project: Project) : ScriptDependenciesLoader(project) {
private val lock = ReentrantReadWriteLock()
@@ -33,7 +30,7 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
override fun isApplicable(file: VirtualFile): Boolean {
val scriptDefinition = file.findScriptDefinition(project) ?: return false
return ScriptDependenciesUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
return ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
}
override fun loadDependencies(file: VirtualFile) {
@@ -73,35 +70,15 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
}
private fun runDependenciesUpdate(file: VirtualFile) {
val scriptDef = runReadAction { file.findScriptDefinition(project) } ?: return
val scriptDef = file.findScriptDefinition(project) ?: return
debug(file) { "start async dependencies loading" }
// runBlocking is using there to avoid loading dependencies asynchronously
// because it leads to starting more than one gradle daemon in case of resolving dependencies in build.gradle.kts
// It is more efficient to use one hot daemon consistently than multiple daemon in parallel
val result = runBlocking {
try {
resolveDependencies(file, scriptDef)
} catch (t: Throwable) {
t.asResolveFailure(scriptDef)
}
}
val result = refineScriptCompilationConfiguration(VirtualFileScriptSource(file), scriptDef, project)
debug(file) { "finish async dependencies loading" }
processResult(result, file, scriptDef)
}
private suspend fun resolveDependencies(file: VirtualFile, scriptDef: KotlinScriptDefinition): DependenciesResolver.ResolveResult {
val dependenciesResolver = scriptDef.dependencyResolver
val scriptContents = contentLoader.getScriptContents(scriptDef, file)
val environment = contentLoader.getEnvironment(scriptDef)
return if (dependenciesResolver is AsyncDependenciesResolver) {
dependenciesResolver.resolveAsync(scriptContents, environment)
} else {
// TODO: shouldn't come here after dropping legacy resolvers, refactor accordingly
dependenciesResolver.resolve(scriptContents, environment)
}
processRefinedConfiguration(result, file)
}
private inner class LoaderBackgroundTask {
@@ -7,8 +7,13 @@ package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.scriptCompilationConfiguration
import org.jetbrains.kotlin.idea.core.script.scriptDependencies
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import kotlin.script.experimental.api.asSuccess
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class FromFileAttributeScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(file: VirtualFile): Boolean {
@@ -16,9 +21,17 @@ class FromFileAttributeScriptDependenciesLoader(project: Project) : ScriptDepend
}
override fun loadDependencies(file: VirtualFile) {
val deserializedDependencies = file.scriptDependencies ?: return
debug(file) { "dependencies from fileAttributes = $deserializedDependencies" }
saveToCache(file, deserializedDependencies)
file.scriptCompilationConfiguration?.let {
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(VirtualFileScriptSource(file), it).apply {
debug(file) { "refined configuration from fileAttributes = $it" }
}
} ?: file.scriptDependencies?.let {
ScriptCompilationConfigurationWrapper.FromLegacy(VirtualFileScriptSource(file), it).apply {
debug(file) { "dependencies from fileAttributes = $it" }
}
}?.let {
saveToCache(file, it.asSuccess(), skipSaveToAttributes = true)
}
}
override fun shouldShowNotification(): Boolean = false
@@ -22,6 +22,7 @@ import com.intellij.psi.ResolveScopeProvider
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
import org.jetbrains.kotlin.idea.core.script.StandardIdeScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
@@ -36,11 +37,13 @@ class KotlinScriptResolveScopeProvider : ResolveScopeProvider() {
// TODO: this should get this particular scripts dependencies
return when {
scriptDefinition == null -> null
// This is a workaround for completion in scripts and REPL to provide module dependencies
scriptDefinition.template == Any::class -> null
scriptDefinition is StandardIdeScriptDefinition -> null
scriptDefinition is KotlinScriptDefinitionFromAnnotatedTemplate -> // TODO: should include the file itself
// This is a workaround for completion in scripts and REPL to provide module dependencies
scriptDefinition.baseClassType.fromClass == Any::class -> null
scriptDefinition.asLegacyOrNull<StandardIdeScriptDefinition>() != null -> null
scriptDefinition is ScriptDefinition.FromConfigurations || scriptDefinition.asLegacyOrNull<KotlinScriptDefinitionFromAnnotatedTemplate>() != null -> {
// TODO: should include the file itself
ScriptDependenciesManager.getInstance(project).getAllScriptsDependenciesClassFilesScope()
}
else -> null
}
}
@@ -18,9 +18,8 @@ class OutsiderFileDependenciesLoader(project: Project) : ScriptDependenciesLoade
override fun loadDependencies(file: VirtualFile) {
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, file) ?: return
val originDependencies = ScriptDependenciesManager.getInstance(project).getScriptDependencies(fileOrigin)
debug(file) { "dependencies for outsider file = $originDependencies" }
saveToCache(file, originDependencies)
val compilationConfiguration = ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(fileOrigin) ?: return
saveToCache(file, compilationConfiguration)
}
override fun shouldShowNotification(): Boolean = false
@@ -15,13 +15,14 @@ import com.intellij.openapi.util.EmptyRunnable
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.*
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptContentLoader
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import org.jetbrains.kotlin.scripting.resolve.adjustByDefinition
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.jvm.compat.mapToLegacyReports
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
abstract class ScriptDependenciesLoader(protected val project: Project) {
abstract fun isApplicable(file: VirtualFile): Boolean
@@ -31,40 +32,40 @@ abstract class ScriptDependenciesLoader(protected val project: Project) {
protected var shouldNotifyRootsChanged = false
protected val contentLoader = ScriptContentLoader(project)
protected val cache: ScriptDependenciesCache = ServiceManager.getService(project, ScriptDependenciesCache::class.java)
protected val cache: ScriptsCompilationConfigurationCache = ServiceManager.getService(project, ScriptsCompilationConfigurationCache::class.java)
private val reporter: ScriptReportSink = ServiceManager.getService(project, ScriptReportSink::class.java)
protected fun processResult(result: DependenciesResolver.ResolveResult, file: VirtualFile, scriptDef: KotlinScriptDefinition) {
debug(file) { "dependencies from ${this.javaClass} received = $result" }
protected fun processRefinedConfiguration(result: ScriptCompilationConfigurationResult, file: VirtualFile) {
debug(file) { "refined script compilation configuration from ${this.javaClass} received = $result" }
if (cache[file] == null) {
saveDependencies(result, file, scriptDef)
attachReportsIfChanged(result, file, scriptDef)
val oldResult = cache[file]
if (oldResult == null) {
save(result, file)
attachReportsIfChanged(result, file)
return
}
val newDependencies = result.dependencies?.adjustByDefinition(scriptDef)
if (cache[file] != newDependencies) {
if (oldResult != result) {
if (shouldShowNotification() && !ApplicationManager.getApplication().isUnitTestMode) {
debug(file) {
"dependencies changed, notification was shown: old = ${cache[file]}, new = $newDependencies"
"dependencies changed, notification is shown: old = $oldResult, new = $result"
}
file.addScriptDependenciesNotificationPanel(result, project) {
saveDependencies(it, file, scriptDef)
attachReportsIfChanged(it, file, scriptDef)
save(it, file)
attachReportsIfChanged(result, file)
submitMakeRootsChange()
}
} else {
debug(file) {
"dependencies changed, new dependencies were applied automatically: old = ${cache[file]}, new = $newDependencies"
"dependencies changed, new dependencies are applied automatically: old = $oldResult, new = $result"
}
saveDependencies(result, file, scriptDef)
attachReportsIfChanged(result, file, scriptDef)
save(result, file)
attachReportsIfChanged(result, file)
}
} else {
attachReportsIfChanged(result, file, scriptDef)
attachReportsIfChanged(result, file)
if (shouldShowNotification()) {
file.removeScriptDependenciesNotificationPanel(project)
@@ -72,28 +73,36 @@ abstract class ScriptDependenciesLoader(protected val project: Project) {
}
}
private fun attachReportsIfChanged(result: DependenciesResolver.ResolveResult, file: VirtualFile, scriptDef: KotlinScriptDefinition) {
private fun attachReportsIfChanged(result: ResultWithDiagnostics<*>, file: VirtualFile) {
if (file.getUserData(IdeScriptReportSink.Reports) != result.reports.takeIf { it.isNotEmpty() }) {
reporter.attachReports(file, result.reports)
reporter.attachReports(file, result.reports.mapToLegacyReports())
}
}
private fun saveDependencies(result: DependenciesResolver.ResolveResult, file: VirtualFile, scriptDef: KotlinScriptDefinition) {
private fun save(compilationConfigurationResult: ScriptCompilationConfigurationResult?, file: VirtualFile) {
if (shouldShowNotification()) {
file.removeScriptDependenciesNotificationPanel(project)
}
val dependencies = result.dependencies?.adjustByDefinition(scriptDef) ?: return
saveToCache(file, dependencies)
if (compilationConfigurationResult != null) {
saveToCache(file, compilationConfigurationResult)
}
}
protected fun saveToCache(file: VirtualFile, dependencies: ScriptDependencies) {
val rootsChanged = cache.hasNotCachedRoots(dependencies)
if (cache.save(file, dependencies)) {
protected fun saveToCache(
file: VirtualFile, compilationConfigurationResult: ScriptCompilationConfigurationResult, skipSaveToAttributes: Boolean = false
) {
val rootsChanged = compilationConfigurationResult.valueOrNull()?.let { cache.hasNotCachedRoots(it) } ?: false
if (cache.save(file, compilationConfigurationResult)
&& !skipSaveToAttributes
&& compilationConfigurationResult is ResultWithDiagnostics.Success
) {
debug(file) {
"dependencies were saved to file attributes: dependencies = $dependencies"
"refined configuration is saved to file attributes: $compilationConfigurationResult"
}
file.scriptDependencies = dependencies
if (compilationConfigurationResult.value is ScriptCompilationConfigurationWrapper.FromLegacy)
file.scriptDependencies = compilationConfigurationResult.value.legacyDependencies
else
file.scriptCompilationConfiguration = compilationConfigurationResult.value.configuration
}
if (rootsChanged) {
@@ -101,6 +110,7 @@ abstract class ScriptDependenciesLoader(protected val project: Project) {
}
}
open fun notifyRootsChanged(): Boolean = submitMakeRootsChange()
protected fun submitMakeRootsChange(): Boolean {
@@ -7,21 +7,23 @@ package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesUpdater
import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
class SyncScriptDependenciesLoader internal constructor(project: Project) : ScriptDependenciesLoader(project) {
class SyncScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(file: VirtualFile): Boolean {
val scriptDefinition = file.findScriptDefinition(project) ?: return false
return !ScriptDependenciesUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
return !ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
}
override fun loadDependencies(file: VirtualFile) {
val scriptDef = file.findScriptDefinition(project) ?: return
debug(file) { "start sync dependencies loading" }
val result = contentLoader.loadContentsAndResolveDependencies(scriptDef, file)
val result = refineScriptCompilationConfiguration(VirtualFileScriptSource(file), scriptDef, project)
debug(file) { "finish sync dependencies loading" }
processResult(result, file, scriptDef)
processRefinedConfiguration(result, file)
}
override fun shouldShowNotification(): Boolean = false
@@ -14,7 +14,7 @@ import com.intellij.util.addOptionTag
import com.intellij.util.attribute
import com.intellij.util.getAttributeBooleanValue
import org.jdom.Element
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
@State(
name = "KotlinScriptingSettings",
@@ -72,22 +72,22 @@ class KotlinScriptingSettings : PersistentStateComponent<Element> {
}
}
fun setOrder(scriptDefinition: KotlinScriptDefinition, order: Int) {
fun setOrder(scriptDefinition: ScriptDefinition, order: Int) {
scriptDefinitions[scriptDefinition.toKey()] = scriptDefinitions[scriptDefinition.toKey()]?.copy(order = order) ?:
KotlinScriptDefinitionValue(order)
}
fun setEnabled(scriptDefinition: KotlinScriptDefinition, isEnabled: Boolean) {
fun setEnabled(scriptDefinition: ScriptDefinition, isEnabled: Boolean) {
scriptDefinitions[scriptDefinition.toKey()] = scriptDefinitions[scriptDefinition.toKey()]?.copy(isEnabled = isEnabled) ?:
KotlinScriptDefinitionValue(scriptDefinitions.size, isEnabled)
}
fun getScriptDefinitionOrder(scriptDefinition: KotlinScriptDefinition): Int {
fun getScriptDefinitionOrder(scriptDefinition: ScriptDefinition): Int {
return scriptDefinitions[scriptDefinition.toKey()]?.order ?: Integer.MAX_VALUE
}
fun isScriptDefinitionEnabled(scriptDefinition: KotlinScriptDefinition): Boolean {
fun isScriptDefinitionEnabled(scriptDefinition: ScriptDefinition): Boolean {
return scriptDefinitions[scriptDefinition.toKey()]?.isEnabled ?: true
}
@@ -99,8 +99,8 @@ class KotlinScriptingSettings : PersistentStateComponent<Element> {
getAttributeValue(KotlinScriptDefinitionKey::className.name)
)
private fun KotlinScriptDefinition.toKey() =
KotlinScriptDefinitionKey(this.name, this::class.qualifiedName ?: "unknown")
private fun ScriptDefinition.toKey() =
KotlinScriptDefinitionKey(this.name, this.definitionId)
private fun Element.addScriptDefinitionContentElement(definition: KotlinScriptDefinitionKey, settings: KotlinScriptDefinitionValue) {
addElement(SCRIPT_DEFINITION_TAG).apply {