Revert due to test failing in 201
This commit is contained in:
+21
-8
@@ -27,6 +27,8 @@ import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.io.URLUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.UserDataProperty
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
|
||||
@@ -67,6 +69,12 @@ interface ScriptConfigurationManager {
|
||||
*/
|
||||
fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper?
|
||||
|
||||
/**
|
||||
* Reload the configuration for [file] even it is already loaded.
|
||||
* [loader] is used to load configuration. Other loaders aren't taken into account.
|
||||
*/
|
||||
fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper?
|
||||
|
||||
@Deprecated("Use getScriptClasspath(KtFile) instead")
|
||||
fun getScriptClasspath(file: VirtualFile): List<VirtualFile>
|
||||
|
||||
@@ -90,9 +98,14 @@ interface ScriptConfigurationManager {
|
||||
fun isConfigurationLoadingInProgress(file: KtFile): Boolean
|
||||
|
||||
/**
|
||||
* Update caches that depends on script definitions and do update if necessary
|
||||
* See [ScriptConfigurationUpdater].
|
||||
*/
|
||||
fun updateScriptDefinitionReferences()
|
||||
val updater: ScriptConfigurationUpdater
|
||||
|
||||
/**
|
||||
* Clear all caches and re-highlighting opened scripts
|
||||
*/
|
||||
fun clearConfigurationCachesAndRehighlight()
|
||||
|
||||
///////////////
|
||||
// classpath roots info:
|
||||
@@ -113,14 +126,14 @@ interface ScriptConfigurationManager {
|
||||
ServiceManager.getService(project, ScriptConfigurationManager::class.java)
|
||||
|
||||
fun toVfsRoots(roots: Iterable<File>): List<VirtualFile> {
|
||||
return roots.mapNotNull { classpathEntryToVfs(it) }
|
||||
return roots.mapNotNull { it.classpathEntryToVfs() }
|
||||
}
|
||||
|
||||
fun classpathEntryToVfs(file: File): VirtualFile? {
|
||||
private fun File.classpathEntryToVfs(): VirtualFile? {
|
||||
val res = when {
|
||||
!file.exists() -> null
|
||||
file.isDirectory -> StandardFileSystems.local()?.findFileByPath(file.canonicalPath)
|
||||
file.isFile -> StandardFileSystems.jar()?.findFileByPath(file.canonicalPath + URLUtil.JAR_SEPARATOR)
|
||||
!exists() -> null
|
||||
isDirectory -> StandardFileSystems.local()?.findFileByPath(this.canonicalPath)
|
||||
isFile -> StandardFileSystems.jar()?.findFileByPath(this.canonicalPath + URLUtil.JAR_SEPARATOR)
|
||||
else -> null
|
||||
}
|
||||
// TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" })
|
||||
@@ -137,7 +150,7 @@ interface ScriptConfigurationManager {
|
||||
@TestOnly
|
||||
fun clearCaches(project: Project) {
|
||||
(getInstance(project) as CompositeScriptConfigurationManager).default
|
||||
.updateScriptDefinitions()
|
||||
.clearCaches()
|
||||
}
|
||||
|
||||
fun clearManualConfigurationLoadingIfNeeded(file: VirtualFile) {
|
||||
|
||||
+1
-8
@@ -27,7 +27,6 @@ import com.intellij.util.containers.SLRUMap
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.getScriptRelatedModuleInfo
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
|
||||
@@ -65,15 +64,9 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
|
||||
private val scriptDefinitionsCacheLock = ReentrantLock()
|
||||
private val scriptDefinitionsCache = SLRUMap<String, ScriptDefinition>(10, 10)
|
||||
|
||||
val configurations = (ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager)
|
||||
|
||||
override fun findDefinition(script: SourceCode): ScriptDefinition? {
|
||||
val locationId = script.locationId ?: return null
|
||||
if (nonScriptId(locationId)) return null
|
||||
|
||||
val fastPath = configurations.tryGetScriptDefinitionFast(locationId)
|
||||
if (fastPath != null) return fastPath
|
||||
|
||||
if (!isReady()) return null
|
||||
|
||||
val cached = scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.get(locationId) }
|
||||
@@ -197,7 +190,7 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
|
||||
scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.clear() }
|
||||
|
||||
// TODO: clear by script type/definition
|
||||
ScriptConfigurationManager.getInstance(project).updateScriptDefinitionReferences()
|
||||
ScriptConfigurationManager.getInstance(project).clearConfigurationCachesAndRehighlight()
|
||||
}
|
||||
|
||||
private fun ScriptDefinitionsSource.safeGetDefinitions(): List<ScriptDefinition> {
|
||||
|
||||
+76
-90
@@ -6,9 +6,6 @@
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration
|
||||
|
||||
import com.intellij.ProjectTopics
|
||||
import com.intellij.codeInsight.daemon.OutsidersPsiFileSupport
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootEvent
|
||||
@@ -18,106 +15,69 @@ import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangesNotifier
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.definitions.isNonScript
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||
|
||||
/**
|
||||
* The [CompositeScriptConfigurationManager] will provide redirection of [ScriptConfigurationManager] calls to the
|
||||
* custom [ScriptingSupport] or [DefaultScriptingSupport] if that script lack of custom [ScriptingSupport].
|
||||
*
|
||||
* The [ScriptConfigurationManager] is implemented by caching all scripts using the [ScriptClassRootsCache].
|
||||
* The [ScriptClassRootsCache] is always available and never blacked by the writer, as all writes occurs
|
||||
* using the copy-on-write strategy.
|
||||
*
|
||||
* This cache are loaded on start and will be updating asynchronously using the [updater].
|
||||
* Sync updates still my be occurred from the [DefaultScriptingSupport].
|
||||
*
|
||||
* We are also watching all script documents:
|
||||
* [notifier] will call first applicable [ScriptChangesNotifier.listeners] when editor is activated or document changed.
|
||||
* Listener should do something to invalidate configuration and schedule reloading.
|
||||
*/
|
||||
class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigurationManager {
|
||||
@Suppress("unused")
|
||||
private val notifier = ScriptChangesNotifier(project)
|
||||
private val notifier = ScriptChangesNotifier(project, updater)
|
||||
|
||||
val updater = ScriptClassRootsUpdater(project, this)
|
||||
private val providers = ScriptingSupport.Provider.EPN.getPoint(project).extensionList
|
||||
|
||||
private val plugins = ScriptingSupport.EPN.getPoint(project).extensionList
|
||||
val default = DefaultScriptingSupport(project)
|
||||
|
||||
val default = DefaultScriptingSupport(this)
|
||||
private val managers
|
||||
get() = mutableListOf<ScriptingSupport>().also { managers ->
|
||||
managers.add(default)
|
||||
providers.forEach { managers.addAll(it.all) }
|
||||
}
|
||||
|
||||
fun tryGetScriptDefinitionFast(locationId: String): ScriptDefinition? {
|
||||
return classpathRoots.getLightScriptInfo(locationId)?.definition
|
||||
}
|
||||
private fun getRelatedManager(file: VirtualFile): ScriptingSupport =
|
||||
providers.firstNotNullResult { it.getSupport(file) } ?: default
|
||||
|
||||
private fun getRelatedManager(file: KtFile): ScriptingSupport =
|
||||
getRelatedManager(file.originalFile.virtualFile)
|
||||
|
||||
private fun getOrLoadConfiguration(
|
||||
virtualFile: VirtualFile,
|
||||
preloadedKtFile: KtFile? = null
|
||||
): ScriptCompilationConfigurationWrapper? {
|
||||
val scriptConfiguration = classpathRoots.getScriptConfiguration(virtualFile)
|
||||
if (scriptConfiguration != null) return scriptConfiguration
|
||||
): ScriptCompilationConfigurationWrapper? =
|
||||
getRelatedManager(virtualFile).getOrLoadConfiguration(virtualFile, preloadedKtFile)
|
||||
|
||||
// check that this script should be loaded later in special way (e.g. gradle project import)
|
||||
// (and not for syntactic diff files)
|
||||
if (!OutsidersPsiFileSupport.isOutsiderFile(virtualFile)) {
|
||||
if (plugins.any { it.isApplicable(virtualFile) }) return null
|
||||
}
|
||||
|
||||
return default.getOrLoadConfiguration(virtualFile, preloadedKtFile)
|
||||
}
|
||||
|
||||
override fun getConfiguration(file: KtFile) =
|
||||
getOrLoadConfiguration(file.originalFile.virtualFile, file)
|
||||
override fun getConfiguration(file: KtFile) = getOrLoadConfiguration(file.originalFile.virtualFile, file)
|
||||
|
||||
override fun hasConfiguration(file: KtFile): Boolean =
|
||||
classpathRoots.contains(file.originalFile.virtualFile)
|
||||
getRelatedManager(file).hasCachedConfiguration(file)
|
||||
|
||||
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean =
|
||||
plugins.firstOrNull { it.isApplicable(file.originalFile.virtualFile) }?.isConfigurationLoadingInProgress(file)
|
||||
?: default.isConfigurationLoadingInProgress(file)
|
||||
getRelatedManager(file).isConfigurationLoadingInProgress(file)
|
||||
|
||||
@Volatile
|
||||
private var classpathRoots: ScriptClassRootsCache = recreateRootsCache()
|
||||
override val updater: ScriptConfigurationUpdater
|
||||
get() = object : ScriptConfigurationUpdater {
|
||||
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) =
|
||||
getRelatedManager(file).updater.ensureUpToDatedConfigurationSuggested(file)
|
||||
|
||||
fun getLightScriptInfo(file: String): ScriptClassRootsCache.LightScriptInfo? =
|
||||
classpathRoots.getLightScriptInfo(file)
|
||||
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean =
|
||||
files.groupBy { getRelatedManager(it) }.all { (manager, files) ->
|
||||
manager.updater.ensureConfigurationUpToDate(files)
|
||||
}
|
||||
|
||||
private fun recreateRootsCache(): ScriptClassRootsCache {
|
||||
val builder = ScriptClassRootsCache.Builder(project)
|
||||
default.collectConfigurations(builder)
|
||||
plugins.forEach { it.collectConfigurations(builder) }
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun collectRootsAndCheckNew(): ScriptClassRootsCache.Updates {
|
||||
val old = classpathRoots
|
||||
val new = recreateRootsCache()
|
||||
classpathRoots = new
|
||||
return new.diff(old)
|
||||
}
|
||||
|
||||
override fun updateScriptDefinitionReferences() {
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
|
||||
default.updateScriptDefinitions()
|
||||
|
||||
if (classpathRoots.customDefinitionsUsed) {
|
||||
updater.ensureUpdateScheduled()
|
||||
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) =
|
||||
getRelatedManager(file).updater.suggestToUpdateConfigurationIfOutOfDate(file)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val connection = project.messageBus.connect(project)
|
||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
||||
override fun rootsChanged(event: ModuleRootEvent) {
|
||||
if (event.isCausedByFileTypesChange) return
|
||||
|
||||
if (classpathRoots.hasInvalidSdk(project)) {
|
||||
updater.ensureUpdateScheduled()
|
||||
managers.forEach {
|
||||
it.clearClassRootsCaches(project)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -128,39 +88,49 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
|
||||
* Loads script configuration if classpath roots don't contain [file] yet
|
||||
*/
|
||||
private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache {
|
||||
try {
|
||||
// we should run default loader if this [file] is not cached in [classpathRoots]
|
||||
// and it is not supported by any of [plugins]
|
||||
// getOrLoadConfiguration will do this
|
||||
// (despite that it's result becomes unused, it still may populate [classpathRoots])
|
||||
getOrLoadConfiguration(file, null)
|
||||
} catch (cancelled: ProcessCanceledException) {
|
||||
// read actions may be cancelled if we are called by impatient reader
|
||||
val manager = getRelatedManager(file)
|
||||
|
||||
val classpathRoots = manager.classpathRoots
|
||||
if (classpathRoots.contains(file)) {
|
||||
return classpathRoots
|
||||
}
|
||||
|
||||
return this.classpathRoots
|
||||
getOrLoadConfiguration(file)
|
||||
|
||||
return manager.classpathRoots
|
||||
}
|
||||
|
||||
override fun getScriptSdk(file: VirtualFile): Sdk? =
|
||||
getActualClasspathRoots(file).getScriptSdk(file)
|
||||
|
||||
override fun getFirstScriptsSdk(): Sdk? =
|
||||
classpathRoots.firstScriptSdk
|
||||
override fun getFirstScriptsSdk(): Sdk? {
|
||||
managers.forEach {
|
||||
it.classpathRoots.firstScriptSdk?.let { sdk -> return sdk }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
|
||||
classpathRoots.getScriptDependenciesClassFilesScope(file)
|
||||
getActualClasspathRoots(file).getScriptDependenciesClassFilesScope(file)
|
||||
|
||||
override fun getAllScriptsDependenciesClassFilesScope(): GlobalSearchScope =
|
||||
classpathRoots.allDependenciesClassFilesScope
|
||||
GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesClassFilesScope })
|
||||
|
||||
override fun getAllScriptDependenciesSourcesScope(): GlobalSearchScope =
|
||||
classpathRoots.allDependenciesSourcesScope
|
||||
GlobalSearchScope.union(managers.map { it.classpathRoots.allDependenciesSourcesScope })
|
||||
|
||||
override fun getAllScriptsDependenciesClassFiles(): List<VirtualFile> =
|
||||
classpathRoots.allDependenciesClassFiles
|
||||
managers.flatMap { it.classpathRoots.allDependenciesClassFiles }
|
||||
|
||||
override fun getAllScriptDependenciesSources(): List<VirtualFile> =
|
||||
classpathRoots.allDependenciesSources
|
||||
managers.flatMap { it.classpathRoots.allDependenciesSources }
|
||||
|
||||
override fun forceReloadConfiguration(file: VirtualFile, loader: ScriptConfigurationLoader): ScriptCompilationConfigurationWrapper? {
|
||||
val ktFile = project.getKtFile(file, null) ?: return null
|
||||
|
||||
return default.forceReloadConfiguration(ktFile, loader)
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// Adapters for deprecated API
|
||||
@@ -174,4 +144,20 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
|
||||
|
||||
override fun getScriptClasspath(file: KtFile): List<VirtualFile> =
|
||||
ScriptConfigurationManager.toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty())
|
||||
|
||||
private fun clearCaches() {
|
||||
managers.forEach {
|
||||
it.clearCaches()
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearConfigurationCachesAndRehighlight() {
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
|
||||
clearCaches()
|
||||
|
||||
ScriptingSupportHelper.updateHighlighting(project) {
|
||||
!it.isNonScript()
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
-191
@@ -20,6 +20,7 @@ import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.core.script.*
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions.LOADER
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.*
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.DefaultScriptConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext
|
||||
@@ -36,73 +37,21 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
|
||||
/**
|
||||
* Standard implementation of scripts configuration loading and caching.
|
||||
*
|
||||
* ## Loading initiation
|
||||
*
|
||||
* [getOrLoadConfiguration] will be called when we need to show or analyze some script file.
|
||||
*
|
||||
* As described in [DefaultScriptingSupportBase], configuration may be loaded from [cache]
|
||||
* or [reloadOutOfDateConfiguration] will be called on [cache] miss.
|
||||
*
|
||||
* There are 2 tiers [cache]: memory and FS. For now FS cache implemented by [ScriptConfigurationLoader]
|
||||
* because we are not storing classpath roots yet. As a workaround cache.all() will return only memory
|
||||
* cached configurations. So, for now we are indexing roots that loaded from FS with
|
||||
* default [reloadOutOfDateConfiguration] mechanics.
|
||||
*
|
||||
* Also, [ensureLoadedFromCache] may be called from [UnusedSymbolInspection]
|
||||
* to ensure that configuration of all scripts containing some symbol are up-to-date or try load it in sync.
|
||||
*
|
||||
* ## Loading
|
||||
*
|
||||
* When requested, configuration will be loaded using first applicable [loaders].
|
||||
* It can work synchronously or asynchronously.
|
||||
*
|
||||
* Synchronous loader will be called just immediately. Despite this, its result may not be applied immediately,
|
||||
* see next section for details.
|
||||
*
|
||||
* Asynchronous loader will be called in background thread (by [BackgroundExecutor]).
|
||||
*
|
||||
* ## Applying
|
||||
*
|
||||
* By default loaded configuration will *not* be applied immediately. Instead, we show in editor notification
|
||||
* that suggests user to apply changed configuration. This was done to avoid sporadically starting indexing of new roots,
|
||||
* which may happens regularly for large Gradle projects.
|
||||
*
|
||||
* Notification will be displayed when configuration is going to be updated. First configuration will be loaded
|
||||
* without notification.
|
||||
*
|
||||
* This behavior may be disabled by enabling "auto reload" in project settings.
|
||||
* When enabled, all loaded configurations will be applied immediately, without any notification.
|
||||
*
|
||||
* ## Concurrency
|
||||
*
|
||||
* Each files may be in on of this state:
|
||||
* - scriptDefinition is not ready
|
||||
* - not loaded
|
||||
* - up-to-date
|
||||
* - invalid, in queue (in [BackgroundExecutor] queue)
|
||||
* - invalid, loading
|
||||
* - invalid, waiting for apply
|
||||
*
|
||||
* [reloadOutOfDateConfiguration] guard this states. See it's docs for more details.
|
||||
*/
|
||||
class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : DefaultScriptingSupportBase(manager) {
|
||||
class DefaultScriptingSupport(project: Project) : DefaultScriptingSupportBase(project) {
|
||||
// TODO public for tests
|
||||
val backgroundExecutor: BackgroundExecutor =
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(manager.updater)
|
||||
else DefaultBackgroundExecutor(project, manager.updater)
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(rootsIndexer)
|
||||
else DefaultBackgroundExecutor(project, rootsIndexer)
|
||||
|
||||
private val outsiderLoader = ScriptOutsiderFileConfigurationLoader(project)
|
||||
private val fileAttributeCache = ScriptConfigurationFileAttributeCache(project)
|
||||
private val defaultLoader = DefaultScriptConfigurationLoader(project)
|
||||
private val loaders: List<ScriptConfigurationLoader>
|
||||
get() = mutableListOf<ScriptConfigurationLoader>().apply {
|
||||
add(outsiderLoader)
|
||||
add(fileAttributeCache)
|
||||
addAll(LOADER.getPoint(project).extensionList)
|
||||
add(defaultLoader)
|
||||
private val loaders: Sequence<ScriptConfigurationLoader>
|
||||
get() = sequence {
|
||||
yield(outsiderLoader)
|
||||
yield(fileAttributeCache)
|
||||
yieldAll(LOADER.getPoint(project).extensionList)
|
||||
yield(defaultLoader)
|
||||
}
|
||||
|
||||
private val saveLock = ReentrantLock()
|
||||
@@ -121,7 +70,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
*
|
||||
* Each files may be in on of the states described below:
|
||||
* - scriptDefinition is not ready. `ScriptDefinitionsManager.getInstance(project).isReady() == false`.
|
||||
* [updateScriptDefinitions] will be called when [ScriptDefinitionsManager] will be ready
|
||||
* [clearConfigurationCachesAndRehighlight] will be called when [ScriptDefinitionsManager] will be ready
|
||||
* which will call [reloadOutOfDateConfiguration] for opened editors.
|
||||
* - unknown. When [isFirstLoad] true (`cache[file] == null`).
|
||||
* - up-to-date. `cache[file]?.upToDate == true`.
|
||||
@@ -155,39 +104,36 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
override fun reloadOutOfDateConfiguration(
|
||||
file: KtFile,
|
||||
isFirstLoad: Boolean,
|
||||
loadEvenWillNotBeApplied: Boolean,
|
||||
forceSync: Boolean,
|
||||
isPostponedLoad: Boolean,
|
||||
fromCacheOnly: Boolean
|
||||
): Boolean {
|
||||
val virtualFile = file.originalFile.virtualFile ?: return false
|
||||
isPostponedLoad: Boolean
|
||||
) {
|
||||
val virtualFile = file.originalFile.virtualFile ?: return
|
||||
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
|
||||
val scriptDefinition = file.findScriptDefinition() ?: return false
|
||||
val autoReloadEnabled = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
||||
val shouldLoad = isFirstLoad || loadEvenWillNotBeApplied || autoReloadEnabled
|
||||
if (!shouldLoad) return
|
||||
|
||||
val postponeLoading = isPostponedLoad && !autoReloadEnabled && !isFirstLoad
|
||||
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return
|
||||
val scriptDefinition = file.findScriptDefinition() ?: return
|
||||
|
||||
val (async, sync) = loaders.partition { it.shouldRunInBackground(scriptDefinition) }
|
||||
|
||||
val syncLoader = sync.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) }
|
||||
if (syncLoader == null) {
|
||||
if (!fromCacheOnly) {
|
||||
if (forceSync) {
|
||||
loaders.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) }
|
||||
} else {
|
||||
val autoReloadEnabled = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
||||
val postponeLoading = isPostponedLoad && !autoReloadEnabled
|
||||
|
||||
if (postponeLoading) {
|
||||
LoadScriptConfigurationNotificationFactory.showNotification(virtualFile, project) {
|
||||
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true)
|
||||
}
|
||||
} else {
|
||||
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false)
|
||||
if (forceSync) {
|
||||
loaders.firstOrNull { it.loadDependencies(isFirstLoad, file, scriptDefinition, loadingContext) }
|
||||
} else {
|
||||
if (postponeLoading) {
|
||||
LoadScriptConfigurationNotificationFactory.showNotification(virtualFile, project) {
|
||||
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true)
|
||||
}
|
||||
} else {
|
||||
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +150,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
val applied = cached?.applied
|
||||
if (applied != null && applied.inputs.isUpToDate(project, virtualFile)) {
|
||||
// in case user reverted to applied configuration
|
||||
suggestOrSaveConfiguration(virtualFile, applied, skipNotification = isLoadingPostponed)
|
||||
suggestOrSaveConfiguration(virtualFile, applied, isLoadingPostponed)
|
||||
} else if (cached == null || !cached.isUpToDate(project, virtualFile)) {
|
||||
// don't start loading if nothing was changed
|
||||
// (in case we checking for up-to-date and loading concurrently)
|
||||
@@ -220,7 +166,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return null
|
||||
val scriptDefinition = file.findScriptDefinition() ?: return null
|
||||
|
||||
manager.updater.update {
|
||||
rootsIndexer.transaction {
|
||||
if (!loader.shouldRunInBackground(scriptDefinition)) {
|
||||
loader.loadDependencies(false, file, scriptDefinition, loadingContext)
|
||||
} else {
|
||||
@@ -234,16 +180,8 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
}
|
||||
|
||||
private val loadingContext = object : ScriptConfigurationLoadingContext {
|
||||
/**
|
||||
* Used from [ScriptOutsiderFileConfigurationLoader] only.
|
||||
*/
|
||||
override fun getCachedConfiguration(file: VirtualFile): ScriptConfigurationSnapshot? =
|
||||
getAppliedConfiguration(file) ?: getFromGlobalCache(file)
|
||||
|
||||
private fun getFromGlobalCache(file: VirtualFile): ScriptConfigurationSnapshot? {
|
||||
val info = manager.getLightScriptInfo(file.path) ?: return null
|
||||
return ScriptConfigurationSnapshot(CachedConfigurationInputs.UpToDate, listOf(), info.buildConfiguration())
|
||||
}
|
||||
getAppliedConfiguration(file)
|
||||
|
||||
override fun suggestNewConfiguration(file: VirtualFile, newResult: ScriptConfigurationSnapshot) {
|
||||
suggestOrSaveConfiguration(file, newResult, false)
|
||||
@@ -286,7 +224,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
file.removeScriptDependenciesNotificationPanel(project)
|
||||
}
|
||||
saveReports(file, newResult.reports)
|
||||
setAppliedConfiguration(file, newResult, syncUpdate = true)
|
||||
setAppliedConfiguration(file, newResult)
|
||||
} else {
|
||||
debug(file) {
|
||||
"configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration"
|
||||
@@ -302,7 +240,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
onClick = {
|
||||
saveReports(file, newResult.reports)
|
||||
file.removeScriptDependenciesNotificationPanel(project)
|
||||
manager.updater.update {
|
||||
rootsIndexer.transaction {
|
||||
setAppliedConfiguration(file, newResult)
|
||||
}
|
||||
}
|
||||
@@ -334,28 +272,10 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project) =
|
||||
(ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager).default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstraction for [DefaultScriptingSupportBase] based [cache] and [reloadOutOfDateConfiguration].
|
||||
* Among this two methods concrete implementation should provide script changes listening.
|
||||
*
|
||||
* Basically all requests routed to [cache]. If there is no entry in [cache] or it is considered out-of-date,
|
||||
* then [reloadOutOfDateConfiguration] will be called, which, in turn, should call [setAppliedConfiguration]
|
||||
* immediately or in some future (e.g. after user will click "apply context" or/and configuration will
|
||||
* be calculated by some background thread).
|
||||
*
|
||||
* [ScriptClassRootsCache] will be calculated based on [cache]d configurations.
|
||||
* Every change in [cache] will invalidate [ScriptClassRootsCache] cache.
|
||||
*/
|
||||
abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigurationManager) {
|
||||
val project: Project
|
||||
get() = manager.project
|
||||
abstract class DefaultScriptingSupportBase(val project: Project) : ScriptingSupport() {
|
||||
protected val rootsIndexer = ScriptClassRootsIndexer(project)
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
protected val cache: ScriptConfigurationCache = createCache()
|
||||
@@ -369,18 +289,19 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
||||
* (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread).
|
||||
*
|
||||
* @param isFirstLoad may be set explicitly for optimization reasons (to avoid expensive fs cache access)
|
||||
* @param loadEvenWillNotBeApplied may should be set to false only on requests from particular editor, when
|
||||
* user can see potential notification and accept new configuration. In other cases this should be `false` since
|
||||
* loaded configuration will be just leaved in hidden user notification and cannot be used in any way.
|
||||
* @param forceSync should be used in tests only
|
||||
* @param isPostponedLoad is used to postspone loading: show a notification for out of date script and start loading when user request
|
||||
* @param fromCacheOnly load only when builtin fast synchronous loaders are available
|
||||
* @return true, if configuration loaded in sync
|
||||
*/
|
||||
protected abstract fun reloadOutOfDateConfiguration(
|
||||
file: KtFile,
|
||||
isFirstLoad: Boolean = getAppliedConfiguration(file.originalFile.virtualFile) == null,
|
||||
loadEvenWillNotBeApplied: Boolean = false,
|
||||
forceSync: Boolean = false,
|
||||
isPostponedLoad: Boolean = false,
|
||||
fromCacheOnly: Boolean = false
|
||||
): Boolean
|
||||
isPostponedLoad: Boolean = false
|
||||
)
|
||||
|
||||
fun getCachedConfigurationState(file: VirtualFile?): ScriptConfigurationState? {
|
||||
if (file == null) return null
|
||||
@@ -390,14 +311,14 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
||||
fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? =
|
||||
getCachedConfigurationState(file)?.applied
|
||||
|
||||
private fun hasCachedConfiguration(file: KtFile): Boolean =
|
||||
override fun hasCachedConfiguration(file: KtFile): Boolean =
|
||||
getAppliedConfiguration(file.originalFile.virtualFile) != null
|
||||
|
||||
fun isConfigurationLoadingInProgress(file: KtFile): Boolean {
|
||||
override fun isConfigurationLoadingInProgress(file: KtFile): Boolean {
|
||||
return !hasCachedConfiguration(file) && !ScriptConfigurationManager.isManualConfigurationLoading(file.originalFile.virtualFile)
|
||||
}
|
||||
|
||||
fun getOrLoadConfiguration(
|
||||
override fun getOrLoadConfiguration(
|
||||
virtualFile: VirtualFile,
|
||||
preloadedKtFile: KtFile?
|
||||
): ScriptCompilationConfigurationWrapper? {
|
||||
@@ -405,86 +326,73 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
||||
if (cached != null) return cached.configuration
|
||||
|
||||
val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null
|
||||
manager.updater.update {
|
||||
rootsIndexer.transaction {
|
||||
reloadOutOfDateConfiguration(ktFile, isFirstLoad = true)
|
||||
}
|
||||
|
||||
return getAppliedConfiguration(virtualFile)?.configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Load new configuration and suggest to apply it (only if it is changed)
|
||||
*/
|
||||
fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
|
||||
reloadIfOutOfDate(file)
|
||||
}
|
||||
override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater {
|
||||
override fun ensureUpToDatedConfigurationSuggested(file: KtFile) {
|
||||
reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification about changed script configuration with action to start loading it
|
||||
*/
|
||||
fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
|
||||
reloadIfOutOfDate(file, isPostponedLoad = true)
|
||||
}
|
||||
override fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean {
|
||||
return reloadIfOutOfDate(files, loadEvenWillNotBeApplied = false, isPostponedLoad = false)
|
||||
}
|
||||
|
||||
private fun reloadIfOutOfDate(file: KtFile, isPostponedLoad: Boolean = false) {
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return
|
||||
|
||||
manager.updater.update {
|
||||
val virtualFile = file.originalFile.virtualFile
|
||||
if (virtualFile != null) {
|
||||
val state = cache[virtualFile]
|
||||
if (state == null || !state.isUpToDate(project, virtualFile, file)) {
|
||||
reloadOutOfDateConfiguration(
|
||||
file,
|
||||
isFirstLoad = state == null,
|
||||
isPostponedLoad = isPostponedLoad
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile) {
|
||||
reloadIfOutOfDate(listOf(file), loadEvenWillNotBeApplied = true, isPostponedLoad = true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that any configuration for [files] is loaded from cache
|
||||
*/
|
||||
fun ensureLoadedFromCache(files: List<KtFile>): Boolean {
|
||||
private fun reloadIfOutOfDate(files: List<KtFile>, loadEvenWillNotBeApplied: Boolean, isPostponedLoad: Boolean): Boolean {
|
||||
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
|
||||
|
||||
var allLoaded = true
|
||||
manager.updater.update {
|
||||
var upToDate = true
|
||||
rootsIndexer.transaction {
|
||||
files.forEach { file ->
|
||||
val virtualFile = file.originalFile.virtualFile
|
||||
if (virtualFile != null) {
|
||||
val state = cache[virtualFile]
|
||||
if (state == null) {
|
||||
if (!reloadOutOfDateConfiguration(
|
||||
file,
|
||||
isFirstLoad = true,
|
||||
fromCacheOnly = true
|
||||
)
|
||||
) {
|
||||
allLoaded = false
|
||||
}
|
||||
if (state == null || !state.isUpToDate(project, virtualFile, file)) {
|
||||
upToDate = false
|
||||
reloadOutOfDateConfiguration(
|
||||
file,
|
||||
isFirstLoad = state == null,
|
||||
loadEvenWillNotBeApplied = loadEvenWillNotBeApplied,
|
||||
isPostponedLoad = isPostponedLoad
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allLoaded
|
||||
return upToDate
|
||||
}
|
||||
|
||||
protected open fun setAppliedConfiguration(
|
||||
file: VirtualFile,
|
||||
newConfigurationSnapshot: ScriptConfigurationSnapshot?,
|
||||
syncUpdate: Boolean = false
|
||||
newConfigurationSnapshot: ScriptConfigurationSnapshot?
|
||||
) {
|
||||
manager.updater.checkInTransaction()
|
||||
rootsIndexer.checkInTransaction()
|
||||
val newConfiguration = newConfigurationSnapshot?.configuration
|
||||
debug(file) { "configuration changed = $newConfiguration" }
|
||||
|
||||
if (newConfiguration != null) {
|
||||
if (hasNotCachedRoots(newConfiguration)) {
|
||||
debug(file) { "new class roots found: $newConfiguration" }
|
||||
rootsIndexer.markNewRoot()
|
||||
}
|
||||
|
||||
cache.setApplied(file, newConfigurationSnapshot)
|
||||
manager.updater.invalidate(file, synchronous = syncUpdate)
|
||||
|
||||
clearClassRootsCaches(project)
|
||||
}
|
||||
|
||||
ScriptingSupportHelper.updateHighlighting(project) {
|
||||
it == file
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,6 +403,10 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
||||
cache.setLoaded(file, configurationSnapshot)
|
||||
}
|
||||
|
||||
private fun hasNotCachedRoots(configuration: ScriptCompilationConfigurationWrapper): Boolean {
|
||||
return classpathRoots.hasNotCachedRoots(DefaultClassRootsCache.extractRoots(project, configuration))
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
internal fun updateScriptDependenciesSynchronously(file: PsiFile) {
|
||||
file.findScriptDefinition() ?: return
|
||||
@@ -504,29 +416,20 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
|
||||
val virtualFile = file.virtualFile
|
||||
if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return
|
||||
|
||||
manager.updater.update {
|
||||
reloadOutOfDateConfiguration(file, forceSync = true)
|
||||
rootsIndexer.transaction {
|
||||
reloadOutOfDateConfiguration(file, forceSync = true, loadEvenWillNotBeApplied = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateScriptDefinitions() {
|
||||
override fun clearCaches() {
|
||||
cache.clear()
|
||||
}
|
||||
|
||||
fun collectConfigurations(builder: ScriptClassRootsCache.Builder) {
|
||||
// todo: drop the hell below
|
||||
// keep this one only:
|
||||
// cache.allApplied().forEach { (vFile, configuration) -> builder.add(vFile, configuration) }
|
||||
|
||||
// own builder for saving to storage
|
||||
val ownBuilder = ScriptClassRootsCache.Builder(project)
|
||||
val rootsStorage = ScriptClassRootsStorage.getInstance(project)
|
||||
|
||||
rootsStorage.load(ownBuilder)
|
||||
cache.allApplied().forEach { (vFile, configuration) -> ownBuilder.add(vFile, configuration) }
|
||||
rootsStorage.save(ownBuilder)
|
||||
|
||||
builder.add(ownBuilder)
|
||||
override fun recreateRootsCache(): ScriptClassRootsCache {
|
||||
return DefaultClassRootsCache(
|
||||
project,
|
||||
cache.allApplied()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+65
-26
@@ -6,37 +6,76 @@
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElementFinder
|
||||
import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Extension point for overriding default Kotlin scripting support.
|
||||
*
|
||||
* Implementation should store script configuration internally (in memory and/or fs),
|
||||
* and provide it inside [collectConfigurations] using the [ScriptClassRootsCache.LightScriptInfo].
|
||||
* Custom data can be attached to [ScriptClassRootsCache.LightScriptInfo] and retrieved
|
||||
* by calling [ScriptClassRootsCache.getLightScriptInfo].
|
||||
*
|
||||
* [ScriptChangeListener] can be used to listen script changes.
|
||||
* [CompositeScriptConfigurationManager.updater] should be used to schedule configuration reloading.
|
||||
*
|
||||
* [isApplicable] should return true for files that is covered by that support.
|
||||
*
|
||||
* [isConfigurationLoadingInProgress] is used to pause analyzing.
|
||||
*
|
||||
* Long read: [idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/README.md].
|
||||
*
|
||||
* @sample GradleBuildRootsManager
|
||||
*/
|
||||
abstract class ScriptingSupport {
|
||||
abstract fun isApplicable(file: VirtualFile): Boolean
|
||||
abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean
|
||||
abstract fun collectConfigurations(builder: ScriptClassRootsCache.Builder)
|
||||
abstract class Provider {
|
||||
abstract val all: Collection<ScriptingSupport>
|
||||
|
||||
companion object {
|
||||
val EPN: ExtensionPointName<ScriptingSupport> =
|
||||
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupport")
|
||||
abstract fun getSupport(file: VirtualFile): ScriptingSupport?
|
||||
|
||||
companion object {
|
||||
val EPN: ExtensionPointName<Provider> =
|
||||
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.scriptingSupportProvider")
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun clearCaches()
|
||||
abstract fun hasCachedConfiguration(file: KtFile): Boolean
|
||||
abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean
|
||||
abstract fun getOrLoadConfiguration(virtualFile: VirtualFile, preloadedKtFile: KtFile? = null): ScriptCompilationConfigurationWrapper?
|
||||
|
||||
abstract val updater: ScriptConfigurationUpdater
|
||||
|
||||
private val classpathRootsLock = ReentrantLock()
|
||||
|
||||
@Volatile
|
||||
private var _classpathRoots: ScriptClassRootsCache? = null
|
||||
val classpathRoots: ScriptClassRootsCache
|
||||
get() {
|
||||
val value1 = _classpathRoots
|
||||
if (value1 != null) return value1
|
||||
|
||||
classpathRootsLock.withLock {
|
||||
val value2 = _classpathRoots
|
||||
if (value2 != null) return value2
|
||||
|
||||
val value3 = recreateRootsCache()
|
||||
value3.saveClassRootsToStorage()
|
||||
_classpathRoots = value3
|
||||
return value3
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun recreateRootsCache(): ScriptClassRootsCache
|
||||
|
||||
fun clearClassRootsCaches(project: Project) {
|
||||
debug { "class roots caches cleared" }
|
||||
|
||||
classpathRootsLock.withLock {
|
||||
_classpathRoots = null
|
||||
}
|
||||
|
||||
val kotlinScriptDependenciesClassFinder =
|
||||
Extensions.getArea(project)
|
||||
.getExtensionPoint(PsiElementFinder.EP_NAME).extensions
|
||||
.filterIsInstance<KotlinScriptDependenciesClassFinder>()
|
||||
.single()
|
||||
|
||||
kotlinScriptDependenciesClassFinder.clearCache()
|
||||
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration
|
||||
|
||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
|
||||
object ScriptingSupportHelper {
|
||||
fun updateHighlighting(project: Project, filter: (VirtualFile) -> Boolean) {
|
||||
if (!project.isOpen) return
|
||||
|
||||
val openFiles = FileEditorManager.getInstance(project).openFiles
|
||||
val openedScripts = openFiles.filter { filter(it) }
|
||||
|
||||
if (openedScripts.isEmpty()) return
|
||||
|
||||
GlobalScope.launch(EDT(project)) {
|
||||
if (project.isDisposed) return@launch
|
||||
|
||||
openedScripts.forEach {
|
||||
PsiManager.getInstance(project).findFile(it)?.let { psiFile ->
|
||||
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -31,7 +31,7 @@ interface ScriptConfigurationCache {
|
||||
fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
|
||||
fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
|
||||
|
||||
fun allApplied(): List<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>>
|
||||
fun allApplied(): Map<VirtualFile, ScriptCompilationConfigurationWrapper>
|
||||
fun clear()
|
||||
|
||||
fun getAnyLoadedScript(): ScriptCompilationConfigurationWrapper?
|
||||
@@ -63,10 +63,6 @@ interface CachedConfigurationInputs: Serializable {
|
||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = false
|
||||
}
|
||||
|
||||
object UpToDate: CachedConfigurationInputs {
|
||||
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean = true
|
||||
}
|
||||
|
||||
data class PsiModificationStamp(
|
||||
val fileModificationStamp: Long,
|
||||
val psiModificationStamp: Long
|
||||
|
||||
+3
-3
@@ -41,11 +41,11 @@ open class ScriptConfigurationMemoryCache(
|
||||
|
||||
@Synchronized
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun allApplied(): List<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>> {
|
||||
val result = mutableListOf<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>>()
|
||||
override fun allApplied(): Map<VirtualFile, ScriptCompilationConfigurationWrapper> {
|
||||
val result = hashMapOf<VirtualFile, ScriptCompilationConfigurationWrapper>()
|
||||
for ((file, configuration) in memoryCache.entrySet()) {
|
||||
if (configuration.applied?.configuration != null) {
|
||||
result.add(Pair(file, configuration.applied.configuration))
|
||||
result[file] = configuration.applied.configuration
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
+4
-4
@@ -9,14 +9,14 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.scripting.definitions.isNonScript
|
||||
|
||||
open class DefaultScriptChangeListener(project: Project) : ScriptChangeListener(project) {
|
||||
override fun editorActivated(vFile: VirtualFile) {
|
||||
override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
|
||||
val file = getAnalyzableKtFileForScript(vFile) ?: return
|
||||
default.ensureUpToDatedConfigurationSuggested(file)
|
||||
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||
}
|
||||
|
||||
override fun documentChanged(vFile: VirtualFile) {
|
||||
override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
|
||||
val file = getAnalyzableKtFileForScript(vFile) ?: return
|
||||
default.ensureUpToDatedConfigurationSuggested(file)
|
||||
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||
}
|
||||
|
||||
override fun isApplicable(vFile: VirtualFile): Boolean {
|
||||
|
||||
+7
-8
@@ -9,21 +9,20 @@ import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
/**
|
||||
* [ScriptChangesNotifier] will call first applicable [ScriptChangeListener] when editor is activated or document changed.
|
||||
* Listener should do something to invalidate configuration and schedule reloading.
|
||||
*
|
||||
* Listener may call [ScriptConfigurationUpdater] to invalidate configuration and schedule reloading.
|
||||
*
|
||||
* @see DefaultScriptConfigurationManager for more details.
|
||||
*/
|
||||
abstract class ScriptChangeListener(val project: Project) {
|
||||
val default: DefaultScriptingSupport
|
||||
get() = DefaultScriptingSupport.getInstance(project)
|
||||
|
||||
abstract fun editorActivated(vFile: VirtualFile)
|
||||
abstract fun documentChanged(vFile: VirtualFile)
|
||||
abstract class ScriptChangeListener(protected val project: Project) {
|
||||
abstract fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater)
|
||||
abstract fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater)
|
||||
|
||||
abstract fun isApplicable(vFile: VirtualFile): Boolean
|
||||
|
||||
|
||||
+11
-9
@@ -22,7 +22,8 @@ import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChange
|
||||
import org.jetbrains.kotlin.idea.core.script.isScriptChangesNotifierDisabled
|
||||
|
||||
internal class ScriptChangesNotifier(
|
||||
private val project: Project
|
||||
private val project: Project,
|
||||
private val updater: ScriptConfigurationUpdater,
|
||||
) {
|
||||
private val scriptsQueue = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project)
|
||||
private val scriptChangesListenerDelay = 1400
|
||||
@@ -45,10 +46,10 @@ internal class ScriptChangesNotifier(
|
||||
|
||||
private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
getListener(project, file)?.editorActivated(file)
|
||||
getListener(project, file)?.editorActivated(file, updater)
|
||||
} else {
|
||||
AppExecutorUtil.getAppExecutorService().submit {
|
||||
getListener(project, file)?.editorActivated(file)
|
||||
getListener(project, file)?.editorActivated(file, updater)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,11 +68,11 @@ internal class ScriptChangesNotifier(
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
getListener(project, file)?.documentChanged(file)
|
||||
getListener(project, file)?.documentChanged(file, updater)
|
||||
} else {
|
||||
scriptsQueue.cancelAllRequests()
|
||||
scriptsQueue.addRequest(
|
||||
{ getListener(project, file)?.documentChanged(file) },
|
||||
{ getListener(project, file)?.documentChanged(file, updater) },
|
||||
scriptChangesListenerDelay,
|
||||
true,
|
||||
)
|
||||
@@ -83,10 +84,11 @@ internal class ScriptChangesNotifier(
|
||||
}
|
||||
|
||||
private val defaultListener = DefaultScriptChangeListener(project)
|
||||
private val listeners: Collection<ScriptChangeListener> = mutableListOf<ScriptChangeListener>().apply {
|
||||
addAll(LISTENER.getPoint(project).extensionList)
|
||||
add(defaultListener)
|
||||
}
|
||||
private val listeners: Sequence<ScriptChangeListener>
|
||||
get() = sequence {
|
||||
yieldAll(LISTENER.getPoint(project).extensionList)
|
||||
yield(defaultListener)
|
||||
}
|
||||
|
||||
private fun getListener(project: Project, file: VirtualFile): ScriptChangeListener? {
|
||||
if (project.isDisposed || areListenersDisabled()) return null
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.listener
|
||||
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCacheScope
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
interface ScriptConfigurationUpdater {
|
||||
/**
|
||||
* Ensure that configuration for [file] is up-to-date,
|
||||
* or new configuration loading is started
|
||||
*/
|
||||
fun ensureUpToDatedConfigurationSuggested(file: KtFile)
|
||||
|
||||
/**
|
||||
* Check that configurations for given [files] is up-to-date or try synchronously update
|
||||
* them. Synchronous update will be occurred only if auto apply is enabled and
|
||||
* configuration for given file will not be loaded in background.
|
||||
*
|
||||
* @return true, if all files are already up-to-date or updated during this call
|
||||
*/
|
||||
fun ensureConfigurationUpToDate(files: List<KtFile>): Boolean
|
||||
|
||||
/**
|
||||
* Suggest to update script configuration if configuration for [file] is out of date
|
||||
*/
|
||||
fun suggestToUpdateConfigurationIfOutOfDate(file: KtFile)
|
||||
}
|
||||
+5
-9
@@ -15,7 +15,6 @@ import com.intellij.util.containers.HashSetQueue
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle
|
||||
import java.util.*
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
/**
|
||||
* Sequentially loads script configuration in background.
|
||||
@@ -33,7 +32,7 @@ import javax.swing.SwingUtilities
|
||||
*/
|
||||
internal class DefaultBackgroundExecutor(
|
||||
val project: Project,
|
||||
val rootsManager: ScriptClassRootsUpdater
|
||||
val rootsManager: ScriptClassRootsIndexer
|
||||
) : BackgroundExecutor {
|
||||
companion object {
|
||||
const val PROGRESS_INDICATOR_DELAY = 1000
|
||||
@@ -138,7 +137,7 @@ internal class DefaultBackgroundExecutor(
|
||||
private fun ensureInTransaction() {
|
||||
if (inTransaction) return
|
||||
inTransaction = true
|
||||
rootsManager.beginUpdating()
|
||||
rootsManager.startTransaction()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -228,12 +227,9 @@ internal class DefaultBackgroundExecutor(
|
||||
override fun start() {
|
||||
super.start()
|
||||
|
||||
// executeOnPooledThread requires read lock, and we may fail to acquire it
|
||||
SwingUtilities.invokeLater {
|
||||
BackgroundTaskUtil.executeOnPooledThread(project, {
|
||||
run()
|
||||
})
|
||||
}
|
||||
BackgroundTaskUtil.executeOnPooledThread(project, Runnable {
|
||||
run()
|
||||
})
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.io.File
|
||||
|
||||
internal class DefaultClassRootsCache(
|
||||
project: Project,
|
||||
private val all: Map<VirtualFile, ScriptCompilationConfigurationWrapper>
|
||||
) : ScriptClassRootsCache(
|
||||
project,
|
||||
ScriptClassRootsStorage.Companion.Key("default"),
|
||||
extractRoots(all, project)
|
||||
) {
|
||||
|
||||
override fun getConfiguration(file: VirtualFile) = all[file]
|
||||
|
||||
override fun contains(file: VirtualFile): Boolean = file in all
|
||||
|
||||
private val scriptsSdksCache: Map<VirtualFile, Sdk?> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
return@createWeakMap getScriptSdkOrDefault(all[file]?.javaHome, project)
|
||||
}
|
||||
|
||||
override fun getScriptSdk(file: VirtualFile): Sdk? = scriptsSdksCache[file]
|
||||
|
||||
override val firstScriptSdk: Sdk? by lazy {
|
||||
getScriptSdkOrDefault(all.values.firstOrNull()?.javaHome, project)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun extractRoots(
|
||||
project: Project,
|
||||
configuration: ScriptCompilationConfigurationWrapper
|
||||
): ScriptClassRootsStorage.Companion.ScriptClassRoots {
|
||||
val scriptSdk = getScriptSdkOrDefault(configuration.javaHome, project)
|
||||
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) {
|
||||
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||
toStringValues(configuration.dependenciesClassPath),
|
||||
toStringValues(configuration.dependenciesSources),
|
||||
setOf(scriptSdk)
|
||||
)
|
||||
}
|
||||
|
||||
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||
toStringValues(configuration.dependenciesClassPath),
|
||||
toStringValues(configuration.dependenciesSources),
|
||||
emptySet()
|
||||
)
|
||||
}
|
||||
|
||||
fun extractRoots(
|
||||
all: Map<VirtualFile, ScriptCompilationConfigurationWrapper>,
|
||||
project: Project
|
||||
): ScriptClassRootsStorage.Companion.ScriptClassRoots {
|
||||
val classpath = mutableSetOf<File>()
|
||||
val sources = mutableSetOf<File>()
|
||||
val sdks = mutableSetOf<Sdk>()
|
||||
|
||||
for ((_, configuration) in all) {
|
||||
val scriptSdk = getScriptSdkOrDefault(configuration.javaHome, project)
|
||||
if (scriptSdk != null && !scriptSdk.isAlreadyIndexed(project)) {
|
||||
sdks.add(scriptSdk)
|
||||
}
|
||||
|
||||
classpath.addAll(configuration.dependenciesClassPath)
|
||||
sources.addAll(configuration.dependenciesSources)
|
||||
}
|
||||
|
||||
return ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||
toStringValues(classpath),
|
||||
toStringValues(sources),
|
||||
sdks
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
-183
@@ -12,101 +12,100 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.NonClasspathDirectoriesScope.compose
|
||||
import com.intellij.psi.search.NonClasspathDirectoriesScope
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
|
||||
import org.jetbrains.kotlin.idea.core.script.LOG
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.classpathEntryToVfs
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.io.File
|
||||
import java.lang.ref.Reference
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
class ScriptClassRootsCache(
|
||||
val scripts: Map<String, LightScriptInfo>,
|
||||
val classes: Set<String>,
|
||||
val sources: Set<String>,
|
||||
val sdks: Map<String?, Sdk?>,
|
||||
private val nonModulesSdks: Collection<Sdk>,
|
||||
val customDefinitionsUsed: Boolean
|
||||
abstract class ScriptClassRootsCache(
|
||||
private val project: Project,
|
||||
private val rootsCacheKey: ScriptClassRootsStorage.Companion.Key,
|
||||
private val roots: ScriptClassRootsStorage.Companion.ScriptClassRoots
|
||||
) {
|
||||
abstract class LightScriptInfo(val definition: ScriptDefinition?) {
|
||||
@Volatile
|
||||
var heavyCache: Reference<HeavyScriptInfo>? = null
|
||||
protected abstract fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper?
|
||||
|
||||
abstract fun buildConfiguration(): ScriptCompilationConfigurationWrapper?
|
||||
}
|
||||
abstract val firstScriptSdk: Sdk?
|
||||
|
||||
class DirectScriptInfo(val result: ScriptCompilationConfigurationWrapper) : LightScriptInfo(null) {
|
||||
override fun buildConfiguration(): ScriptCompilationConfigurationWrapper = result
|
||||
}
|
||||
abstract fun getScriptSdk(file: VirtualFile): Sdk?
|
||||
|
||||
class HeavyScriptInfo(
|
||||
abstract fun contains(file: VirtualFile): Boolean
|
||||
|
||||
private class Fat(
|
||||
val scriptConfiguration: ScriptCompilationConfigurationWrapper,
|
||||
val classFilesScope: GlobalSearchScope,
|
||||
val sdk: Sdk?
|
||||
val classFilesScope: GlobalSearchScope
|
||||
)
|
||||
|
||||
class Builder(val project: Project) {
|
||||
val scripts = mutableMapOf<String, LightScriptInfo>()
|
||||
val allDependenciesClassFiles by lazy {
|
||||
ScriptClassRootsStorage.getInstance(project).loadClasspathRoots(rootsCacheKey)
|
||||
}
|
||||
|
||||
private val defaultSdk = getScriptDefaultSdk()
|
||||
val sdks = mutableMapOf<String?, Sdk?>(null to defaultSdk)
|
||||
val allDependenciesSources by lazy {
|
||||
ScriptClassRootsStorage.getInstance(project).loadSourcesRoots(rootsCacheKey)
|
||||
}
|
||||
|
||||
val classes = mutableSetOf<String>()
|
||||
val sources = mutableSetOf<String>()
|
||||
private var customDefinitionsUsed: Boolean = false
|
||||
val allDependenciesClassFilesScope by lazy {
|
||||
NonClasspathDirectoriesScope.compose(allDependenciesClassFiles)
|
||||
}
|
||||
|
||||
fun build(): ScriptClassRootsCache {
|
||||
val nonIndexedSdks = collectNonIndexedSdks()
|
||||
return ScriptClassRootsCache(scripts, classes, sources, sdks, nonIndexedSdks, customDefinitionsUsed)
|
||||
}
|
||||
val allDependenciesSourcesScope by lazy {
|
||||
NonClasspathDirectoriesScope.compose(allDependenciesSources)
|
||||
}
|
||||
|
||||
fun collectNonIndexedSdks(): MutableSet<Sdk> {
|
||||
val nonIndexedSdks = sdks.values.filterNotNullTo(mutableSetOf())
|
||||
ModuleManager.getInstance(project).modules.map {
|
||||
nonIndexedSdks.remove(ModuleRootManager.getInstance(it).sdk)
|
||||
private val scriptsDependenciesCache: MutableMap<VirtualFile, Fat> =
|
||||
ConcurrentFactoryMap.createWeakMap { file ->
|
||||
val configuration = getConfiguration(file) ?: return@createWeakMap null
|
||||
|
||||
val roots = configuration.dependenciesClassPath
|
||||
val sdk = getScriptSdk(file)
|
||||
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (sdk == null) {
|
||||
return@createWeakMap Fat(
|
||||
configuration,
|
||||
NonClasspathDirectoriesScope.compose(ScriptConfigurationManager.toVfsRoots(roots))
|
||||
)
|
||||
}
|
||||
return nonIndexedSdks
|
||||
|
||||
return@createWeakMap Fat(
|
||||
configuration,
|
||||
NonClasspathDirectoriesScope.compose(
|
||||
sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + ScriptConfigurationManager.toVfsRoots(roots)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun useCustomScriptDefinition() {
|
||||
customDefinitionsUsed = true
|
||||
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
|
||||
return scriptsDependenciesCache[file]?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE
|
||||
}
|
||||
|
||||
fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? {
|
||||
return scriptsDependenciesCache[file]?.scriptConfiguration
|
||||
}
|
||||
|
||||
fun hasNotCachedRoots(roots: ScriptClassRootsStorage.Companion.ScriptClassRoots): Boolean {
|
||||
return !ScriptClassRootsStorage.getInstance(project).containsAll(rootsCacheKey, roots)
|
||||
}
|
||||
|
||||
fun saveClassRootsToStorage() {
|
||||
val rootsStorage = ScriptClassRootsStorage.getInstance(project)
|
||||
if (roots.classpathFiles.isNotEmpty() || roots.sourcesFiles.isNotEmpty() || roots.sdks.isNotEmpty()) {
|
||||
rootsStorage.save(rootsCacheKey, roots)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun toStringValues(prop: Collection<File>): Set<String> {
|
||||
return prop.mapNotNull { it.absolutePath }.toSet()
|
||||
}
|
||||
|
||||
fun add(
|
||||
vFile: VirtualFile,
|
||||
configuration: ScriptCompilationConfigurationWrapper
|
||||
) {
|
||||
addSdk(configuration.javaHome)
|
||||
|
||||
configuration.dependenciesClassPath.forEach { classes.add(it.absolutePath) }
|
||||
configuration.dependenciesSources.forEach { sources.add(it.absolutePath) }
|
||||
|
||||
scripts[vFile.path] = DirectScriptInfo(configuration)
|
||||
|
||||
useCustomScriptDefinition()
|
||||
}
|
||||
|
||||
fun add(other: Builder) {
|
||||
classes.addAll(other.classes)
|
||||
sources.addAll(other.sources)
|
||||
sdks.putAll(other.sdks)
|
||||
scripts.putAll(other.scripts)
|
||||
}
|
||||
|
||||
fun addSdk(javaHome: File?): Sdk? {
|
||||
if (javaHome == null) return defaultSdk
|
||||
val canonicalPath = toSystemIndependentName(javaHome.canonicalPath)
|
||||
return sdks.getOrPut(canonicalPath) {
|
||||
getScriptSdkByJavaHome(javaHome) ?: defaultSdk
|
||||
}
|
||||
fun getScriptSdkOrDefault(javaHome: File?, project: Project): Sdk? {
|
||||
return javaHome?.let { getScriptSdkByJavaHome(it) } ?: getScriptDefaultSdk(project)
|
||||
}
|
||||
|
||||
private fun getScriptSdkByJavaHome(javaHome: File): Sdk? {
|
||||
@@ -120,13 +119,7 @@ class ScriptClassRootsCache(
|
||||
return getProjectJdkTableSafe().allJdks.find { it.homeDirectory == javaHomeVF }
|
||||
}
|
||||
|
||||
fun addSdkByName(sdkName: String) {
|
||||
val sdk = getProjectJdkTableSafe().allJdks.find { it.name == sdkName } ?: defaultSdk ?: return
|
||||
val homePath = sdk.homePath ?: return
|
||||
sdks[homePath] = sdk
|
||||
}
|
||||
|
||||
private fun getScriptDefaultSdk(): Sdk? {
|
||||
private fun getScriptDefaultSdk(project: Project): Sdk? {
|
||||
val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() }
|
||||
if (projectSdk != null) return projectSdk
|
||||
|
||||
@@ -140,118 +133,32 @@ class ScriptClassRootsCache(
|
||||
"projectSdk = ${ProjectRootManager.getInstance(project).projectSdk}, " +
|
||||
"all sdks = ${getAllProjectSdks().joinToString("\n")}"
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Sdk.canBeUsedForScript() = sdkType is JavaSdkType
|
||||
}
|
||||
|
||||
fun getLightScriptInfo(file: String) = scripts[file]
|
||||
|
||||
fun contains(file: VirtualFile): Boolean = file.path in scripts
|
||||
|
||||
private fun getHeavyScriptInfo(file: String): HeavyScriptInfo? {
|
||||
val lightScriptInfo = getLightScriptInfo(file) ?: return null
|
||||
val heavy0 = lightScriptInfo.heavyCache?.get()
|
||||
if (heavy0 != null) return heavy0
|
||||
synchronized(lightScriptInfo) {
|
||||
val heavy1 = lightScriptInfo.heavyCache?.get()
|
||||
if (heavy1 != null) return heavy1
|
||||
val heavy2 = computeHeavy(lightScriptInfo)
|
||||
lightScriptInfo.heavyCache = SoftReference(heavy2)
|
||||
return heavy2
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeHeavy(lightScriptInfo: LightScriptInfo): HeavyScriptInfo? {
|
||||
val configuration = lightScriptInfo.buildConfiguration() ?: return null
|
||||
|
||||
val roots = configuration.dependenciesClassPath
|
||||
val sdk = sdks[configuration.javaHome?.canonicalPath]
|
||||
|
||||
return if (sdk == null) {
|
||||
HeavyScriptInfo(configuration, compose(toVfsRoots(roots)), null)
|
||||
} else {
|
||||
val sdkClasses = sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList()
|
||||
HeavyScriptInfo(configuration, compose(sdkClasses + toVfsRoots(roots)), sdk)
|
||||
}
|
||||
}
|
||||
|
||||
val firstScriptSdk: Sdk? = sdks.values.firstOrNull()
|
||||
|
||||
val allDependenciesClassFiles: List<VirtualFile>
|
||||
|
||||
val allDependenciesSources: List<VirtualFile>
|
||||
|
||||
init {
|
||||
allDependenciesClassFiles = mutableSetOf<VirtualFile>().also { result ->
|
||||
nonModulesSdks.forEach { result.addAll(it.rootProvider.getFiles(OrderRootType.CLASSES)) }
|
||||
classes.mapNotNullTo(result) { classpathEntryToVfs(File(it)) }
|
||||
}.toList()
|
||||
|
||||
allDependenciesSources = mutableSetOf<VirtualFile>().also { result ->
|
||||
nonModulesSdks.forEach { result.addAll(it.rootProvider.getFiles(OrderRootType.SOURCES)) }
|
||||
sources.mapNotNullTo(result) { classpathEntryToVfs(File(it)) }
|
||||
}.toList()
|
||||
}
|
||||
|
||||
val allDependenciesClassFilesScope = compose(allDependenciesClassFiles)
|
||||
|
||||
val allDependenciesSourcesScope = compose(allDependenciesSources)
|
||||
|
||||
fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? =
|
||||
getHeavyScriptInfo(file.path)?.scriptConfiguration
|
||||
|
||||
fun getScriptSdk(file: VirtualFile): Sdk? =
|
||||
getHeavyScriptInfo(file.path)?.sdk
|
||||
|
||||
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
|
||||
getHeavyScriptInfo(file.path)?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE
|
||||
|
||||
fun hasInvalidSdk(project: Project): Boolean {
|
||||
val builder = Builder(project)
|
||||
if (sdks.any { (home, sdk) -> builder.addSdk(home?.let(::File)) != sdk }) return true
|
||||
if (builder.collectNonIndexedSdks() != nonModulesSdks) return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun diff(old: ScriptClassRootsCache): Updates =
|
||||
Updates(
|
||||
hasNewRoots(old),
|
||||
getChangedScripts(old)
|
||||
)
|
||||
|
||||
private fun hasNewRoots(old: ScriptClassRootsCache): Boolean {
|
||||
return classes.any { it !in old.classes }
|
||||
|| sources.any { it !in old.sources }
|
||||
|| sdks.any { it.key !in old.sdks }
|
||||
}
|
||||
|
||||
private fun getChangedScripts(old: ScriptClassRootsCache): Set<String> {
|
||||
val changed = mutableSetOf<String>()
|
||||
|
||||
scripts.forEach {
|
||||
if (old.scripts[it.key] != it.value) {
|
||||
changed.add(it.key)
|
||||
}
|
||||
fun Sdk.isAlreadyIndexed(project: Project): Boolean {
|
||||
return ModuleManager.getInstance(project).modules.any { ModuleRootManager.getInstance(it).sdk == this }
|
||||
}
|
||||
|
||||
old.scripts.forEach {
|
||||
if (it.key !in scripts) {
|
||||
changed.add(it.key)
|
||||
}
|
||||
fun empty(project: Project) = object : ScriptClassRootsCache(
|
||||
project,
|
||||
ScriptClassRootsStorage.Companion.Key("empty"),
|
||||
ScriptClassRootsStorage.Companion.ScriptClassRoots(
|
||||
setOf(),
|
||||
setOf(),
|
||||
setOf()
|
||||
)
|
||||
) {
|
||||
override fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = null
|
||||
|
||||
override val firstScriptSdk: Sdk? = null
|
||||
|
||||
override fun getScriptSdk(file: VirtualFile): Sdk? = null
|
||||
|
||||
override fun contains(file: VirtualFile): Boolean = true
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
|
||||
class Updates(
|
||||
val hasNewRoots: Boolean,
|
||||
val updatedScripts: Set<String>
|
||||
) {
|
||||
val changed: Boolean
|
||||
get() = hasNewRoots || updatedScripts.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.TransactionGuard
|
||||
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 org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Utility for postponing indexing of new roots to the end of some bulk operation.
|
||||
*/
|
||||
class ScriptClassRootsIndexer(val project: Project) {
|
||||
private var newRootsPresent: Boolean = false
|
||||
private val concurrentTransactions = AtomicInteger()
|
||||
|
||||
@Synchronized
|
||||
fun markNewRoot() {
|
||||
checkInTransaction()
|
||||
newRootsPresent = true
|
||||
}
|
||||
|
||||
fun checkInTransaction() {
|
||||
check(concurrentTransactions.get() > 0)
|
||||
}
|
||||
|
||||
inline fun <T> transaction(body: () -> T): T {
|
||||
startTransaction()
|
||||
return try {
|
||||
body()
|
||||
} finally {
|
||||
commit()
|
||||
}
|
||||
}
|
||||
|
||||
fun startTransaction() {
|
||||
concurrentTransactions.incrementAndGet()
|
||||
}
|
||||
|
||||
fun commit() {
|
||||
concurrentTransactions.decrementAndGet()
|
||||
|
||||
// run indexing even in inner transaction
|
||||
// (outer transaction may be async, so it would be better to not wait it)
|
||||
startIndexingIfNeeded()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun startIndexingIfNeeded() {
|
||||
if (!newRootsPresent) return
|
||||
newRootsPresent = false
|
||||
|
||||
val doNotifyRootsChanged = Runnable {
|
||||
runWriteAction {
|
||||
if (project.isDisposed) return@runWriteAction
|
||||
|
||||
debug { "roots change event" }
|
||||
|
||||
ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true)
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
}
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
TransactionGuard.submitTransaction(project, doNotifyRootsChanged)
|
||||
} else {
|
||||
TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
-22
@@ -7,44 +7,144 @@ package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.vfs.JarFileSystem
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
|
||||
/**
|
||||
* This cache is used by [org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport] only.
|
||||
* @see org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport.collectConfigurations
|
||||
*
|
||||
*/
|
||||
@State(
|
||||
name = "ScriptClassRootsStorage",
|
||||
storages = [Storage(StoragePathMacros.CACHE_FILE)]
|
||||
)
|
||||
class ScriptClassRootsStorage(val project: Project) : PersistentStateComponent<ScriptClassRootsStorage> {
|
||||
private var classpath: Set<String> = hashSetOf()
|
||||
private var sources: Set<String> = hashSetOf()
|
||||
private var sdks: Set<String> = hashSetOf()
|
||||
class ScriptClassRootsStorage : PersistentStateComponent<Element> {
|
||||
|
||||
override fun getState(): ScriptClassRootsStorage? {
|
||||
return this
|
||||
private var classpath: MutableMap<String, Set<String>> = hashMapOf()
|
||||
private var sources: MutableMap<String, Set<String>> = hashMapOf()
|
||||
private var sdks: MutableMap<String, Set<String>> = hashMapOf()
|
||||
|
||||
override fun getState(): Element {
|
||||
val root = Element("ScriptClassRootsStorage")
|
||||
|
||||
storeCollection(root, classpath, "classpath")
|
||||
storeCollection(root, sources, "sources")
|
||||
storeCollection(root, sdks, "sdk")
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
override fun loadState(state: ScriptClassRootsStorage) {
|
||||
XmlSerializerUtil.copyBean(state, this)
|
||||
private fun storeCollection(root: Element, col: Map<String, Set<String>>, name: String) {
|
||||
for ((key, paths) in col) {
|
||||
for (path in paths) {
|
||||
val element = Element(name)
|
||||
element.setAttribute("path", path)
|
||||
element.setAttribute("key", key)
|
||||
root.addContent(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun save(roots: ScriptClassRootsCache.Builder) {
|
||||
classpath = roots.classes
|
||||
sources = roots.sources
|
||||
sdks = roots.sdks.values.mapNotNullTo(mutableSetOf()) { it?.name }
|
||||
override fun loadState(state: Element) {
|
||||
classpath = readCollection(state, "classpath")
|
||||
sources = readCollection(state, "sources")
|
||||
sdks = readCollection(state, "sdks")
|
||||
}
|
||||
|
||||
fun load(builder: ScriptClassRootsCache.Builder) {
|
||||
builder.sources.addAll(sources)
|
||||
builder.classes.addAll(classpath)
|
||||
sdks.forEach(builder::addSdkByName)
|
||||
private fun readCollection(root: Element, name: String): MutableMap<String, Set<String>> {
|
||||
val result: MutableMap<String, HashSet<String>> = hashMapOf()
|
||||
for (it in root.getChildren(name)) {
|
||||
result.getOrPut(it.getAttributeValue("key")) {
|
||||
hashSetOf()
|
||||
}.add(it.getAttributeValue("path"))
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as MutableMap<String, Set<String>>
|
||||
}
|
||||
|
||||
private fun toStringNames(sdks: Collection<Sdk>): Set<String> {
|
||||
return sdks.map { it.name }.toSet()
|
||||
}
|
||||
|
||||
private fun toVirtualFiles(prop: Set<String>?, sources: Boolean): List<VirtualFile> {
|
||||
if (prop == null) return emptyList()
|
||||
|
||||
val rootType = if (sources) OrderRootType.SOURCES else OrderRootType.CLASSES
|
||||
return prop.mapNotNull { ProjectJdkTable.getInstance().findJdk(it) }
|
||||
.flatMap { it.rootProvider.getFiles(rootType).toList() }
|
||||
}
|
||||
|
||||
private fun toVirtualFiles(prop: Collection<String>?): List<VirtualFile> {
|
||||
if (prop == null) return emptyList()
|
||||
|
||||
return prop.mapNotNull {
|
||||
if (it.endsWith(JarFileSystem.PROTOCOL)) {
|
||||
StandardFileSystems.jar()?.findFileByPath(it + JarFileSystem.JAR_SEPARATOR)?.let {
|
||||
return@mapNotNull it
|
||||
}
|
||||
}
|
||||
|
||||
StandardFileSystems.local()?.findFileByPath(it)?.let {
|
||||
return@mapNotNull it
|
||||
}
|
||||
|
||||
// TODO: report this somewhere, but do not throw: assert(res != null, { "Invalid classpath entry '$this': exists: ${exists()}, is directory: $isDirectory, is file: $isFile" })
|
||||
|
||||
null
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
fun containsAll(key: Key, configuration: ScriptClassRoots): Boolean {
|
||||
if (configuration.classpathFiles.isNotEmpty() && classpath[key.value]?.containsAll(configuration.classpathFiles) != true) {
|
||||
debug { "class roots were changed: old = $classpath, new = ${configuration.classpathFiles}" }
|
||||
return false
|
||||
}
|
||||
if (configuration.sourcesFiles.isNotEmpty() && sources[key.value]?.containsAll(configuration.sourcesFiles) != true) {
|
||||
debug { "source roots were changed: old = $sources, new = ${configuration.sourcesFiles}" }
|
||||
return false
|
||||
}
|
||||
if (configuration.sdks.isNotEmpty() && sdks[key.value]?.containsAll(toStringNames(configuration.sdks)) != true) {
|
||||
debug { "sdk classes were changed: old = $sdks, new = ${configuration.sdks.map { it.homePath }}" }
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun save(key: Key, configuration: ScriptClassRoots) {
|
||||
// TODO: do not drop all storage on save: KT-34444
|
||||
classpath.getOrPut(key.value, { emptySet() })
|
||||
classpath.replace(key.value, configuration.classpathFiles)
|
||||
|
||||
sources.getOrPut(key.value, { emptySet() })
|
||||
sources.replace(key.value, configuration.sourcesFiles)
|
||||
|
||||
sdks.getOrPut(key.value, { emptySet() })
|
||||
sdks.replace(key.value, toStringNames(configuration.sdks))
|
||||
}
|
||||
|
||||
fun loadClasspathRoots(key: Key): List<VirtualFile> {
|
||||
return toVirtualFiles(sdks[key.value], false) + toVirtualFiles(classpath[key.value])
|
||||
}
|
||||
|
||||
fun loadSourcesRoots(key: Key): List<VirtualFile> {
|
||||
return toVirtualFiles(sdks[key.value], true) + toVirtualFiles(sources[key.value])
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): ScriptClassRootsStorage =
|
||||
ServiceManager.getService(project, ScriptClassRootsStorage::class.java)
|
||||
|
||||
data class ScriptClassRoots(
|
||||
val classpathFiles: Set<String>,
|
||||
val sourcesFiles: Set<String>,
|
||||
val sdks: Set<Sdk>
|
||||
)
|
||||
|
||||
val EMPTY = ScriptClassRoots(emptySet(), emptySet(), emptySet())
|
||||
|
||||
data class Key(val value: String)
|
||||
}
|
||||
}
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||
|
||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.TransactionGuard
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.util.BackgroundTaskUtil
|
||||
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.psi.PsiElementFinder
|
||||
import com.intellij.psi.PsiManager
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
|
||||
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.core.script.debug
|
||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Utility for postponing indexing of new roots to the end of some bulk operation.
|
||||
*/
|
||||
class ScriptClassRootsUpdater(
|
||||
val project: Project,
|
||||
val manager: CompositeScriptConfigurationManager
|
||||
) {
|
||||
private var invalidated: Boolean = false
|
||||
private var syncUpdateRequired: Boolean = false
|
||||
private val concurrentUpdates = AtomicInteger()
|
||||
|
||||
/**
|
||||
* @param synchronous Used from legacy FS cache only, don't use
|
||||
*/
|
||||
@Synchronized
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun invalidate(file: VirtualFile, synchronous: Boolean = false) {
|
||||
// todo: record invalided files for some optimisations in update
|
||||
invalidate(synchronous)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param synchronous Used from legacy FS cache only, don't use
|
||||
*/
|
||||
@Synchronized
|
||||
fun invalidate(synchronous: Boolean = false) {
|
||||
checkInTransaction()
|
||||
invalidated = true
|
||||
if (synchronous) {
|
||||
syncUpdateRequired = true
|
||||
}
|
||||
}
|
||||
|
||||
fun checkInTransaction() {
|
||||
check(concurrentUpdates.get() > 0)
|
||||
}
|
||||
|
||||
inline fun <T> update(body: () -> T): T {
|
||||
beginUpdating()
|
||||
return try {
|
||||
body()
|
||||
} finally {
|
||||
commit()
|
||||
}
|
||||
}
|
||||
|
||||
fun beginUpdating() {
|
||||
concurrentUpdates.incrementAndGet()
|
||||
}
|
||||
|
||||
fun commit() {
|
||||
concurrentUpdates.decrementAndGet()
|
||||
|
||||
// run update even in inner transaction
|
||||
// (outer transaction may be async, so it would be better to not wait it)
|
||||
scheduleUpdateIfInvalid()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun scheduleUpdateIfInvalid() {
|
||||
if (!invalidated) return
|
||||
invalidated = false
|
||||
|
||||
if (syncUpdateRequired || ApplicationManager.getApplication().isUnitTestMode) {
|
||||
syncUpdateRequired = false
|
||||
updateSynchronously()
|
||||
} else {
|
||||
ensureUpdateScheduled()
|
||||
}
|
||||
}
|
||||
|
||||
private val syncLock = ReentrantLock()
|
||||
private var scheduledUpdate: ProgressIndicator? = null
|
||||
|
||||
@Synchronized
|
||||
fun ensureUpdateScheduled() {
|
||||
scheduledUpdate?.cancel()
|
||||
scheduledUpdate = BackgroundTaskUtil.executeOnPooledThread(project) {
|
||||
doUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun updateSynchronously() {
|
||||
scheduledUpdate?.cancel()
|
||||
doUpdate(false)
|
||||
}
|
||||
|
||||
private fun doUpdate(underProgressManager: Boolean = true) {
|
||||
syncLock.withLock {
|
||||
try {
|
||||
val updates = manager.collectRootsAndCheckNew()
|
||||
|
||||
if (!updates.changed) return
|
||||
|
||||
if (underProgressManager) {
|
||||
ProgressManager.checkCanceled()
|
||||
}
|
||||
|
||||
if (updates.hasNewRoots) {
|
||||
notifyRootsChanged()
|
||||
}
|
||||
|
||||
PsiElementFinder.EP.findExtensionOrFail(KotlinScriptDependenciesClassFinder::class.java, project)
|
||||
.clearCache()
|
||||
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
|
||||
if (updates.updatedScripts.isNotEmpty()) {
|
||||
updateHighlighting(project) {
|
||||
it.path in updates.updatedScripts
|
||||
}
|
||||
}
|
||||
} catch (cancel: ProcessCanceledException) {
|
||||
if (underProgressManager) throw cancel
|
||||
} finally {
|
||||
synchronized(this) {
|
||||
scheduledUpdate = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyRootsChanged() {
|
||||
val doNotifyRootsChanged = Runnable {
|
||||
runWriteAction {
|
||||
if (project.isDisposed) return@runWriteAction
|
||||
|
||||
debug { "roots change event" }
|
||||
|
||||
ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true)
|
||||
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
|
||||
}
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
TransactionGuard.submitTransaction(project, doNotifyRootsChanged)
|
||||
} else {
|
||||
TransactionGuard.getInstance().submitTransactionLater(project, doNotifyRootsChanged)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateHighlighting(project: Project, filter: (VirtualFile) -> Boolean) {
|
||||
if (!project.isOpen) return
|
||||
|
||||
val openFiles = FileEditorManager.getInstance(project).openFiles
|
||||
val openedScripts = openFiles.filter { filter(it) }
|
||||
|
||||
if (openedScripts.isEmpty()) return
|
||||
|
||||
GlobalScope.launch(EDT(project)) {
|
||||
if (project.isDisposed) return@launch
|
||||
|
||||
openedScripts.forEach {
|
||||
PsiManager.getInstance(project).findFile(it)?.let { psiFile ->
|
||||
DaemonCodeAnalyzer.getInstance(project).restart(psiFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -10,7 +10,7 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.containers.HashSetQueue
|
||||
|
||||
class TestingBackgroundExecutor internal constructor(
|
||||
private val rootsManager: ScriptClassRootsUpdater
|
||||
private val rootsManager: ScriptClassRootsIndexer
|
||||
) : BackgroundExecutor {
|
||||
val backgroundQueue = HashSetQueue<BackgroundTask>()
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestingBackgroundExecutor internal constructor(
|
||||
|
||||
actions()
|
||||
|
||||
rootsManager.update {
|
||||
rootsManager.transaction {
|
||||
copy.forEach {
|
||||
it.actions()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user