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.idea.scratch.repl.KtScratchReplExecutor
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
class KtScratchFileLanguageProvider: ScratchFileLanguageProvider() {
|
class KtScratchFileLanguageProvider : ScratchFileLanguageProvider() {
|
||||||
override fun createFile(psiFile: PsiFile): ScratchFile? {
|
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 class ScratchFileLanguageProvider {
|
||||||
abstract fun createFile(psiFile: PsiFile): ScratchFile?
|
abstract fun createFile(psiFile: PsiFile): ScratchFile?
|
||||||
|
abstract fun createReplExecutor(file: ScratchFile): ScratchExecutor?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val EXTENSION = LanguageExtension<ScratchFileLanguageProvider>("org.jetbrains.kotlin.scratchFileLanguageProvider")
|
private val EXTENSION = LanguageExtension<ScratchFileLanguageProvider>("org.jetbrains.kotlin.scratchFileLanguageProvider")
|
||||||
|
|||||||
@@ -46,7 +46,14 @@ class RunScratchAction : AnAction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val runnable = r@ {
|
val runnable = r@ {
|
||||||
// todo
|
val executor = provider.createReplExecutor(scratchFile)
|
||||||
|
if (executor == null) {
|
||||||
|
return@r
|
||||||
|
}
|
||||||
|
|
||||||
|
e.presentation.isEnabled = false
|
||||||
|
|
||||||
|
executor.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMakeBeforeRun) {
|
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
|
import javax.swing.JSeparator
|
||||||
|
|
||||||
val ScratchFile.scratchTopPanel: ScratchTopPanel?
|
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)) {
|
class ScratchTopPanel private constructor(val scratchFile: ScratchFile) : JPanel(HorizontalLayout(5)) {
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import com.intellij.openapi.roots.ModuleRootManager
|
|||||||
import com.intellij.openapi.roots.OrderEnumerator
|
import com.intellij.openapi.roots.OrderEnumerator
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.util.SystemProperties
|
import com.intellij.util.SystemProperties
|
||||||
import org.jetbrains.kotlin.console.actions.errorNotification
|
|
||||||
import org.jetbrains.kotlin.utils.PathUtil
|
import org.jetbrains.kotlin.utils.PathUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@@ -45,7 +44,7 @@ class KotlinConsoleKeeper(val project: Project) {
|
|||||||
|
|
||||||
fun run(module: Module, previousCompilationFailed: Boolean = false): KotlinConsoleRunner? {
|
fun run(module: Module, previousCompilationFailed: Boolean = false): KotlinConsoleRunner? {
|
||||||
val path = module.moduleFilePath
|
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)
|
val consoleRunner = KotlinConsoleRunner(module, cmdLine, previousCompilationFailed, project, REPL_TITLE, path)
|
||||||
consoleRunner.initAndRun()
|
consoleRunner.initAndRun()
|
||||||
@@ -54,61 +53,61 @@ class KotlinConsoleKeeper(val project: Project) {
|
|||||||
return consoleRunner
|
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 {
|
companion object {
|
||||||
@JvmStatic fun getInstance(project: Project) = ServiceManager.getService(project, KotlinConsoleKeeper::class.java)
|
@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