From a17363459aca41a3becb2e90104cce6fa27387cd Mon Sep 17 00:00:00 2001 From: Dmitry Kovanikov Date: Mon, 24 Aug 2015 16:31:35 +0300 Subject: [PATCH] [cli-repl][ide-console] Show runtime errors in IDE console --- .../kotlin/cli/jvm/repl/ReplFromTerminal.java | 9 ++-- .../kotlin/cli/jvm/repl/ReplInterpreter.java | 37 +++++++++++----- .../jvm/repl/messages/ReplSystemOutWrapper.kt | 10 +++-- .../repl/AbstractReplInterpreterTest.kt | 3 +- .../kotlin/console/KotlinReplOutputHandler.kt | 44 ++++++++++--------- .../highlight/KotlinReplOutputHighlighter.kt | 9 +++- 6 files changed, 73 insertions(+), 39 deletions(-) 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 f44c8d7abd6..ec421fbbaa5 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 @@ -134,7 +134,7 @@ public class ReplFromTerminal { ((FileHistory) consoleReader.getHistory()).flush(); } catch (Exception e) { - System.err.println("failed to flush history: " + e); + replWriter.printlnInnerError("failed to flush history: " + e); } } } @@ -203,8 +203,11 @@ public class ReplFromTerminal { else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) { replWriter.printlnIncomplete(); } - else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) { - replWriter.printlnError(lineResult.getErrorText()); + else if (lineResult.getType() == ReplInterpreter.LineResultType.COMPILE_ERROR) { + replWriter.printlnCompileError(lineResult.getErrorText()); + } + else if (lineResult.getType() == ReplInterpreter.LineResultType.RUNTIME_ERROR) { + replWriter.printlnRuntimeError(lineResult.getErrorText()); } else { throw new IllegalStateException("unknown line result type: " + lineResult); diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.java index 92198b2361c..b8df8742cb6 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.java @@ -166,7 +166,8 @@ public class ReplInterpreter { public enum LineResultType { SUCCESS, - ERROR, + COMPILE_ERROR, + RUNTIME_ERROR, INCOMPLETE, } @@ -209,11 +210,8 @@ public class ReplInterpreter { return errorText; } - public static LineResult successful(Object value, boolean unit) { - return new LineResult(value, unit, null, LineResultType.SUCCESS); - } - - public static LineResult error(@NotNull String errorText) { + @NotNull + private static LineResult error(@NotNull String errorText, @NotNull LineResultType errorType) { if (errorText.isEmpty()) { errorText = ""; } @@ -221,7 +219,22 @@ public class ReplInterpreter { errorText += "\n"; } - return new LineResult(null, false, errorText, LineResultType.ERROR); + return new LineResult(null, false, errorText, errorType); + } + + @NotNull + public static LineResult successful(Object value, boolean unit) { + return new LineResult(value, unit, null, LineResultType.SUCCESS); + } + + @NotNull + public static LineResult compileError(@NotNull String errorText) { + return error(errorText, LineResultType.COMPILE_ERROR); + } + + @NotNull + public static LineResult runtimeError(@NotNull String errorText) { + return error(errorText, LineResultType.RUNTIME_ERROR); } public static LineResult incomplete() { @@ -265,7 +278,7 @@ public class ReplInterpreter { previousIncompleteLines.clear(); if (syntaxErrorReport.isHasErrors()) { - return LineResult.error(errorHolder.getRenderedDiagnostics()); + return LineResult.compileError(errorHolder.getRenderedDiagnostics()); } prepareForTheNextReplLine(topDownAnalysisContext); @@ -276,7 +289,7 @@ public class ReplInterpreter { ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorHolder); if (scriptDescriptor == null) { - return LineResult.error(errorHolder.getRenderedDiagnostics()); + return LineResult.compileError(errorHolder.getRenderedDiagnostics()); } List> earlierScripts = Lists.newArrayList(); @@ -314,7 +327,7 @@ public class ReplInterpreter { scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); } catch (Throwable e) { - return LineResult.error(renderStackTrace(e.getCause())); + return LineResult.runtimeError(renderStackTrace(e.getCause())); } Field rvField = scriptClass.getDeclaredField("rv"); rvField.setAccessible(true); @@ -351,7 +364,9 @@ public class ReplInterpreter { } } Collections.reverse(newTrace); - cause.setStackTrace(newTrace.toArray(new StackTraceElement[newTrace.size()])); + List resultingTrace = newTrace.subList(0, newTrace.size() - 1); + + cause.setStackTrace(resultingTrace.toArray(new StackTraceElement[resultingTrace.size()])); return Throwables.getStackTraceAsString(cause); } 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 8c75f88a6f2..5b2057c8e7f 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 @@ -28,7 +28,9 @@ public class ReplSystemOutWrapper(private val standardOut: PrintStream, private USER_OUTPUT, REPL_RESULT, REPL_INCOMPLETE, - ERROR + COMPILE_ERROR, + RUNTIME_ERROR, + INNER_ERROR } override fun print(x: Boolean) = printWithEscaping(x.toString()) @@ -54,8 +56,10 @@ public class ReplSystemOutWrapper(private val standardOut: PrintStream, private return "${EscapeConstants.XML_PREAMBLE}${StringUtil.escapeXml(singleLine)}" } - fun printlnResult(x: Any?) = printlnWithEscaping(x.toString(), EscapeType.REPL_RESULT) - fun printlnError(x: String) = printlnWithEscaping(x, EscapeType.ERROR) fun printlnInit(x: String) = printlnWithEscaping(x, EscapeType.INITIAL_PROMPT) + fun printlnResult(x: Any?) = printlnWithEscaping(x.toString(), EscapeType.REPL_RESULT) fun printlnIncomplete() = printlnWithEscaping("", EscapeType.REPL_INCOMPLETE) + fun printlnCompileError(x: String) = printlnWithEscaping(x, EscapeType.COMPILE_ERROR) + fun printlnRuntimeError(x: String) = printlnWithEscaping(x, EscapeType.RUNTIME_ERROR) + fun printlnInnerError(x: String) = printlnWithEscaping(x, EscapeType.INNER_ERROR) } \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt index 8c521540c15..3e0dba1109f 100644 --- a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt @@ -94,7 +94,8 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() { val actual = when (lineResult.getType()) { ReplInterpreter.LineResultType.SUCCESS -> lineResult.getValue()?.toString() ?: "" - ReplInterpreter.LineResultType.ERROR -> lineResult.getErrorText() + ReplInterpreter.LineResultType.RUNTIME_ERROR, + ReplInterpreter.LineResultType.COMPILE_ERROR -> lineResult.getErrorText() ReplInterpreter.LineResultType.INCOMPLETE -> INCOMPLETE_LINE_MESSAGE } 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 bedc19eb556..f863c3add2c 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt @@ -20,6 +20,7 @@ import com.intellij.execution.process.OSProcessHandler 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.KotlinReplOutputHighlighter import org.jetbrains.kotlin.diagnostics.Severity import org.w3c.dom.Element @@ -50,32 +51,35 @@ public class KotlinReplOutputHandler( when (outputType) { "INITIAL_PROMPT" -> super.notifyTextAvailable("$content\n", key) "USER_OUTPUT" -> outputHighlighter.printUserOutput(content) - "REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon("$content\n") + "REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon(content) "REPL_INCOMPLETE" -> outputHighlighter.changeIndicatorOnIncomplete() - "ERROR" -> { - val compilerMessages = arrayListOf() - - val report = dBuilder.parse(strToSource(content, Charsets.UTF_16)) - val entries = report.getElementsByTagName("reportEntry") - for (i in 0..entries.length - 1) { - val reportEntry = entries.item(i) as Element - - val severityLevel = reportEntry.getAttribute("severity").toSeverity() - val rangeStart = reportEntry.getAttribute("rangeStart").toInt() - val rangeEnd = reportEntry.getAttribute("rangeEnd").toInt() - val description = reportEntry.textContent - - compilerMessages.add(SeverityDetails(severityLevel, description, TextRange(rangeStart, rangeEnd))) - } - - outputHighlighter.highlightErrors(compilerMessages) - } - else -> super.notifyTextAvailable("UNEXPECTED TEXT: $content\n", key) + "COMPILE_ERROR" -> outputHighlighter.highlightCompilerErrors(createCompilerMessages(content)) + "RUNTIME_ERROR" -> outputHighlighter.printRuntimeError(content) + "INNER_ERROR" -> logError(javaClass, content) } } private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding))) + private fun createCompilerMessages(runtimeErrorsReport: String): List { + val compilerMessages = arrayListOf() + + val report = dBuilder.parse(strToSource(runtimeErrorsReport, Charsets.UTF_16)) + val entries = report.getElementsByTagName("reportEntry") + for (i in 0..entries.length - 1) { + val reportEntry = entries.item(i) as Element + + val severityLevel = reportEntry.getAttribute("severity").toSeverity() + val rangeStart = reportEntry.getAttribute("rangeStart").toInt() + val rangeEnd = reportEntry.getAttribute("rangeEnd").toInt() + val description = reportEntry.textContent + + compilerMessages.add(SeverityDetails(severityLevel, description, TextRange(rangeStart, rangeEnd))) + } + + return compilerMessages + } + private fun String.toSeverity() = when (this) { "ERROR" -> Severity.ERROR "WARNING" -> Severity.WARNING diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt index 4180b75899f..ed2fcc2f66c 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.console.highlight import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfoType +import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.colors.CodeInsightColors @@ -84,7 +85,7 @@ public class KotlinReplOutputHighlighter( runner.changeEditorIndicatorIcon(consoleView.consoleEditor, ReplIcons.INCOMPLETE_INDICATOR) } - fun highlightErrors(compilerMessages: List) = WriteCommandAction.runWriteCommandAction(runner.project) { + fun highlightCompilerErrors(compilerMessages: List) = WriteCommandAction.runWriteCommandAction(runner.project) { resetConsoleEditorIndicator() historyManager.lastCommandType = ReplOutputType.ERROR @@ -112,6 +113,12 @@ public class KotlinReplOutputHighlighter( } } + fun printRuntimeError(errorText: String) { + resetConsoleEditorIndicator() + historyManager.lastCommandType = ReplOutputType.ERROR + consoleView.print("\t$errorText", ConsoleViewContentType.ERROR_OUTPUT) + } + private fun getAttributesForSeverity(start: Int, end: Int, severity: Severity): TextAttributes { val attributes = when (severity) { Severity.ERROR -> getAttributesForSeverity(HighlightInfoType.ERROR, HighlightSeverity.ERROR, CodeInsightColors.ERRORS_ATTRIBUTES, start, end)