From 082916be7003cc9680c649c8464be281ddb753d8 Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Sat, 28 Apr 2018 13:14:35 +0300 Subject: [PATCH] Refactoring of ScriptDependenciesUpdater Extract classes for sync and async dependencies loading --- .../kotlin/script/ScriptContentLoader.kt | 2 - .../core/script/ScriptDefinitionsManager.kt | 2 +- .../core/script/ScriptDependenciesManager.kt | 4 +- .../core/script/ScriptDependenciesUpdater.kt | 217 +----------------- .../AsyncScriptDependenciesLoader.kt | 128 +++++++++++ ...omFileAttributeScriptDependenciesLoader.kt | 34 +++ .../dependencies/ScriptDependenciesLoader.kt | 122 ++++++++++ .../SyncScriptDependenciesLoader.kt | 25 ++ 8 files changed, 322 insertions(+), 212 deletions(-) create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt diff --git a/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt b/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt index 3aa844d02da..3d3b24f2651 100644 --- a/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt +++ b/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.script import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile @@ -78,7 +77,6 @@ class ScriptContentLoader(private val project: Project) { catch (e: Throwable) { e.asResolveFailure(scriptDef) } - ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports) return result } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt index 381c50034ba..5e4516dfb40 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt @@ -99,7 +99,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit definitions = definitionsByContributor.values.flattenTo(mutableListOf()).asSequence() clearCache() // TODO: clear by script type/definition - ServiceManager.getService(project, ScriptDependenciesUpdater::class.java).clear() + ServiceManager.getService(project, ScriptDependenciesCache::class.java).clear() } private fun ScriptDefinitionContributor.safeGetDefinitions(): List { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt index 8aac4afd082..5e8dac0e8a9 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesManager.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.io.URLUtil import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependenciesLoader import org.jetbrains.kotlin.script.ScriptDefinitionProvider import org.jetbrains.kotlin.script.ScriptDependenciesProvider import org.jetbrains.kotlin.script.findScriptDefinition @@ -78,8 +79,7 @@ class ScriptDependenciesManager internal constructor( fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) { with(getInstance(project)) { val scriptDefinition = ScriptDefinitionProvider.getInstance(project).findScriptDefinition(virtualFile)!! - cacheUpdater.updateSync(virtualFile, scriptDefinition) - cacheUpdater.notifyRootsChanged() + SyncScriptDependenciesLoader(virtualFile, scriptDefinition, project, shouldNotifyRootsChanged = true).updateDependencies() } } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt index d839be38bdd..5dd5da6f2b0 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt @@ -18,103 +18,52 @@ package org.jetbrains.kotlin.idea.core.script import com.intellij.openapi.application.Application import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.ServiceManager -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.openapi.project.isProjectOrWorkspaceFile import com.intellij.openapi.roots.ProjectRootManager -import com.intellij.openapi.roots.ex.ProjectRootManagerEx -import com.intellij.openapi.util.EmptyRunnable import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.VFileEvent -import com.intellij.openapi.wm.WindowManager -import com.intellij.openapi.wm.ex.StatusBarEx -import com.intellij.openapi.wm.ex.WindowManagerEx -import kotlinx.coroutines.experimental.CoroutineDispatcher -import kotlinx.coroutines.experimental.asCoroutineDispatcher -import kotlinx.coroutines.experimental.launch import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.idea.core.util.EDT -import org.jetbrains.kotlin.idea.core.util.cancelOnDisposal -import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.idea.core.script.dependencies.FromFileAttributeScriptDependenciesLoader +import org.jetbrains.kotlin.idea.core.script.dependencies.ScriptDependenciesLoader import org.jetbrains.kotlin.psi.NotNullableUserDataProperty -import org.jetbrains.kotlin.script.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Executors -import kotlin.script.experimental.dependencies.AsyncDependenciesResolver -import kotlin.script.experimental.dependencies.DependenciesResolver +import org.jetbrains.kotlin.script.ScriptDefinitionProvider +import org.jetbrains.kotlin.script.findScriptDefinition import kotlin.script.experimental.dependencies.ScriptDependencies -import kotlin.script.experimental.dependencies.ScriptReport class ScriptDependenciesUpdater( private val project: Project, private val cache: ScriptDependenciesCache, private val scriptDefinitionProvider: ScriptDefinitionProvider ) { - private val requests = ConcurrentHashMap() - private val contentLoader = ScriptContentLoader(project) - private val asyncUpdatesDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() - private val legacyUpdatesDispatcher = - Executors.newFixedThreadPool( - (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) - ).asCoroutineDispatcher() - private val modifiedScripts = mutableSetOf() init { listenToVfsChanges() } - private class TimeStampedJob(val actualJob: Task.Backgroundable, val timeStamp: TimeStamp) { - fun stampBy(virtualFile: VirtualFile) = ModStampedRequest(virtualFile.modificationStamp, this) - } - - private class ModStampedRequest( - val modificationStamp: Long, - val job: TimeStampedJob? - ) { - fun cancel() { - val actualJob = job?.actualJob ?: return - val frame = (WindowManager.getInstance() as? WindowManagerEx)?.findFrameFor(actualJob.project) - val statusBar = frame?.statusBar as? StatusBarEx ?: return - statusBar.backgroundProcesses.find { it.first == actualJob }?.second?.cancel() - } - } - fun getCurrentDependencies(file: VirtualFile): ScriptDependencies { cache[file]?.let { return it } - tryLoadingFromDisk(file) - performUpdate(file) + val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) ?: return ScriptDependencies.Empty + + FromFileAttributeScriptDependenciesLoader(file, scriptDef, project).updateDependencies() + ScriptDependenciesLoader.updateDependencies(file, scriptDef, project, shouldNotifyRootsChanged = false) return cache[file] ?: ScriptDependencies.Empty } fun reloadModifiedScripts() { for (it in modifiedScripts.filter { cache[it] != null }) { - performUpdate(it) + val scriptDef = scriptDefinitionProvider.findScriptDefinition(it) ?: return + ScriptDependenciesLoader.updateDependencies(it, scriptDef, project, shouldNotifyRootsChanged = true) } modifiedScripts.clear() } - private fun tryLoadingFromDisk(file: VirtualFile): Boolean { - val deserializedDependencies = file.scriptDependencies ?: return false - saveToCache(deserializedDependencies, file) - return true - } - - private fun saveToCache(deserialized: ScriptDependencies, file: VirtualFile) { - val rootsChanged = cache.hasNotCachedRoots(deserialized) - cache.save(file, deserialized) - if (rootsChanged) { - notifyRootsChanged() - } - } - fun requestUpdate(files: Iterable) { files.forEach { file -> if (!file.isValid) { @@ -125,137 +74,6 @@ class ScriptDependenciesUpdater( } } - private fun performUpdate(file: VirtualFile) { - val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) ?: return - when (scriptDef.dependencyResolver) { - is AsyncDependenciesResolver, is LegacyResolverWrapper -> { - updateAsync(file, scriptDef) - } - else -> updateSync(file, scriptDef) - } - } - - private fun updateAsync( - file: VirtualFile, - scriptDefinition: KotlinScriptDefinition - ) { - val path = file.path - val lastRequest = requests[path] - - if (!shouldSendNewRequest(file, lastRequest)) { - return - } - - lastRequest?.cancel() - - requests[path] = sendRequest(file, scriptDefinition).stampBy(file) - return - } - - private fun shouldSendNewRequest(file: VirtualFile, previousRequest: ModStampedRequest?): Boolean { - if (previousRequest == null) return true - - return file.modificationStamp != previousRequest.modificationStamp - } - - private fun sendRequest( - file: VirtualFile, - scriptDef: KotlinScriptDefinition - ): TimeStampedJob { - val currentTimeStamp = TimeStamps.next() - - val newJob = object : Task.Backgroundable(project, "Kotlin: Loading dependencies for ${file.name} ...", true) { - override fun run(indicator: ProgressIndicator) { - if (updateSync(file, scriptDef)) { - notifyRootsChanged() - } - } - } - newJob.queue() - return TimeStampedJob(newJob, currentTimeStamp) - } - - private fun launchAsyncUpdate( - dispatcher: CoroutineDispatcher, - file: VirtualFile, - currentTimeStamp: TimeStamp, - scriptDef: KotlinScriptDefinition, - doResolve: suspend () -> DependenciesResolver.ResolveResult - ) = launch(dispatcher + project.cancelOnDisposal) { - val result = try { - doResolve() - } catch (t: Throwable) { - t.asResolveFailure(scriptDef) - } - - processResult(file, currentTimeStamp, result, scriptDef) - } - - private fun processResult( - file: VirtualFile, - currentTimeStamp: TimeStamp, - result: DependenciesResolver.ResolveResult, - scriptDef: KotlinScriptDefinition - ) { - val lastRequest = requests[file.path] - val lastTimeStamp = lastRequest?.job?.timeStamp - val isLastSentRequest = lastTimeStamp == null || lastTimeStamp == currentTimeStamp - if (isLastSentRequest) { - if (lastRequest != null) { - // no job running atm unless there is a job started while we process this result - requests.replace(file.path, lastRequest, ModStampedRequest(lastRequest.modificationStamp, job = null)) - } - ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports) - val resultingDependencies = result.dependencies?.adjustByDefinition(scriptDef) ?: return - if (saveNewDependencies(resultingDependencies, file, result.reports.any { it.severity == ScriptReport.Severity.FATAL })) { - notifyRootsChanged() - } - } - } - - - fun updateSync(file: VirtualFile, scriptDef: KotlinScriptDefinition): Boolean { - val result = contentLoader.loadContentsAndResolveDependencies(scriptDef, file) - val newDeps = result.dependencies?.adjustByDefinition(scriptDef) ?: ScriptDependencies.Empty - return saveNewDependencies(newDeps, file, result.reports.any { it.severity == ScriptReport.Severity.FATAL }) - } - - private fun saveNewDependencies( - new: ScriptDependencies, - file: VirtualFile, - hasFatalErrors: Boolean - ): Boolean { - val rootsChanged = cache.hasNotCachedRoots(new) - if (cache.save(file, new)) { - if (hasFatalErrors) { - file.scriptDependencies = null - } else { - file.scriptDependencies = new - } - } - return rootsChanged - } - - fun notifyRootsChanged() { - val rootsChangesRunnable = { - runWriteAction { - if (project.isDisposed) return@runWriteAction - - ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) - ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() - } - } - - val application = ApplicationManager.getApplication() - if (application.isUnitTestMode) { - rootsChangesRunnable() - } else { - launch(EDT(project)) { - rootsChangesRunnable() - } - } - } - private fun listenToVfsChanges() { project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() { val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex @@ -279,21 +97,6 @@ class ScriptDependenciesUpdater( } }) } - - fun clear() { - cache.clear() - requests.clear() - } -} - -private data class TimeStamp(private val stamp: Long) { - operator fun compareTo(other: TimeStamp) = this.stamp.compareTo(other.stamp) -} - -private object TimeStamps { - private var current: Long = 0 - - fun next() = TimeStamp(current++) } @set: TestOnly diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt new file mode 100644 index 00000000000..95c9e5afcea --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt @@ -0,0 +1,128 @@ +/* + * 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.core.script.dependencies + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import kotlinx.coroutines.experimental.* +import org.jetbrains.kotlin.idea.core.util.cancelOnDisposal +import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.LegacyResolverWrapper +import org.jetbrains.kotlin.script.asResolveFailure +import java.util.concurrent.Executors +import kotlin.script.experimental.dependencies.AsyncDependenciesResolver +import kotlin.script.experimental.dependencies.DependenciesResolver + +@Suppress("EXPERIMENTAL_FEATURE_WARNING") +class AsyncScriptDependenciesLoader( + file: VirtualFile, + scriptDef: KotlinScriptDefinition, + project: Project +) : ScriptDependenciesLoader(file, scriptDef, project, true) { + + override fun loadDependencies() { + if (!shouldSendNewRequest(lastRequest)) { + return + } + + lastRequest?.cancel() + lastRequest = sendRequest().stampBy(file) + + runBlocking { + lastRequest?.job?.actualJob?.join() + } + } + + override fun shouldUseBackgroundThread() = true + + private var lastRequest: ModStampedRequest? = null + + private val asyncUpdatesDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() + private val legacyUpdatesDispatcher = + Executors.newFixedThreadPool( + (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) + ).asCoroutineDispatcher() + + private fun shouldSendNewRequest(previousRequest: ModStampedRequest?): Boolean { + if (previousRequest == null) return true + return file.modificationStamp != previousRequest.modificationStamp + } + + private fun sendRequest(): TimeStampedJob { + val currentTimeStamp = TimeStamps.next() + + val dependenciesResolver = scriptDef.dependencyResolver + val scriptContents = contentLoader.getScriptContents(scriptDef, file) + val environment = contentLoader.getEnvironment(scriptDef) + val newJob = if (dependenciesResolver is AsyncDependenciesResolver) { + launchAsyncUpdate(asyncUpdatesDispatcher, currentTimeStamp) { + dependenciesResolver.resolveAsync(scriptContents, environment) + } + } else { + assert(dependenciesResolver is LegacyResolverWrapper) + launchAsyncUpdate(legacyUpdatesDispatcher, currentTimeStamp) { + dependenciesResolver.resolve(scriptContents, environment) + } + } + + return TimeStampedJob(newJob, currentTimeStamp) + } + + private fun launchAsyncUpdate( + dispatcher: CoroutineDispatcher, + currentTimeStamp: TimeStamp, + doResolve: suspend () -> DependenciesResolver.ResolveResult + ): Job = launch(dispatcher + project.cancelOnDisposal) { + val result = try { + doResolve() + } catch (t: Throwable) { + t.asResolveFailure(scriptDef) + } + + processResult(currentTimeStamp, result) + } + + private fun processResult( + currentTimeStamp: TimeStamp, + result: DependenciesResolver.ResolveResult + ) { + val lastTimeStamp = lastRequest?.job?.timeStamp + val isLastSentRequest = lastTimeStamp == null || lastTimeStamp == currentTimeStamp + if (isLastSentRequest) { + if (lastRequest != null) { + // no job running atm unless there is a job started while we process this result + lastRequest = ModStampedRequest(lastRequest!!.modificationStamp, job = null) + } + + processResult(result) + } + } + + private class ModStampedRequest(val modificationStamp: Long, val job: TimeStampedJob?) { + fun cancel() = job?.actualJob?.cancel() + } + + private class TimeStampedJob(val actualJob: Job, val timeStamp: TimeStamp) { + fun stampBy(virtualFile: VirtualFile) = + ModStampedRequest( + virtualFile.modificationStamp, + this + ) + } + + private data class TimeStamp(private val stamp: Long) { + operator fun compareTo(other: TimeStamp) = this.stamp.compareTo(other.stamp) + } + + private object TimeStamps { + private var current: Long = 0 + + fun next() = + TimeStamp(current++) + } +} + + diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt new file mode 100644 index 00000000000..31023ffe418 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt @@ -0,0 +1,34 @@ +/* + * 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.core.script.dependencies + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.core.script.scriptDependencies +import org.jetbrains.kotlin.script.KotlinScriptDefinition +import kotlin.script.experimental.dependencies.ScriptDependencies + +class FromFileAttributeScriptDependenciesLoader( + file: VirtualFile, + scriptDef: KotlinScriptDefinition, + project: Project +) : ScriptDependenciesLoader(file, scriptDef, project, true) { + + override fun loadDependencies() { + val deserializedDependencies = file.scriptDependencies ?: return + saveToCache(deserializedDependencies) + } + + private fun saveToCache(deserialized: ScriptDependencies) { + val rootsChanged = cache.hasNotCachedRoots(deserialized) + cache.save(file, deserialized) + if (rootsChanged) { + notifyRootsChanged() + } + } + + override fun shouldUseBackgroundThread() = false +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt new file mode 100644 index 00000000000..4c24cd0c308 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt @@ -0,0 +1,122 @@ +/* + * 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.core.script.dependencies + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ex.ProjectRootManagerEx +import com.intellij.openapi.util.EmptyRunnable +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.containers.SLRUMap +import kotlinx.coroutines.experimental.launch +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesCache +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker +import org.jetbrains.kotlin.idea.core.script.scriptDependencies +import org.jetbrains.kotlin.idea.core.util.EDT +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.script.* +import kotlin.script.experimental.dependencies.AsyncDependenciesResolver +import kotlin.script.experimental.dependencies.DependenciesResolver +import kotlin.script.experimental.dependencies.ScriptDependencies +import kotlin.script.experimental.dependencies.ScriptReport + +abstract class ScriptDependenciesLoader( + protected val file: VirtualFile, + protected val scriptDef: KotlinScriptDefinition, + protected val project: Project, + private val shouldNotifyRootsChanged: Boolean +) { + companion object { + private val loaders = SLRUMap(10, 10) + + fun updateDependencies( + file: VirtualFile, + scriptDef: KotlinScriptDefinition, + project: Project, + shouldNotifyRootsChanged: Boolean + ) { + val existingLoader = loaders[file] + if (existingLoader != null) return existingLoader.updateDependencies() + + val newLoader = when (scriptDef.dependencyResolver) { + is AsyncDependenciesResolver, + is LegacyResolverWrapper -> AsyncScriptDependenciesLoader(file, scriptDef, project) + else -> SyncScriptDependenciesLoader(file, scriptDef, project, shouldNotifyRootsChanged) + } + loaders.put(file, newLoader) + newLoader.updateDependencies() + } + } + + fun updateDependencies() { + if (shouldUseBackgroundThread()) { + object : Task.Backgroundable(project, "Kotlin: Loading dependencies for ${file.name} ...", true) { + override fun run(indicator: ProgressIndicator) { + loadDependencies() + } + }.queue() + } else { + loadDependencies() + } + } + + protected abstract fun loadDependencies() + protected abstract fun shouldUseBackgroundThread(): Boolean + + protected val contentLoader = ScriptContentLoader(project) + protected val cache: ScriptDependenciesCache = ServiceManager.getService(project, ScriptDependenciesCache::class.java) + + protected fun processResult(result: DependenciesResolver.ResolveResult) { + saveDependencies(result) + } + + private fun saveDependencies(result: DependenciesResolver.ResolveResult) { + ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports) + + val newDependencies = result.dependencies?.adjustByDefinition(scriptDef) ?: ScriptDependencies.Empty + + val rootsChanged = cache.hasNotCachedRoots(newDependencies) + if (cache.save(file, newDependencies)) { + if (result.reports.any { it.severity == ScriptReport.Severity.FATAL }) { + file.scriptDependencies = null + } else { + file.scriptDependencies = newDependencies + } + } + + if (rootsChanged) { + notifyRootsChanged() + } + + loaders.remove(file) + } + + @Suppress("EXPERIMENTAL_FEATURE_WARNING") + protected fun notifyRootsChanged() { + if (!shouldNotifyRootsChanged) return + + val rootsChangesRunnable = { + runWriteAction { + if (project.isDisposed) return@runWriteAction + + ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) + ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() + } + } + + val application = ApplicationManager.getApplication() + if (application.isUnitTestMode) { + rootsChangesRunnable() + } else { + launch(EDT(project)) { + rootsChangesRunnable() + } + } + } +} diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt new file mode 100644 index 00000000000..6f0f7eb61f3 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt @@ -0,0 +1,25 @@ +/* + * 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.core.script.dependencies + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.script.KotlinScriptDefinition + +class SyncScriptDependenciesLoader( + file: VirtualFile, + scriptDef: KotlinScriptDefinition, + project: Project, + shouldNotifyRootsChanged: Boolean +) : ScriptDependenciesLoader(file, scriptDef, project, shouldNotifyRootsChanged) { + + override fun loadDependencies() { + val result = contentLoader.loadContentsAndResolveDependencies(scriptDef, file) + processResult(result) + } + + override fun shouldUseBackgroundThread(): Boolean = false +} \ No newline at end of file