diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt index ed1d5679fc9..311bcd91f14 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt @@ -62,6 +62,16 @@ class ScriptDefinitionsManager(private val project: Project): ScriptDefinitionPr updateDefinitions() } + fun getDefinitionsBy(contributor: ScriptDefinitionContributor): List = lock.write { + val notLoadedYet = definitions.isEmpty() + if (notLoadedYet) return emptyList() + + if (contributor !in definitionsByContributor) error("Unknown contributor: ${contributor.id}") + + if (contributor.isError()) return emptyList() + return definitionsByContributor[contributor] ?: emptyList() + } + private fun currentDefinitions(): List { val hasDefinitions = definitions.isNotEmpty() when { 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 0d6a6536db4..2f01336cd76 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 @@ -107,7 +107,7 @@ class ScriptDependenciesUpdater( } } - private fun requestUpdate(files: Iterable) { + fun requestUpdate(files: Iterable) { files.forEach { file -> if (!file.isValid) { cache.delete(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 9a22f792273..853c0e8b695 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 @@ -71,6 +71,8 @@ class GradleScriptDefinitionsContributor(private val project: Project): ScriptDe } } project.messageBus.connect(project).subscribe(GradleSettingsListener.TOPIC, listener) + + ServiceManager.getService(project, ScriptModificationListener::class.java) } override fun getDefinitions(): List { diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/ScriptModificationListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/ScriptModificationListener.kt new file mode 100644 index 00000000000..e5cd89d9006 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/core/script/ScriptModificationListener.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2000-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.application.ApplicationManager +import com.intellij.openapi.application.Result +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManager +import com.intellij.openapi.externalSystem.util.ExternalSystemUtil +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.newvfs.BulkFileListener +import com.intellij.openapi.vfs.newvfs.events.VFileEvent +import com.intellij.psi.PsiDocumentManager +import com.intellij.util.ui.update.MergingUpdateQueue +import com.intellij.util.ui.update.MergingUpdateQueue.ANY_COMPONENT +import com.intellij.util.ui.update.Update +import org.jetbrains.plugins.gradle.service.project.GradleAutoImportAware + +class ScriptModificationListener(private val project: Project) { + private val changedDocuments = HashSet() + private val changedDocumentsQueue = MergingUpdateQueue("ScriptModificationListener: Scripts queue", 1000, false, ANY_COMPONENT, project) + + init { + showNotificationIfScriptChangedListener() + + saveScriptAfterModificationListener() + } + + private fun showNotificationIfScriptChangedListener() { + project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() { + override fun after(events: List) { + if (ApplicationManager.getApplication().isUnitTestMode) return + + val modifiedScripts = events.mapNotNull { + it.file?.takeIf { isGradleScript(it) } + } + + // Workaround for IDEA-182367 (fixed in IDEA 181.3666) + if (modifiedScripts.isNotEmpty()) { + if (modifiedScripts.any { + GradleAutoImportAware().getAffectedExternalProjectPath(it.path, project) != null + }) { + return + } + ExternalProjectsManager.getInstance(project).externalProjectsWatcher.markDirty(project.basePath) + } + } + }) + } + + private fun saveScriptAfterModificationListener() { + // partially copied from ExternalSystemProjectsWatcherImpl before fix will be implemented in IDEA: + // "Gradle projects need to be imported" notification should be shown when kotlin script is modified + val busConnection = project.messageBus.connect(changedDocumentsQueue) + changedDocumentsQueue.activate() + + EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener { + override fun documentChanged(event: DocumentEvent) { + if (project.isDisposed) return + + val doc = event.document + val file = FileDocumentManager.getInstance().getFile(doc) ?: return + + if (isGradleScript(file)) { + synchronized(changedDocuments) { + changedDocuments.add(doc) + } + + changedDocumentsQueue.queue(object : Update(this) { + override fun run() { + var copy: Array = emptyArray() + + synchronized(changedDocuments) { + copy = changedDocuments.toTypedArray() + changedDocuments.clear() + } + + ExternalSystemUtil.invokeLater(project) { + object : WriteAction() { + override fun run(result: Result) { + for (each in copy) { + PsiDocumentManager.getInstance(project).commitDocument(each) + (FileDocumentManager.getInstance() as? FileDocumentManagerImpl)?.saveDocument(each, false) + } + } + }.execute() + } + } + }) + } + } + }, busConnection) + } + + private fun isGradleScript(file: VirtualFile): Boolean { + if (!ProjectRootManager.getInstance(project).fileIndex.isInContent(file)) return false + + val contributor = ScriptDefinitionContributor.find(project) ?: return false + return ScriptDefinitionsManager.getInstance(project).getDefinitionsBy(contributor).any { + it.isScript(file.name) + } + } +} diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 9ec0f6a20c6..c3ef37d8447 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -341,6 +341,9 @@ + +