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 bee60bec946..f44c8d7abd6 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 @@ -25,7 +25,7 @@ import jline.console.history.FileHistory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.cli.common.KotlinVersion; -import org.jetbrains.kotlin.cli.jvm.repl.messages.FromIdeXmlMessagesParser; +import org.jetbrains.kotlin.cli.jvm.repl.messages.IdeXmlMessagesParser; import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplSystemOutWrapper; import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.utils.UtilsPackage; @@ -42,6 +42,7 @@ public class ReplFromTerminal { private final boolean ideMode; private final ReplSystemOutWrapper replWriter; + private final IdeXmlMessagesParser ideXmlParser = new IdeXmlMessagesParser(); private final ConsoleReader consoleReader; @@ -186,7 +187,7 @@ public class ReplFromTerminal { private String readLine(@Nullable String prompt) throws IOException { String line = consoleReader.readLine(prompt); if (ideMode && line != null) { - line = FromIdeXmlMessagesParser.parse(line); + line = ideXmlParser.parse(line); } return line; } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/EscapeConstants.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/EscapeConstants.kt new file mode 100644 index 00000000000..c9298e18542 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/EscapeConstants.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2015 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.cli.jvm.repl.messages + +public object EscapeConstants { + public val XML_PREAMBLE: String = "" + + // using '#' to avoid collisions with xml escaping + public val SOURCE_CHARS: Array = arrayOf("\n", "#") + public val XML_REPLACEMENTS: Array = arrayOf("#n", "#diez") +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/FromIdeXmlMessagesParser.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/IdeXmlMessagesParser.kt similarity index 84% rename from compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/FromIdeXmlMessagesParser.kt rename to compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/IdeXmlMessagesParser.kt index d425cdd8e57..4e60a973a3d 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/FromIdeXmlMessagesParser.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/IdeXmlMessagesParser.kt @@ -21,18 +21,17 @@ import org.w3c.dom.Element import org.xml.sax.InputSource import java.io.ByteArrayInputStream import javax.xml.parsers.DocumentBuilderFactory -import kotlin.platform.platformStatic -public object FromIdeXmlMessagesParser { +public class IdeXmlMessagesParser { private fun strToSource(s: String) = InputSource(ByteArrayInputStream(s.toByteArray())) - platformStatic fun parse(inputMessage: String): String { + fun parse(inputMessage: String): String { val docFactory = DocumentBuilderFactory.newInstance() val docBuilder = docFactory.newDocumentBuilder() val input = docBuilder.parse(strToSource(inputMessage)) val root = input.firstChild as Element - val inputContent = StringUtil.unescapeStringCharacters(root.textContent).trim() + val inputContent = StringUtil.replace(root.textContent, EscapeConstants.XML_REPLACEMENTS, EscapeConstants.SOURCE_CHARS).trim() return inputContent } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplIdeDiagnosticMessageHolder.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplIdeDiagnosticMessageHolder.kt index c3cd1d501f0..d1858d2e5be 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplIdeDiagnosticMessageHolder.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplIdeDiagnosticMessageHolder.kt @@ -27,7 +27,7 @@ public class ReplIdeDiagnosticMessageHolder : DiagnosticMessageHolder { private val diagnostics = arrayListOf>() override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) { - diagnostics.add(diagnostic to render) + diagnostics.add(Pair(diagnostic, render)) } override val renderedDiagnostics: String 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 335aa119c34..8c75f88a6f2 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 @@ -21,7 +21,6 @@ import com.intellij.util.LineSeparator import java.io.PrintStream public class ReplSystemOutWrapper(private val standardOut: PrintStream, private val ideMode: Boolean) : PrintStream(standardOut, true) { - private val XML_PREFIX = "" private val END_LINE = LineSeparator.getSystemLineSeparator().separatorString private enum class EscapeType { @@ -51,8 +50,8 @@ public class ReplSystemOutWrapper(private val standardOut: PrintStream, private } private fun xmlEscape(s: String, escapeType: EscapeType): String { - val singleLine = StringUtil.escapeLineBreak(s) - return "$XML_PREFIX${StringUtil.escapeXml(singleLine)}" + val singleLine = StringUtil.replace(s, EscapeConstants.SOURCE_CHARS, EscapeConstants.XML_REPLACEMENTS) + return "${EscapeConstants.XML_PREAMBLE}${StringUtil.escapeXml(singleLine)}" } fun printlnResult(x: Any?) = printlnWithEscaping(x.toString(), EscapeType.REPL_RESULT) 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 80dc5359ef3..f8c25ff566c 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleExecutor.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleExecutor.kt @@ -26,7 +26,6 @@ public class KotlinConsoleExecutor( private val runner: KotlinConsoleRunner, private val historyManager: KotlinConsoleHistoryManager ) { - private val XML_PREFIX = "" private val historyHighlighter = KotlinHistoryHighlighter(runner) fun executeCommand() = WriteCommandAction.runWriteCommandAction(runner.project) { @@ -50,7 +49,12 @@ public class KotlinConsoleExecutor( val processInputOS = processHandler.processInput ?: return logError(javaClass, "

Broken process stream

") val charset = (processHandler as? BaseOSProcessHandler)?.charset ?: Charsets.UTF_8 - val xmlRes = "$XML_PREFIX${StringUtil.escapeXml(StringUtil.escapeLineBreak(command.trim()))}" + val xmlRes = "${ReplConstants.XML_PREAMBLE}" + + "" + + "${StringUtil.escapeXml( + StringUtil.replace(command.trim(), ReplConstants.SOURCE_CHARS, ReplConstants.XML_REPLACEMENTS) + )}" + + "" val bytes = ("$xmlRes\n").toByteArray(charset) processInputOS.write(bytes) processInputOS.flush() diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleHistoryManager.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleHistoryManager.kt index 5d21aa64a74..19299a27085 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleHistoryManager.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleHistoryManager.kt @@ -79,7 +79,7 @@ public class KotlinConsoleHistoryManager(private val ktConsole: KotlinConsoleRun historyPos = Math.max(historyPos - 1, 0) WriteCommandAction.runWriteCommandAction(ktConsole.project) { - document.setText(history[historyPos]) + document.setText(history[historyPos].trim()) caret.moveToOffset(0) prevCaretOffset = 0 } @@ -92,7 +92,7 @@ public class KotlinConsoleHistoryManager(private val ktConsole: KotlinConsoleRun historyPos = Math.min(historyPos + 1, history.size()) WriteCommandAction.runWriteCommandAction(ktConsole.project) { - document.setText(if (historyPos == history.size()) unfinishedCommand else history[historyPos]) + document.setText(if (historyPos == history.size()) unfinishedCommand else history[historyPos].trim()) caret.moveToOffset(document.textLength) prevCaretOffset = document.textLength } 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 cf086257b8f..bedc19eb556 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt @@ -35,17 +35,17 @@ public class KotlinReplOutputHandler( process: Process, commandLine: String ) : OSProcessHandler(process, commandLine) { - private val XML_START = "?) { // skip "/usr/lib/jvm/java-8-oracle/bin/java -cp ..." intro - if (!text.startsWith(XML_START)) return super.notifyTextAvailable(text, key) + if (!text.startsWith(ReplConstants.XML_PREFIX)) return super.notifyTextAvailable(text, key) val output = dBuilder.parse(strToSource(text)) val root = output.firstChild as Element val outputType = root.getAttribute("type") - val content = StringUtil.unescapeStringCharacters(root.textContent).trim() + val content = StringUtil.replace(root.textContent, ReplConstants.XML_REPLACEMENTS, ReplConstants.SOURCE_CHARS).trim() when (outputType) { "INITIAL_PROMPT" -> super.notifyTextAvailable("$content\n", key) diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/ReplConstants.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/ReplConstants.kt new file mode 100644 index 00000000000..523fc39d420 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/ReplConstants.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2015 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.console + +public object ReplConstants { + public val XML_PREFIX: String = "" + + public val SOURCE_CHARS: Array = arrayOf("\n", "#") + public val XML_REPLACEMENTS: Array = arrayOf("#n", "#diez") +} \ No newline at end of file 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 41b915935d2..0cf7e763c8d 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.impl.ConsoleViewUtil +import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter import com.intellij.openapi.editor.markup.HighlighterLayer @@ -27,13 +28,10 @@ import org.jetbrains.kotlin.console.gutter.ReplIcons public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner ) { fun addAndHighlightNewCommand(command: String) { val historyEditor = runner.consoleView.historyViewer + + addLineBreakIfNeeded(historyEditor) + val historyDocument = historyEditor.document - - if (historyDocument.textLength == 0) { // this will work first time after 'Clear all' action - historyDocument.setText("\n") - runner.addGutterIndicator(historyEditor, ReplIcons.HISTORY_INDICATOR) - } - val oldHistoryLength = historyDocument.textLength historyDocument.insertString(oldHistoryLength, command) EditorUtil.scrollToTheEnd(historyEditor) @@ -61,4 +59,17 @@ public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner ) lexer.advance() } } + + private fun addLineBreakIfNeeded(historyEditor: EditorEx) { + val historyDocument = historyEditor.document + val historyText = historyDocument.text + + if (!historyText.endsWith('\n')) { + val textLength = historyText.length() + historyDocument.insertString(textLength, "\n") + + if (textLength == 0) // this will work first time after 'Clear all' action + runner.addGutterIndicator(historyEditor, ReplIcons.HISTORY_INDICATOR) + } + } } \ No newline at end of file