diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt index aa1dfcfc610..06aa561e65f 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDependenciesUpdater.kt @@ -31,7 +31,6 @@ import com.intellij.util.Alarm import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.core.script.dependencies.FromFileAttributeScriptDependenciesLoader import org.jetbrains.kotlin.idea.core.script.dependencies.ScriptDependenciesLoader -import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings import org.jetbrains.kotlin.psi.NotNullableUserDataProperty import org.jetbrains.kotlin.script.ScriptDefinitionProvider import org.jetbrains.kotlin.script.findScriptDefinition @@ -42,7 +41,6 @@ class ScriptDependenciesUpdater( private val cache: ScriptDependenciesCache, private val scriptDefinitionProvider: ScriptDefinitionProvider ) { - private val modifiedScripts = mutableSetOf() private val scriptsQueue = Alarm(Alarm.ThreadToUse.SWING_THREAD, project) private val scriptChangesListenerDelay = 1400 @@ -61,24 +59,6 @@ class ScriptDependenciesUpdater( return cache[file] ?: ScriptDependencies.Empty } - fun reloadModifiedScripts() { - for (it in modifiedScripts.filter { cache[it] != null }) { - val scriptDef = scriptDefinitionProvider.findScriptDefinition(it) ?: return - ScriptDependenciesLoader.updateDependencies(it, scriptDef, project, shouldNotifyRootsChanged = true) - } - modifiedScripts.clear() - } - - fun requestUpdate(files: Iterable) { - files.forEach { file -> - if (!file.isValid) { - cache.delete(file) - } else if (cache[file] != null) { // only update dependencies for scripts that were touched recently - modifiedScripts.add(file) - } - } - } - private fun listenForChangesInScripts() { project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener { override fun fileOpened(source: FileEditorManager, file: VirtualFile) { @@ -95,17 +75,24 @@ class ScriptDependenciesUpdater( val document = event.document val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return + if (!file.isValid) { + cache.delete(file) + return + } + + // only update dependencies for scripts that were touched recently + if (cache[file] == null) { + return + } + + val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) ?: return scriptsQueue.cancelAllRequests() scriptsQueue.addRequest( { FileDocumentManager.getInstance().saveDocument(document) - requestUpdate(listOf(file)) - - if (KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled) { - reloadModifiedScripts() - } + ScriptDependenciesLoader.updateDependencies(file, scriptDef, project, shouldNotifyRootsChanged = true) }, scriptChangesListenerDelay, true diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptNewDependenciesNotification.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptNewDependenciesNotification.kt new file mode 100644 index 00000000000..ca7c4912f22 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptNewDependenciesNotification.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.core.script + +import com.intellij.openapi.fileEditor.FileEditor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.Ref +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.ui.EditorNotificationPanel +import com.intellij.ui.HyperlinkLabel +import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings +import org.jetbrains.kotlin.psi.UserDataProperty +import kotlin.script.experimental.dependencies.DependenciesResolver + +fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) { + val editor = FileEditorManager.getInstance(project).getSelectedEditor(this) ?: return + editor.notificationPanel?.let { FileEditorManager.getInstance(project).removeTopComponent(editor, it) } + editor.notificationPanel = null +} + +fun VirtualFile.addScriptDependenciesNotificationPanel( + result: DependenciesResolver.ResolveResult, + project: Project, + onClick: (DependenciesResolver.ResolveResult) -> Unit +) { + val editor = FileEditorManager.getInstance(project).getSelectedEditor(this) ?: return + val existingPanel = editor.notificationPanel + if (existingPanel != null) { + if (existingPanel.result == result) return + editor.notificationPanel?.let { + FileEditorManager.getInstance(project).removeTopComponent(editor, it) + } + } + + val panel = NewScriptDependenciesNotificationPanel(onClick, result, project) + editor.notificationPanel = panel + FileEditorManager.getInstance(project).addTopComponent(editor, panel) +} + +private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty(Key.create("script.dependencies.panel")) + +private class NewScriptDependenciesNotificationPanel( + onClick: (DependenciesResolver.ResolveResult) -> Unit, + val result: DependenciesResolver.ResolveResult, + project: Project +) : EditorNotificationPanel() { + + init { + setText("There are new script dependencies available.") + createComponentActionLabel("Reload dependencies") { + onClick(result) + } + + createComponentActionLabel("Enable auto-reload") { + onClick(result) + KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled = true + } + } + + private fun EditorNotificationPanel.createComponentActionLabel(labelText: String, callback: (HyperlinkLabel) -> Unit) { + val label: Ref = Ref.create() + val action = Runnable { + callback(label.get()) + } + label.set(createActionLabel(labelText, action)) + } +} \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt index b29ce24ba19..9635e9dff35 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/AsyncScriptDependenciesLoader.kt @@ -40,6 +40,7 @@ class AsyncScriptDependenciesLoader( } override fun shouldUseBackgroundThread() = KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled + override fun shouldShowNotification(): Boolean = !KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled private var lastRequest: ModStampedRequest? = null diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt index 31023ffe418..2e4122bb97d 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/FromFileAttributeScriptDependenciesLoader.kt @@ -31,4 +31,5 @@ class FromFileAttributeScriptDependenciesLoader( } override fun shouldUseBackgroundThread() = false + override fun shouldShowNotification(): Boolean = false } \ No newline at end of file diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt index 4c24cd0c308..e1aad9d5e3c 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/ScriptDependenciesLoader.kt @@ -15,9 +15,7 @@ import com.intellij.openapi.util.EmptyRunnable import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.containers.SLRUMap import kotlinx.coroutines.experimental.launch -import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesCache -import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker -import org.jetbrains.kotlin.idea.core.script.scriptDependencies +import org.jetbrains.kotlin.idea.core.script.* import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.script.* @@ -68,16 +66,36 @@ abstract class ScriptDependenciesLoader( protected abstract fun loadDependencies() protected abstract fun shouldUseBackgroundThread(): Boolean + protected abstract fun shouldShowNotification(): Boolean protected val contentLoader = ScriptContentLoader(project) protected val cache: ScriptDependenciesCache = ServiceManager.getService(project, ScriptDependenciesCache::class.java) protected fun processResult(result: DependenciesResolver.ResolveResult) { - saveDependencies(result) + ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports) + + val newDependencies = result.dependencies?.adjustByDefinition(scriptDef) ?: return + if (cache[file] != newDependencies) { + if (shouldShowNotification() && cache[file] != null && !ApplicationManager.getApplication().isUnitTestMode) { + file.addScriptDependenciesNotificationPanel(result, project) { + saveDependencies(result) + } + } else { + saveDependencies(result) + } + } else { + if (shouldShowNotification()) { + file.removeScriptDependenciesNotificationPanel(project) + } + } + + loaders.remove(file) } private fun saveDependencies(result: DependenciesResolver.ResolveResult) { - ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports) + if (shouldShowNotification()) { + file.removeScriptDependenciesNotificationPanel(project) + } val newDependencies = result.dependencies?.adjustByDefinition(scriptDef) ?: ScriptDependencies.Empty @@ -93,8 +111,6 @@ abstract class ScriptDependenciesLoader( if (rootsChanged) { notifyRootsChanged() } - - loaders.remove(file) } @Suppress("EXPERIMENTAL_FEATURE_WARNING") diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt index 6f0f7eb61f3..a1491dfee68 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/SyncScriptDependenciesLoader.kt @@ -22,4 +22,5 @@ class SyncScriptDependenciesLoader( } override fun shouldUseBackgroundThread(): Boolean = false + override fun shouldShowNotification(): Boolean = false } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt index 465b6fb5239..03d4e119d43 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt @@ -283,8 +283,6 @@ class ReloadGradleTemplatesOnSync : ExternalSystemTaskNotificationListenerAdapte val project = id.findProject() ?: return val gradleDefinitionsContributor = ScriptDefinitionContributor.find(project) gradleDefinitionsContributor?.reloadIfNeccessary() - - ServiceManager.getService(project, ScriptDependenciesUpdater::class.java).reloadModifiedScripts() } } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt.172 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt.172 index 524a782944b..fa0b8e594c9 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt.172 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt.172 @@ -285,8 +285,6 @@ class ReloadGradleTemplatesOnSync : ExternalSystemTaskNotificationListenerAdapte val project = id.findProject() ?: return val gradleDefinitionsContributor = ScriptDefinitionContributor.find(project) gradleDefinitionsContributor?.reloadIfNeccessary() - - ServiceManager.getService(project, ScriptDependenciesUpdater::class.java).reloadModifiedScripts() } } }