Implement REPL Mode for scratches
^KT-27963 Fixed
This commit is contained in:
@@ -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<String>) {
|
||||
model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false)
|
||||
}
|
||||
|
||||
testClass<AbstractScratchLineMarkersTest> {
|
||||
model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS)
|
||||
}
|
||||
|
||||
testClass<AbstractFirMultiModuleResolveTest> {
|
||||
model("fir/multiModule", recursive = false, extension = null)
|
||||
}
|
||||
|
||||
@@ -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<String>) {
|
||||
model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false)
|
||||
}
|
||||
|
||||
testClass<AbstractScratchLineMarkersTest> {
|
||||
model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS)
|
||||
}
|
||||
|
||||
testClass<AbstractFirMultiModuleResolveTest> {
|
||||
model("fir/multiModule", recursive = false, extension = null)
|
||||
}
|
||||
|
||||
@@ -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<String>) {
|
||||
model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false)
|
||||
}
|
||||
|
||||
testClass<AbstractScratchLineMarkersTest> {
|
||||
model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS)
|
||||
}
|
||||
|
||||
testClass<AbstractFirMultiModuleResolveTest> {
|
||||
model("fir/multiModule", recursive = false, extension = null)
|
||||
}
|
||||
|
||||
@@ -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<String>) {
|
||||
model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false)
|
||||
}
|
||||
|
||||
testClass<AbstractScratchLineMarkersTest> {
|
||||
model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS)
|
||||
}
|
||||
|
||||
testClass<AbstractFirMultiModuleResolveTest> {
|
||||
model("fir/multiModule", recursive = false, extension = null)
|
||||
}
|
||||
|
||||
@@ -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<String>) {
|
||||
model("scratch/multiFile", extension = null, testMethod = "doMultiFileTest", recursive = false)
|
||||
}
|
||||
|
||||
testClass<AbstractScratchLineMarkersTest> {
|
||||
model("scratch/lineMarker", testMethod = "doScratchTest", pattern = KT_OR_KTS)
|
||||
}
|
||||
|
||||
testClass<AbstractFirMultiModuleResolveTest> {
|
||||
model("fir/multiModule", recursive = false, extension = null)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<ScratchOutputHandler>()
|
||||
class CompositeOutputHandler : ScratchOutputHandler {
|
||||
private val handlers = mutableSetOf<ScratchOutputHandler>()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ScratchExpression> = 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<ScratchExpression>
|
||||
abstract fun hasErrors(): Boolean
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
+37
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -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<KtNamedDeclaration>()
|
||||
if (declaration != null && declaration !is KtParameter && declaration.nameIdentifier == element) {
|
||||
return isLastExecutedExpression(element)
|
||||
}
|
||||
|
||||
val scriptInitializer = element.getParentOfType<KtScriptInitializer>(true)?.body
|
||||
if (scriptInitializer != null) {
|
||||
if (scriptInitializer.findDescendantOfType<LeafPsiElement>() == 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()
|
||||
}
|
||||
}
|
||||
@@ -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, "<p>Broken execute stream</p>")
|
||||
val charset = osProcessHandler.charset ?: Charsets.UTF_8
|
||||
val processInputOS = osProcessHandler?.processInput ?: return logError(this::class.java, "<p>Broken execute stream</p>")
|
||||
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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -143,6 +143,8 @@
|
||||
|
||||
<scratch.creationHelper language="kotlin"
|
||||
implementationClass="org.jetbrains.kotlin.idea.scratch.KtScratchFileCreationHelper"/>
|
||||
<runLineMarkerContributor language="kotlin"
|
||||
implementationClass="org.jetbrains.kotlin.idea.scratch.actions.ScratchRunLineMarkerContributor"/>
|
||||
|
||||
<localInspection
|
||||
groupName="Plugin DevKit"
|
||||
|
||||
@@ -142,6 +142,8 @@
|
||||
|
||||
<scratch.creationHelper language="kotlin"
|
||||
implementationClass="org.jetbrains.kotlin.idea.scratch.KtScratchFileCreationHelper"/>
|
||||
<runLineMarkerContributor language="kotlin"
|
||||
implementationClass="org.jetbrains.kotlin.idea.scratch.actions.ScratchRunLineMarkerContributor"/>
|
||||
|
||||
<localInspection
|
||||
groupName="Plugin DevKit"
|
||||
|
||||
@@ -143,6 +143,8 @@
|
||||
|
||||
<scratch.creationHelper language="kotlin"
|
||||
implementationClass="org.jetbrains.kotlin.idea.scratch.KtScratchFileCreationHelper"/>
|
||||
<runLineMarkerContributor language="kotlin"
|
||||
implementationClass="org.jetbrains.kotlin.idea.scratch.actions.ScratchRunLineMarkerContributor"/>
|
||||
|
||||
<localInspection
|
||||
groupName="Plugin DevKit"
|
||||
|
||||
@@ -311,7 +311,9 @@ class SelectionAwareScopeHighlighter(val editor: Editor) {
|
||||
|
||||
fun PsiElement.getLineNumber(start: Boolean = true): Int {
|
||||
val document = containingFile.viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(containingFile)
|
||||
return document?.getLineNumber(if (start) this.startOffset else this.endOffset) ?: 0
|
||||
val index = if (start) this.startOffset else this.endOffset
|
||||
if (index > document?.textLength ?: 0) return 0
|
||||
return document?.getLineNumber(index) ?: 0
|
||||
}
|
||||
|
||||
class SeparateFileWrapper(manager: PsiManager) : LightElement(manager, KotlinLanguage.INSTANCE) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<lineMarker>1</lineMarker> + 1
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,3 @@
|
||||
<lineMarker>1</lineMarker> + 2 + 3
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,3 @@
|
||||
<lineMarker>1</lineMarker>
|
||||
|
||||
// REPL_MODE: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<lineMarker>for</lineMarker> (i in 0..4) {
|
||||
println(i)
|
||||
}
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,5 @@
|
||||
fun <lineMarker>foo</lineMarker>(): Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// REPL_MODE: true
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<lineMarker>run</lineMarker> {
|
||||
1
|
||||
}
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,5 @@
|
||||
<lineMarker>{</lineMarker>
|
||||
1
|
||||
}()
|
||||
|
||||
// REPL_MODE: true
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<lineMarker>object</lineMarker>: Comparable<String> {
|
||||
override fun compareTo(other: String): Int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,3 @@
|
||||
<lineMarker>(</lineMarker>1 + 2)
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,3 @@
|
||||
<lineMarker>kotlin</lineMarker>.io.println("Hi")
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,5 @@
|
||||
<lineMarker>kotlin</lineMarker>
|
||||
.io
|
||||
.println("Hi")
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -0,0 +1,3 @@
|
||||
<lineMarker>println</lineMarker>("Hi")
|
||||
|
||||
// REPL_MODE: true
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
val <lineMarker>a</lineMarker> = 1
|
||||
|
||||
// REPL_MODE: true
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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<Pair<String, String>>) {
|
||||
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() })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user