Scratch: implement compiling executor
This commit is contained in:
@@ -735,7 +735,8 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
testClass<AbstractScratchRunActionTest> {
|
||||
model("scratch", extension = "kts")
|
||||
model("scratch", extension = "kts", testMethod = "doCompilingTest", testClassName = "Compiling")
|
||||
model("scratch", extension = "kts", testMethod = "doReplTest", testClassName = "Repl")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.scratch
|
||||
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.idea.scratch.compile.KtCompilingExecutor
|
||||
import org.jetbrains.kotlin.idea.scratch.output.InlayScratchOutputHandler
|
||||
import org.jetbrains.kotlin.idea.scratch.repl.KtScratchReplExecutor
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
@@ -27,5 +28,7 @@ class KtScratchFileLanguageProvider : ScratchFileLanguageProvider() {
|
||||
}
|
||||
|
||||
override fun createReplExecutor(file: ScratchFile) = KtScratchReplExecutor(file)
|
||||
override fun createCompilingExecutor(file: ScratchFile) = KtCompilingExecutor(file)
|
||||
|
||||
override fun getOutputHandler() = InlayScratchOutputHandler
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandler
|
||||
abstract class ScratchFileLanguageProvider {
|
||||
abstract fun createFile(psiFile: PsiFile): ScratchFile?
|
||||
abstract fun createReplExecutor(file: ScratchFile): ScratchExecutor?
|
||||
abstract fun createCompilingExecutor(file: ScratchFile): ScratchExecutor?
|
||||
|
||||
abstract fun getOutputHandler(): ScratchOutputHandler
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.scratch.ScratchFile
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchFileLanguageProvider
|
||||
import org.jetbrains.kotlin.idea.scratch.getScratchPanelFromSelectedEditor
|
||||
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputHandlerAdapter
|
||||
import org.jetbrains.kotlin.idea.scratch.ui.scratchTopPanel
|
||||
|
||||
class RunScratchAction : AnAction(
|
||||
KotlinBundle.message("scratch.run.button"),
|
||||
@@ -38,7 +39,7 @@ class RunScratchAction : AnAction(
|
||||
val scratchFile = scratchTopPanel.scratchFile
|
||||
|
||||
val isMakeBeforeRun = false // todo use property from panel
|
||||
val isRepl = true //todo use property from panel
|
||||
val isRepl = scratchTopPanel.isRepl()
|
||||
|
||||
val provider = ScratchFileLanguageProvider.get(scratchFile.psiFile.language) ?: return
|
||||
|
||||
@@ -52,9 +53,9 @@ class RunScratchAction : AnAction(
|
||||
}
|
||||
|
||||
val runnable = r@ {
|
||||
val executor = provider.createReplExecutor(scratchFile)
|
||||
val executor = if (isRepl) provider.createReplExecutor(scratchFile) else provider.createCompilingExecutor(scratchFile)
|
||||
if (executor == null) {
|
||||
handler.error(scratchFile, "Couldn't run file using REPL")
|
||||
handler.error(scratchFile, "Couldn't run ${scratchFile.psiFile.name}")
|
||||
handler.onFinish(scratchFile)
|
||||
return@r
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.scratch.compile
|
||||
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.compiler.ex.CompilerPathsEx
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.OrderEnumerator
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
||||
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
||||
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
|
||||
import org.jetbrains.kotlin.codegen.filterClassFiles
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.console.KotlinConsoleKeeper
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchExecutor
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchExpression
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchFile
|
||||
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutput
|
||||
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputType
|
||||
import org.jetbrains.kotlin.idea.scratch.ui.scratchTopPanel
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import java.io.File
|
||||
|
||||
class KtCompilingExecutor(file: ScratchFile) : ScratchExecutor(file) {
|
||||
private val log = Logger.getInstance(this.javaClass)
|
||||
|
||||
override fun execute() {
|
||||
handlers.forEach { it.onStart(file) }
|
||||
|
||||
val module = file.scratchTopPanel?.getModule() ?: return error("Module should be selected")
|
||||
|
||||
if (!checkForErrors(file.psiFile as KtFile)) {
|
||||
return error("Compilation Error")
|
||||
}
|
||||
|
||||
val result = KtScratchSourceFileProcessor().process(file)
|
||||
when (result) {
|
||||
is KtScratchSourceFileProcessor.Result.Error -> return error(result.message)
|
||||
is KtScratchSourceFileProcessor.Result.OK -> {
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
val modifiedScratchSourceFile =
|
||||
KtPsiFactory(file.psiFile.project).createFileWithLightClassSupport("tmp.kt", result.code, file.psiFile)
|
||||
|
||||
try {
|
||||
val tempDir = compileFileToTempDir(modifiedScratchSourceFile) ?: return@invokeLater
|
||||
|
||||
try {
|
||||
val handler = CapturingProcessHandler(createCommandLine(module, result.mainClassName, tempDir.path))
|
||||
ProcessOutputParser().parse(handler.runProcess())
|
||||
} finally {
|
||||
tempDir.delete()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
log.info(result.code, e)
|
||||
handlers.forEach { it.error(file, e.message ?: "Couldn't compile ${file.psiFile.name}") }
|
||||
} finally {
|
||||
handlers.forEach { it.onFinish(file) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileFileToTempDir(psiFile: KtFile): File? {
|
||||
if (!checkForErrors(psiFile)) return null
|
||||
|
||||
val resolutionFacade = psiFile.getResolutionFacade()
|
||||
val (bindingContext, files) = DebuggerUtils.analyzeInlinedFunctions(resolutionFacade, psiFile, false)
|
||||
|
||||
val generateClassFilter = object : GenerationState.GenerateClassFilter() {
|
||||
override fun shouldGeneratePackagePart(ktFile: KtFile) = ktFile == psiFile
|
||||
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
|
||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == psiFile
|
||||
override fun shouldGenerateScript(script: KtScript) = false
|
||||
}
|
||||
|
||||
val state = GenerationState.Builder(
|
||||
file.psiFile.project,
|
||||
ClassBuilderFactories.binaries(false),
|
||||
resolutionFacade.moduleDescriptor,
|
||||
bindingContext,
|
||||
files,
|
||||
CompilerConfiguration.EMPTY
|
||||
).generateDeclaredClassFilter(generateClassFilter).build()
|
||||
|
||||
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
|
||||
|
||||
return writeClassFilesToTempDir(state)
|
||||
}
|
||||
|
||||
private fun writeClassFilesToTempDir(state: GenerationState): File {
|
||||
val classFiles = state.factory.asList().filterClassFiles()
|
||||
|
||||
val dir = FileUtil.createTempDirectory("compile", "scratch")
|
||||
for (classFile in classFiles) {
|
||||
val tmpOutFile = File(dir, classFile.relativePath)
|
||||
tmpOutFile.parentFile.mkdirs()
|
||||
tmpOutFile.createNewFile()
|
||||
tmpOutFile.writeBytes(classFile.asByteArray())
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
private fun createCommandLine(module: Module, mainClassName: String, tempOutDir: String): GeneralCommandLine {
|
||||
val javaParameters = KotlinConsoleKeeper.createJavaParametersWithSdk(module)
|
||||
javaParameters.mainClass = mainClassName
|
||||
|
||||
val compiledModulePath = CompilerPathsEx.getOutputPaths(arrayOf(module)).toList()
|
||||
val moduleDependencies = OrderEnumerator.orderEntries(module).recursively().pathsList.pathList
|
||||
|
||||
javaParameters.classPath.add(tempOutDir)
|
||||
javaParameters.classPath.addAll(compiledModulePath)
|
||||
javaParameters.classPath.addAll(moduleDependencies)
|
||||
|
||||
return javaParameters.toCommandLine()
|
||||
}
|
||||
|
||||
private fun checkForErrors(psiFile: KtFile): Boolean {
|
||||
return runReadAction {
|
||||
try {
|
||||
AnalyzingUtils.checkForSyntacticErrors(psiFile)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
handlers.forEach { it.error(file, e.message ?: "Couldn't compile ${psiFile.name}") }
|
||||
return@runReadAction false
|
||||
}
|
||||
|
||||
val analysisResult = psiFile.analyzeFullyAndGetResult()
|
||||
|
||||
if (analysisResult.isError()) {
|
||||
handlers.forEach { it.error(file, analysisResult.error.message ?: "Couldn't compile ${psiFile.name}") }
|
||||
return@runReadAction false
|
||||
}
|
||||
|
||||
val bindingContext = analysisResult.bindingContext
|
||||
val diagnostics = bindingContext.diagnostics.filter { it.severity == Severity.ERROR }
|
||||
if (diagnostics.isNotEmpty()) {
|
||||
diagnostics.forEach { diagnostic ->
|
||||
if (diagnostic.psiElement.containingFile == psiFile) {
|
||||
val scratchExpression = file.findExpression(
|
||||
diagnostic.psiElement.getLineNumber(true),
|
||||
diagnostic.psiElement.getLineNumber(false)
|
||||
)
|
||||
handlers.forEach {
|
||||
it.handle(
|
||||
file,
|
||||
scratchExpression,
|
||||
ScratchOutput(DefaultErrorMessages.render(diagnostic), ScratchOutputType.ERROR)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return@runReadAction false
|
||||
}
|
||||
return@runReadAction true
|
||||
}
|
||||
}
|
||||
|
||||
private fun error(message: String) {
|
||||
handlers.forEach { it.error(file, message) }
|
||||
handlers.forEach { it.onFinish(file) }
|
||||
}
|
||||
|
||||
private fun ScratchFile.findExpression(lineStart: Int, lineEnd: Int): ScratchExpression {
|
||||
return getExpressions().first { it.lineStart == lineStart && it.lineEnd == lineEnd }
|
||||
}
|
||||
|
||||
private inner class ProcessOutputParser {
|
||||
fun parse(processOutput: ProcessOutput) {
|
||||
val out = processOutput.stdout
|
||||
val err = processOutput.stderr
|
||||
if (err.isNotBlank()) {
|
||||
handlers.forEach { it.error(file, err) }
|
||||
}
|
||||
if (out.isNotBlank()) {
|
||||
parseStdOut(out)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStdOut(out: String) {
|
||||
var results = arrayListOf<String>()
|
||||
var userOutput = arrayListOf<String>()
|
||||
for (line in out.split("\n")) {
|
||||
if (isOutputEnd(line)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isGeneratedOutput(line)) {
|
||||
val lineWoPrefix = line.removePrefix(KtScratchSourceFileProcessor.GENERATED_OUTPUT_PREFIX)
|
||||
if (isResultEnd(lineWoPrefix)) {
|
||||
val (startLine, endLine) = extractLineInfoFrom(lineWoPrefix)
|
||||
?: return error("Couldn't extract line info from line: $lineWoPrefix")
|
||||
val scratchExpression = file.findExpression(startLine, endLine)
|
||||
userOutput.forEach { output ->
|
||||
handlers.forEach {
|
||||
it.handle(file, scratchExpression, ScratchOutput(output, ScratchOutputType.OUTPUT))
|
||||
}
|
||||
}
|
||||
|
||||
results.forEach { result ->
|
||||
handlers.forEach {
|
||||
it.handle(file, scratchExpression, ScratchOutput(result, ScratchOutputType.RESULT))
|
||||
}
|
||||
}
|
||||
|
||||
results = arrayListOf()
|
||||
userOutput = arrayListOf()
|
||||
} else if (lineWoPrefix != Unit.toString()) {
|
||||
results.add(lineWoPrefix)
|
||||
}
|
||||
} else {
|
||||
userOutput.add(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isOutputEnd(line: String) = line.removeSuffix("\n") == KtScratchSourceFileProcessor.END_OUTPUT_MARKER
|
||||
private fun isResultEnd(line: String) = line.startsWith(KtScratchSourceFileProcessor.LINES_INFO_MARKER)
|
||||
private fun isGeneratedOutput(line: String) = line.startsWith(KtScratchSourceFileProcessor.GENERATED_OUTPUT_PREFIX)
|
||||
|
||||
private fun extractLineInfoFrom(encoded: String): Pair<Int, Int>? {
|
||||
val lineInfo = encoded
|
||||
.removePrefix(KtScratchSourceFileProcessor.LINES_INFO_MARKER)
|
||||
.removeSuffix("\n")
|
||||
.split('|')
|
||||
if (lineInfo.size == 2) {
|
||||
try {
|
||||
val (a, b) = lineInfo[0].toInt() to lineInfo[1].toInt()
|
||||
if (a > -1 && b > -1) {
|
||||
return a to b
|
||||
}
|
||||
} catch (e: NumberFormatException) {
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.scratch.compile
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.RenderingContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchExpression
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchFile
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KtScratchSourceFileProcessor {
|
||||
companion object {
|
||||
const val GENERATED_OUTPUT_PREFIX = "##scratch##generated##"
|
||||
const val LINES_INFO_MARKER = "end##"
|
||||
const val END_OUTPUT_MARKER = "end##!@#%^&*"
|
||||
|
||||
const val OBJECT_NAME = "ScratchFileRunnerGenerated"
|
||||
const val INSTANCE_NAME = "instanceScratchFileRunner"
|
||||
const val PACKAGE_NAME = "org.jetbrains.kotlin.idea.scratch.generated"
|
||||
const val GET_RES_FUN_NAME_PREFIX = "generated_get_instance_res"
|
||||
}
|
||||
|
||||
fun process(file: ScratchFile): Result {
|
||||
val sourceProcessor = KtSourceProcessor()
|
||||
file.getExpressions().forEach {
|
||||
sourceProcessor.process(it)
|
||||
}
|
||||
|
||||
val codeResult =
|
||||
"""
|
||||
package $PACKAGE_NAME
|
||||
|
||||
${sourceProcessor.imports.joinToString("\n") { it.text }}
|
||||
|
||||
object $OBJECT_NAME {
|
||||
class $OBJECT_NAME {
|
||||
${sourceProcessor.classBuilder}
|
||||
}
|
||||
|
||||
@JvmStatic fun main(args: Array<String>) {
|
||||
val $INSTANCE_NAME = $OBJECT_NAME()
|
||||
${sourceProcessor.objectBuilder}
|
||||
println("$END_OUTPUT_MARKER")
|
||||
}
|
||||
}
|
||||
"""
|
||||
return Result.OK("$PACKAGE_NAME.$OBJECT_NAME", codeResult)
|
||||
}
|
||||
|
||||
class KtSourceProcessor {
|
||||
val classBuilder = StringBuilder()
|
||||
val objectBuilder = StringBuilder()
|
||||
val imports = arrayListOf<KtImportDirective>()
|
||||
|
||||
private var resCount = 0
|
||||
|
||||
fun process(expression: ScratchExpression) {
|
||||
val psiElement = expression.element
|
||||
when (psiElement) {
|
||||
is KtVariableDeclaration -> processDeclaration(expression, psiElement)
|
||||
is KtFunction -> processDeclaration(expression, psiElement)
|
||||
is KtClassOrObject -> processDeclaration(expression, psiElement)
|
||||
is KtImportDirective -> imports.add(psiElement)
|
||||
is KtExpression -> processExpression(expression, psiElement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processDeclaration(e: ScratchExpression, c: KtDeclaration) {
|
||||
classBuilder.append(c.text).newLine()
|
||||
|
||||
val descriptor = c.resolveToDescriptorIfAny() ?: return
|
||||
|
||||
val context = RenderingContext.of(descriptor)
|
||||
objectBuilder.println(Renderers.COMPACT.render(descriptor, context))
|
||||
objectBuilder.appendLineInfo(e)
|
||||
}
|
||||
|
||||
private fun processExpression(e: ScratchExpression, expr: KtExpression) {
|
||||
val resName = "$GET_RES_FUN_NAME_PREFIX$resCount"
|
||||
|
||||
classBuilder.append("fun $resName() = run { ${expr.text} }").newLine()
|
||||
|
||||
objectBuilder.printlnObj("$INSTANCE_NAME.$resName()")
|
||||
objectBuilder.appendLineInfo(e)
|
||||
|
||||
resCount += 1
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendLineInfo(e: ScratchExpression) {
|
||||
println("$LINES_INFO_MARKER${e.lineStart}|${e.lineEnd}")
|
||||
}
|
||||
|
||||
private fun StringBuilder.println(str: String) = append("println(\"$GENERATED_OUTPUT_PREFIX$str\")").newLine()
|
||||
private fun StringBuilder.printlnObj(str: String) = append("println(\"$GENERATED_OUTPUT_PREFIX\${$str}\")").newLine()
|
||||
private fun StringBuilder.newLine() = append("\n")
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
class Error(val message: String) : Result()
|
||||
class OK(val mainClassName: String, val code: String) : Result()
|
||||
}
|
||||
}
|
||||
@@ -27,15 +27,13 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.ui.components.panels.HorizontalLayout
|
||||
import com.intellij.uiDesigner.core.Spacer
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchFile
|
||||
import org.jetbrains.kotlin.idea.scratch.ScratchFileLanguageProvider
|
||||
import org.jetbrains.kotlin.idea.scratch.actions.ClearScratchAction
|
||||
import org.jetbrains.kotlin.idea.scratch.actions.RunScratchAction
|
||||
import org.jetbrains.kotlin.idea.scratch.getScratchPanel
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.JSeparator
|
||||
import javax.swing.*
|
||||
|
||||
val ScratchFile.scratchTopPanel: ScratchTopPanel?
|
||||
get() = getScratchPanel(psiFile)?.takeIf { it.scratchFile == this@scratchTopPanel }
|
||||
@@ -50,10 +48,14 @@ class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel
|
||||
}
|
||||
|
||||
private val moduleChooser: ModulesComboBox
|
||||
private val isReplCheckbox: JCheckBox
|
||||
|
||||
init {
|
||||
add(createActionsToolbar())
|
||||
add(JSeparator())
|
||||
isReplCheckbox = JCheckBox("Use Repl", false)
|
||||
add(isReplCheckbox)
|
||||
add(JSeparator())
|
||||
add(JLabel("Use classpath of module: "))
|
||||
moduleChooser = createModuleChooser(scratchFile.psiFile.project)
|
||||
add(moduleChooser)
|
||||
@@ -68,6 +70,13 @@ class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel
|
||||
}
|
||||
}
|
||||
|
||||
fun isRepl() = isReplCheckbox.isEnabled
|
||||
|
||||
@TestOnly
|
||||
fun setReplMode(isEnabled: Boolean) {
|
||||
isReplCheckbox.isEnabled = isEnabled
|
||||
}
|
||||
|
||||
private fun createActionsToolbar(): JComponent {
|
||||
val toolbarGroup = DefaultActionGroup().apply {
|
||||
add(RunScratchAction())
|
||||
|
||||
@@ -85,7 +85,7 @@ class KotlinConsoleKeeper(val project: Project) {
|
||||
return commandLine
|
||||
}
|
||||
|
||||
private fun createJavaParametersWithSdk(module: Module): JavaParameters {
|
||||
fun createJavaParametersWithSdk(module: Module): JavaParameters {
|
||||
val params = JavaParameters()
|
||||
params.charset = null
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package myTest;
|
||||
|
||||
public class MyJavaClass {
|
||||
public int test() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
for (i in 0..5) { // OUTPUT: 0; 1; 2; 3; 4; 5
|
||||
println(i)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
val a = 1 // RESULT: val a: Int
|
||||
a // RESULT: 1
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(): Int { // RESULT: fun foo(): Int
|
||||
return 1
|
||||
}
|
||||
|
||||
foo() // RESULT: 1
|
||||
+1
@@ -0,0 +1 @@
|
||||
foo() // ERROR: Unresolved reference: foo
|
||||
+1
@@ -0,0 +1 @@
|
||||
println("hello") // OUTPUT: hello
|
||||
@@ -32,12 +32,22 @@ import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.scratch.actions.RunScratchAction
|
||||
import org.jetbrains.kotlin.idea.scratch.output.InlayScratchOutputHandler
|
||||
import org.jetbrains.kotlin.idea.scratch.ui.scratchTopPanel
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
|
||||
fun doTest(fileName: String) {
|
||||
fun doReplTest(fileName: String) {
|
||||
doTest(fileName, true)
|
||||
}
|
||||
|
||||
fun doCompilingTest(fileName: String) {
|
||||
doTest(fileName, false)
|
||||
}
|
||||
|
||||
fun doTest(fileName: String, isRepl: Boolean) {
|
||||
val sourceFile = File(testDataPath, fileName)
|
||||
val fileText = sourceFile.readText()
|
||||
|
||||
@@ -51,9 +61,13 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
|
||||
|
||||
myFixture.openFileInEditor(scratchFile)
|
||||
|
||||
ScratchFileLanguageProvider.createFile(myFixture.file)?.scratchTopPanel?.setReplMode(isRepl)
|
||||
|
||||
val event = getActionEvent(myFixture.file.virtualFile, RunScratchAction())
|
||||
launchAction(event, RunScratchAction())
|
||||
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val start = System.currentTimeMillis()
|
||||
// wait until output is displayed in editor or for 1 minute
|
||||
while (!event.presentation.isEnabled && (System.currentTimeMillis() - start) < 60000) {
|
||||
@@ -62,9 +76,8 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
|
||||
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
val editors = FileEditorManager.getInstance(project).getEditors(myFixture.file.virtualFile).filterIsInstance<TextEditor>()
|
||||
val doc =
|
||||
PsiDocumentManager.getInstance(project).getDocument(myFixture.file) ?: error("Document for ${myFixture.file.name} is null")
|
||||
val editors = FileEditorManager.getInstance(project).getEditors(scratchFile).filterIsInstance<TextEditor>()
|
||||
val doc = PsiDocumentManager.getInstance(project).getDocument(myFixture.file) ?: error("Document for ${myFixture.file.name} is null")
|
||||
|
||||
val actualOutput = StringBuilder(myFixture.file.text)
|
||||
for (line in doc.lineCount - 1 downTo 0) {
|
||||
@@ -79,7 +92,7 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
val expectedFileName = fileName.replace(".kts", ".repl.after")
|
||||
val expectedFileName = if (isRepl) fileName.replace(".kts", ".repl.after") else fileName.replace(".kts", ".comp.after")
|
||||
val expectedFile = File("$testDataPath/$expectedFileName")
|
||||
KotlinTestUtils.assertEqualsToFile(expectedFile, actualOutput.toString())
|
||||
}
|
||||
@@ -98,4 +111,6 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
|
||||
}
|
||||
|
||||
override fun getTestDataPath() = KotlinTestUtils.getHomeDirectory()
|
||||
|
||||
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE_FULL_JDK
|
||||
}
|
||||
+81
-33
@@ -17,47 +17,95 @@ 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")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class ScratchRunActionTestGenerated extends AbstractScratchRunActionTest {
|
||||
public void testAllFilesPresentInScratch() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/scratch"), Pattern.compile("^(.+)\\.kts$"), TargetBackend.ANY, true);
|
||||
@TestMetadata("idea/testData/scratch")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Compiling extends AbstractScratchRunActionTest {
|
||||
public void testAllFilesPresentInCompiling() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/scratch"), Pattern.compile("^(.+)\\.kts$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("for.kts")
|
||||
public void testFor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/for.kts");
|
||||
doCompilingTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kts")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/simple.kts");
|
||||
doCompilingTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simpleFun.kts")
|
||||
public void testSimpleFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/simpleFun.kts");
|
||||
doCompilingTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("stdlibFun.kts")
|
||||
public void testStdlibFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/stdlibFun.kts");
|
||||
doCompilingTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unresolved.kts")
|
||||
public void testUnresolved() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/unresolved.kts");
|
||||
doCompilingTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("userOutput.kts")
|
||||
public void testUserOutput() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/userOutput.kts");
|
||||
doCompilingTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("for.kts")
|
||||
public void testFor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/for.kts");
|
||||
doTest(fileName);
|
||||
}
|
||||
@TestMetadata("idea/testData/scratch")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Repl extends AbstractScratchRunActionTest {
|
||||
public void testAllFilesPresentInRepl() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/scratch"), Pattern.compile("^(.+)\\.kts$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kts")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/simple.kts");
|
||||
doTest(fileName);
|
||||
}
|
||||
@TestMetadata("for.kts")
|
||||
public void testFor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/for.kts");
|
||||
doReplTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simpleFun.kts")
|
||||
public void testSimpleFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/simpleFun.kts");
|
||||
doTest(fileName);
|
||||
}
|
||||
@TestMetadata("simple.kts")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/simple.kts");
|
||||
doReplTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("stdlibFun.kts")
|
||||
public void testStdlibFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/stdlibFun.kts");
|
||||
doTest(fileName);
|
||||
}
|
||||
@TestMetadata("simpleFun.kts")
|
||||
public void testSimpleFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/simpleFun.kts");
|
||||
doReplTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unresolved.kts")
|
||||
public void testUnresolved() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/unresolved.kts");
|
||||
doTest(fileName);
|
||||
}
|
||||
@TestMetadata("stdlibFun.kts")
|
||||
public void testStdlibFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/stdlibFun.kts");
|
||||
doReplTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("userOutput.kts")
|
||||
public void testUserOutput() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/userOutput.kts");
|
||||
doTest(fileName);
|
||||
@TestMetadata("unresolved.kts")
|
||||
public void testUnresolved() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/unresolved.kts");
|
||||
doReplTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("userOutput.kts")
|
||||
public void testUserOutput() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/scratch/userOutput.kts");
|
||||
doReplTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user