Scratch: implement run action with repl

This commit is contained in:
Natalia Selezneva
2017-12-27 11:57:14 +03:00
parent 2ccf4b7d7f
commit 01e62c9bdb
7 changed files with 230 additions and 60 deletions
@@ -20,8 +20,10 @@ import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.scratch.repl.KtScratchReplExecutor
import org.jetbrains.kotlin.psi.KtFile
class KtScratchFileLanguageProvider: ScratchFileLanguageProvider() {
class KtScratchFileLanguageProvider : ScratchFileLanguageProvider() {
override fun createFile(psiFile: PsiFile): ScratchFile? {
return (psiFile as? KtFile)?.let { KtScratchFile(psiFile)}
return (psiFile as? KtFile)?.let { KtScratchFile(psiFile) }
}
override fun createReplExecutor(file: ScratchFile) = KtScratchReplExecutor(file)
}
@@ -0,0 +1,21 @@
/*
* 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
abstract class ScratchExecutor(protected val file: ScratchFile) {
abstract fun execute()
}
@@ -24,6 +24,7 @@ import com.intellij.psi.PsiFile
abstract class ScratchFileLanguageProvider {
abstract fun createFile(psiFile: PsiFile): ScratchFile?
abstract fun createReplExecutor(file: ScratchFile): ScratchExecutor?
companion object {
private val EXTENSION = LanguageExtension<ScratchFileLanguageProvider>("org.jetbrains.kotlin.scratchFileLanguageProvider")
@@ -46,7 +46,14 @@ class RunScratchAction : AnAction(
}
val runnable = r@ {
// todo
val executor = provider.createReplExecutor(scratchFile)
if (executor == null) {
return@r
}
e.presentation.isEnabled = false
executor.execute()
}
if (isMakeBeforeRun) {
@@ -0,0 +1,140 @@
/*
* 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.repl
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.OSProcessHandler
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.console.KotlinConsoleKeeper
import org.jetbrains.kotlin.console.SOURCE_CHARS
import org.jetbrains.kotlin.console.XML_REPLACEMENTS
import org.jetbrains.kotlin.console.actions.logError
import org.jetbrains.kotlin.idea.scratch.ScratchExecutor
import org.jetbrains.kotlin.idea.scratch.ScratchExpression
import org.jetbrains.kotlin.idea.scratch.ScratchFile
import org.w3c.dom.Element
import org.xml.sax.InputSource
import java.io.ByteArrayInputStream
import java.nio.charset.Charset
import javax.xml.parsers.DocumentBuilderFactory
private val XML_PREAMBLE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
class KtScratchReplExecutor(file: ScratchFile) : ScratchExecutor(file) {
private val history: ReplHistory = ReplHistory()
private lateinit var osProcessHandler: OSProcessHandler
override fun execute() {
val module = file.module ?: return
val cmdLine = KotlinConsoleKeeper.createCommandLine(module)
osProcessHandler = ReplOSProcessHandler(cmdLine)
osProcessHandler.startNotify()
file.getExpressions().forEach { expression ->
history.addEntry(expression)
sendCommandToProcess(expression.element.text)
}
sendCommandToProcess(":quit")
}
private fun sendCommandToProcess(command: String) {
val processInputOS = osProcessHandler.processInput ?: return logError(this::class.java, "<p>Broken execute stream</p>")
val charset = osProcessHandler.charset ?: Charsets.UTF_8
val xmlRes = XML_PREAMBLE +
"<input>" +
StringUtil.escapeXml(
StringUtil.replace(command, SOURCE_CHARS, XML_REPLACEMENTS)
) +
"</input>"
val bytes = ("$xmlRes\n").toByteArray(charset)
processInputOS.write(bytes)
processInputOS.flush()
}
private class ReplHistory {
private var entries = arrayListOf<ScratchExpression>()
private var processedEntriesCount: Int = 0
fun addEntry(entry: ScratchExpression) {
entries.add(entry)
}
fun lastUnprocessedEntry(): ScratchExpression? {
return entries.takeIf { processedEntriesCount < entries.size }?.get(processedEntriesCount)
}
fun lastProcessedEntry(): ScratchExpression? {
val lastProcessedEntryIndex = processedEntriesCount - 1
return entries.takeIf { lastProcessedEntryIndex < entries.size }?.get(lastProcessedEntryIndex)
}
fun entryProcessed() {
processedEntriesCount++
}
}
private inner class ReplOSProcessHandler(cmd: GeneralCommandLine) : OSProcessHandler(cmd) {
private val factory = DocumentBuilderFactory.newInstance()
override fun notifyTextAvailable(text: String, outputType: Key<*>) {
if (text.startsWith("warning: classpath entry points to a non-existent location")) return
if (outputType == ProcessOutputTypes.STDOUT) {
handleReplMessage(text)
}
}
private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding)))
private fun handleReplMessage(text: String) {
if (text.isBlank()) return
val output = try {
factory.newDocumentBuilder().parse(strToSource(text))
} catch (e: Exception) {
return
}
val root = output.firstChild as Element
val outputType = root.getAttribute("type")
val content = StringUtil.replace(root.textContent, XML_REPLACEMENTS, SOURCE_CHARS)
if (outputType in setOf("SUCCESS", "COMPILE_ERROR", "INTERNAL_ERROR", "RUNTIME_ERROR", "READLINE_END")) {
history.entryProcessed()
}
val result = content
if (result != null) {
val lastExpression = if (outputType == "USER_OUTPUT") {
// success command is printed after user output
history.lastUnprocessedEntry()
} else {
history.lastProcessedEntry()
}
if (lastExpression != null) {
}
}
}
}
}
@@ -38,7 +38,7 @@ import javax.swing.JPanel
import javax.swing.JSeparator
val ScratchFile.scratchTopPanel: ScratchTopPanel?
get() = getScratchPanel(psiFile).firstOrNull { it.scratchFile == this@scratchTopPanel }
get() = getScratchPanel(psiFile)?.takeIf { it.scratchFile == this@scratchTopPanel }
class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel(HorizontalLayout(5)) {
companion object {
@@ -29,7 +29,6 @@ import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.OrderEnumerator
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.SystemProperties
import org.jetbrains.kotlin.console.actions.errorNotification
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import java.util.concurrent.ConcurrentHashMap
@@ -45,7 +44,7 @@ class KotlinConsoleKeeper(val project: Project) {
fun run(module: Module, previousCompilationFailed: Boolean = false): KotlinConsoleRunner? {
val path = module.moduleFilePath
val cmdLine = createCommandLine(module) ?: return run { errorNotification(project, "Module SDK not found"); null }
val cmdLine = createCommandLine(module)
val consoleRunner = KotlinConsoleRunner(module, cmdLine, previousCompilationFailed, project, REPL_TITLE, path)
consoleRunner.initAndRun()
@@ -54,61 +53,61 @@ class KotlinConsoleKeeper(val project: Project) {
return consoleRunner
}
private fun createCommandLine(module: Module): GeneralCommandLine? {
val javaParameters = createJavaParametersWithSdk(module)
javaParameters.mainClass = "dummy"
val commandLine = javaParameters.toCommandLine()
val paramList = commandLine.parametersList
paramList.clearAll()
// use to debug repl process
//paramList.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005")
val kotlinPaths = PathUtil.kotlinPathsForIdeaPlugin
val replClassPath = listOf(kotlinPaths.compilerPath, kotlinPaths.reflectPath, kotlinPaths.stdlibPath, kotlinPaths.scriptRuntimePath)
.joinToString(File.pathSeparator) { it.absolutePath }
paramList.add("-cp")
paramList.add(replClassPath)
paramList.add("-Dkotlin.repl.ideMode=true")
paramList.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
addPathToCompiledOutput(paramList, module)
return commandLine
}
private fun createJavaParametersWithSdk(module: Module): JavaParameters {
val params = JavaParameters()
params.charset = null
val sdk = ModuleRootManager.getInstance(module).sdk
if (sdk != null && sdk.sdkType is JavaSdkType && File(sdk.homePath).exists()) {
params.jdk = sdk
}
if (params.jdk == null) {
params.jdk = SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome())
}
return params
}
private fun addPathToCompiledOutput(paramList: ParametersList, module: Module) {
val compiledModulePath = CompilerPathsEx.getOutputPaths(arrayOf(module)).joinToString(File.pathSeparator)
val moduleDependencies = OrderEnumerator.orderEntries(module).recursively().pathsList.pathsString
val compiledOutputClasspath = "$compiledModulePath${File.pathSeparator}$moduleDependencies"
paramList.add("-cp")
paramList.add(compiledOutputClasspath)
}
companion object {
@JvmStatic fun getInstance(project: Project) = ServiceManager.getService(project, KotlinConsoleKeeper::class.java)
fun createCommandLine(module: Module): GeneralCommandLine {
val javaParameters = createJavaParametersWithSdk(module)
javaParameters.mainClass = "dummy"
val commandLine = javaParameters.toCommandLine()
val paramList = commandLine.parametersList
paramList.clearAll()
// use to debug repl process
//paramList.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005")
val kotlinPaths = PathUtil.kotlinPathsForIdeaPlugin
val replClassPath = listOf(kotlinPaths.compilerPath, kotlinPaths.reflectPath, kotlinPaths.stdlibPath, kotlinPaths.scriptRuntimePath)
.joinToString(File.pathSeparator) { it.absolutePath }
paramList.add("-cp")
paramList.add(replClassPath)
paramList.add("-Dkotlin.repl.ideMode=true")
paramList.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
addPathToCompiledOutput(paramList, module)
return commandLine
}
private fun createJavaParametersWithSdk(module: Module): JavaParameters {
val params = JavaParameters()
params.charset = null
val sdk = ModuleRootManager.getInstance(module).sdk
if (sdk != null && sdk.sdkType is JavaSdkType && File(sdk.homePath).exists()) {
params.jdk = sdk
}
if (params.jdk == null) {
params.jdk = SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome())
}
return params
}
private fun addPathToCompiledOutput(paramList: ParametersList, module: Module) {
val compiledModulePath = CompilerPathsEx.getOutputPaths(arrayOf(module)).joinToString(File.pathSeparator)
val moduleDependencies = OrderEnumerator.orderEntries(module).recursively().pathsList.pathsString
val compiledOutputClasspath = "$compiledModulePath${File.pathSeparator}$moduleDependencies"
paramList.add("-cp")
paramList.add(compiledOutputClasspath)
}
}
}