Scratch: implement run action with repl
This commit is contained in:
+4
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user