Refactoring of ScriptDependenciesUpdater
Extract classes for sync and async dependencies loading
This commit is contained in:
@@ -17,7 +17,6 @@
|
|||||||
package org.jetbrains.kotlin.script
|
package org.jetbrains.kotlin.script
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.components.ServiceManager
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
@@ -78,7 +77,6 @@ class ScriptContentLoader(private val project: Project) {
|
|||||||
catch (e: Throwable) {
|
catch (e: Throwable) {
|
||||||
e.asResolveFailure(scriptDef)
|
e.asResolveFailure(scriptDef)
|
||||||
}
|
}
|
||||||
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -99,7 +99,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
|
|||||||
definitions = definitionsByContributor.values.flattenTo(mutableListOf()).asSequence()
|
definitions = definitionsByContributor.values.flattenTo(mutableListOf()).asSequence()
|
||||||
clearCache()
|
clearCache()
|
||||||
// TODO: clear by script type/definition
|
// 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<KotlinScriptDefinition> {
|
private fun ScriptDefinitionContributor.safeGetDefinitions(): List<KotlinScriptDefinition> {
|
||||||
|
|||||||
+2
-2
@@ -23,6 +23,7 @@ import com.intellij.openapi.vfs.StandardFileSystems
|
|||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.util.io.URLUtil
|
import com.intellij.util.io.URLUtil
|
||||||
import org.jetbrains.annotations.TestOnly
|
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.ScriptDefinitionProvider
|
||||||
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
|
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
|
||||||
import org.jetbrains.kotlin.script.findScriptDefinition
|
import org.jetbrains.kotlin.script.findScriptDefinition
|
||||||
@@ -78,8 +79,7 @@ class ScriptDependenciesManager internal constructor(
|
|||||||
fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) {
|
fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) {
|
||||||
with(getInstance(project)) {
|
with(getInstance(project)) {
|
||||||
val scriptDefinition = ScriptDefinitionProvider.getInstance(project).findScriptDefinition(virtualFile)!!
|
val scriptDefinition = ScriptDefinitionProvider.getInstance(project).findScriptDefinition(virtualFile)!!
|
||||||
cacheUpdater.updateSync(virtualFile, scriptDefinition)
|
SyncScriptDependenciesLoader(virtualFile, scriptDefinition, project, shouldNotifyRootsChanged = true).updateDependencies()
|
||||||
cacheUpdater.notifyRootsChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-207
@@ -18,103 +18,52 @@ package org.jetbrains.kotlin.idea.core.script
|
|||||||
|
|
||||||
import com.intellij.openapi.application.Application
|
import com.intellij.openapi.application.Application
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
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.Project
|
||||||
import com.intellij.openapi.project.isProjectOrWorkspaceFile
|
import com.intellij.openapi.project.isProjectOrWorkspaceFile
|
||||||
import com.intellij.openapi.roots.ProjectRootManager
|
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.util.Key
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.openapi.vfs.VirtualFileManager
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
||||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
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.annotations.TestOnly
|
||||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
import org.jetbrains.kotlin.idea.core.script.dependencies.FromFileAttributeScriptDependenciesLoader
|
||||||
import org.jetbrains.kotlin.idea.core.util.cancelOnDisposal
|
import org.jetbrains.kotlin.idea.core.script.dependencies.ScriptDependenciesLoader
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
|
||||||
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
||||||
import org.jetbrains.kotlin.script.*
|
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import org.jetbrains.kotlin.script.findScriptDefinition
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
|
|
||||||
import kotlin.script.experimental.dependencies.DependenciesResolver
|
|
||||||
import kotlin.script.experimental.dependencies.ScriptDependencies
|
import kotlin.script.experimental.dependencies.ScriptDependencies
|
||||||
import kotlin.script.experimental.dependencies.ScriptReport
|
|
||||||
|
|
||||||
class ScriptDependenciesUpdater(
|
class ScriptDependenciesUpdater(
|
||||||
private val project: Project,
|
private val project: Project,
|
||||||
private val cache: ScriptDependenciesCache,
|
private val cache: ScriptDependenciesCache,
|
||||||
private val scriptDefinitionProvider: ScriptDefinitionProvider
|
private val scriptDefinitionProvider: ScriptDefinitionProvider
|
||||||
) {
|
) {
|
||||||
private val requests = ConcurrentHashMap<String, ModStampedRequest>()
|
|
||||||
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<VirtualFile>()
|
private val modifiedScripts = mutableSetOf<VirtualFile>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
listenToVfsChanges()
|
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 {
|
fun getCurrentDependencies(file: VirtualFile): ScriptDependencies {
|
||||||
cache[file]?.let { return it }
|
cache[file]?.let { return it }
|
||||||
|
|
||||||
tryLoadingFromDisk(file)
|
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) ?: return ScriptDependencies.Empty
|
||||||
performUpdate(file)
|
|
||||||
|
FromFileAttributeScriptDependenciesLoader(file, scriptDef, project).updateDependencies()
|
||||||
|
ScriptDependenciesLoader.updateDependencies(file, scriptDef, project, shouldNotifyRootsChanged = false)
|
||||||
|
|
||||||
return cache[file] ?: ScriptDependencies.Empty
|
return cache[file] ?: ScriptDependencies.Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadModifiedScripts() {
|
fun reloadModifiedScripts() {
|
||||||
for (it in modifiedScripts.filter { cache[it] != null }) {
|
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()
|
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<VirtualFile>) {
|
fun requestUpdate(files: Iterable<VirtualFile>) {
|
||||||
files.forEach { file ->
|
files.forEach { file ->
|
||||||
if (!file.isValid) {
|
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() {
|
private fun listenToVfsChanges() {
|
||||||
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() {
|
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() {
|
||||||
val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex
|
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
|
@set: TestOnly
|
||||||
|
|||||||
+128
@@ -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++)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+34
@@ -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
|
||||||
|
}
|
||||||
+122
@@ -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<VirtualFile, ScriptDependenciesLoader>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user