diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java index 9bd8fc3f824..a51e610d5b1 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/AnalyzerWithCompilerReport.java @@ -53,7 +53,7 @@ import static org.jetbrains.kotlin.diagnostics.DiagnosticUtils.sortedDiagnostics public final class AnalyzerWithCompilerReport { @NotNull - private static CompilerMessageSeverity convertSeverity(@NotNull Severity severity) { + public static CompilerMessageSeverity convertSeverity(@NotNull Severity severity) { switch (severity) { case INFO: return CompilerMessageSeverity.INFO; @@ -76,7 +76,7 @@ public final class AnalyzerWithCompilerReport { private static boolean reportDiagnostic( @NotNull Diagnostic diagnostic, - @NotNull MessageCollector messageCollector, + @NotNull DiagnosticMessageReporter reporter, boolean incompatibleFilesFound ) { if (!diagnostic.isValid()) return false; @@ -96,11 +96,7 @@ public final class AnalyzerWithCompilerReport { } PsiFile file = diagnostic.getPsiFile(); - messageCollector.report( - convertSeverity(diagnostic.getSeverity()), - render, - MessageUtil.psiFileToMessageLocation(file, file.getName(), DiagnosticUtils.getLineAndColumn(diagnostic)) - ); + reporter.report(diagnostic, file, render); return diagnostic.getSeverity() == Severity.ERROR; } @@ -175,20 +171,16 @@ public final class AnalyzerWithCompilerReport { } } - private static boolean reportDiagnostics( - @NotNull Diagnostics diagnostics, - @NotNull MessageCollector messageCollector, - boolean incompatibleFilesFound - ) { + public static boolean reportDiagnostics(@NotNull Diagnostics diagnostics, @NotNull DiagnosticMessageReporter reporter, boolean incompatibleFilesFound) { boolean hasErrors = false; for (Diagnostic diagnostic : sortedDiagnostics(diagnostics.all())) { - hasErrors |= reportDiagnostic(diagnostic, messageCollector, incompatibleFilesFound); + hasErrors |= reportDiagnostic(diagnostic, reporter, incompatibleFilesFound); } return hasErrors; } - public static boolean reportDiagnostics(@NotNull Diagnostics diagnostics, @NotNull MessageCollector collector) { - return reportDiagnostics(diagnostics, collector, false); + public static boolean reportDiagnostics(@NotNull Diagnostics diagnostics, @NotNull MessageCollector messageCollector, boolean incompatibleFilesFound) { + return reportDiagnostics(diagnostics, new DefaultDiagnosticReporter(messageCollector), incompatibleFilesFound); } private void reportSyntaxErrors(@NotNull Collection files) { @@ -215,14 +207,17 @@ public final class AnalyzerWithCompilerReport { } } - public static SyntaxErrorReport reportSyntaxErrors(@NotNull final PsiElement file, @NotNull final MessageCollector messageCollector) { + public static SyntaxErrorReport reportSyntaxErrors( + @NotNull final PsiElement file, + @NotNull final DiagnosticMessageReporter reporter + ) { class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor { boolean hasErrors = false; boolean allErrorsAtEof = true; private void reportDiagnostic(E element, DiagnosticFactory0 factory, String message) { MyDiagnostic diagnostic = new MyDiagnostic(element, factory, message); - AnalyzerWithCompilerReport.reportDiagnostic(diagnostic, messageCollector, false); + AnalyzerWithCompilerReport.reportDiagnostic(diagnostic, reporter, false); if (element.getTextRange().getStartOffset() != file.getTextRange().getEndOffset()) { allErrorsAtEof = false; } @@ -242,34 +237,8 @@ public final class AnalyzerWithCompilerReport { return new SyntaxErrorReport(visitor.hasErrors, visitor.allErrorsAtEof); } - public static SyntaxErrorReport reportSyntaxErrorsWithDiagnostics(@NotNull final PsiElement file, @NotNull final MessageCollector messageCollector, - @NotNull final List diagnostics) { - class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor { - boolean hasErrors = false; - boolean allErrorsAtEof = true; - - private void reportDiagnostic(E element, DiagnosticFactory0 factory, String message) { - MyDiagnostic diagnostic = new MyDiagnostic(element, factory, message); - AnalyzerWithCompilerReport.reportDiagnostic(diagnostic, messageCollector); - if (element.getTextRange().getStartOffset() != file.getTextRange().getEndOffset()) { - allErrorsAtEof = false; - } - hasErrors = true; - - diagnostics.add(diagnostic); - } - - @Override - public void visitErrorElement(PsiErrorElement element) { - String description = element.getErrorDescription(); - reportDiagnostic(element, SYNTAX_ERROR_FACTORY, StringUtil.isEmpty(description) ? "Syntax error" : description); - } - } - ErrorReportingVisitor visitor = new ErrorReportingVisitor(); - - file.accept(visitor); - - return new SyntaxErrorReport(visitor.hasErrors, visitor.allErrorsAtEof); + public static SyntaxErrorReport reportSyntaxErrors(@NotNull PsiElement file, @NotNull MessageCollector messageCollector) { + return reportSyntaxErrors(file, new DefaultDiagnosticReporter(messageCollector)); } @Nullable diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DefaultDiagnosticReporter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DefaultDiagnosticReporter.kt new file mode 100644 index 00000000000..62b19e547c0 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DefaultDiagnosticReporter.kt @@ -0,0 +1,37 @@ +/* + * 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.common.messages + +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticUtils + +/** + * This class behaviour is the same as [MessageCollector.report] in [AnalyzerWithCompilerReport.reportDiagnostic]. + */ +public class DefaultDiagnosticReporter(override val messageCollector: MessageCollector) : MessageCollectorBasedReporter + +public interface MessageCollectorBasedReporter : DiagnosticMessageReporter { + + val messageCollector: MessageCollector + + override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) = messageCollector.report( + AnalyzerWithCompilerReport.convertSeverity(diagnostic.severity), + render, + MessageUtil.psiFileToMessageLocation(file, file.name, DiagnosticUtils.getLineAndColumn(diagnostic)) + ) +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DiagnosticMessageReporter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DiagnosticMessageReporter.kt new file mode 100644 index 00000000000..aa11bd8c918 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/DiagnosticMessageReporter.kt @@ -0,0 +1,24 @@ +/* + * 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.common.messages + +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.diagnostics.Diagnostic + +public interface DiagnosticMessageReporter { + fun report(diagnostic: Diagnostic, file: PsiFile, render: String) +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageRenderer.java b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageRenderer.java index bf6eb85744c..7a20a714a0e 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageRenderer.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/messages/MessageRenderer.java @@ -26,6 +26,14 @@ public interface MessageRenderer { MessageRenderer XML = new XmlMessageRenderer(); + MessageRenderer WITHOUT_PATHS = new PlainTextMessageRenderer() { + @Nullable + @Override + protected String getPath(@NotNull CompilerMessageLocation location) { + return null; + } + }; + MessageRenderer PLAIN_FULL_PATHS = new PlainTextMessageRenderer() { @Nullable @Override 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 8e89ad0f39f..c997d4e3cb0 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 @@ -23,13 +23,14 @@ import com.intellij.testFramework.LightVirtualFile; import jline.console.ConsoleReader; 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.ReplSystemOutWrapper; import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.utils.UtilsPackage; -import java.io.File; -import java.io.OutputStream; -import java.io.PrintWriter; +import java.io.*; import java.util.Arrays; import java.util.List; @@ -40,18 +41,22 @@ public class ReplFromTerminal { private final Object waitRepl = new Object(); private final boolean ideMode; - private ReplSystemOutWrapper replWriter; + private final ReplSystemOutWrapper replWriter; private final ConsoleReader consoleReader; public ReplFromTerminal( @NotNull final Disposable disposable, - @NotNull final CompilerConfiguration compilerConfiguration) { + @NotNull final CompilerConfiguration compilerConfiguration + ) { + String replIdeMode = System.getProperty("repl.ideMode"); + ideMode = replIdeMode != null && replIdeMode.equals("true"); + new Thread("initialize-repl") { @Override public void run() { try { - replInterpreter = new ReplInterpreter(disposable, compilerConfiguration); + replInterpreter = new ReplInterpreter(disposable, compilerConfiguration, ideMode); } catch (Throwable e) { replInitializationFailed = e; @@ -62,8 +67,11 @@ public class ReplFromTerminal { } }.start(); - String replIdeMode = System.getProperty("repl.ideMode"); - ideMode = replIdeMode != null && replIdeMode.equals("true"); + // wrapper 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(System.out, ideMode); + System.setOut(replWriter); try { OutputStream outStream = System.out; @@ -72,11 +80,6 @@ public class ReplFromTerminal { // to prevent such behaviour we redirect his output to dummy output stream VirtualFile consoleOutputRedirect = new LightVirtualFile("kotlinConsoleReplRedirect"); outStream = consoleOutputRedirect.getOutputStream(null); - - // wrapper is required to escape every input; - // if user calls [System.setOut(...)] then undefined behaviour - replWriter = new ReplSystemOutWrapper(System.out); - System.setOut(replWriter); } consoleReader = new ConsoleReader("kotlin", System.in, outStream, null); @@ -87,8 +90,6 @@ public class ReplFromTerminal { catch (Exception e) { throw UtilsPackage.rethrow(e); } - - getReplInterpreter().setIdeMode(ideMode); } private ReplInterpreter getReplInterpreter() { @@ -113,9 +114,9 @@ public class ReplFromTerminal { private void doRun() { try { - System.out.println("Welcome to Kotlin version " + KotlinVersion.VERSION + + replWriter.println("Welcome to Kotlin version " + KotlinVersion.VERSION + " (JRE " + System.getProperty("java.runtime.version") + ")"); - System.out.println("Type :help for help, :quit for quit"); + replWriter.println("Type :help for help, :quit for quit"); WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE; while (true) { next = one(next); @@ -147,7 +148,7 @@ public class ReplFromTerminal { private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) { try { String prompt = getPrompt(next); - String line = consoleReader.readLine(prompt); + String line = readLine(prompt); if (line == null) { return WhatNextAfterOneLine.QUIT; @@ -171,6 +172,7 @@ public class ReplFromTerminal { } } + @Nullable private String getPrompt(@NotNull WhatNextAfterOneLine next) { String prompt = null; // consoleReader always print prompt; in [ideMode] we don't allow it @@ -180,22 +182,28 @@ public class ReplFromTerminal { return prompt; } + @Nullable + private String readLine(@Nullable String prompt) throws IOException { + String line = consoleReader.readLine(prompt); + if (ideMode && line != null) { + line = FromIdeXmlMessagesParser.parse(line); + } + return line; + } + @NotNull private ReplInterpreter.LineResultType eval(@NotNull String line) { ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line); if (lineResult.getType() == ReplInterpreter.LineResultType.SUCCESS) { if (!lineResult.isUnit()) { - System.out.println(lineResult.getValue()); + replWriter.printlnResult(lineResult.getValue()); } } else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) { + replWriter.printlnIncomplete(); } else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) { - if (ideMode) { - replWriter.printlnWithEscaping(lineResult.getErrorText(), EscapeType.REPORT); - } else { - System.out.print(lineResult.getErrorText()); - } + replWriter.printlnError(lineResult.getErrorText()); } else { throw new IllegalStateException("unknown line result type: " + lineResult); @@ -206,15 +214,15 @@ public class ReplFromTerminal { private boolean oneCommand(@NotNull String command) throws Exception { List split = splitCommand(command); if (split.size() >= 1 && command.equals("help")) { - System.out.println("Available commands:"); - System.out.println(":help show this help"); - System.out.println(":quit exit the interpreter"); - System.out.println(":dump bytecode dump classes to terminal"); - System.out.println(":load load script from specified file"); + replWriter.println("Available commands:"); + replWriter.println(":help show this help"); + replWriter.println(":quit exit the interpreter"); + replWriter.println(":dump bytecode dump classes to terminal"); + replWriter.println(":load load script from specified file"); return true; } else if (split.size() >= 2 && split.get(0).equals("dump") && split.get(1).equals("bytecode")) { - getReplInterpreter().dumpClasses(new PrintWriter(System.out)); + getReplInterpreter().dumpClasses(new PrintWriter(replWriter)); return true; } else if (split.size() >= 1 && split.get(0).equals("quit")) { @@ -227,8 +235,8 @@ public class ReplFromTerminal { return true; } else { - System.out.println("Unknown command"); - System.out.println("Type :help for help"); + replWriter.println("Unknown command"); + replWriter.println("Type :help for help"); return true; } } 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 1c93e80ac1d..92198b2361c 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 @@ -21,8 +21,6 @@ import com.google.common.collect.Lists; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFileFactory; @@ -35,11 +33,15 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.backend.common.output.OutputFile; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; +import org.jetbrains.kotlin.cli.common.messages.DiagnosticMessageReporter; import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.cli.jvm.repl.di.ContainerForReplWithJava; import org.jetbrains.kotlin.cli.jvm.repl.di.DiPackage; +import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder; +import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplIdeDiagnosticMessageHolder; +import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder; import org.jetbrains.kotlin.codegen.ClassBuilderFactories; import org.jetbrains.kotlin.codegen.CompilationErrorHandler; import org.jetbrains.kotlin.codegen.KotlinCodegenFacade; @@ -49,7 +51,6 @@ import org.jetbrains.kotlin.context.MutableModuleContext; import org.jetbrains.kotlin.descriptors.ScriptDescriptor; import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider; import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; -import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.idea.JetLanguage; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.parsing.JetParserDefinition; @@ -68,24 +69,9 @@ import org.jetbrains.kotlin.resolve.scopes.JetScope; import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.utils.UtilsPackage; import org.jetbrains.org.objectweb.asm.Type; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.ls.DOMImplementationLS; -import org.w3c.dom.ls.LSSerializer; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; import java.io.File; import java.io.PrintWriter; -import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.MalformedURLException; @@ -117,9 +103,9 @@ public class ReplInterpreter { private final ResolveSession resolveSession; private final ScriptMutableDeclarationProviderFactory scriptDeclarationFactory; - private boolean ideMode; + private final boolean ideMode; - public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) { + public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration, boolean ideMode) { KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES); Project project = environment.getProject(); @@ -170,27 +156,20 @@ public class ReplInterpreter { } this.classLoader = new ReplClassLoader(new URLClassLoader(classpath.toArray(new URL[classpath.size()]), null)); + + this.ideMode = ideMode; } private static void prepareForTheNextReplLine(@NotNull TopDownAnalysisContext c) { c.getScripts().clear(); } - public void setIdeMode(boolean ideMode) { - this.ideMode = ideMode; - } - public enum LineResultType { SUCCESS, ERROR, INCOMPLETE, } - private enum ErrorType { - CODE_ERROR, - THROWABLE - } - public static class LineResult { private final Object value; private final boolean unit; @@ -234,82 +213,28 @@ public class ReplInterpreter { return new LineResult(value, unit, null, LineResultType.SUCCESS); } - public static LineResult error(@NotNull String errorText, boolean ideMode, - @NotNull ErrorType errType, @Nullable Collection diagnostics - ) { + public static LineResult error(@NotNull String errorText) { if (errorText.isEmpty()) { errorText = ""; } - - String[] lines = errorTextToLinesWithPreprocessing(errorText); - - if (ideMode && errType == ErrorType.CODE_ERROR) { - assert diagnostics != null : "diagnostics should not be null in not-throwable case"; - errorText = convertLinesToXML(lines, diagnostics); - } - else { - errorText = StringUtil.join(lines, "\n"); - } - - if (!errorText.endsWith("\n")) { + else if (!errorText.endsWith("\n")) { errorText += "\n"; } return new LineResult(null, false, errorText, LineResultType.ERROR); } - private static String[] errorTextToLinesWithPreprocessing(@NotNull String errorText) { - // cut "/line_i.kts:row:column" prefix and capitalize "error:" and "warning:" - String[] lines = errorText.split("\\n"); - for (int i = 0; i < lines.length; i++) { - if (lines[i].contains("error:")) { - int pos = lines[i].indexOf("error:"); - lines[i] = "E" + lines[i].substring(pos + 1); - } - else if (lines[i].contains("warning:")) { - int pos = lines[i].indexOf("warning:"); - lines[i] = "W" + lines[i].substring(pos + 1); - } - } - return lines; - } - - private static String convertLinesToXML(@NotNull String[] lines, @NotNull Collection diagnostics) { - try { - DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); - Document errorReport = docBuilder.newDocument(); - - Element rootElement = errorReport.createElement("report"); - errorReport.appendChild(rootElement); - - Iterator di = diagnostics.iterator(); - for (String s : lines) { - if (s.startsWith("Error:") || s.startsWith("Warning:")) { - TextRange errorRange = di.hasNext() ? di.next().getTextRanges().get(0) : TextRange.EMPTY_RANGE; - - Element reportEntry = errorReport.createElement("reportEntry"); - reportEntry.setAttribute("rangeStart", String.valueOf(errorRange.getStartOffset())); - reportEntry.setAttribute("rangeEnd", String.valueOf(errorRange.getEndOffset())); - reportEntry.appendChild(errorReport.createTextNode(StringUtil.escapeXml(s))); - - rootElement.appendChild(reportEntry); - } - } - - DOMImplementationLS domImplementation = (DOMImplementationLS) errorReport.getImplementation(); - LSSerializer lsSerializer = domImplementation.createLSSerializer(); - return lsSerializer.writeToString(errorReport); - } catch (ParserConfigurationException e) { - throw UtilsPackage.rethrow(e); - } - } - public static LineResult incomplete() { return new LineResult(null, false, null, LineResultType.INCOMPLETE); } } + @NotNull + private DiagnosticMessageHolder createDiagnosticHolder() { + return ideMode ? new ReplIdeDiagnosticMessageHolder() + : new ReplTerminalDiagnosticMessageHolder(); + } + @NotNull public LineResult eval(@NotNull String line) { ++lineNumber; @@ -328,11 +253,9 @@ public class ReplInterpreter { JetFile psiFile = (JetFile) psiFileFactory.trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText; - ReplMessageCollectorWrapper errorCollector = new ReplMessageCollectorWrapper(); - List syntaxDiagnostics = new ArrayList(); + DiagnosticMessageHolder errorHolder = createDiagnosticHolder(); - AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = - AnalyzerWithCompilerReport.reportSyntaxErrorsWithDiagnostics(psiFile, errorCollector.getMessageCollector(), syntaxDiagnostics); + AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder); if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) { previousIncompleteLines.add(line); @@ -342,7 +265,7 @@ public class ReplInterpreter { previousIncompleteLines.clear(); if (syntaxErrorReport.isHasErrors()) { - return LineResult.error(errorCollector.getString(), ideMode, ErrorType.CODE_ERROR, syntaxDiagnostics); + return LineResult.error(errorHolder.getRenderedDiagnostics()); } prepareForTheNextReplLine(topDownAnalysisContext); @@ -351,9 +274,9 @@ public class ReplInterpreter { //noinspection ConstantConditions psiFile.getScript().putUserData(ScriptPriorities.PRIORITY_KEY, lineNumber); - ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector); + ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorHolder); if (scriptDescriptor == null) { - return LineResult.error(errorCollector.getString(), ideMode, ErrorType.CODE_ERROR, trace.getBindingContext().getDiagnostics().all()); + return LineResult.error(errorHolder.getRenderedDiagnostics()); } List> earlierScripts = Lists.newArrayList(); @@ -391,7 +314,7 @@ public class ReplInterpreter { scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs); } catch (Throwable e) { - return LineResult.error(renderStackTrace(e.getCause()), ideMode, ErrorType.THROWABLE, null); + return LineResult.error(renderStackTrace(e.getCause())); } Field rvField = scriptClass.getDeclaredField("rv"); rvField.setAccessible(true); @@ -433,7 +356,7 @@ public class ReplInterpreter { } @Nullable - private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull ReplMessageCollectorWrapper messageCollector) { + private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull DiagnosticMessageReporter errorReporter) { scriptDeclarationFactory.setDelegateFactory( new FileBasedDeclarationProviderFactory(resolveSession.getStorageManager(), Collections.singletonList(psiFile))); @@ -446,9 +369,7 @@ public class ReplInterpreter { trace.record(BindingContext.FILE_TO_PACKAGE_FRAGMENT, psiFile, resolveSession.getPackageFragment(FqName.ROOT)); } - boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics( - trace.getBindingContext().getDiagnostics(), messageCollector.getMessageCollector() - ); + boolean hasErrors = AnalyzerWithCompilerReport.reportDiagnostics(trace.getBindingContext().getDiagnostics(), errorReporter); if (hasErrors) { return null; } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplMessageCollectorWrapper.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplMessageCollectorWrapper.java deleted file mode 100644 index 444b85c3b4e..00000000000 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplMessageCollectorWrapper.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.cli.common.messages.MessageCollector; -import org.jetbrains.kotlin.cli.common.messages.MessageRenderer; -import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -public class ReplMessageCollectorWrapper { - private static final Charset UTF8 = Charset.forName("utf-8"); - - private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - private final MessageCollector actualCollector = - new PrintingMessageCollector(new PrintStream(outputStream), MessageRenderer.PLAIN_FULL_PATHS, false); - - @NotNull - public MessageCollector getMessageCollector() { - return actualCollector; - } - - @NotNull - public String getString() { - return UTF8.decode(ByteBuffer.wrap(outputStream.toByteArray())).toString(); - } -} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplSystemOutWrapper.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplSystemOutWrapper.kt deleted file mode 100644 index 146f8892902..00000000000 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplSystemOutWrapper.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 - -import com.intellij.openapi.util.text.StringUtil -import java.io.PrintStream - -enum class EscapeType { - ORDINARY, REPORT -} - -public class ReplSystemOutWrapper(private val standardOut: PrintStream) : PrintStream(standardOut, true) { - private val XML_PREFIX = "" - - private fun xmlEscape(s: String, escapeType: EscapeType): String { - val singleLine = StringUtil.escapeLineBreak(s) - return "$XML_PREFIX${StringUtil.escapeXml(singleLine)}" - } - - override fun print(x: Any) = printWithEscaping(x.toString()) - private fun printWithEscaping(text: String, escapeType: EscapeType = EscapeType.ORDINARY) = super.print(xmlEscape(text, escapeType)) - - override fun println(x: String) = printlnWithEscaping(x) - override fun println(x: Any) = printlnWithEscaping(x.toString()) - public fun printlnWithEscaping(text: String, escapeType: EscapeType = EscapeType.ORDINARY): Unit = super.println(xmlEscape(text, escapeType)) -} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/DiagnosticMessageHolder.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/DiagnosticMessageHolder.kt new file mode 100644 index 00000000000..c8eacaa3c8b --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/DiagnosticMessageHolder.kt @@ -0,0 +1,23 @@ +/* + * 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 + +import org.jetbrains.kotlin.cli.common.messages.DiagnosticMessageReporter + +public interface DiagnosticMessageHolder : DiagnosticMessageReporter { + val renderedDiagnostics: String +} \ 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/FromIdeXmlMessagesParser.kt new file mode 100644 index 00000000000..d425cdd8e57 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/FromIdeXmlMessagesParser.kt @@ -0,0 +1,39 @@ +/* + * 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 + +import com.intellij.openapi.util.text.StringUtil +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 { + private fun strToSource(s: String) = InputSource(ByteArrayInputStream(s.toByteArray())) + + platformStatic 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() + + return inputContent + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..c3cd1d501f0 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplIdeDiagnosticMessageHolder.kt @@ -0,0 +1,58 @@ +/* + * 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 + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticUtils +import org.w3c.dom.ls.DOMImplementationLS +import javax.xml.parsers.DocumentBuilderFactory + +public class ReplIdeDiagnosticMessageHolder : DiagnosticMessageHolder { + private val diagnostics = arrayListOf>() + + override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) { + diagnostics.add(diagnostic to render) + } + + override val renderedDiagnostics: String + get() { + val docFactory = DocumentBuilderFactory.newInstance() + val docBuilder = docFactory.newDocumentBuilder() + val errorReport = docBuilder.newDocument() + + val rootElement = errorReport.createElement("report") + errorReport.appendChild(rootElement) + + for ((diagnostic, message) in diagnostics) { + val errorRange = DiagnosticUtils.firstRange(diagnostic.textRanges) + + val reportEntry = errorReport.createElement("reportEntry") + reportEntry.setAttribute("severity", diagnostic.severity.toString()) + reportEntry.setAttribute("rangeStart", errorRange.startOffset.toString()) + reportEntry.setAttribute("rangeEnd", errorRange.endOffset.toString()) + reportEntry.appendChild(errorReport.createTextNode(StringUtil.escapeXml(message))) + + rootElement.appendChild(reportEntry) + } + + val domImplementation = errorReport.implementation as DOMImplementationLS + val lsSerializer = domImplementation.createLSSerializer() + return lsSerializer.writeToString(errorReport) + } +} 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 new file mode 100644 index 00000000000..335aa119c34 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplSystemOutWrapper.kt @@ -0,0 +1,62 @@ +/* + * 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 + +import com.intellij.openapi.util.text.StringUtil +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 { + INITIAL_PROMPT, + USER_OUTPUT, + REPL_RESULT, + REPL_INCOMPLETE, + ERROR + } + + override fun print(x: Boolean) = printWithEscaping(x.toString()) + override fun print(x: Char) = printWithEscaping(x.toString()) + override fun print(x: Int) = printWithEscaping(x.toString()) + override fun print(x: Long) = printWithEscaping(x.toString()) + override fun print(x: Float) = printWithEscaping(x.toString()) + override fun print(x: Double) = printWithEscaping(x.toString()) + override fun print(x: String) = printWithEscaping(x) + override fun print(x: Any?) = printWithEscaping(x.toString()) + + private fun printlnWithEscaping(text: String, escapeType: EscapeType = EscapeType.USER_OUTPUT) = printWithEscaping("$text$END_LINE", escapeType) + + private fun printWithEscaping(text: String, escapeType: EscapeType = EscapeType.USER_OUTPUT) { + if (ideMode) + super.print("${xmlEscape(text, escapeType)}$END_LINE") + else + super.print(text) + } + + private fun xmlEscape(s: String, escapeType: EscapeType): String { + val singleLine = StringUtil.escapeLineBreak(s) + return "$XML_PREFIX${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 printlnIncomplete() = printlnWithEscaping("", EscapeType.REPL_INCOMPLETE) +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplTerminalDiagnosticMessageHolder.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplTerminalDiagnosticMessageHolder.kt new file mode 100644 index 00000000000..2fff57e610c --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplTerminalDiagnosticMessageHolder.kt @@ -0,0 +1,30 @@ +/* + * 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 + +import org.jetbrains.kotlin.cli.common.messages.* +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import java.nio.ByteBuffer + +public class ReplTerminalDiagnosticMessageHolder() : MessageCollectorBasedReporter, DiagnosticMessageHolder { + private val outputStream = ByteArrayOutputStream() + override val messageCollector = PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.WITHOUT_PATHS, false) + + override val renderedDiagnostics: String + get() = Charsets.UTF_8.decode(ByteBuffer.wrap(outputStream.toByteArray())).toString() +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java index d207192ea0d..c9827c98462 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticUtils.java @@ -157,7 +157,7 @@ public class DiagnosticUtils { } @NotNull - private static TextRange firstRange(@NotNull List ranges) { + public static TextRange firstRange(@NotNull List ranges) { return Collections.min(ranges, TEXT_RANGE_COMPARATOR); } diff --git a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt index 35ac6d94312..8c521540c15 100644 --- a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt @@ -83,7 +83,7 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() { protected fun doTest(path: String) { val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK) - val repl = ReplInterpreter(getTestRootDisposable()!!, configuration) + val repl = ReplInterpreter(getTestRootDisposable()!!, configuration, false) for ((code, expected) in loadLines(File(path))) { val lineResult = repl.eval(code) 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 704e8172c31..163b62e1474 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt @@ -32,12 +32,13 @@ public class KotlinReplOutputHandler( process: Process, commandLine: String ) : OSProcessHandler(process, commandLine) { + private val XML_START = " = ConcurrentLinkedQueue() override fun notifyTextAvailable(text: String, key: Key<*>?) { // skip "/usr/lib/jvm/java-8-oracle/bin/java -cp ..." intro - if (!text.startsWith("