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 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 MPP = IconLoader.getIcon("/org/jetbrains/kotlin/idea/icons/kotlin_multiplatform_project.svg");
@@ -58,9 +58,8 @@ class DefaultScriptConfigurationNotificationFactory : ScriptConfigurationNotific
withSelectedEditor(project) { manager ->
val existingPanel = notificationPanel
if (existingPanel != null) {
notificationPanel?.let {
manager.removeTopComponent(this, it)
}
existingPanel.onClick = onClick
return@withSelectedEditor
}
val panel = NewLoadConfigurationNotificationPanel(onClick, project)
@@ -84,7 +83,7 @@ class DefaultScriptConfigurationNotificationFactory : ScriptConfigurationNotific
by UserDataProperty<FileEditor, NewLoadConfigurationNotificationPanel>(Key.create("load.script.configuration.panel"))
private class NewLoadConfigurationNotificationPanel(
val onClick: () -> Unit,
var onClick: () -> Unit,
project: Project
) : EditorNotificationPanel() {
@@ -191,17 +191,16 @@ internal class DefaultScriptConfigurationManager(project: Project) :
if (postponeLoading) {
ScriptConfigurationNotificationFactory.NOTIFICATION_FACTORY.getPoint(project).extensionList.firstOrNull {
it.showNotification(virtualFile, project) {
runAsyncLoaders(file, virtualFile, scriptDefinition, async, postponeLoading)
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = true)
}
}
} else {
runAsyncLoaders(file, virtualFile, scriptDefinition, async, postponeLoading)
runAsyncLoaders(file, virtualFile, scriptDefinition, async, isLoadingPostponed = false)
}
}
}
}
// в нотификации остается старый колбек - нужно перезаписывать колбек
private fun runAsyncLoaders(
file: KtFile,
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.
action.label.import.project=Import Project
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
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.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
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.util.GradleConstants
@@ -25,4 +32,77 @@ fun runPartialGradleImport(project: Project) {
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)
.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
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.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
import com.intellij.openapi.fileEditor.FileDocumentManager
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.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 java.util.function.Predicate
fun runPartialGradleImport(project: Project) {
val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID)
@@ -25,9 +41,62 @@ fun runPartialGradleImport(project: Project) {
projectSettings.externalProjectPath,
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)
.projectResolverPolicy(
GradleIncrementalResolverPolicy {
it is KotlinDslScriptModelResolver
}
GradlePartialResolverPolicy(Predicate<GradleProjectResolverExtension?> { 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">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<notificationFactory
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationNotificationFactory"
/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
+7 -1
View File
@@ -15,6 +15,9 @@
<extensions defaultExtensionNs="org.jetbrains.kotlin.scripting.idea">
<listener order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptListener"/>
<loader order="first" implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationLoader"/>
<notificationFactory
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptConfigurationNotificationFactory"
/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
@@ -29,7 +32,6 @@
<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.KotlinGradleProjectSettingsDataService"/>
<externalSystemTaskNotificationListener implementation="org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelsUpdater"/>
<registryKey key="kotlin.mpp.tests.force.gradle"
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"
text="Show Kotlin Gradle DSL Logs" description="Show Kotlin Gradle DSL logs">
</action>
<action id="LoadConfigurationAction" class="org.jetbrains.kotlin.idea.scripting.gradle.LoadConfigurationAction">
<add-to-group group-id="ProjectRefreshActionGroup" anchor="first"/>
</action>
</actions>
</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