[cli-repl][ide-console] Show runtime errors in IDE console

This commit is contained in:
Dmitry Kovanikov
2015-08-24 16:31:35 +03:00
committed by Pavel V. Talanov
parent e23441907c
commit a17363459a
6 changed files with 73 additions and 39 deletions
@@ -134,7 +134,7 @@ public class ReplFromTerminal {
((FileHistory) consoleReader.getHistory()).flush(); ((FileHistory) consoleReader.getHistory()).flush();
} }
catch (Exception e) { 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) { else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) {
replWriter.printlnIncomplete(); replWriter.printlnIncomplete();
} }
else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) { else if (lineResult.getType() == ReplInterpreter.LineResultType.COMPILE_ERROR) {
replWriter.printlnError(lineResult.getErrorText()); replWriter.printlnCompileError(lineResult.getErrorText());
}
else if (lineResult.getType() == ReplInterpreter.LineResultType.RUNTIME_ERROR) {
replWriter.printlnRuntimeError(lineResult.getErrorText());
} }
else { else {
throw new IllegalStateException("unknown line result type: " + lineResult); throw new IllegalStateException("unknown line result type: " + lineResult);
@@ -166,7 +166,8 @@ public class ReplInterpreter {
public enum LineResultType { public enum LineResultType {
SUCCESS, SUCCESS,
ERROR, COMPILE_ERROR,
RUNTIME_ERROR,
INCOMPLETE, INCOMPLETE,
} }
@@ -209,11 +210,8 @@ public class ReplInterpreter {
return errorText; return errorText;
} }
public static LineResult successful(Object value, boolean unit) { @NotNull
return new LineResult(value, unit, null, LineResultType.SUCCESS); private static LineResult error(@NotNull String errorText, @NotNull LineResultType errorType) {
}
public static LineResult error(@NotNull String errorText) {
if (errorText.isEmpty()) { if (errorText.isEmpty()) {
errorText = "<unknown error>"; errorText = "<unknown error>";
} }
@@ -221,7 +219,22 @@ public class ReplInterpreter {
errorText += "\n"; 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() { public static LineResult incomplete() {
@@ -265,7 +278,7 @@ public class ReplInterpreter {
previousIncompleteLines.clear(); previousIncompleteLines.clear();
if (syntaxErrorReport.isHasErrors()) { if (syntaxErrorReport.isHasErrors()) {
return LineResult.error(errorHolder.getRenderedDiagnostics()); return LineResult.compileError(errorHolder.getRenderedDiagnostics());
} }
prepareForTheNextReplLine(topDownAnalysisContext); prepareForTheNextReplLine(topDownAnalysisContext);
@@ -276,7 +289,7 @@ public class ReplInterpreter {
ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorHolder); ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorHolder);
if (scriptDescriptor == null) { if (scriptDescriptor == null) {
return LineResult.error(errorHolder.getRenderedDiagnostics()); return LineResult.compileError(errorHolder.getRenderedDiagnostics());
} }
List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList(); List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList();
@@ -314,7 +327,7 @@ public class ReplInterpreter {
scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
} }
catch (Throwable e) { catch (Throwable e) {
return LineResult.error(renderStackTrace(e.getCause())); return LineResult.runtimeError(renderStackTrace(e.getCause()));
} }
Field rvField = scriptClass.getDeclaredField("rv"); Field rvField = scriptClass.getDeclaredField("rv");
rvField.setAccessible(true); rvField.setAccessible(true);
@@ -351,7 +364,9 @@ public class ReplInterpreter {
} }
} }
Collections.reverse(newTrace); Collections.reverse(newTrace);
cause.setStackTrace(newTrace.toArray(new StackTraceElement[newTrace.size()])); List<StackTraceElement> resultingTrace = newTrace.subList(0, newTrace.size() - 1);
cause.setStackTrace(resultingTrace.toArray(new StackTraceElement[resultingTrace.size()]));
return Throwables.getStackTraceAsString(cause); return Throwables.getStackTraceAsString(cause);
} }
@@ -28,7 +28,9 @@ public class ReplSystemOutWrapper(private val standardOut: PrintStream, private
USER_OUTPUT, USER_OUTPUT,
REPL_RESULT, REPL_RESULT,
REPL_INCOMPLETE, REPL_INCOMPLETE,
ERROR COMPILE_ERROR,
RUNTIME_ERROR,
INNER_ERROR
} }
override fun print(x: Boolean) = printWithEscaping(x.toString()) override fun print(x: Boolean) = printWithEscaping(x.toString())
@@ -54,8 +56,10 @@ public class ReplSystemOutWrapper(private val standardOut: PrintStream, private
return "${EscapeConstants.XML_PREAMBLE}<output type=\"$escapeType\">${StringUtil.escapeXml(singleLine)}</output>" return "${EscapeConstants.XML_PREAMBLE}<output type=\"$escapeType\">${StringUtil.escapeXml(singleLine)}</output>"
} }
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 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 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)
} }
@@ -94,7 +94,8 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
val actual = when (lineResult.getType()) { val actual = when (lineResult.getType()) {
ReplInterpreter.LineResultType.SUCCESS -> lineResult.getValue()?.toString() ?: "" 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 ReplInterpreter.LineResultType.INCOMPLETE -> INCOMPLETE_LINE_MESSAGE
} }
@@ -20,6 +20,7 @@ import com.intellij.execution.process.OSProcessHandler
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil 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.console.highlight.KotlinReplOutputHighlighter
import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.diagnostics.Severity
import org.w3c.dom.Element import org.w3c.dom.Element
@@ -50,32 +51,35 @@ public class KotlinReplOutputHandler(
when (outputType) { when (outputType) {
"INITIAL_PROMPT" -> super.notifyTextAvailable("$content\n", key) "INITIAL_PROMPT" -> super.notifyTextAvailable("$content\n", key)
"USER_OUTPUT" -> outputHighlighter.printUserOutput(content) "USER_OUTPUT" -> outputHighlighter.printUserOutput(content)
"REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon("$content\n") "REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon(content)
"REPL_INCOMPLETE" -> outputHighlighter.changeIndicatorOnIncomplete() "REPL_INCOMPLETE" -> outputHighlighter.changeIndicatorOnIncomplete()
"ERROR" -> { "COMPILE_ERROR" -> outputHighlighter.highlightCompilerErrors(createCompilerMessages(content))
val compilerMessages = arrayListOf<SeverityDetails>() "RUNTIME_ERROR" -> outputHighlighter.printRuntimeError(content)
"INNER_ERROR" -> logError(javaClass, content)
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)
} }
} }
private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding))) private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding)))
private fun createCompilerMessages(runtimeErrorsReport: String): List<SeverityDetails> {
val compilerMessages = arrayListOf<SeverityDetails>()
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) { private fun String.toSeverity() = when (this) {
"ERROR" -> Severity.ERROR "ERROR" -> Severity.ERROR
"WARNING" -> Severity.WARNING "WARNING" -> Severity.WARNING
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.console.highlight
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.HighlightInfoType import com.intellij.codeInsight.daemon.impl.HighlightInfoType
import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.lang.annotation.HighlightSeverity import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.colors.CodeInsightColors import com.intellij.openapi.editor.colors.CodeInsightColors
@@ -84,7 +85,7 @@ public class KotlinReplOutputHighlighter(
runner.changeEditorIndicatorIcon(consoleView.consoleEditor, ReplIcons.INCOMPLETE_INDICATOR) runner.changeEditorIndicatorIcon(consoleView.consoleEditor, ReplIcons.INCOMPLETE_INDICATOR)
} }
fun highlightErrors(compilerMessages: List<SeverityDetails>) = WriteCommandAction.runWriteCommandAction(runner.project) { fun highlightCompilerErrors(compilerMessages: List<SeverityDetails>) = WriteCommandAction.runWriteCommandAction(runner.project) {
resetConsoleEditorIndicator() resetConsoleEditorIndicator()
historyManager.lastCommandType = ReplOutputType.ERROR 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 { private fun getAttributesForSeverity(start: Int, end: Int, severity: Severity): TextAttributes {
val attributes = when (severity) { val attributes = when (severity) {
Severity.ERROR -> getAttributesForSeverity(HighlightInfoType.ERROR, HighlightSeverity.ERROR, CodeInsightColors.ERRORS_ATTRIBUTES, start, end) Severity.ERROR -> getAttributesForSeverity(HighlightInfoType.ERROR, HighlightSeverity.ERROR, CodeInsightColors.ERRORS_ATTRIBUTES, start, end)