diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt index 4928b29f410..f381989eeab 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt @@ -24,6 +24,7 @@ import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.core.script.* import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache +import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCacheScope import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationState import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater @@ -130,13 +131,8 @@ internal abstract class AbstractScriptConfigurationManager( return reloadIfOutOfDate(files, false) } - override fun forceConfigurationReload(file: KtFile) { - val virtualFile = file.originalFile.virtualFile ?: return - cache.markOutOfDate(virtualFile) - - rootsIndexer.transaction { - reloadOutOfDateConfiguration(file, loadEvenWillNotBeApplied = true) - } + override fun postponeConfigurationReload(scope: ScriptConfigurationCacheScope) { + cache.markOutOfDate(scope) } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt index 681f8537290..55c2b90b6e8 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/DefaultScriptConfigurationManager.kt @@ -9,7 +9,6 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.ServiceManager 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.PsiManager @@ -18,12 +17,9 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch 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.DefaultScriptConfigurationManagerExtensions.LISTENER import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot -import org.jetbrains.kotlin.idea.core.script.configuration.listener.DefaultScriptChangeListener -import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener 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.DefaultScriptConfigurationLoader @@ -119,14 +115,7 @@ internal class DefaultScriptConfigurationManager(project: Project) : yield(defaultLoader) } - private val defaultListener = DefaultScriptChangeListener() - private val listeners: Sequence - get() = sequence { - yieldAll(LISTENER.getPoint(project).extensionList) - yield(defaultListener) - } - - private val notifier = ScriptChangesNotifier(project, updater, listeners) + private val notifier = ScriptChangesNotifier(project, updater) private val saveLock = ReentrantLock() @@ -322,9 +311,6 @@ internal class DefaultScriptConfigurationManager(project: Project) : object DefaultScriptConfigurationManagerExtensions { val LOADER: ExtensionPointName = ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.loader") - - val LISTENER: ExtensionPointName = - ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.listener") } val ScriptConfigurationManager.testingBackgroundExecutor diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt index ca979608be5..20a0b5d93cf 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationCache.kt @@ -29,12 +29,18 @@ interface ScriptConfigurationCache { fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) - fun markOutOfDate(file: VirtualFile) + fun markOutOfDate(scope: ScriptConfigurationCacheScope) fun allApplied(): Collection> fun clear() } +sealed class ScriptConfigurationCacheScope { + object All : ScriptConfigurationCacheScope() + class File(val file: KtFile) : ScriptConfigurationCacheScope() + class Except(val file: KtFile) : ScriptConfigurationCacheScope() +} + data class ScriptConfigurationState( val applied: ScriptConfigurationSnapshot? = null, val loaded: ScriptConfigurationSnapshot? = null diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt index fa67a48cd24..98ec1f8ef55 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/cache/ScriptConfigurationMemoryCache.kt @@ -37,14 +37,26 @@ open class ScriptConfigurationMemoryCache( } @Synchronized - override fun markOutOfDate(file: VirtualFile) { - val old = memoryCache[file] ?: ScriptConfigurationState() - memoryCache.put( - file, old.copy( - applied = old.applied?.copy(inputs = CachedConfigurationInputs.OutOfDate), - loaded = old.loaded?.copy(inputs = CachedConfigurationInputs.OutOfDate) - ) - ) + override fun markOutOfDate(scope: ScriptConfigurationCacheScope) { + when (scope) { + is ScriptConfigurationCacheScope.File -> { + val file = scope.file.originalFile.virtualFile + markFileOutOfDate(file) + } + is ScriptConfigurationCacheScope.Except -> { + val file = scope.file.originalFile.virtualFile + memoryCache.entrySet().forEach { + if (it.key != file) { + markFileOutOfDate(it.key) + } + } + } + is ScriptConfigurationCacheScope.All ->{ + memoryCache.entrySet().forEach { + markFileOutOfDate(it.key) + } + } + } } @Synchronized @@ -59,4 +71,15 @@ open class ScriptConfigurationMemoryCache( override fun clear() { memoryCache.clear() } + + @Synchronized + private fun markFileOutOfDate(file: VirtualFile) { + val old = memoryCache[file] ?: return + memoryCache.put( + file, old.copy( + applied = old.applied?.copy(inputs = CachedConfigurationInputs.OutOfDate), + loaded = old.loaded?.copy(inputs = CachedConfigurationInputs.OutOfDate) + ) + ) + } } \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt index 54b5ba93126..3240a8e53a2 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/DefaultScriptChangeListener.kt @@ -4,16 +4,22 @@ */ package org.jetbrains.kotlin.idea.core.script.configuration.listener -import org.jetbrains.kotlin.psi.KtFile +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.scripting.definitions.isNonScript -open class DefaultScriptChangeListener : ScriptChangeListener { - override fun editorActivated(file: KtFile, updater: ScriptConfigurationUpdater): Boolean { +open class DefaultScriptChangeListener(project: Project) : ScriptChangeListener(project) { + override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { + val file = getAnalyzableKtFileForScript(vFile) ?: return updater.ensureUpToDatedConfigurationSuggested(file) - return true } - override fun documentChanged(file: KtFile, updater: ScriptConfigurationUpdater): Boolean { + override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { + val file = getAnalyzableKtFileForScript(vFile) ?: return updater.ensureUpToDatedConfigurationSuggested(file) - return true + } + + override fun isApplicable(vFile: VirtualFile): Boolean { + return vFile.isValid && !vFile.isNonScript() } } \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt index 92b3a26fc9a..859c486a543 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangeListener.kt @@ -5,18 +5,37 @@ package org.jetbrains.kotlin.idea.core.script.configuration.listener -import org.jetbrains.kotlin.psi.KtFile +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.DefaultScriptConfigurationManager +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.psi.KtFile /** * [ScriptChangesNotifier] will call first applicable [ScriptChangeListener] when editor is activated or document changed. - * (it treated as applicable if [editorActivated] or [documentChanged] will return true). * * Listener may call [ScriptConfigurationUpdater] to invalidate configuration and schedule reloading. * * @see DefaultScriptConfigurationManager for more details. */ -interface ScriptChangeListener { - fun editorActivated(file: KtFile, updater: ScriptConfigurationUpdater): Boolean - fun documentChanged(file: KtFile, updater: ScriptConfigurationUpdater): Boolean +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 + + protected fun getAnalyzableKtFileForScript(vFile: VirtualFile): KtFile? { + if (project.isDisposed) return null + + return (PsiManager.getInstance(project).findFile(vFile) as? KtFile)?.takeIf { + ProjectRootsUtil.isInProjectSource(it, includeScriptsOutsideSourceRoots = true) + } + } + + companion object { + val LISTENER: ExtensionPointName = + ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.listener") + } } \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt index cd065867f99..d28fb35d396 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptChangesNotifier.kt @@ -15,17 +15,13 @@ import com.intellij.openapi.fileEditor.FileEditorManagerEvent import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiManager import com.intellij.util.Alarm +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener.Companion.LISTENER import org.jetbrains.kotlin.idea.core.script.isScriptChangesNotifierDisabled -import org.jetbrains.kotlin.idea.util.ProjectRootsUtil -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.scripting.definitions.isNonScript internal class ScriptChangesNotifier( private val project: Project, - private val updater: ScriptConfigurationUpdater, - private val listeners: Sequence + private val updater: ScriptConfigurationUpdater ) { private val scriptsQueue = Alarm(Alarm.ThreadToUse.SWING_THREAD, project) private val scriptChangesListenerDelay = 1400 @@ -45,9 +41,7 @@ internal class ScriptChangesNotifier( } private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) { - val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return - - listeners.first { it.editorActivated(ktFile, updater) } + getListener(project, file)?.editorActivated(file, updater) } }) @@ -55,12 +49,12 @@ internal class ScriptChangesNotifier( override fun documentChanged(event: DocumentEvent) { val document = event.document val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return - val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return + + val listener = getListener(project, file) ?: return scriptsQueue.cancelAllRequests() - scriptsQueue.addRequest( - { listeners.first { it.documentChanged(ktFile, updater) } }, + { listener.documentChanged(file, updater) }, scriptChangesListenerDelay, true ) @@ -68,23 +62,20 @@ internal class ScriptChangesNotifier( }, project.messageBus.connect()) } - private fun getKtFileToStartConfigurationUpdate(file: VirtualFile): KtFile? { - if (project.isDisposed || !file.isValid || file.isNonScript()) { - return null + private val defaultListener = DefaultScriptChangeListener(project) + private val listeners: Sequence + get() = sequence { + yieldAll(LISTENER.getPoint(project).extensionList) + yield(defaultListener) } - if ( - ApplicationManager.getApplication().isUnitTestMode && - ApplicationManager.getApplication().isScriptChangesNotifierDisabled == true - ) { - return null - } + private fun getListener(project: Project, file: VirtualFile): ScriptChangeListener? { + if (project.isDisposed || areListenersDisabled()) return null - val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null - if (ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)) { - return ktFile - } - - return null + return listeners.firstOrNull { it.isApplicable(file) } } -} \ No newline at end of file + + private fun areListenersDisabled(): Boolean { + return ApplicationManager.getApplication().isUnitTestMode && ApplicationManager.getApplication().isScriptChangesNotifierDisabled == true + } +} diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptConfigurationUpdater.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptConfigurationUpdater.kt index 4c2b11435fb..1a7eb818ab6 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptConfigurationUpdater.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/listener/ScriptConfigurationUpdater.kt @@ -5,6 +5,7 @@ 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 { @@ -24,7 +25,7 @@ interface ScriptConfigurationUpdater { fun ensureConfigurationUpToDate(files: List): Boolean /** - * Invalidate current configuration and start loading new + * Invalidate current configuration */ - fun forceConfigurationReload(file: KtFile) + fun postponeConfigurationReload(scope: ScriptConfigurationCacheScope) } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt index 8ce500ae183..cae8e84ecdd 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptListener.kt @@ -5,24 +5,49 @@ package org.jetbrains.kotlin.idea.scripting.gradle -import org.jetbrains.kotlin.idea.core.script.configuration.listener.DefaultScriptChangeListener +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCacheScope +import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater -import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.plugins.gradle.GradleManager +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.util.GradleConstants -class GradleScriptListener : DefaultScriptChangeListener() { - private val listenGradleRelatedFiles = false // todo +class GradleScriptListener(project: Project) : ScriptChangeListener(project) { - override fun editorActivated(file: KtFile, updater: ScriptConfigurationUpdater): Boolean { - if (!isGradleKotlinScript(file.virtualFile)) return false - if (listenGradleRelatedFiles) { - updater.ensureUpToDatedConfigurationSuggested(file) - // todo: force reload if related files changed + override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { + if (!isGradleKotlinScript(vFile)) return + + if (useScriptConfigurationFromImportOnly()) { + // do nothing } else { - // configuration will be reloaded after editor activation, even it is already up-to-date - // this is required for Gradle scripts, since it's classpath may depend on other files (`.properties` for example) - updater.forceConfigurationReload(file) + val file = getAnalyzableKtFileForScript(vFile) ?: return + updater.ensureUpToDatedConfigurationSuggested(file) } + } - return true + override fun documentChanged(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { + val file = getAnalyzableKtFileForScript(vFile) + if (file != null) { + // *.gradle.kts file was changed + updater.ensureUpToDatedConfigurationSuggested(file) + updater.postponeConfigurationReload(ScriptConfigurationCacheScope.Except(file)) + } else { + updater.postponeConfigurationReload(ScriptConfigurationCacheScope.All) + } + } + + override fun isApplicable(vFile: VirtualFile): Boolean { + val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) + if (gradleSettings.getLinkedProjectsSettings().isEmpty()) return false + + val projectSettings = gradleSettings.getLinkedProjectsSettings().filterIsInstance().firstOrNull() + ?: return false + + val affectedFiles = ExternalSystemApiUtil.getAllManagers().filterIsInstance().firstOrNull() + ?.getAffectedExternalProjectFiles(projectSettings.externalProjectPath, project) + return affectedFiles?.any { it.path == vFile.path } == true } } \ No newline at end of file