From 2511f70bd9b006c5594898120040f3bb2dc0cb1e Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Tue, 11 Dec 2018 10:31:57 +0300 Subject: [PATCH] Scratch: implement stop action ^KT-28643 Fixed --- .../kotlin/idea/KotlinBundle.properties | 1 + .../kotlin/idea/scratch/ScratchExecutor.kt | 1 + .../idea/scratch/actions/RunScratchAction.kt | 30 ++++++++++---- .../actions/ScratchCompilationSupport.kt | 39 +++++++++++++++++++ .../idea/scratch/actions/StopScratchAction.kt | 31 +++++++++++++++ .../scratch/compile/KtCompilingExecutor.kt | 12 ++++++ .../scratch/repl/KtScratchReplExecutor.kt | 8 ++++ .../kotlin/idea/scratch/ui/ScratchTopPanel.kt | 19 +++++++-- idea/resources/META-INF/plugin-common.xml | 3 ++ .../scratch/AbstractScratchRunActionTest.kt | 12 +++--- 10 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchCompilationSupport.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/StopScratchAction.kt diff --git a/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties b/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties index 879f759fcf1..531a9421097 100644 --- a/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties +++ b/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties @@ -250,4 +250,5 @@ android.klint.inspections.group.name=Android Lint for Kotlin # Scratch Files scratch.run.button=Run Scratch File +scratch.stop.button=Stop scratch execution scratch.clear.button=Clear results diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchExecutor.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchExecutor.kt index b86808939fa..56f56a81406 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchExecutor.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchExecutor.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandler abstract class ScratchExecutor(protected val file: ScratchFile) { abstract fun execute() + abstract fun stop() protected val handlers = mutableListOf() diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt index c71db5eecdb..24ae021d396 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt @@ -23,11 +23,8 @@ import com.intellij.openapi.keymap.KeymapUtil import com.intellij.openapi.project.DumbService import com.intellij.task.ProjectTaskManager import org.jetbrains.kotlin.idea.KotlinBundle -import org.jetbrains.kotlin.idea.scratch.ScratchFile -import org.jetbrains.kotlin.idea.scratch.ScratchFileLanguageProvider -import org.jetbrains.kotlin.idea.scratch.getScratchPanelFromSelectedEditor +import org.jetbrains.kotlin.idea.scratch.* import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandlerAdapter -import org.jetbrains.kotlin.idea.scratch.printDebugMessage import org.jetbrains.kotlin.idea.scratch.LOG as log class RunScratchAction : ScratchAction( @@ -75,11 +72,13 @@ class RunScratchAction : ScratchAction( executor.addOutputHandler(object : ScratchOutputHandlerAdapter() { override fun onStart(file: ScratchFile) { - e.presentation.isEnabled = false + ScratchCompilationSupport.start(file, executor) + scratchPanel.updateToolbar() } override fun onFinish(file: ScratchFile) { - e.presentation.isEnabled = true + ScratchCompilationSupport.stop() + scratchPanel.updateToolbar() } }) @@ -109,4 +108,21 @@ class RunScratchAction : ScratchAction( executeScratch() } } -} + + override fun update(e: AnActionEvent) { + super.update(e) + + e.presentation.isEnabled = !ScratchCompilationSupport.isAnyInProgress() + + if (e.presentation.isEnabled) { + e.presentation.text = templatePresentation.text + } else { + e.presentation.text = "Other Scratch file execution is in progress" + } + + val project = e.project ?: return + val panel = getScratchPanelFromSelectedEditor(project) ?: return + + e.presentation.isVisible = !ScratchCompilationSupport.isInProgress(panel.scratchFile) + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchCompilationSupport.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchCompilationSupport.kt new file mode 100644 index 00000000000..f6bb1ddd3db --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchCompilationSupport.kt @@ -0,0 +1,39 @@ +/* + * 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.scratch.actions + +import org.jetbrains.kotlin.idea.scratch.ScratchExecutor +import org.jetbrains.kotlin.idea.scratch.ScratchFile + +object ScratchCompilationSupport { + private var inProgress = false + + private var file: ScratchFile? = null + private var executor: ScratchExecutor? = null + + fun isInProgress(file: ScratchFile): Boolean = inProgress && this.file == file + fun isAnyInProgress(): Boolean = inProgress + + fun start(file: ScratchFile, executor: ScratchExecutor) { + this.inProgress = true + + this.file = file + this.executor = executor + } + + fun stop() { + this.inProgress = false + + this.file = null + this.executor = null + } + + fun forceStop() { + this.executor?.stop() + + stop() + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/StopScratchAction.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/StopScratchAction.kt new file mode 100644 index 00000000000..03eca39c022 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/StopScratchAction.kt @@ -0,0 +1,31 @@ +/* + * 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.scratch.actions + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.AnActionEvent +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.scratch.getScratchPanelFromSelectedEditor +import org.jetbrains.kotlin.idea.scratch.LOG as log + +class StopScratchAction : ScratchAction( + KotlinBundle.message("scratch.stop.button"), + AllIcons.Actions.Suspend +) { + + override fun actionPerformed(e: AnActionEvent) { + ScratchCompilationSupport.forceStop() + } + + override fun update(e: AnActionEvent) { + super.update(e) + + val project = e.project ?: return + val panel = getScratchPanelFromSelectedEditor(project) ?: return + + e.presentation.isEnabledAndVisible = ScratchCompilationSupport.isInProgress(panel.scratchFile) + } +} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtCompilingExecutor.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtCompilingExecutor.kt index babedf45b7d..2bfad38662a 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtCompilingExecutor.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/compile/KtCompilingExecutor.kt @@ -58,6 +58,8 @@ class KtCompilingExecutor(file: ScratchFile) : ScratchExecutor(file) { private val TIMEOUT_MS = 30000 } + private var backgroundProcessIndicator: ProgressIndicator? = null + override fun execute() { handlers.forEach { it.onStart(file) } @@ -76,6 +78,8 @@ class KtCompilingExecutor(file: ScratchFile) : ScratchExecutor(file) { object : Task.Backgroundable(psiFile.project, "Running Kotlin Scratch...", true) { override fun run(indicator: ProgressIndicator) { + backgroundProcessIndicator = indicator + val modifiedScratchSourceFile = runReadAction { KtPsiFactory(psiFile.project).createFileWithLightClassSupport("tmp.kt", result.code, psiFile) } @@ -114,6 +118,14 @@ class KtCompilingExecutor(file: ScratchFile) : ScratchExecutor(file) { } } + override fun stop() { + try { + backgroundProcessIndicator?.cancel() + } finally { + handlers.forEach { it.onFinish(file) } + } + } + private fun compileFileToTempDir(psiFile: KtFile): File? { if (!checkForErrors(psiFile)) return null diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/repl/KtScratchReplExecutor.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/repl/KtScratchReplExecutor.kt index 1089a7f4842..6d27c438df9 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/repl/KtScratchReplExecutor.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/repl/KtScratchReplExecutor.kt @@ -58,6 +58,14 @@ class KtScratchReplExecutor(file: ScratchFile) : ScratchExecutor(file) { sendCommandToProcess(":quit") } + override fun stop() { + try { + osProcessHandler.process.destroy() + } finally { + handlers.forEach { it.onFinish(file) } + } + } + private fun sendCommandToProcess(command: String) { LOG.printDebugMessage("Send to REPL: ${command}") diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt index 23df40fc524..ec4bcfa700b 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ui/ScratchTopPanel.kt @@ -22,7 +22,9 @@ import com.intellij.execution.ui.ConfigurationModuleSelector import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.ActionToolbar import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager @@ -38,6 +40,7 @@ import org.jetbrains.kotlin.idea.scratch.ScratchFile import org.jetbrains.kotlin.idea.scratch.ScratchFileLanguageProvider import org.jetbrains.kotlin.idea.scratch.actions.ClearScratchAction import org.jetbrains.kotlin.idea.scratch.actions.RunScratchAction +import org.jetbrains.kotlin.idea.scratch.actions.StopScratchAction import org.jetbrains.kotlin.idea.scratch.addScratchPanel import org.jetbrains.kotlin.idea.scratch.removeScratchPanel import javax.swing.* @@ -59,8 +62,11 @@ class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel private val isReplCheckbox: JCheckBox private val isMakeBeforeRunCheckbox: JCheckBox + private val actionsToolbar: ActionToolbar + init { - add(createActionsToolbar()) + actionsToolbar = createActionsToolbar() + add(actionsToolbar.component) moduleChooser = createModuleChooser(scratchFile.project) add(JLabel("Use classpath of module")) @@ -123,14 +129,21 @@ class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel isMakeBeforeRunCheckbox.isSelected = isSelected } - private fun createActionsToolbar(): JComponent { + fun updateToolbar() { + ApplicationManager.getApplication().invokeLater { + actionsToolbar.updateActionsImmediately() + } + } + + private fun createActionsToolbar(): ActionToolbar { val toolbarGroup = DefaultActionGroup().apply { add(RunScratchAction()) + add(StopScratchAction()) addSeparator() add(ClearScratchAction()) } - return ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, toolbarGroup, true).component + return ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, toolbarGroup, true) } private fun createModuleChooser(project: Project): ModulesComboBox { diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index f93e8218984..87708b65580 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -198,6 +198,9 @@ + + diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt index 2984596b859..78b6312a6d9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingUtil import org.jetbrains.kotlin.idea.scratch.actions.RunScratchAction +import org.jetbrains.kotlin.idea.scratch.actions.ScratchCompilationSupport import org.jetbrains.kotlin.idea.scratch.output.InlayScratchFileRenderer import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor import org.jetbrains.kotlin.idea.test.PluginTestCaseBase @@ -113,15 +114,13 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { val (editor, scratchPanel) = getEditorWithScratchPanel(myManager, scratchFile)?: error("Couldn't find scratch panel") scratchPanel.setReplMode(isRepl) - val action = RunScratchAction() - val event = getActionEvent(scratchFile, action) - launchAction(event, action) + launchScratch(scratchFile) UIUtil.dispatchAllInvocationEvents() val start = System.currentTimeMillis() // wait until output is displayed in editor or for 1 minute - while (!event.presentation.isEnabled && (System.currentTimeMillis() - start) < 60000) { + while (ScratchCompilationSupport.isAnyInProgress() && (System.currentTimeMillis() - start) < 60000) { Thread.sleep(100) } @@ -151,7 +150,10 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { KotlinTestUtils.assertEqualsToFile(expectedFile, actualOutput.toString()) } - private fun launchAction(e: TestActionEvent, action: AnAction) { + private fun launchScratch(scratchFile: VirtualFile) { + val action = RunScratchAction() + val e = getActionEvent(scratchFile, action) + action.beforeActionPerformedUpdate(e) Assert.assertTrue(e.presentation.isEnabled && e.presentation.isVisible) action.actionPerformed(e)