Permalink
Browse files

Add kotlin console repl

  • Loading branch information...
1 parent 80cbee8 commit a11f411f7de59f1b8da039d009d835b1985f2d3b @ChShersh ChShersh committed with pTalanov Jul 27, 2015
@@ -46,6 +46,7 @@
<element id="module-output" name="container" />
<element id="module-output" name="rmi-interface" />
<element id="module-output" name="kotlinr" />
+ <element id="module-output" name="idea-repl" />
</element>
<element id="library" level="project" name="javax.inject" />
<element id="directory" name="jps">
View
@@ -36,6 +36,7 @@
<module fileurl="file://$PROJECT_DIR$/idea/idea-core/idea-core.iml" filepath="$PROJECT_DIR$/idea/idea-core/idea-core.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea/idea-jps-common/idea-jps-common.iml" filepath="$PROJECT_DIR$/idea/idea-jps-common/idea-jps-common.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea/idea-js/idea-js.iml" filepath="$PROJECT_DIR$/idea/idea-js/idea-js.iml" group="ide" />
+ <module fileurl="file://$PROJECT_DIR$/idea/idea-repl/idea-repl.iml" filepath="$PROJECT_DIR$/idea/idea-repl/idea-repl.iml" />
<module fileurl="file://$PROJECT_DIR$/idea-runner/idea-runner.iml" filepath="$PROJECT_DIR$/idea-runner/idea-runner.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea/idea-test-framework/idea-test-framework.iml" filepath="$PROJECT_DIR$/idea/idea-test-framework/idea-test-framework.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/compiler/preloader/instrumentation/instrumentation.iml" filepath="$PROJECT_DIR$/compiler/preloader/instrumentation/instrumentation.iml" group="compiler/cli" />
View
@@ -47,4 +47,4 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
-</module>
+</module>
@@ -242,6 +242,36 @@ public void visitErrorElement(PsiErrorElement element) {
return new SyntaxErrorReport(visitor.hasErrors, visitor.allErrorsAtEof);
}
+ public static SyntaxErrorReport reportSyntaxErrorsWithDiagnostics(@NotNull final PsiElement file, @NotNull final MessageCollector messageCollector,
+ @NotNull final List<Diagnostic> diagnostics) {
+ class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor {
+ boolean hasErrors = false;
+ boolean allErrorsAtEof = true;
+
+ private <E extends PsiElement> void reportDiagnostic(E element, DiagnosticFactory0<E> factory, String message) {
+ MyDiagnostic<?> diagnostic = new MyDiagnostic<E>(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);
+ }
+
@Nullable
public AnalysisResult getAnalysisResult() {
return analysisResult;
@@ -18,6 +18,8 @@
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.testFramework.LightVirtualFile;
import jline.console.ConsoleReader;
import jline.console.history.FileHistory;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +28,7 @@
import org.jetbrains.kotlin.utils.UtilsPackage;
import java.io.File;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
@@ -36,6 +39,9 @@
private Throwable replInitializationFailed;
private final Object waitRepl = new Object();
+ private final boolean ideMode;
+ private ReplSystemOutWrapper replWriter;
+
private final ConsoleReader consoleReader;
public ReplFromTerminal(
@@ -56,15 +62,33 @@ public void run() {
}
}.start();
+ String replIdeMode = System.getProperty("repl.ideMode");
+ ideMode = replIdeMode != null && replIdeMode.equals("true");
+
try {
- consoleReader = new ConsoleReader("kotlin", System.in, System.out, null);
+ OutputStream outStream = System.out;
+ if (ideMode) {
+ // consoleReader duplicates his input in his output stream;
+ // 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);
consoleReader.setHistoryEnabled(true);
consoleReader.setExpandEvents(false);
consoleReader.setHistory(new FileHistory(new File(new File(System.getProperty("user.home")), ".kotlin_history")));
}
catch (Exception e) {
throw UtilsPackage.rethrow(e);
}
+
+ getReplInterpreter().setIdeMode(ideMode);
}
private ReplInterpreter getReplInterpreter() {
@@ -122,7 +146,9 @@ private void doRun() {
@NotNull
private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) {
try {
- String line = consoleReader.readLine(next == WhatNextAfterOneLine.INCOMPLETE ? "... " : ">>> ");
+ String prompt = getPrompt(next);
+ String line = consoleReader.readLine(prompt);
+
if (line == null) {
return WhatNextAfterOneLine.QUIT;
}
@@ -145,6 +171,15 @@ private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) {
}
}
+ private String getPrompt(@NotNull WhatNextAfterOneLine next) {
+ String prompt = null;
+ // consoleReader always print prompt; in [ideMode] we don't allow it
+ if (!ideMode) {
+ prompt = next == WhatNextAfterOneLine.INCOMPLETE ? "... " : ">>> ";
+ }
+ return prompt;
+ }
+
@NotNull
private ReplInterpreter.LineResultType eval(@NotNull String line) {
ReplInterpreter.LineResult lineResult = getReplInterpreter().eval(line);
@@ -156,7 +191,11 @@ private WhatNextAfterOneLine one(@NotNull WhatNextAfterOneLine next) {
else if (lineResult.getType() == ReplInterpreter.LineResultType.INCOMPLETE) {
}
else if (lineResult.getType() == ReplInterpreter.LineResultType.ERROR) {
- System.out.print(lineResult.getErrorText());
+ if (ideMode) {
+ replWriter.printlnWithEscaping(lineResult.getErrorText(), EscapeType.REPORT);
+ } else {
+ System.out.print(lineResult.getErrorText());
+ }
}
else {
throw new IllegalStateException("unknown line result type: " + lineResult);
@@ -21,6 +21,8 @@
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;
@@ -47,6 +49,7 @@
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;
@@ -65,18 +68,30 @@
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;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.getJvmClasspathRoots;
import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.getModuleName;
@@ -102,6 +117,8 @@
private final ResolveSession resolveSession;
private final ScriptMutableDeclarationProviderFactory scriptDeclarationFactory;
+ private boolean ideMode;
+
public ReplInterpreter(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
KotlinCoreEnvironment environment =
KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
@@ -159,12 +176,21 @@ 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;
@@ -208,16 +234,77 @@ public static LineResult successful(Object value, boolean unit) {
return new LineResult(value, unit, null, LineResultType.SUCCESS);
}
- public static LineResult error(@NotNull String errorText) {
+ public static LineResult error(@NotNull String errorText, boolean ideMode,
+ @NotNull ErrorType errType, @Nullable Collection<Diagnostic> diagnostics
+ ) {
if (errorText.isEmpty()) {
errorText = "<unknown error>";
}
- else if (!errorText.endsWith("\n")) {
+
+ 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")) {
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<Diagnostic> diagnostics) {
+ try {
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+ Document errorReport = docBuilder.newDocument();
+
+ Element rootElement = errorReport.createElement("report");
+ errorReport.appendChild(rootElement);
+
+ Iterator<Diagnostic> 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);
}
@@ -242,9 +329,10 @@ public LineResult eval(@NotNull String line) {
assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText;
ReplMessageCollectorWrapper errorCollector = new ReplMessageCollectorWrapper();
+ List<Diagnostic> syntaxDiagnostics = new ArrayList<Diagnostic>();
AnalyzerWithCompilerReport.SyntaxErrorReport syntaxErrorReport =
- AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorCollector.getMessageCollector());
+ AnalyzerWithCompilerReport.reportSyntaxErrorsWithDiagnostics(psiFile, errorCollector.getMessageCollector(), syntaxDiagnostics);
if (syntaxErrorReport.isHasErrors() && syntaxErrorReport.isAllErrorsAtEof()) {
previousIncompleteLines.add(line);
@@ -254,7 +342,7 @@ public LineResult eval(@NotNull String line) {
previousIncompleteLines.clear();
if (syntaxErrorReport.isHasErrors()) {
- return LineResult.error(errorCollector.getString());
+ return LineResult.error(errorCollector.getString(), ideMode, ErrorType.CODE_ERROR, syntaxDiagnostics);
}
prepareForTheNextReplLine(topDownAnalysisContext);
@@ -265,7 +353,7 @@ public LineResult eval(@NotNull String line) {
ScriptDescriptor scriptDescriptor = doAnalyze(psiFile, errorCollector);
if (scriptDescriptor == null) {
- return LineResult.error(errorCollector.getString());
+ return LineResult.error(errorCollector.getString(), ideMode, ErrorType.CODE_ERROR, trace.getBindingContext().getDiagnostics().all());
}
List<Pair<ScriptDescriptor, Type>> earlierScripts = Lists.newArrayList();
@@ -303,7 +391,7 @@ public LineResult eval(@NotNull String line) {
scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
}
catch (Throwable e) {
- return LineResult.error(renderStackTrace(e.getCause()));
+ return LineResult.error(renderStackTrace(e.getCause()), ideMode, ErrorType.THROWABLE, null);
}
Field rvField = scriptClass.getDeclaredField("rv");
rvField.setAccessible(true);
Oops, something went wrong.

3 comments on commit a11f411

@udalov
Member
udalov commented on a11f411 Sep 21, 2015

There's a lot of unnecessary changes to plugin.xml in this commit, please be careful the next time 👹

@pTalanov
Member

Sorry, do you think I should revert this?

@udalov
Member
udalov commented on a11f411 Sep 22, 2015

No I don't, that's why I said "the next time"

Please sign in to comment.