Implement global notification for configurations update of gradle scripts

For 193: it will be a simple notification at the bottom of the IDEA window
For 201: a floating notification in the editor is implemented near "Load Gradle Changes" icon
^KT-34443 Fixed
This commit is contained in:
Natalia Selezneva
2020-03-18 10:47:56 +03:00
parent e300b30de1
commit a35523b6d0
11 changed files with 213 additions and 12 deletions
@@ -56,6 +56,7 @@ public interface KotlinIcons {
Icon EXPECT = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/expect.svg"); Icon EXPECT = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/expect.svg");
Icon LAUNCH = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_launch_configuration.svg"); Icon LAUNCH = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_launch_configuration.svg");
Icon LOAD_SCRIPT_CONFIGURATION = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/loadScriptConfiguration.svg");
Icon JS = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_js.svg"); Icon JS = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_js.svg");
Icon MPP = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_multiplatform_project.svg"); Icon MPP = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_multiplatform_project.svg");
@@ -58,9 +58,8 @@ class DefaultScriptConfigurationNotificationFactory : ScriptConfigurationNotific
withSelectedEditor(project) { manager -> withSelectedEditor(project) { manager ->
val existingPanel = notificationPanel val existingPanel = notificationPanel
if (existingPanel != null) { if (existingPanel != null) {
notificationPanel?.let { existingPanel.onClick = onClick
manager.removeTopComponent(this, it) return@withSelectedEditor
}
} }
val panel = NewLoadConfigurationNotificationPanel(onClick, project) val panel = NewLoadConfigurationNotificationPanel(onClick, project)
@@ -84,7 +83,7 @@ class DefaultScriptConfigurationNotificationFactory : ScriptConfigurationNotific
by UserDataProperty<FileEditor, NewLoadConfigurationNotificationPanel>(Key.create("load.script.configuration.panel")) by UserDataProperty<FileEditor, NewLoadConfigurationNotificationPanel>(Key.create("load.script.configuration.panel"))
private class NewLoadConfigurationNotificationPanel( private class NewLoadConfigurationNotificationPanel(
val onClick: () -> Unit, var onClick: () -> Unit,
project: Project project: Project
) : EditorNotificationPanel() { ) : EditorNotificationPanel() {
@@ -191,17 +191,16 @@ internal class DefaultScriptConfigurationManager(project: Project) :
if (postponeLoading) { if (postponeLoading) {
ScriptConfigurationNotificationFactory.NOTIFICATION_FACTORY.getPoint(project).extensionList.firstOrNull { ScriptConfigurationNotificationFactory.NOTIFICATION_FACTORY.getPoint(project).extensionList.firstOrNull {
it.showNotification(virtualFile, project) { it.showNotification(virtualFile, project) {
runAsyncLoaders(file, virtualFile, scriptDefinition, async, postponeLoading) runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true)
} }
} }
} else { } else {
runAsyncLoaders(file, virtualFile, scriptDefinition, async, postponeLoading) runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false)
} }
} }
} }
} }
// в нотификации остается старый колбек - нужно перезаписывать колбек
private fun runAsyncLoaders( private fun runAsyncLoaders(
file: KtFile, file: KtFile,
virtualFile: VirtualFile, virtualFile: VirtualFile,
@@ -53,3 +53,5 @@ notification.title.script.configuration.has.been.changed=Script configurations h
notification.text.script.configuration.has.been.changed=Gradle Project needs to be imported to load changes. notification.text.script.configuration.has.been.changed=Gradle Project needs to be imported to load changes.
action.label.import.project=Import Project action.label.import.project=Import Project
action.label.enable.auto.import=Enable Auto-Import action.label.enable.auto.import=Enable Auto-Import
action.text.load.script.configurations=Load Script Configurations
action.description.load.script.configurations=Gradle Kotlin DSL script configuration has been changed. Load changes to get script code insight without loading the external Gradle project.
@@ -7,10 +7,17 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.notification.*
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleConstants
@@ -26,3 +33,76 @@ fun runPartialGradleImport(project: Project) {
.build() .build()
) )
} }
private val kotlinDslNotificationGroupId = "Gradle Kotlin DSL Scripts"
private var Project.notificationPanel: ScriptConfigurationChangedNotification?
by UserDataProperty<Project, ScriptConfigurationChangedNotification>(Key.create("load.script.configuration.panel"))
fun showNotificationForProjectImport(project: Project, callback: () -> Unit) {
val existingPanel = project.notificationPanel
if (existingPanel != null) {
existingPanel.update(callback)
return
}
val notificationGroup = NotificationGroup.findRegisteredGroup(kotlinDslNotificationGroupId)
if (notificationGroup == null) {
NotificationsConfiguration.getNotificationsConfiguration().register(
kotlinDslNotificationGroupId, NotificationDisplayType.STICKY_BALLOON, false
)
}
val notification = ScriptConfigurationChangedNotification(project, callback)
project.notificationPanel = notification
notification.notify(project)
}
fun hideNotificationForProjectImport(project: Project): Boolean {
if (project.notificationPanel == null) return false
project.notificationPanel?.expire()
return true
}
private class ScriptConfigurationChangedNotification(val project: Project, onClick: () -> Unit) :
Notification(
kotlinDslNotificationGroupId,
KotlinIcons.LOAD_SCRIPT_CONFIGURATION,
KotlinIdeaGradleBundle.message("notification.title.script.configuration.has.been.changed"),
null,
KotlinIdeaGradleBundle.message("notification.text.script.configuration.has.been.changed"),
NotificationType.INFORMATION,
null
) {
init {
//NotificationGroup.findRegisteredGroup(s) ?: NotificationGroup.balloonGroup(s)
addAction(LoadConfigurationAction(onClick))
addAction(NotificationAction.createSimple(KotlinIdeaGradleBundle.message("action.label.enable.auto.import")) {
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID)
val projectSettings = gradleSettings.getLinkedProjectsSettings()
.filterIsInstance<GradleProjectSettings>()
.firstOrNull()
if (projectSettings != null) {
projectSettings.isUseAutoImport = true
}
})
}
fun update(onClick: () -> Unit) {
actions.filterIsInstance<LoadConfigurationAction>().forEach { it.onClick = onClick }
}
override fun expire() {
super.expire()
project.notificationPanel = null
}
private class LoadConfigurationAction(var onClick: () -> Unit) : AnAction(KotlinIdeaGradleBundle.message("action.label.import.project")) {
override fun actionPerformed(e: AnActionEvent) {
onClick()
}
}
}
@@ -7,13 +7,29 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.diff.util.DiffUtil
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.testFramework.LightVirtualFileBase
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver
import org.jetbrains.plugins.gradle.service.project.GradleIncrementalResolverPolicy import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.plugins.gradle.service.project.GradlePartialResolverPolicy
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtension
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleConstants
import java.util.function.Predicate
fun runPartialGradleImport(project: Project) { fun runPartialGradleImport(project: Project) {
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID)
@@ -25,9 +41,62 @@ fun runPartialGradleImport(project: Project) {
projectSettings.externalProjectPath, projectSettings.externalProjectPath,
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID) ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)
.projectResolverPolicy( .projectResolverPolicy(
GradleIncrementalResolverPolicy { GradlePartialResolverPolicy(Predicate<GradleProjectResolverExtension?> { it is KotlinDslScriptModelResolver })
it is KotlinDslScriptModelResolver
}
) )
) )
} }
private var Project.notificationPanel: Boolean?
by UserDataProperty<Project, Boolean>(Key.create("load.script.configuration.panel"))
fun showNotificationForProjectImport(project: Project, callback: () -> Unit) {
project.notificationPanel = true
(ActionManager.getInstance().getAction("LoadConfigurationAction") as LoadConfigurationAction).onClick = callback
}
fun hideNotificationForProjectImport(project: Project): Boolean {
project.notificationPanel = false
return true
}
class LoadConfigurationAction : AnAction(
KotlinIdeaGradleBundle.message("action.text.load.script.configurations"),
KotlinIdeaGradleBundle.message("action.description.load.script.configurations"),
KotlinIcons.LOAD_SCRIPT_CONFIGURATION
) {
var onClick = {}
override fun actionPerformed(e: AnActionEvent) {
onClick()
}
override fun update(e: AnActionEvent) {
ensureValidActionVisibility(e)
e.presentation.description = KotlinIdeaGradleBundle.message("action.description.load.script.configurations")
}
private fun ensureValidActionVisibility(e: AnActionEvent) {
if (e.project?.notificationPanel != true) {
e.presentation.isVisible = false
return
}
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
when {
DiffUtil.isDiffEditor(editor) -> e.presentation.isVisible = false
!editor.isScriptEditor() -> e.presentation.isVisible = false
}
}
private fun Editor.isScriptEditor(): Boolean {
val documentManager = FileDocumentManager.getInstance()
val virtualFile = documentManager.getFile(document)
if (virtualFile is LightVirtualFileBase) return false
if (virtualFile == null || !virtualFile.isValid) return false
val project = project ?: return false
return virtualFile.findScriptDefinition(project) != null
}
}
@@ -0,0 +1,32 @@
/*
* 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.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationNotificationFactory
class GradleScriptConfigurationNotificationFactory : ScriptConfigurationNotificationFactory {
override fun showNotification(file: VirtualFile, project: Project, onClick: () -> Unit): Boolean {
if (isGradleKotlinScript(file) && isGradleImportCanBeUsed(project)) {
showNotificationForProjectImport(project, onClick)
return true
}
return false
}
override fun hideNotification(file: VirtualFile, project: Project): Boolean {
return hideNotificationForProjectImport(project)
}
private fun isGradleImportCanBeUsed(project: Project): Boolean {
val gradleVersion = getGradleVersion(project)
if (gradleVersion != null && kotlinDslScriptsModelImportSupported(gradleVersion)) {
return true
}
return false
}
}
+3
View File
@@ -15,6 +15,9 @@
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea"> <extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/> <listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/> <loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<notificationFactory
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationNotificationFactory"
/>
</extensions> </extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle"> <extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
+7 -1
View File
@@ -15,6 +15,9 @@
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea"> <extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/> <listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/> <loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<notificationFactory
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationNotificationFactory"
/>
</extensions> </extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle"> <extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
@@ -29,7 +32,6 @@
<projectTaskRunner implementation="org.jetbrains.kotlin.idea.gradle.execution.KotlinMPPGradleProjectTaskRunner" order="first, before gradle"/> <projectTaskRunner implementation="org.jetbrains.kotlin.idea.gradle.execution.KotlinMPPGradleProjectTaskRunner" order="first, before gradle"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinSourceSetDataService"/> <externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleProjectSettingsDataService"/> <externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleProjectSettingsDataService"/>
<externalSystemTaskNotificationListener implementation="org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelsUpdater"/>
<registryKey key="kotlin.mpp.tests.force.gradle" <registryKey key="kotlin.mpp.tests.force.gradle"
description="Run multi-platform tests with Gradle runner even if the platform runner is used by default. description="Run multi-platform tests with Gradle runner even if the platform runner is used by default.
@@ -44,5 +46,9 @@
<action id="Kotlin.Gradle.ShowDslLogs" class="org.jetbrains.kotlin.idea.actions.ShowKotlinGradleDslLogs" <action id="Kotlin.Gradle.ShowDslLogs" class="org.jetbrains.kotlin.idea.actions.ShowKotlinGradleDslLogs"
text="Show Kotlin Gradle DSL Logs" description="Show Kotlin Gradle DSL logs"> text="Show Kotlin Gradle DSL Logs" description="Show Kotlin Gradle DSL logs">
</action> </action>
<action id="LoadConfigurationAction" class="org.jetbrains.kotlin.idea.scripting.gradle.LoadConfigurationAction">
<add-to-group group-id="ProjectRefreshActionGroup" anchor="first"/>
</action>
</actions> </actions>
</idea-plugin> </idea-plugin>
@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.2 1L0 4.2H3.2V1Z" fill="#6E6E6E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 5V1H8V6.22036C7.65722 6.3498 7.32863 6.53708 7.00004 6.79995C5.4637 8.02898 5.47971 9.69698 5.49651 11.4482C5.49827 11.6313 5.50004 11.8154 5.50004 12V12H0V5H4Z" fill="#6E6E6E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.6456 12.0171L15.6456 15.5887L14.3872 14.3294C13.6029 15.1298 12.4916 15.6024 11.2845 15.5391C9.29176 15.4347 7.71156 13.9053 7.4795 11.9901L8.84058 12.1084C9.10844 13.2651 10.1149 14.1538 11.3598 14.2191C12.1761 14.2619 12.9276 13.9418 13.4574 13.3998L12.0739 12.0171L15.6456 12.0171ZM11.708 7.45806C13.6743 7.5611 15.2389 9.05146 15.503 10.9309L14.1505 10.8131C13.8623 9.68563 12.8692 8.82621 11.6458 8.7621C10.7935 8.71743 10.012 9.06834 9.47952 9.65429L10.9131 11.0878L7.34133 11.0878L7.34133 7.51612L8.54986 8.7255C9.33683 7.89016 10.4722 7.39329 11.708 7.45806Z" fill="#389FD6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.2 1L0 4.2H3.2V1Z" fill="#AFB1B3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 5V1H8V6.22036C7.65722 6.3498 7.32863 6.53708 7.00004 6.79995C5.4637 8.02898 5.47971 9.69698 5.49651 11.4482C5.49827 11.6313 5.50004 11.8154 5.50004 12V12H0V5H4Z" fill="#AFB1B3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.6456 12.0171L15.6456 15.5887L14.3872 14.3294C13.6029 15.1298 12.4916 15.6024 11.2845 15.5391C9.29176 15.4347 7.71156 13.9053 7.4795 11.9901L8.84058 12.1084C9.10844 13.2651 10.1149 14.1538 11.3598 14.2191C12.1761 14.2619 12.9276 13.9418 13.4574 13.3998L12.0739 12.0171L15.6456 12.0171ZM11.708 7.45806C13.6743 7.5611 15.2389 9.05146 15.503 10.9309L14.1505 10.8131C13.8623 9.68563 12.8692 8.82621 11.6458 8.7621C10.7935 8.71743 10.012 9.06834 9.47952 9.65429L10.9131 11.0878L7.34133 11.0878L7.34133 7.51612L8.54986 8.7255C9.33683 7.89016 10.4722 7.39329 11.708 7.45806Z" fill="#3592C4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB