From 414f98aa294745af24939a953074d54c52549cb3 Mon Sep 17 00:00:00 2001 From: Natalia Selezneva Date: Mon, 3 Jun 2019 11:40:43 +0300 Subject: [PATCH] Implement REPL Mode for scratches ^KT-27963 Fixed --- .../kotlin/generators/tests/GenerateTests.kt | 5 + .../generators/tests/GenerateTests.kt.183 | 5 + .../generators/tests/GenerateTests.kt.as33 | 5 + .../generators/tests/GenerateTests.kt.as34 | 5 + .../generators/tests/GenerateTests.kt.as35 | 5 + .../kotlin/idea/KotlinBundle.properties | 1 + .../kotlin/idea/scratch/KtScratchFile.kt | 8 +- .../kotlin/idea/scratch/ScratchExecutor.kt | 103 +++++++++++++++++- .../kotlin/idea/scratch/ScratchFile.kt | 7 ++ .../idea/scratch/ScratchFileAutoRunner.kt | 17 ++- .../scratch/ScratchFileLanguageProvider.kt | 33 +++++- .../idea/scratch/actions/RunScratchAction.kt | 59 ++++------ .../actions/RunScratchFromHereAction.kt | 37 +++++++ .../ScratchRunLineMarkerContributor.kt | 81 ++++++++++++++ .../scratch/repl/KtScratchReplExecutor.kt | 83 ++++++++++---- .../kotlin/idea/scratch/ui/ScratchTopPanel.kt | 43 +++++++- idea/resources/META-INF/jvm.xml | 2 + idea/resources/META-INF/jvm.xml.182 | 2 + idea/resources/META-INF/jvm.xml.183 | 2 + .../idea/refactoring/kotlinRefactoringUtil.kt | 4 +- .../scratch/lineMarker/binaryExpression.kts | 3 + .../scratch/lineMarker/binaryExpression2.kts | 3 + .../scratch/lineMarker/constantExpression.kts | 3 + idea/testData/scratch/lineMarker/for.kts | 5 + idea/testData/scratch/lineMarker/function.kts | 5 + idea/testData/scratch/lineMarker/lambda.kts | 5 + idea/testData/scratch/lineMarker/lambda2.kts | 5 + idea/testData/scratch/lineMarker/object.kts | 7 ++ .../scratch/lineMarker/parentesized.kts | 3 + .../testData/scratch/lineMarker/qualified.kts | 3 + .../scratch/lineMarker/qualifiedMultiLine.kts | 5 + .../lineMarker/referenceExpression.kts | 3 + idea/testData/scratch/lineMarker/val.kts | 3 + .../scratch/AbstractScratchRunActionTest.kt | 48 ++++---- .../idea/scratch/ScratchLineMarkersTest.kt | 63 +++++++++++ .../ScratchLineMarkersTestGenerated.java | 96 ++++++++++++++++ .../scratch/SequentialScratchExecutorTest.kt | 70 ++++++++++++ 37 files changed, 739 insertions(+), 98 deletions(-) create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchFromHereAction.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchRunLineMarkerContributor.kt create mode 100644 idea/testData/scratch/lineMarker/binaryExpression.kts create mode 100644 idea/testData/scratch/lineMarker/binaryExpression2.kts create mode 100644 idea/testData/scratch/lineMarker/constantExpression.kts create mode 100644 idea/testData/scratch/lineMarker/for.kts create mode 100644 idea/testData/scratch/lineMarker/function.kts create mode 100644 idea/testData/scratch/lineMarker/lambda.kts create mode 100644 idea/testData/scratch/lineMarker/lambda2.kts create mode 100644 idea/testData/scratch/lineMarker/object.kts create mode 100644 idea/testData/scratch/lineMarker/parentesized.kts create mode 100644 idea/testData/scratch/lineMarker/qualified.kts create mode 100644 idea/testData/scratch/lineMarker/qualifiedMultiLine.kts create mode 100644 idea/testData/scratch/lineMarker/referenceExpression.kts create mode 100644 idea/testData/scratch/lineMarker/val.kts create mode 100644 idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTestGenerated.java create mode 100644 idea/tests/org/jetbrains/kotlin/idea/scratch/SequentialScratchExecutorTest.kt diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index f9488abf617..f673d5b9f1a 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -123,6 +123,7 @@ import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractMultiModuleSafeD import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractSafeDeleteTest import org.jetbrains.kotlin.idea.repl.AbstractIdeReplCompletionTest import org.jetbrains.kotlin.idea.resolve.* +import org.jetbrains.kotlin.idea.scratch.AbstractScratchLineMarkersTest import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationCompletionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingTest @@ -808,6 +809,10 @@ fun main(args: Array) { model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false) } + testClass { + model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS) + } + testClass { model("fir/multiModule", recursive = false, extension = null) } diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 index f9a1da4c64f..c92b09337b2 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.183 @@ -129,6 +129,7 @@ import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractMultiModuleSafeD import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractSafeDeleteTest import org.jetbrains.kotlin.idea.repl.AbstractIdeReplCompletionTest import org.jetbrains.kotlin.idea.resolve.* +import org.jetbrains.kotlin.idea.scratch.AbstractScratchLineMarkersTest import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationCompletionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingTest @@ -818,6 +819,10 @@ fun main(args: Array) { model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false) } + testClass { + model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS) + } + testClass { model("fir/multiModule", recursive = false, extension = null) } diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 index 9821ada1cce..0f581a33db2 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 @@ -117,6 +117,7 @@ import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractMultiModuleSafeD import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractSafeDeleteTest import org.jetbrains.kotlin.idea.repl.AbstractIdeReplCompletionTest import org.jetbrains.kotlin.idea.resolve.* +import org.jetbrains.kotlin.idea.scratch.AbstractScratchLineMarkersTest import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationCompletionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingTest @@ -782,6 +783,10 @@ fun main(args: Array) { model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false) } + testClass { + model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS) + } + testClass { model("fir/multiModule", recursive = false, extension = null) } diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 index da4fc214ac7..39e41df535b 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 @@ -117,6 +117,7 @@ import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractMultiModuleSafeD import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractSafeDeleteTest import org.jetbrains.kotlin.idea.repl.AbstractIdeReplCompletionTest import org.jetbrains.kotlin.idea.resolve.* +import org.jetbrains.kotlin.idea.scratch.AbstractScratchLineMarkersTest import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationCompletionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingTest @@ -786,6 +787,10 @@ fun main(args: Array) { model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false) } + testClass { + model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS) + } + testClass { model("fir/multiModule", recursive = false, extension = null) } diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 index 4ccd92aaed0..aa5eb319713 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as35 @@ -117,6 +117,7 @@ import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractMultiModuleSafeD import org.jetbrains.kotlin.idea.refactoring.safeDelete.AbstractSafeDeleteTest import org.jetbrains.kotlin.idea.repl.AbstractIdeReplCompletionTest import org.jetbrains.kotlin.idea.resolve.* +import org.jetbrains.kotlin.idea.scratch.AbstractScratchLineMarkersTest import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationCompletionTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingTest @@ -784,6 +785,10 @@ fun main(args: Array) { model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false) } + testClass { + model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS) + } + testClass { model("fir/multiModule", recursive = false, extension = null) } 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 a9120a92576..6150754eae1 100644 --- a/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties +++ b/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties @@ -251,6 +251,7 @@ android.klint.inspections.group.name=Android Lint for Kotlin # Scratch Files scratch.run.button=Run Scratch File +scratch.run.from.here.button=Run Scratch From Here scratch.stop.button=Stop scratch execution scratch.clear.button=Clear results diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt index 969cd8e206f..f6271e4cfd5 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/KtScratchFile.kt @@ -19,18 +19,19 @@ package org.jetbrains.kotlin.idea.scratch import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.PsiUtil import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils -import org.jetbrains.kotlin.idea.core.util.getLineNumber import org.jetbrains.kotlin.idea.core.util.getLineStartOffset import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.AnalyzingUtils class KtScratchFile(project: Project, editor: TextEditor) : ScratchFile(project, editor) { @@ -65,6 +66,11 @@ class KtScratchFile(project: Project, editor: TextEditor) : ScratchFile(project, return result } + fun PsiElement.getLineNumber(start: Boolean = true): Int { + val document = PsiDocumentManager.getInstance(project).getLastCommittedDocument(containingFile) + return document?.getLineNumber(if (start) startOffset else endOffset) ?: 0 + } + override fun hasErrors(): Boolean { val psiFile = getPsiFile() as? KtFile ?: return false try { 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 2de25d66338..e1fdf0ad065 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 @@ -1,5 +1,5 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. + * Copyrig()ht 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,13 @@ package org.jetbrains.kotlin.idea.scratch +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.psi.PsiManager import org.jetbrains.kotlin.idea.scratch.output.ScratchOutput import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandler @@ -39,13 +46,17 @@ abstract class ScratchExecutor(protected val file: ScratchFile) { if (e != null) LOG.error(e) } - protected class CompositeOutputHandler : ScratchOutputHandler { - private val handlers = mutableListOf() + class CompositeOutputHandler : ScratchOutputHandler { + private val handlers = mutableSetOf() fun add(handler: ScratchOutputHandler) { handlers.add(handler) } + fun remove(handler: ScratchOutputHandler) { + handlers.remove(handler) + } + override fun onStart(file: ScratchFile) { handlers.forEach { it.onStart(file) } } @@ -66,4 +77,90 @@ abstract class ScratchExecutor(protected val file: ScratchFile) { handlers.forEach { it.clear(file) } } } +} + +abstract class SequentialScratchExecutor(file: ScratchFile) : ScratchExecutor(file) { + abstract fun executeStatement(expression: ScratchExpression) + + protected abstract fun startExecution() + protected abstract fun stopExecution(callback: (() -> Unit)? = null) + + protected abstract fun needProcessToStart(): Boolean + + private var lastExecuted = 0 + + fun start() { + EditorFactory.getInstance().eventMulticaster.addDocumentListener(listener, file.project.messageBus.connect()) + + startExecution() + } + + override fun stop() { + EditorFactory.getInstance().eventMulticaster.removeDocumentListener(listener) + + stopExecution() + } + + fun executeNew() { + handler.onStart(file) + + for ((index, expression) in file.getExpressions().withIndex()) { + if (index + 1 <= lastExecuted) continue + executeStatement(expression) + lastExecuted = index + 1 + } + } + + override fun execute() { + if (needToRestartProcess()) { + lastExecuted = 0 + handler.clear(file) + + handler.onStart(file) + stopExecution { + ApplicationManager.getApplication().invokeLater { + executeNew() + } + } + } else { + executeNew() + } + } + + fun getFirstNewExpression(): ScratchExpression? { + val expressions = runReadAction { file.getExpressions() } + if (lastExecuted in expressions.indices) { + return expressions[lastExecuted] + } + return null + } + + private fun needToRestartProcess(): Boolean { + return lastExecuted > 0 + } + + private val listener = object : DocumentListener { + override fun documentChanged(event: DocumentEvent) { + if (event.newFragment.isBlank() && event.oldFragment.isBlank()) return + if (!needToRestartProcess()) return + + val document = event.document + val virtualFile = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return + if (!virtualFile.isValid) { + return + } + + if (PsiManager.getInstance(file.project).findFile(virtualFile) != file.getPsiFile()) return + + val changedLine = document.getLineNumber(event.offset) + val changedExpression = file.getExpressionAtLine(changedLine) ?: return + val changedExpressionIndex = file.getExpressions().indexOf(changedExpression) + 1 + if (changedExpressionIndex <= lastExecuted) { + lastExecuted = 0 + handler.clear(file) + + stopExecution() + } + } + } } \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt index 75278d9c125..56ed81b32eb 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFile.kt @@ -26,6 +26,9 @@ import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.scratch.ui.scratchFileOptions abstract class ScratchFile(val project: Project, val editor: TextEditor) { + var replScratchExecutor: SequentialScratchExecutor? = null + var compilingScratchExecutor: ScratchExecutor? = null + fun getExpressions(): List = runReadAction { getPsiFile()?.let { getExpressions(it) } ?: emptyList() } @@ -49,6 +52,10 @@ abstract class ScratchFile(val project: Project, val editor: TextEditor) { } } + fun getExpressionAtLine(line: Int): ScratchExpression? { + return getExpressions().find { line in it.lineStart..it.lineEnd } + } + abstract fun getExpressions(psiFile: PsiFile): List abstract fun hasErrors(): Boolean } diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileAutoRunner.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileAutoRunner.kt index f141f85c311..8ef4245a0ab 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileAutoRunner.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileAutoRunner.kt @@ -17,6 +17,7 @@ import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.Alarm import org.jetbrains.kotlin.idea.scratch.actions.RunScratchAction +import org.jetbrains.kotlin.idea.scratch.actions.RunScratchFromHereAction import org.jetbrains.kotlin.idea.scratch.actions.ScratchCompilationSupport import org.jetbrains.kotlin.idea.scratch.ui.ScratchTopPanel @@ -46,22 +47,26 @@ class ScratchFileAutoRunner(private val project: Project) : DocumentListener { if (!panel.scratchFile.options.isInteractiveMode) return if (!event.newFragment.isBlank()) { - runScratch(panel) + runScratch(panel.scratchFile) } } - private fun runScratch(panel: ScratchTopPanel) { + private fun runScratch(scratchFile: ScratchFile) { myAlarm.cancelAllRequests() - if (ScratchCompilationSupport.isInProgress(panel.scratchFile)) { + if (ScratchCompilationSupport.isInProgress(scratchFile) && !scratchFile.options.isRepl) { ScratchCompilationSupport.forceStop() } myAlarm.addRequest( { - val psiFile = panel.scratchFile.getPsiFile() - if (psiFile != null && psiFile.isValid && !panel.scratchFile.hasErrors()) { - RunScratchAction.doAction(panel, true) + val psiFile = scratchFile.getPsiFile() + if (psiFile != null && psiFile.isValid && !scratchFile.hasErrors()) { + if (scratchFile.options.isRepl) { + RunScratchFromHereAction.doAction(scratchFile) + } else { + RunScratchAction.doAction(scratchFile, true) + } } }, auto_run_delay, true ) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt index fa69628844e..6191ad5d6ef 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/ScratchFileLanguageProvider.kt @@ -22,12 +22,39 @@ import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.LanguageFileType import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.scratch.actions.ScratchCompilationSupport import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandler +import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandlerAdapter abstract class ScratchFileLanguageProvider { - abstract fun createFile(project: Project, editor: TextEditor): ScratchFile? - abstract fun createReplExecutor(file: ScratchFile): ScratchExecutor? - abstract fun createCompilingExecutor(file: ScratchFile): ScratchExecutor? + fun newScratchFile(project: Project, editor: TextEditor): ScratchFile? { + val scratchFile = createFile(project, editor) ?: return null + + scratchFile.replScratchExecutor = createReplExecutor(scratchFile) + scratchFile.compilingScratchExecutor = createCompilingExecutor(scratchFile) + + scratchFile.replScratchExecutor?.addOutputHandlers() + scratchFile.compilingScratchExecutor?.addOutputHandlers() + + return scratchFile + } + + private fun ScratchExecutor.addOutputHandlers() { + addOutputHandler(getOutputHandler()) + addOutputHandler(object : ScratchOutputHandlerAdapter() { + override fun onStart(file: ScratchFile) { + ScratchCompilationSupport.start(file, this@addOutputHandlers) + } + + override fun onFinish(file: ScratchFile) { + ScratchCompilationSupport.stop() + } + }) + } + + protected abstract fun createFile(project: Project, editor: TextEditor): ScratchFile? + protected abstract fun createReplExecutor(file: ScratchFile): SequentialScratchExecutor? + protected abstract fun createCompilingExecutor(file: ScratchFile): ScratchExecutor? abstract fun getOutputHandler(): ScratchOutputHandler 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 dc8abedc234..a7ee4fcd170 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,9 +23,10 @@ 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.* -import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandlerAdapter -import org.jetbrains.kotlin.idea.scratch.ui.ScratchTopPanel +import org.jetbrains.kotlin.idea.scratch.ScratchFile +import org.jetbrains.kotlin.idea.scratch.SequentialScratchExecutor +import org.jetbrains.kotlin.idea.scratch.getScratchPanelFromSelectedEditor +import org.jetbrains.kotlin.idea.scratch.printDebugMessage import org.jetbrains.kotlin.idea.scratch.LOG as log class RunScratchAction : ScratchAction( @@ -43,56 +44,36 @@ class RunScratchAction : ScratchAction( val project = e.project ?: return val scratchPanel = getScratchPanelFromSelectedEditor(project) ?: return - doAction(scratchPanel, false) + doAction(scratchPanel.scratchFile, false) } companion object { - fun doAction(scratchPanel: ScratchTopPanel, isAutoRun: Boolean) { - val scratchFile = scratchPanel.scratchFile - val psiFile = scratchFile.getPsiFile() ?: return - - val isMakeBeforeRun = scratchFile.options.isMakeBeforeRun + fun doAction(scratchFile: ScratchFile, isAutoRun: Boolean) { val isRepl = scratchFile.options.isRepl + val executor = (if (isRepl) scratchFile.replScratchExecutor else scratchFile.compilingScratchExecutor) ?: return - val provider = ScratchFileLanguageProvider.get(psiFile.language) ?: return - - log.printDebugMessage("Run Action: isMakeBeforeRun = $isMakeBeforeRun, isRepl = $isRepl") - - val defaultOutputHandler = provider.getOutputHandler() - - val module = scratchPanel.getModule() - - val executor = if (isRepl) provider.createReplExecutor(scratchFile) else provider.createCompilingExecutor(scratchFile) - - @Suppress("FoldInitializerAndIfToElvis") - if (executor == null) { - return defaultOutputHandler.error(scratchFile, "Couldn't run ${psiFile.name}") - } - - executor.addOutputHandler(defaultOutputHandler) - - executor.addOutputHandler(object : ScratchOutputHandlerAdapter() { - override fun onStart(file: ScratchFile) { - ScratchCompilationSupport.start(file, executor) - scratchPanel.updateToolbar() - } - - override fun onFinish(file: ScratchFile) { - ScratchCompilationSupport.stop() - scratchPanel.updateToolbar() - } - }) + log.printDebugMessage("Run Action: isRepl = $isRepl") fun executeScratch() { try { - executor.execute() + if (isAutoRun && executor is SequentialScratchExecutor) { + executor.executeNew() + } else { + executor.execute() + } } catch (ex: Throwable) { executor.errorOccurs("Exception occurs during Run Scratch Action", ex, true) } } + val isMakeBeforeRun = scratchFile.options.isMakeBeforeRun + log.printDebugMessage("Run Action: isMakeBeforeRun = $isMakeBeforeRun") + + val module = scratchFile.getModule() + log.printDebugMessage("Run Action: module = ${module?.name}") + if (!isAutoRun && module != null && isMakeBeforeRun) { - val project = scratchPanel.scratchFile.project + val project = scratchFile.project ProjectTaskManager.getInstance(project).build(arrayOf(module)) { result -> if (result.isAborted || result.errors > 0) { executor.errorOccurs("There were compilation errors in module ${module.name}") diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchFromHereAction.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchFromHereAction.kt new file mode 100644 index 00000000000..82767ed1fca --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchFromHereAction.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2019 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.ScratchFile +import org.jetbrains.kotlin.idea.scratch.getScratchPanelFromSelectedEditor + +class RunScratchFromHereAction : ScratchAction( + KotlinBundle.message("scratch.run.from.here.button"), + AllIcons.Diff.ArrowRight +) { + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val scratchPanel = getScratchPanelFromSelectedEditor(project) ?: return + + doAction(scratchPanel.scratchFile) + } + + companion object { + fun doAction(scratchFile: ScratchFile) { + val executor = scratchFile.replScratchExecutor ?: return + + try { + executor.executeNew() + } catch (ex: Throwable) { + executor.errorOccurs("Exception occurs during Run Scratch Action", ex, true) + } + } + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchRunLineMarkerContributor.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchRunLineMarkerContributor.kt new file mode 100644 index 00000000000..ee3ebd6d605 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/ScratchRunLineMarkerContributor.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2019 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.execution.lineMarker.RunLineMarkerContributor +import com.intellij.ide.scratch.ScratchFileService +import com.intellij.ide.scratch.ScratchRootType +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.impl.source.tree.LeafPsiElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.idea.core.util.getLineCount +import org.jetbrains.kotlin.idea.refactoring.getLineNumber +import org.jetbrains.kotlin.idea.scratch.ScratchExpression +import org.jetbrains.kotlin.idea.scratch.getEditorWithScratchPanel +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +class ScratchRunLineMarkerContributor : RunLineMarkerContributor() { + override fun getInfo(element: PsiElement): Info? { + val ktFile = element.containingFile as? KtFile + if (ktFile?.isScript() != true) return null + val file = ktFile.virtualFile + if (ScratchFileService.getInstance().getRootType(file) !is ScratchRootType) return null + + val declaration = element.getStrictParentOfType() + if (declaration != null && declaration !is KtParameter && declaration.nameIdentifier == element) { + return isLastExecutedExpression(element) + } + + val scriptInitializer = element.getParentOfType(true)?.body + if (scriptInitializer != null) { + if (scriptInitializer.findDescendantOfType() == element) { + return isLastExecutedExpression(element) + } + return null + } + + // Show arrow for last added empty line + if (declaration is KtScript && element is PsiWhiteSpace) { + val expression = getLastExecutedExpression(element) + if (expression == null) { + if (element.getLineNumber() == element.containingFile.getLineCount() + || element.getLineNumber(false) == element.containingFile.getLineCount()) { + return Info(RunScratchFromHereAction()) + } + } + } + return null + } + + private fun isLastExecutedExpression(element: PsiElement): Info? { + val expression = getLastExecutedExpression(element) ?: return null + if (element.getLineNumber(true) != expression.lineStart) { + return null + } + + if (PsiTreeUtil.isAncestor(expression.element, element, false)) { + return Info(RunScratchFromHereAction()) + } + return null + } + + private fun getLastExecutedExpression(element: PsiElement): ScratchExpression? { + val (_, panel) = getEditorWithScratchPanel( + FileEditorManager.getInstance(element.project), + element.containingFile.virtualFile + ) ?: return null + + val scratchFile = panel.scratchFile + if (!scratchFile.options.isRepl) return null + val replExecutor = scratchFile.replScratchExecutor ?: return null + return replExecutor.getFirstNewExpression() + } +} 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 de84082bb04..c63a46fd321 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 @@ -18,8 +18,9 @@ package org.jetbrains.kotlin.idea.scratch.repl import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.process.OSProcessHandler +import com.intellij.execution.process.ProcessAdapter +import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessOutputTypes -import com.intellij.openapi.application.runReadAction import com.intellij.openapi.util.Key import org.jetbrains.kotlin.cli.common.repl.replInputAsXml import org.jetbrains.kotlin.cli.common.repl.replNormalizeLineBreaks @@ -36,42 +37,68 @@ import java.io.ByteArrayInputStream import java.nio.charset.Charset import javax.xml.parsers.DocumentBuilderFactory -class KtScratchReplExecutor(file: ScratchFile) : ScratchExecutor(file) { +class KtScratchReplExecutor(file: ScratchFile) : SequentialScratchExecutor(file) { private val history: ReplHistory = ReplHistory() - private lateinit var osProcessHandler: OSProcessHandler - - override fun execute() { - handler.onStart(file) + private var osProcessHandler: OSProcessHandler? = null + override fun startExecution() { val module = file.getModule() val cmdLine = KotlinConsoleKeeper.createReplCommandLine(file.project, module) LOG.printDebugMessage("Execute REPL: ${cmdLine.commandLineString}") osProcessHandler = ReplOSProcessHandler(cmdLine) - osProcessHandler.startNotify() - - file.getExpressions().forEach { expression -> - history.addEntry(expression) - sendCommandToProcess(runReadAction { expression.element.text }) - } - - sendCommandToProcess(":quit") + osProcessHandler?.startNotify() } - override fun stop() { + override fun stopExecution(callback: (() -> Unit)?) { + val osProcessHandler = osProcessHandler ?: return + try { - osProcessHandler.process.destroy() - } finally { - handler.onFinish(file) + if (callback != null) { + osProcessHandler.addProcessListener(object : ProcessAdapter() { + override fun processTerminated(event: ProcessEvent) { + callback() + } + }) + } + sendCommandToProcess(":quit") + } catch (e: Exception) { + errorOccurs("Couldn't stop REPL process", e, false) + + osProcessHandler.destroyProcess() + clearState() } } + private fun clearState() { + history.clear() + osProcessHandler = null + handler.onFinish(file) + } + + override fun executeStatement(expression: ScratchExpression) { + if (needProcessToStart()) { + startExecution() + } + + history.addEntry(expression) + try { + sendCommandToProcess(expression.element.text) + } catch (e: Throwable) { + errorOccurs("Couldn't execute statement: ${expression.element.text}", e, true) + } + } + + override fun needProcessToStart(): Boolean { + return osProcessHandler == null + } + private fun sendCommandToProcess(command: String) { LOG.printDebugMessage("Send to REPL: ${command}") - val processInputOS = osProcessHandler.processInput ?: return logError(this::class.java, "

Broken execute stream

") - val charset = osProcessHandler.charset ?: Charsets.UTF_8 + val processInputOS = osProcessHandler?.processInput ?: return logError(this::class.java, "

Broken execute stream

") + val charset = osProcessHandler?.charset ?: Charsets.UTF_8 val xmlRes = command.replInputAsXml() @@ -93,6 +120,8 @@ class KtScratchReplExecutor(file: ScratchFile) : ScratchExecutor(file) { } fun lastProcessedEntry(): ScratchExpression? { + if (processedEntriesCount < 1) return null + val lastProcessedEntryIndex = processedEntriesCount - 1 return entries.takeIf { lastProcessedEntryIndex < entries.size }?.get(lastProcessedEntryIndex) } @@ -100,6 +129,13 @@ class KtScratchReplExecutor(file: ScratchFile) : ScratchExecutor(file) { fun entryProcessed() { processedEntriesCount++ } + + fun clear() { + entries = arrayListOf() + processedEntriesCount = 0 + } + + fun isAllProcessed() = entries.size == processedEntriesCount } private inner class ReplOSProcessHandler(cmd: GeneralCommandLine) : OSProcessHandler(cmd) { @@ -115,7 +151,9 @@ class KtScratchReplExecutor(file: ScratchFile) : ScratchExecutor(file) { } override fun notifyProcessTerminated(exitCode: Int) { - handler.onFinish(file) + super.notifyProcessTerminated(exitCode) + + clearState() } private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding))) @@ -136,6 +174,9 @@ class KtScratchReplExecutor(file: ScratchFile) : ScratchExecutor(file) { if (outputType in setOf("SUCCESS", "COMPILE_ERROR", "INTERNAL_ERROR", "RUNTIME_ERROR", "READLINE_END")) { history.entryProcessed() + if (history.isAllProcessed()) { + handler.onFinish(file) + } } val result = parseReplOutput(content, outputType) 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 8910407e6ce..981828bb83e 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 @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.scratch.ui import com.intellij.application.options.ModulesComboBox +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.execution.ui.ConfigurationModuleSelector import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.ActionManager @@ -43,19 +44,50 @@ 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.output.ScratchOutputHandlerAdapter import org.jetbrains.kotlin.idea.scratch.removeScratchPanel import javax.swing.* class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel(HorizontalLayout(5)), Disposable { override fun dispose() { + scratchFile.replScratchExecutor?.stop() + scratchFile.compilingScratchExecutor?.stop() scratchFile.editor.removeScratchPanel() } companion object { fun createPanel(project: Project, virtualFile: VirtualFile, editor: TextEditor) { val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return - val scratchFile = ScratchFileLanguageProvider.get(psiFile.language)?.createFile(project, editor) ?: return - editor.addScratchPanel(ScratchTopPanel(scratchFile)) + val scratchFile = ScratchFileLanguageProvider.get(psiFile.language)?.newScratchFile(project, editor) ?: return + val panel = ScratchTopPanel(scratchFile) + + val toolbarHandler = createUpdateToolbarHandler(panel) + scratchFile.replScratchExecutor?.addOutputHandler(object : ScratchOutputHandlerAdapter() { + override fun onFinish(file: ScratchFile) { + ApplicationManager.getApplication().invokeLater { + if (!file.project.isDisposed) { + val scratch = file.getPsiFile() + if (scratch?.isValid == true) { + DaemonCodeAnalyzer.getInstance(project).restart(scratch) + } + } + } + } + }) + scratchFile.replScratchExecutor?.addOutputHandler(toolbarHandler) + scratchFile.compilingScratchExecutor?.addOutputHandler(toolbarHandler) + + editor.addScratchPanel(panel) + } + + private fun createUpdateToolbarHandler(panel: ScratchTopPanel) = object : ScratchOutputHandlerAdapter() { + override fun onStart(file: ScratchFile) { + panel.updateToolbar() + } + + override fun onFinish(file: ScratchFile) { + panel.updateToolbar() + } } } @@ -104,6 +136,13 @@ class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel scratchFile.saveOptions { copy(isRepl = isReplCheckbox.isSelected) } + if (isReplCheckbox.isSelected) { + // TODO start REPL process when checkbox is selected to speed up execution + // Now it is switched off due to KT-18355: REPL process is keep alive if no command is executed + //scratchFile.replScratchExecutor?.start() + } else { + scratchFile.replScratchExecutor?.stop() + } } add(JSeparator(SwingConstants.VERTICAL)) diff --git a/idea/resources/META-INF/jvm.xml b/idea/resources/META-INF/jvm.xml index dcd36cea62e..e6baf00d31f 100644 --- a/idea/resources/META-INF/jvm.xml +++ b/idea/resources/META-INF/jvm.xml @@ -143,6 +143,8 @@ + + + document?.textLength ?: 0) return 0 + return document?.getLineNumber(index) ?: 0 } class SeparateFileWrapper(manager: PsiManager) : LightElement(manager, KotlinLanguage.INSTANCE) { diff --git a/idea/testData/scratch/lineMarker/binaryExpression.kts b/idea/testData/scratch/lineMarker/binaryExpression.kts new file mode 100644 index 00000000000..55b68a945d7 --- /dev/null +++ b/idea/testData/scratch/lineMarker/binaryExpression.kts @@ -0,0 +1,3 @@ +1 + 1 + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/binaryExpression2.kts b/idea/testData/scratch/lineMarker/binaryExpression2.kts new file mode 100644 index 00000000000..1347945fd31 --- /dev/null +++ b/idea/testData/scratch/lineMarker/binaryExpression2.kts @@ -0,0 +1,3 @@ +1 + 2 + 3 + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/constantExpression.kts b/idea/testData/scratch/lineMarker/constantExpression.kts new file mode 100644 index 00000000000..837bfc35a59 --- /dev/null +++ b/idea/testData/scratch/lineMarker/constantExpression.kts @@ -0,0 +1,3 @@ +1 + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/for.kts b/idea/testData/scratch/lineMarker/for.kts new file mode 100644 index 00000000000..eca719431eb --- /dev/null +++ b/idea/testData/scratch/lineMarker/for.kts @@ -0,0 +1,5 @@ +for (i in 0..4) { + println(i) +} + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/function.kts b/idea/testData/scratch/lineMarker/function.kts new file mode 100644 index 00000000000..339d89102d5 --- /dev/null +++ b/idea/testData/scratch/lineMarker/function.kts @@ -0,0 +1,5 @@ +fun foo(): Int { + return 1 +} + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/lambda.kts b/idea/testData/scratch/lineMarker/lambda.kts new file mode 100644 index 00000000000..ebc67db0dd8 --- /dev/null +++ b/idea/testData/scratch/lineMarker/lambda.kts @@ -0,0 +1,5 @@ +run { + 1 +} + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/lambda2.kts b/idea/testData/scratch/lineMarker/lambda2.kts new file mode 100644 index 00000000000..ba7a5df9e83 --- /dev/null +++ b/idea/testData/scratch/lineMarker/lambda2.kts @@ -0,0 +1,5 @@ +{ + 1 +}() + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/object.kts b/idea/testData/scratch/lineMarker/object.kts new file mode 100644 index 00000000000..001b6f604fe --- /dev/null +++ b/idea/testData/scratch/lineMarker/object.kts @@ -0,0 +1,7 @@ +object: Comparable { + override fun compareTo(other: String): Int { + return 0 + } +} + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/parentesized.kts b/idea/testData/scratch/lineMarker/parentesized.kts new file mode 100644 index 00000000000..21f989ae78c --- /dev/null +++ b/idea/testData/scratch/lineMarker/parentesized.kts @@ -0,0 +1,3 @@ +(1 + 2) + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/qualified.kts b/idea/testData/scratch/lineMarker/qualified.kts new file mode 100644 index 00000000000..9b040f9b85a --- /dev/null +++ b/idea/testData/scratch/lineMarker/qualified.kts @@ -0,0 +1,3 @@ +kotlin.io.println("Hi") + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/qualifiedMultiLine.kts b/idea/testData/scratch/lineMarker/qualifiedMultiLine.kts new file mode 100644 index 00000000000..18af529d8b0 --- /dev/null +++ b/idea/testData/scratch/lineMarker/qualifiedMultiLine.kts @@ -0,0 +1,5 @@ +kotlin + .io + .println("Hi") + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/referenceExpression.kts b/idea/testData/scratch/lineMarker/referenceExpression.kts new file mode 100644 index 00000000000..6fa8d83504e --- /dev/null +++ b/idea/testData/scratch/lineMarker/referenceExpression.kts @@ -0,0 +1,3 @@ +println("Hi") + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/testData/scratch/lineMarker/val.kts b/idea/testData/scratch/lineMarker/val.kts new file mode 100644 index 00000000000..9871967b378 --- /dev/null +++ b/idea/testData/scratch/lineMarker/val.kts @@ -0,0 +1,3 @@ +val a = 1 + +// REPL_MODE: true \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt index 4f032944961..9fc3a223ffa 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/AbstractScratchRunActionTest.kt @@ -140,27 +140,6 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { return inlineElementsInRange.map { it.renderer as InlayScratchFileRenderer } } - private fun configureOptions( - scratchPanel: ScratchTopPanel, - fileText: String - ) { - if (InTextDirectivesUtils.getPrefixedBoolean(fileText, "// INTERACTIVE_MODE: ") != true) { - scratchPanel.scratchFile.saveOptions { - copy(isInteractiveMode = false) - } - } - - if (InTextDirectivesUtils.getPrefixedBoolean(fileText, "// REPL_MODE: ") == true) { - scratchPanel.scratchFile.saveOptions { - copy(isRepl = true) - } - } - - if (!InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_MODULE")) { - scratchPanel.setModule(myFixture.module) - } - } - protected fun configureScratchByText(name: String, text: String): ScratchTopPanel { val scratchFile = ScratchRootType.getInstance().createScratchFile( project, @@ -177,7 +156,7 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { val (_, scratchPanel) = getEditorWithScratchPanel(myManager, myFixture.file.virtualFile) ?: error("Couldn't find scratch panel") - configureOptions(scratchPanel, text) + configureOptions(scratchPanel, text, myFixture.module) return scratchPanel } @@ -240,6 +219,12 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { } override fun tearDown() { + if (myFixture.file != null) { + val (_, scratchPanel) = getEditorWithScratchPanel(myManager, myFixture.file.virtualFile) + ?: error("Couldn't find scratch panel") + scratchPanel.scratchFile.replScratchExecutor?.stop() + } + super.tearDown() VfsRootAccess.disallowRootAccess(KotlinTestUtils.getHomeDirectory()) @@ -262,5 +247,24 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() { private val INSTANCE_WITHOUT_RUNTIME = object : KotlinLightProjectDescriptor() { override fun getSdk() = PluginTestCaseBase.fullJdk() } + + fun configureOptions( + scratchPanel: ScratchTopPanel, + fileText: String, + module: Module? + ) { + if (InTextDirectivesUtils.getPrefixedBoolean(fileText, "// INTERACTIVE_MODE: ") != true) { + scratchPanel.setInteractiveMode(false) + } + + if (InTextDirectivesUtils.getPrefixedBoolean(fileText, "// REPL_MODE: ") == true) { + scratchPanel.setReplMode(true) + } + + if (module != null && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_MODULE")) { + scratchPanel.setModule(module) + } + } + } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt b/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt new file mode 100644 index 00000000000..a5960dd12c4 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2019 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.scratch + +import com.intellij.ide.scratch.ScratchFileService +import com.intellij.ide.scratch.ScratchRootType +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiDocumentManager +import com.intellij.testFramework.ExpectedHighlightingData +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.codeInsight.AbstractLineMarkersTest +import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager +import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest.Companion.configureOptions +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.KtFile +import java.io.File + +abstract class AbstractScratchLineMarkersTest : AbstractLineMarkersTest() { + fun doScratchTest(path: String) { + val fileText = FileUtil.loadFile(File(path)) + + val scratchFile = ScratchRootType.getInstance().createScratchFile( + project, + "scratch.kts", + KotlinLanguage.INSTANCE, + fileText, + ScratchFileService.Option.create_if_missing + ) ?: error("Couldn't create scratch file") + + myFixture.openFileInEditor(scratchFile) + + ScriptDependenciesManager.updateScriptDependenciesSynchronously(scratchFile, project) + + val (_, scratchPanel) = getEditorWithScratchPanel(FileEditorManager.getInstance(project), myFixture.file.virtualFile) + ?: error("Couldn't find scratch panel") + + configureOptions(scratchPanel, fileText, null) + + val project = myFixture.project + val document = myFixture.editor.document + + val data = ExpectedHighlightingData(document, false, false, false, myFixture.file) + data.init() + + PsiDocumentManager.getInstance(project).commitAllDocuments() + + val markers = doAndCheckHighlighting(document, data, File(path)) + + assertNavigationElements(myFixture.project, myFixture.file as KtFile, markers) + } + + override fun tearDown() { + super.tearDown() + + ScratchFileService.getInstance().scratchesMapping.mappings.forEach { file, _ -> + runWriteAction { file.delete(this) } + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTestGenerated.java new file mode 100644 index 00000000000..0460c86cc4f --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTestGenerated.java @@ -0,0 +1,96 @@ +/* + * Copyright 2010-2019 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.scratch; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/testData/scratch/lineMarker") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ScratchLineMarkersTestGenerated extends AbstractScratchLineMarkersTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doScratchTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInLineMarker() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/scratch/lineMarker"), Pattern.compile("^(.+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("binaryExpression.kts") + public void testBinaryExpression() throws Exception { + runTest("idea/testData/scratch/lineMarker/binaryExpression.kts"); + } + + @TestMetadata("binaryExpression2.kts") + public void testBinaryExpression2() throws Exception { + runTest("idea/testData/scratch/lineMarker/binaryExpression2.kts"); + } + + @TestMetadata("constantExpression.kts") + public void testConstantExpression() throws Exception { + runTest("idea/testData/scratch/lineMarker/constantExpression.kts"); + } + + @TestMetadata("for.kts") + public void testFor() throws Exception { + runTest("idea/testData/scratch/lineMarker/for.kts"); + } + + @TestMetadata("function.kts") + public void testFunction() throws Exception { + runTest("idea/testData/scratch/lineMarker/function.kts"); + } + + @TestMetadata("lambda.kts") + public void testLambda() throws Exception { + runTest("idea/testData/scratch/lineMarker/lambda.kts"); + } + + @TestMetadata("lambda2.kts") + public void testLambda2() throws Exception { + runTest("idea/testData/scratch/lineMarker/lambda2.kts"); + } + + @TestMetadata("object.kts") + public void testObject() throws Exception { + runTest("idea/testData/scratch/lineMarker/object.kts"); + } + + @TestMetadata("parentesized.kts") + public void testParentesized() throws Exception { + runTest("idea/testData/scratch/lineMarker/parentesized.kts"); + } + + @TestMetadata("qualified.kts") + public void testQualified() throws Exception { + runTest("idea/testData/scratch/lineMarker/qualified.kts"); + } + + @TestMetadata("qualifiedMultiLine.kts") + public void testQualifiedMultiLine() throws Exception { + runTest("idea/testData/scratch/lineMarker/qualifiedMultiLine.kts"); + } + + @TestMetadata("referenceExpression.kts") + public void testReferenceExpression() throws Exception { + runTest("idea/testData/scratch/lineMarker/referenceExpression.kts"); + } + + @TestMetadata("val.kts") + public void testVal() throws Exception { + runTest("idea/testData/scratch/lineMarker/val.kts"); + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/scratch/SequentialScratchExecutorTest.kt b/idea/tests/org/jetbrains/kotlin/idea/scratch/SequentialScratchExecutorTest.kt new file mode 100644 index 00000000000..992583218ce --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/scratch/SequentialScratchExecutorTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2019 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.scratch + +import org.jetbrains.kotlin.idea.scratch.actions.RunScratchFromHereAction +import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner +import org.junit.runner.RunWith + +@RunWith(JUnit3WithIdeaConfigurationRunner::class) +class SequentialScratchExecutorTest : AbstractScratchRunActionTest() { + + fun testSingleLine() { + doTest( + listOf( + "2 + 2\n" to "RESULT: res1: kotlin.Int = 4" + ) + ) + } + + fun testMultipleLinesAtOnce() { + doTest( + listOf( + "2\n3\n4\n" to "RESULT: res1: kotlin.Int = 2, RESULT: res2: kotlin.Int = 3, RESULT: res3: kotlin.Int = 4" + ) + ) + } + + fun testMultipleLinesAfterEach() { + doTest( + listOf( + "2\n" to "RESULT: res1: kotlin.Int = 2", + "3\n" to "RESULT: res2: kotlin.Int = 3", + "4\n" to "RESULT: res3: kotlin.Int = 4" + ) + ) + } + + fun doTest(expression: List>) { + val scratchPanel = configureScratchByText("scratch_1.kts", testScratchText().inlinePropertiesValues(isRepl = true)) + + myFixture.editor.caretModel.moveToOffset(myFixture.file.textLength) + + try { + launchScratch() + waitUntilScratchFinishes() + + for ((text, expected) in expression) { + typeAndCheckOutput(text, expected) + } + } finally { + scratchPanel.scratchFile.replScratchExecutor?.stop() + } + } + + private fun typeAndCheckOutput(text: String, expected: String) { + val inlaysBefore = getInlays() + + myFixture.type(text) + + launchAction(RunScratchFromHereAction()) + waitUntilScratchFinishes() + + val inlayAfter = getInlays().filterNot { inlaysBefore.contains(it) } + + assertEquals(expected, inlayAfter.joinToString { it.toString().trim() }) + } +} \ No newline at end of file