diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.java index a7f80735c2b..6664eb5c466 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.java @@ -54,10 +54,16 @@ public class ReplFromTerminal { String replIdeMode = System.getProperty("repl.ideMode"); ideMode = replIdeMode != null && replIdeMode.equals("true"); + // wrapper for `out` is required to escape every input in [ideMode]; + // if [ideMode == false] then just redirects all input to [System.out] + // if user calls [System.setOut(...)] then undefined behaviour + replWriter = new ReplSystemOutWrapper(ideMode, System.out); + System.setOut(replWriter); + // wrapper for `in` is required to give user possibility of calling // [readLine] from ide-console repl if (ideMode) { - replReader = new ReplSystemInWrapper(System.in); + replReader = new ReplSystemInWrapper(System.in, replWriter); System.setIn(replReader); } @@ -76,12 +82,6 @@ public class ReplFromTerminal { } }.start(); - // wrapper for `out` is required to escape every input in [ideMode]; - // if [ideMode == false] then just redirects all input to [System.out] - // if user calls [System.setOut(...)] then undefined behaviour - replWriter = new ReplSystemOutWrapper(ideMode, System.out); - System.setOut(replWriter); - try { OutputStream outStream = System.out; if (ideMode) { diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemInWrapper.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemInWrapper.kt index bb1215809c1..e3470a4f104 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemInWrapper.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemInWrapper.kt @@ -19,7 +19,10 @@ package org.jetbrains.kotlin.cli.jvm.repl.messages import java.io.ByteArrayOutputStream import java.io.InputStream -public class ReplSystemInWrapper(private val stdin: InputStream) : InputStream() { +public class ReplSystemInWrapper( + private val stdin: InputStream, + private val replWriter: ReplSystemOutWrapper +) : InputStream() { private var isXmlIncomplete = true private var isLastScriptByteProcessed = false private var byteBuilder = ByteArrayOutputStream() @@ -30,6 +33,14 @@ public class ReplSystemInWrapper(private val stdin: InputStream) : InputStream() get() = curBytePos == inputByteArray.size() var isReplScriptExecuting = false + set(value) { + if (value) + replWriter.printlnReadLineStart() + else + replWriter.printlnReadLineEnd() + + $isReplScriptExecuting = value + } override synchronized fun read(): Int { if (isLastScriptByteProcessed && isReplScriptExecuting) { diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapper.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapper.kt index 122babc8650..dd1b89c30c5 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapper.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapper.kt @@ -29,6 +29,8 @@ public class ReplSystemOutWrapper(private val ideMode: Boolean, standardOut: Pri HELP_PROMPT, USER_OUTPUT, REPL_RESULT, + READLINE_START, + READLINE_END, REPL_INCOMPLETE, COMPILE_ERROR, RUNTIME_ERROR, @@ -61,6 +63,8 @@ public class ReplSystemOutWrapper(private val ideMode: Boolean, standardOut: Pri fun printlnInit(x: String) = printlnWithEscaping(x, EscapeType.INITIAL_PROMPT) fun printlnHelp(x: String) = printlnWithEscaping(x, EscapeType.HELP_PROMPT) fun printlnResult(x: Any?) = printlnWithEscaping(x.toString(), EscapeType.REPL_RESULT) + fun printlnReadLineStart() = printlnWithEscaping("", EscapeType.READLINE_START) + fun printlnReadLineEnd() = printlnWithEscaping("", EscapeType.READLINE_END) fun printlnIncomplete() = printlnWithEscaping("", EscapeType.REPL_INCOMPLETE) fun printlnCompileError(x: String) = printlnWithEscaping(x, EscapeType.COMPILE_ERROR) fun printlnRuntimeError(x: String) = printlnWithEscaping(x, EscapeType.RUNTIME_ERROR) diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleExecutor.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleExecutor.kt index d76b89e1de6..a400cdbdce2 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleExecutor.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleExecutor.kt @@ -26,10 +26,9 @@ private val XML_PREAMBLE = "" public class KotlinConsoleExecutor( private val runner: KotlinConsoleRunner, - private val historyManager: KotlinConsoleHistoryManager + private val historyManager: KotlinConsoleHistoryManager, + private val historyHighlighter: KotlinHistoryHighlighter ) { - private val historyHighlighter = KotlinHistoryHighlighter(runner) - fun executeCommand() = WriteCommandAction.runWriteCommandAction(runner.project) { val consoleView = runner.consoleView val document = consoleView.editorDocument diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt index 24602f3ed70..e0dc3a9c3cc 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt @@ -34,11 +34,13 @@ import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction import org.jetbrains.kotlin.console.gutter.KotlinConsoleGutterContentProvider import org.jetbrains.kotlin.console.gutter.KotlinConsoleIndicatorRenderer import org.jetbrains.kotlin.console.gutter.ReplIcons +import org.jetbrains.kotlin.console.highlight.KotlinHistoryHighlighter import org.jetbrains.kotlin.console.highlight.KotlinReplOutputHighlighter import org.jetbrains.kotlin.console.highlight.ReplColors import org.jetbrains.kotlin.idea.JetLanguage import java.awt.Color import java.awt.Font +import java.util.concurrent.ConcurrentHashMap import javax.swing.Icon import kotlin.properties.Delegates @@ -51,10 +53,12 @@ public class KotlinConsoleRunner( title: String, path: String? ) : AbstractConsoleRunnerWithHistory(myProject, title, path) { + private val editorToHighlighter = ConcurrentHashMap() private val historyManager = KotlinConsoleHistoryManager(this) + private val historyHighlighter = KotlinHistoryHighlighter(this) private var disposableDescriptor: RunContentDescriptor by Delegates.notNull() - val executor = KotlinConsoleExecutor(this, historyManager) + val executor = KotlinConsoleExecutor(this, historyManager, historyHighlighter) var compilerHelper: KotlinConsoleCompilerHelper by Delegates.notNull() override fun createProcess() = cmdLine.createProcess() @@ -79,7 +83,10 @@ public class KotlinConsoleRunner( override fun createProcessHandler(process: Process): OSProcessHandler { val processHandler = KotlinReplOutputHandler( - KotlinReplOutputHighlighter(this, historyManager, testMode, previousCompilationFailed), process, cmdLine.commandLineString + historyHighlighter, + KotlinReplOutputHighlighter(this, historyManager, testMode, previousCompilationFailed), + process, + cmdLine.commandLineString ) val consoleFile = consoleView.virtualFile val keeper = KotlinConsoleKeeper.getInstance(project) @@ -162,9 +169,15 @@ public class KotlinConsoleRunner( 0, editor.document.textLength, HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE ) + editorToHighlighter[editor] = indicatorHighlighter indicatorHighlighter.gutterIconRenderer = indicator } + fun changeConsoleEditorIndicator(newIcon: Icon) { + val editorHighlighter = editorToHighlighter[consoleView.consoleEditor] + editorHighlighter?.gutterIconRenderer = KotlinConsoleIndicatorRenderer(newIcon) + } + // this method shouldn't be called in normal usage; it is for test purpose only fun dispose() { processHandler.destroyProcess() diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt index cf6410707fd..a90b3e6404a 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt @@ -21,6 +21,7 @@ import com.intellij.openapi.util.Key import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.console.actions.logError +import org.jetbrains.kotlin.console.highlight.KotlinHistoryHighlighter import org.jetbrains.kotlin.console.highlight.KotlinReplOutputHighlighter import org.jetbrains.kotlin.diagnostics.Severity import org.w3c.dom.Element @@ -37,6 +38,7 @@ public val SOURCE_CHARS: Array = arrayOf("\n", "#") data class SeverityDetails(val severity: Severity, val description: String, val range: TextRange) public class KotlinReplOutputHandler( + private val historyHighlighter: KotlinHistoryHighlighter, private val outputHighlighter: KotlinReplOutputHighlighter, process: Process, commandLine: String @@ -61,6 +63,8 @@ public class KotlinReplOutputHandler( "HELP_PROMPT" -> outputHighlighter.printHelp(content) "USER_OUTPUT" -> outputHighlighter.printUserOutput(content) "REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon(content) + "READLINE_START" -> historyHighlighter.isReadLineMode = true + "READLINE_END" -> historyHighlighter.isReadLineMode = false "REPL_INCOMPLETE", "COMPILE_ERROR" -> outputHighlighter.highlightCompilerErrors(createCompilerMessages(content)) "RUNTIME_ERROR" -> outputHighlighter.printRuntimeError(content) diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt index d3f5184c02d..2cb903ed463 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt @@ -21,11 +21,12 @@ import org.jetbrains.kotlin.idea.JetIcons import javax.swing.Icon public object ReplIcons { - public val BUILD_WARNING_INDICATOR: Icon = AllIcons.General.Information + public val BUILD_WARNING_INDICATOR: Icon = AllIcons.Ide.Warning_notifications public val HISTORY_INDICATOR: Icon = AllIcons.General.MessageHistory public val EDITOR_INDICATOR: Icon = JetIcons.LAUNCH - public val INCOMPLETE_INDICATOR: Icon = AllIcons.Nodes.Desktop + public val EDITOR_READLINE_INDICATOR: Icon = AllIcons.General.Balloon public val COMMAND_MARKER: Icon = AllIcons.General.Run + public val READLINE_MARKER: Icon = AllIcons.Icons.Ide.SpeedSearchPrompt // command result icons public val SYSTEM_HELP: Icon = AllIcons.Actions.Menu_help diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinHistoryHighlighter.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinHistoryHighlighter.kt index 8e65099ad26..da69c3f2a13 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinHistoryHighlighter.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinHistoryHighlighter.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.console.highlight import com.intellij.execution.console.LanguageConsoleImpl +import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.markup.HighlighterLayer @@ -29,6 +30,18 @@ import org.jetbrains.kotlin.console.gutter.ReplIcons public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner ) { private val consoleView: LanguageConsoleImpl by lazy { runner.consoleView as LanguageConsoleImpl } + var isReadLineMode: Boolean = false + set(value) { + WriteCommandAction.runWriteCommandAction(runner.project) { + if (value) + runner.changeConsoleEditorIndicator(ReplIcons.EDITOR_READLINE_INDICATOR) + else + runner.changeConsoleEditorIndicator(ReplIcons.EDITOR_INDICATOR) + } + + $isReadLineMode = value + } + fun printNewCommandInHistory(trimmedCommandText: String) { val historyEditor = consoleView.historyViewer @@ -50,11 +63,14 @@ public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner ) historyEditor.markupModel let { it.addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST, null, HighlighterTargetArea.EXACT_RANGE) } apply { - gutterIconRenderer = KotlinConsoleIndicatorRenderer(ReplIcons.COMMAND_MARKER) + val historyMarker = if (isReadLineMode) ReplIcons.READLINE_MARKER else ReplIcons.COMMAND_MARKER + gutterIconRenderer = KotlinConsoleIndicatorRenderer(historyMarker) } } private fun addLineBreakIfNeeded(historyEditor: EditorEx) { + if (isReadLineMode) return + val historyDocument = historyEditor.document val historyText = historyDocument.text val textLength = historyText.length()