Scratch: implement stop action

^KT-28643 Fixed
This commit is contained in:
Natalia Selezneva
2018-12-11 10:31:57 +03:00
parent d403514cee
commit 2511f70bd9
10 changed files with 141 additions and 15 deletions
@@ -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
@@ -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<ScratchOutputHandler>()
@@ -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)
}
}
@@ -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()
}
}
@@ -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)
}
}
@@ -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
@@ -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}")
@@ -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 {
@@ -198,6 +198,9 @@
<action id="Kotlin.ClearScratch" class="org.jetbrains.kotlin.idea.scratch.actions.ClearScratchAction"
text="Clear Kotlin Scratch" description="Clear Kotlin Scratch">
</action>
<action id="Kotlin.StopScratch" class="org.jetbrains.kotlin.idea.scratch.actions.StopScratchAction"
text="Stop scratch execution" description="Stop scratch execution">
</action>
</actions>
<extensions defaultExtensionNs="com.intellij">
@@ -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)