diff --git a/plugins/kotlin/base/scripting.k2/resources/kotlin.base.scripting.k2.xml b/plugins/kotlin/base/scripting.k2/resources/kotlin.base.scripting.k2.xml
index 1c418eccde15..ddeffb608c63 100644
--- a/plugins/kotlin/base/scripting.k2/resources/kotlin.base.scripting.k2.xml
+++ b/plugins/kotlin/base/scripting.k2/resources/kotlin.base.scripting.k2.xml
@@ -18,6 +18,7 @@
+
@@ -38,7 +39,7 @@
+ description="Enable displaying Kotlin Script definition name in status bar" restartRequired="false"/>
-
+
+
+
@@ -80,18 +86,23 @@
order="last"
implementation="org.jetbrains.kotlin.idea.core.script.k2.definitions.BundledScriptDefinitionSource"/>
-
+
-
+
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/base/scripting.k2/src/org/jetbrains/kotlin/idea/core/script/k2/ReloadScriptConfiguration.kt b/plugins/kotlin/base/scripting.k2/src/org/jetbrains/kotlin/idea/core/script/k2/ReloadScriptConfiguration.kt
index 0d08985aca14..a4643a4e9d78 100644
--- a/plugins/kotlin/base/scripting.k2/src/org/jetbrains/kotlin/idea/core/script/k2/ReloadScriptConfiguration.kt
+++ b/plugins/kotlin/base/scripting.k2/src/org/jetbrains/kotlin/idea/core/script/k2/ReloadScriptConfiguration.kt
@@ -1,44 +1,81 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.core.script.k2
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.CommonDataKeys
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.actionSystem.*
+import com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.application.readAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
+import com.intellij.openapi.editor.EditorFactory
+import com.intellij.openapi.editor.event.DocumentEvent
+import com.intellij.openapi.editor.event.DocumentListener
+import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider
+import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent
+import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.Key
+import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiManager
+import com.intellij.util.application
+import com.intellij.util.messages.Topic
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.intellij.lang.annotations.Language
+import org.jetbrains.annotations.ApiStatus
import org.jetbrains.kotlin.idea.base.scripting.KotlinBaseScriptingBundle
import org.jetbrains.kotlin.idea.core.script.alwaysVirtualFile
+import org.jetbrains.kotlin.idea.core.script.k2.ReloadScriptConfigurationService.Companion.TOPIC
import org.jetbrains.kotlin.idea.core.script.k2.configurations.getConfigurationResolver
import org.jetbrains.kotlin.idea.core.script.k2.highlighting.DefaultScriptResolutionStrategy
import org.jetbrains.kotlin.idea.core.script.k2.modules.KotlinScriptModuleManager.Companion.removeScriptModules
+import org.jetbrains.kotlin.idea.core.script.scriptDiagnostics
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
+import org.jetbrains.kotlin.scripting.definitions.isNonScript
+
+@Language("devkit-action-id")
+private const val ACTION_GROUP = "KotlinScripts.ReloadConfigurationActionGroup"
+
+private val SHOW_NOTIFICATION = Key.create("SHOW_NOTIFICATION")
+
+@ApiStatus.Internal
+class ScriptConfigurationFloatingToolbarProvider : AbstractFloatingToolbarProvider(ACTION_GROUP) {
+ override val autoHideable: Boolean = false
+
+ override fun isApplicable(dataContext: DataContext): Boolean {
+ val ktFile = getProjectKtFile(dataContext) ?: return false
+ return ktFile.name.endsWith(".kts")
+ }
+
+ override fun register(dataContext: DataContext, component: FloatingToolbarComponent, parentDisposable: Disposable) {
+ val project = dataContext.getData(PROJECT) ?: return
+ val service = ReloadScriptConfigurationService.getInstance(project)
+
+ application.messageBus.connect(parentDisposable)
+ .subscribe(TOPIC, object : ReloadScriptConfigurationService.Listener {
+ override fun onNotificationChanged(virtualFile: VirtualFile) {
+ service.handleFloatingToolbarComponent(virtualFile, component)
+ }
+ })
+ }
+}
internal class ReloadScriptConfiguration : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
- val ktFile = getProjectKtFile(e) ?: return
+ val ktFile = getProjectKtFile(e.dataContext) ?: return
ReloadScriptConfigurationService.getInstance(project).reloadScriptData(ktFile)
}
- private fun getProjectKtFile(e: AnActionEvent): KtFile? {
- val project = e.project ?: return null
- val editor = e.getData(CommonDataKeys.EDITOR)
- return if (editor != null) {
- PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
- } else {
- e.getData(CommonDataKeys.PSI_FILE)
- } as? KtFile
- }
-
override fun update(e: AnActionEvent) {
- val ktFile = getProjectKtFile(e)
+ val ktFile = getProjectKtFile(e.dataContext)
if (ktFile != null && ktFile.name.endsWith(".kts")) {
e.presentation.isEnabledAndVisible = true
e.presentation.text = KotlinBaseScriptingBundle.message("reload.script.configuration.text", ktFile.name)
@@ -51,19 +88,75 @@ internal class ReloadScriptConfiguration : AnAction() {
}
@Service(Service.Level.PROJECT)
-class ReloadScriptConfigurationService(private val project: Project, private val coroutineScope: CoroutineScope) {
+class ReloadScriptConfigurationService(private val project: Project, private val scope: CoroutineScope) : Disposable {
+ init {
+ EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener {
+ override fun documentChanged(event: DocumentEvent) {
+ scope.launch {
+ val virtualFile = FileDocumentManager.getInstance().getFile(event.document)?.takeIf { it.isInLocalFileSystem }
+ if (virtualFile == null || virtualFile.isNonScript() || !virtualFile.name.endsWith(".main.kts")) return@launch
+
+ val ktFile = readAction { PsiManager.getInstance(project).findFile(virtualFile) } as? KtFile
+ if (ktFile != null) {
+ ktFile.putUserData(SHOW_NOTIFICATION, true)
+ ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onNotificationChanged(virtualFile)
+ }
+ }
+ }
+ }, this)
+ }
+
fun reloadScriptData(ktFile: KtFile) {
val definition = ktFile.findScriptDefinition() ?: return
+ val virtualFile = ktFile.alwaysVirtualFile
- coroutineScope.launch {
- definition.getConfigurationResolver(project).remove(ktFile.alwaysVirtualFile)
- project.removeScriptModules(listOf(ktFile.alwaysVirtualFile))
+ scope.launch {
+ definition.getConfigurationResolver(project).remove(virtualFile)
+ project.removeScriptModules(listOf(virtualFile))
DefaultScriptResolutionStrategy.getInstance(project).execute(ktFile).join()
+
+ ktFile.putUserData(SHOW_NOTIFICATION, false)
+ ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onNotificationChanged(virtualFile)
}
}
+ fun handleFloatingToolbarComponent(virtualFile: VirtualFile, component: FloatingToolbarComponent) {
+ scope.launch {
+ val ktFile = readAction { PsiManager.getInstance(project).findFile(virtualFile) } as? KtFile
+ val shouldShow = !virtualFile.scriptDiagnostics.isNullOrEmpty() || ktFile?.getUserData(SHOW_NOTIFICATION) ?: false
+
+ withContext(Dispatchers.EDT) {
+ if (shouldShow) {
+ component.scheduleShow()
+ } else {
+ component.scheduleHide()
+ }
+ }
+ }
+ }
+
+ override fun dispose() {}
+
+ interface Listener {
+ fun onNotificationChanged(virtualFile: VirtualFile) {}
+ }
+
companion object {
+ @JvmField
+ @Topic.ProjectLevel
+ val TOPIC: Topic = Topic.create("ReloadScriptConfigurationService", Listener::class.java)
+
@JvmStatic
fun getInstance(project: Project): ReloadScriptConfigurationService = project.service()
}
}
+
+private fun getProjectKtFile(context: DataContext): KtFile? {
+ val project = context.getData(PROJECT) ?: return null
+ val editor = context.getData(CommonDataKeys.EDITOR)
+ return if (editor != null) {
+ PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
+ } else {
+ context.getData(CommonDataKeys.PSI_FILE)
+ } as? KtFile
+}