From a11f411f7de59f1b8da039d009d835b1985f2d3b Mon Sep 17 00:00:00 2001 From: Dmitry Kovanikov Date: Mon, 27 Jul 2015 17:58:38 +0300 Subject: [PATCH] Add kotlin console repl --- .idea/artifacts/KotlinPlugin.xml | 1 + .idea/modules.xml | 1 + Kotlin.iml | 2 +- .../messages/AnalyzerWithCompilerReport.java | 30 ++ .../kotlin/cli/jvm/repl/ReplFromTerminal.java | 45 ++- .../kotlin/cli/jvm/repl/ReplInterpreter.java | 108 ++++++- .../cli/jvm/repl/ReplSystemOutWrapper.kt | 40 +++ idea/idea-repl/idea-repl.iml | 14 + .../kotlin/console/KotlinConsoleKeeper.kt | 108 +++++++ .../kotlin/console/KotlinConsoleRunner.kt | 163 +++++++++++ .../kotlin/console/KotlinReplOutputHandler.kt | 65 +++++ .../console/KotlinReplResultHighlighter.kt | 101 +++++++ .../kotlin/console/KtConsoleKeyListener.kt | 88 ++++++ .../console/actions/KtExecuteCommandAction.kt | 44 +++ .../console/actions/RunKotlinConsoleAction.kt | 62 ++++ idea/idea.iml | 1 + idea/src/META-INF/plugin.xml | 269 +++++++++--------- .../kotlin/idea/console/KotlinReplTest.kt | 91 ++++++ 18 files changed, 1092 insertions(+), 141 deletions(-) create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplSystemOutWrapper.kt create mode 100644 idea/idea-repl/idea-repl.iml create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplResultHighlighter.kt create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/console/KtConsoleKeyListener.kt create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/console/actions/KtExecuteCommandAction.kt create mode 100644 idea/idea-repl/src/org/jetbrains/kotlin/console/actions/RunKotlinConsoleAction.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/console/KotlinReplTest.kt diff --git a/.idea/artifacts/KotlinPlugin.xml b/.idea/artifacts/KotlinPlugin.xml index 1ff79edf65e..ccd66228da9 100644 --- a/.idea/artifacts/KotlinPlugin.xml +++ b/.idea/artifacts/KotlinPlugin.xml @@ -46,6 +46,7 @@ + diff --git a/.idea/modules.xml b/.idea/modules.xml index ef2bbf68e47..57d3de81d84 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -36,6 +36,7 @@ + diff --git a/Kotlin.iml b/Kotlin.iml index e54fb89ad69..9d06698efe3 100644 --- a/Kotlin.iml +++ b/Kotlin.iml @@ -47,4 +47,4 @@ - + \ No newline at end of file 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 1379d341a20..9bd8fc3f824 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 @@ -242,6 +242,36 @@ 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); + } + @Nullable public AnalysisResult getAnalysisResult() { return analysisResult; 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 f81eb14a929..8e89ad0f39f 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 @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.cli.jvm.repl; 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.config.CompilerConfiguration; 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 @@ public class ReplFromTerminal { private Throwable replInitializationFailed; private final Object waitRepl = new Object(); + private final boolean ideMode; + private ReplSystemOutWrapper replWriter; + private final ConsoleReader consoleReader; public ReplFromTerminal( @@ -56,8 +62,24 @@ public class ReplFromTerminal { } }.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"))); @@ -65,6 +87,8 @@ public class ReplFromTerminal { catch (Exception e) { throw UtilsPackage.rethrow(e); } + + getReplInterpreter().setIdeMode(ideMode); } private ReplInterpreter getReplInterpreter() { @@ -122,7 +146,9 @@ public class ReplFromTerminal { @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 @@ public class ReplFromTerminal { } } + 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 @@ public class ReplFromTerminal { 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); 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 8c792cd11a1..1c93e80ac1d 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,6 +21,8 @@ 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; @@ -47,6 +49,7 @@ 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; @@ -65,18 +68,30 @@ 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; 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 @@ public class ReplInterpreter { 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 @@ public class ReplInterpreter { 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 class ReplInterpreter { 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 diagnostics + ) { if (errorText.isEmpty()) { errorText = ""; } - 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 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); } @@ -242,9 +329,10 @@ public class ReplInterpreter { assert psiFile != null : "Script file not analyzed at line " + lineNumber + ": " + fullText; ReplMessageCollectorWrapper errorCollector = new ReplMessageCollectorWrapper(); + List syntaxDiagnostics = new ArrayList(); 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 class ReplInterpreter { 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 class ReplInterpreter { 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> earlierScripts = Lists.newArrayList(); @@ -303,7 +391,7 @@ public class ReplInterpreter { 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); 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 new file mode 100644 index 00000000000..146f8892902 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplSystemOutWrapper.kt @@ -0,0 +1,40 @@ +/* + * 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/idea/idea-repl/idea-repl.iml b/idea/idea-repl/idea-repl.iml new file mode 100644 index 00000000000..bf72f114c54 --- /dev/null +++ b/idea/idea-repl/idea-repl.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt new file mode 100644 index 00000000000..04c2a0767d1 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt @@ -0,0 +1,108 @@ +/* + * 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 + +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.configurations.JavaParameters +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.JavaSdkType +import com.intellij.openapi.projectRoots.JdkUtil +import com.intellij.openapi.projectRoots.SimpleJavaSdkType +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.SystemProperties +import org.jetbrains.kotlin.console.actions.errorNotification +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import kotlin.platform.platformStatic + +public class KotlinConsoleKeeper(val project: Project) { + private val consoleMap: MutableMap = ConcurrentHashMap() + + fun getConsoleByVirtualFile(virtualFile: VirtualFile) = consoleMap.get(virtualFile) + fun putVirtualFileToConsole(virtualFile: VirtualFile, console: KotlinConsoleRunner) = consoleMap.put(virtualFile, console) + fun removeConsole(virtualFile: VirtualFile?) = consoleMap.remove(virtualFile) + + fun run(module: Module): KotlinConsoleRunner? { + val path = module.moduleFilePath + val cmdLine = createCommandLine(module) + if (cmdLine == null) { + errorNotification(project, "

Module SDK not found

") + return null + } + + val consoleRunner = KotlinConsoleRunner(path, cmdLine, project, REPL_TITLE) + consoleRunner.initAndRun() + consoleRunner.setupGutters() + + return consoleRunner + } + + private fun createCommandLine(module: Module): GeneralCommandLine? { + val javaParameters = createJavaParametersWithSdk(module) + val sdk = javaParameters.jdk ?: return null + val sdkType = sdk.sdkType + val exePath = (sdkType as JavaSdkType).getVMExecutablePath(sdk) + + val commandLine = JdkUtil.setupJVMCommandLine(exePath, javaParameters, true) + + // set parameters to run compiler + val paramList = commandLine.parametersList + paramList.clearAll() + + // use to debug repl process + //paramList.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005") + + // set classpath for process to compiler + paramList.add("-cp") + paramList.add("${PathUtil.getKotlinPathsForIdeaPlugin().libPath.absolutePath}/*") + + // set param to prevent process from repeating input backwards + paramList.add("-Drepl.ideMode=true") + + // path to compiler and his options + paramList.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler") + + return commandLine + } + + private fun createJavaParametersWithSdk(module: Module?): JavaParameters { + val params = JavaParameters() + params.charset = null + + if (module != null) { + val sdk = ModuleRootManager.getInstance(module).sdk + if (sdk != null && sdk.sdkType is JavaSdkType && File(sdk.homePath).exists()) { + params.jdk = sdk + } + } + if (params.jdk == null) { + params.jdk = SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome()) + } + + return params + } + + companion object { + private val REPL_TITLE = "Kotlin REPL" + + platformStatic fun getInstance(project: Project) = ServiceManager.getService(project, javaClass()) + } +} \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt new file mode 100644 index 00000000000..598ee808ad5 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt @@ -0,0 +1,163 @@ +/* + * 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 + +import com.intellij.execution.Executor +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.console.* +import com.intellij.execution.process.* +import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory +import com.intellij.execution.ui.RunContentDescriptor +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.editor.event.DocumentAdapter +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.editor.ex.EditorEx +import com.intellij.openapi.editor.markup.* +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction +import org.jetbrains.kotlin.console.actions.logError +import org.jetbrains.kotlin.idea.JetLanguage +import java.awt.Color +import javax.swing.Icon +import kotlin.properties.Delegates + +public class KotlinConsoleRunner( + val title: String, + private val cmdLine: GeneralCommandLine, + myProject: Project, + path: String? +) : AbstractConsoleRunnerWithHistory(myProject, title, path) { + companion object { + private val HISTORY_GUTTER_ICON = AllIcons.Debugger.Console + private val EDITOR_GUTTER_ICON = AllIcons.Debugger.CommandLine + } + + private val keyEventListener = KtConsoleKeyListener(this) + private var historyHighlighter: KotlinReplResultHighlighter by Delegates.notNull() + val history: MutableList = arrayListOf() + + override fun createProcess() = cmdLine.createProcess() + + override fun createConsoleView(): LanguageConsoleView? { + val consoleView = LanguageConsoleBuilder().build(project, JetLanguage.INSTANCE) + consoleView.prompt = null + + val historyEditor = consoleView.historyViewer + val consoleEditor = consoleView.consoleEditor + consoleEditor.contentComponent.addKeyListener(keyEventListener) + + historyEditor.document.addDocumentListener(object : DocumentAdapter() { + override fun documentChanged(e: DocumentEvent): Unit = + if (historyEditor.document.textLength == 0) addGutterIcon(historyEditor, HISTORY_GUTTER_ICON) + }) + + historyHighlighter = KotlinReplResultHighlighter(historyEditor) + historyEditor.document.addDocumentListener(historyHighlighter) + + consoleEditor.setPlaceholder(" to execute") + consoleEditor.setShowPlaceholderWhenFocused(true) + val placeholderAttrs = consoleEditor.foldingModel.placeholderAttributes + placeholderAttrs.foregroundColor = Color.GRAY + + val executeAction = KtExecuteCommandAction(consoleView.virtualFile) + executeAction.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, consoleView.consoleEditor.component) + + return consoleView + } + + override fun createProcessHandler(process: Process): OSProcessHandler { + val processHandler = KotlinReplOutputHandler(process, cmdLine.commandLineString) + historyHighlighter.rangeQueue = processHandler.rangeQueue + val consoleFile = consoleView.virtualFile + val keeper = KotlinConsoleKeeper.getInstance(project) + + keeper.putVirtualFileToConsole(consoleFile, this) + processHandler.addProcessListener(object : ProcessAdapter() { + override fun processTerminated(event: ProcessEvent) { + keeper.removeConsole(consoleFile) + } + }) + + return processHandler + } + + override fun createExecuteActionHandler() = object : ProcessBackedConsoleExecuteActionHandler(processHandler, false) { + override fun sendText(line: String) { + submitCommand(line) + } + } + + override fun fillToolBarActions(toolbarActions: DefaultActionGroup, + defaultExecutor: Executor, + contentDescriptor: RunContentDescriptor + ): List { + val actionList = arrayListOf( + createCloseAction(defaultExecutor, contentDescriptor), + createConsoleExecAction(consoleExecuteActionHandler) + ) + toolbarActions.addAll(actionList) + return actionList + } + + override fun createConsoleExecAction(consoleExecuteActionHandler: ProcessBackedConsoleExecuteActionHandler) + = ConsoleExecuteAction(consoleView, consoleExecuteActionHandler, "KotlinShellExecute", consoleExecuteActionHandler) + + fun setupGutters() { + fun configureEditorGutter(editor: EditorEx, color: Color, icon: Icon) { + editor.settings.isLineMarkerAreaShown = true // hack to show gutter + editor.settings.isFoldingOutlineShown = true + editor.gutterComponentEx.setPaintBackground(true) + val editorColorScheme = editor.colorsScheme + editorColorScheme.setColor(EditorColors.GUTTER_BACKGROUND, color) + editor.colorsScheme = editorColorScheme + + addGutterIcon(editor, icon) + } + + val consoleView = consoleView + val lightGray = Color(0xF2, 0xF2, 0xF2) + val lightBlue = Color(0x93, 0xDE, 0xFF) + configureEditorGutter(consoleView.historyViewer, lightGray, HISTORY_GUTTER_ICON) + configureEditorGutter(consoleView.consoleEditor, lightBlue, EDITOR_GUTTER_ICON) + } + + private fun addGutterIcon(editor: EditorEx, icon: Icon) { + val editorMarkup = editor.markupModel + val highlighter = editorMarkup.addRangeHighlighter(0, editor.document.textLength, HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE) + highlighter.gutterIconRenderer = object : GutterIconRenderer() { + override fun getIcon() = icon + override fun hashCode() = System.identityHashCode(this) + override fun equals(other: Any?) = this === other + } + } + + public fun submitCommand(command: String) { + val res = command.trim() + if (res.isEmpty()) return + + history.add(res) + keyEventListener.resetHistoryPosition() + + val processInputOS = processHandler.processInput ?: return logError(javaClass, "

Broken process stream

") + val charset = (processHandler as? BaseOSProcessHandler)?.charset ?: Charsets.UTF_8 + val bytes = ("$res\n").toByteArray(charset) + processInputOS.write(bytes) + processInputOS.flush() + } +} \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt new file mode 100644 index 00000000000..704e8172c31 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt @@ -0,0 +1,65 @@ +/* + * 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 + +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.w3c.dom.Element +import org.xml.sax.InputSource +import java.io.ByteArrayInputStream +import java.nio.charset.Charset +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue +import javax.xml.parsers.DocumentBuilderFactory + +public class KotlinReplOutputHandler( + process: Process, + commandLine: String +) : OSProcessHandler(process, commandLine) { + private val dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val rangeQueue: Queue = ConcurrentLinkedQueue() + + override fun notifyTextAvailable(text: String, key: Key<*>?) { + // skip "/usr/lib/jvm/java-8-oracle/bin/java -cp ..." intro + if (!text.startsWith(" super.notifyTextAvailable("$content\n", key) + "REPORT" -> { + val report = dBuilder.parse(strToSource(content, Charsets.UTF_16BE)) + val entries = report.getElementsByTagName("reportEntry") + for (i in 0..entries.length - 1) { + val reportEntry = entries.item(i) as Element + val rangeStart = reportEntry.getAttribute("rangeStart").toInt() + val rangeEnd = reportEntry.getAttribute("rangeEnd").toInt() + + rangeQueue.add(TextRange(rangeStart, rangeEnd)) + super.notifyTextAvailable("${StringUtil.unescapeXml(reportEntry.textContent)}\n", key) + } + } + } + } + + private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding))) +} \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplResultHighlighter.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplResultHighlighter.kt new file mode 100644 index 00000000000..12e9feeb9c7 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplResultHighlighter.kt @@ -0,0 +1,101 @@ +/* + * 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 + +import com.intellij.execution.console.BasicGutterContentProvider +import com.intellij.openapi.editor.event.DocumentAdapter +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.editor.ex.EditorEx +import com.intellij.openapi.editor.markup.EffectType +import com.intellij.openapi.editor.markup.HighlighterLayer +import com.intellij.openapi.editor.markup.HighlighterTargetArea +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.openapi.util.TextRange +import com.intellij.ui.JBColor +import java.util.* +import kotlin.properties.Delegates + +public class KotlinReplResultHighlighter(private val editor: EditorEx) : DocumentAdapter() { + private val EVAL_MARKERS_LENGTH = BasicGutterContentProvider.EVAL_IN_MARKER.length() + private val ERROR_PREFIX = "Error: " + private val WARNING_PREFIX = "Warning: " + + var rangeQueue: Queue by Delegates.notNull() + private var curLine = 0 + + private enum class LineType(val color: JBColor) { + ERROR(JBColor.RED), WARNING(JBColor.YELLOW), USUAL(JBColor.BLACK) + } + + private fun lineTypeByPrefix(s: String) = + if (s.startsWith(ERROR_PREFIX)) + LineType.ERROR + else if (s.startsWith(WARNING_PREFIX)) + LineType.WARNING + else + LineType.USUAL + + override fun documentChanged(e: DocumentEvent) { + val document = editor.document + val docText = document.text + if (docText.isEmpty()) { + curLine = 0 + return + } + + val lastErrorPos = docText.lastIndexOf(ERROR_PREFIX) + val lastWarningPos = docText.lastIndexOf(WARNING_PREFIX) + if (lastErrorPos <= curLine && lastWarningPos <= curLine) return + + val text = document.text + val totalLines = document.lineCount + var codeLineOffset = -1 + while (curLine < totalLines) { + val lineStart = document.getLineStartOffset(curLine) + val lineEnd = document.getLineEndOffset(curLine) + val lineText = text.substring(lineStart, lineEnd) + val lineType = lineTypeByPrefix(lineText) + + if (lineType != LineType.USUAL) { + if (codeLineOffset == -1) codeLineOffset = document.getLineStartOffset(curLine - 1) + EVAL_MARKERS_LENGTH + highlightLine(codeLineOffset, lineStart, lineEnd, lineType) + } + curLine++ + } + + } + + private fun highlightLine(codeLineOffset: Int, msgLineStart: Int, msgLineEne: Int, lineType: LineType) { + val historyMarkup = editor.markupModel + + // highlight error or warning message + val msgTextAttributes = TextAttributes() + msgTextAttributes.foregroundColor = lineType.color + msgTextAttributes.backgroundColor = JBColor.LIGHT_GRAY + historyMarkup.addRangeHighlighter(msgLineStart, msgLineEne, HighlighterLayer.LAST, msgTextAttributes, HighlighterTargetArea.LINES_IN_RANGE) + + // highlight range in [codeLine] + val range = rangeQueue.poll() + val highlightedPlaceStart = codeLineOffset + range.startOffset + val highlightedPlaceSEnd = codeLineOffset + range.endOffset + if (range.endOffset == range.startOffset) 1 else 0 + + val errorWaveAttrs = TextAttributes() + errorWaveAttrs.effectType = EffectType.WAVE_UNDERSCORE + errorWaveAttrs.effectColor = lineType.color + historyMarkup.addRangeHighlighter(highlightedPlaceStart, highlightedPlaceSEnd, HighlighterLayer.LAST, errorWaveAttrs, HighlighterTargetArea.EXACT_RANGE) + } +} \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KtConsoleKeyListener.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KtConsoleKeyListener.kt new file mode 100644 index 00000000000..718fe14566e --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KtConsoleKeyListener.kt @@ -0,0 +1,88 @@ +/* + * 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 + +import com.intellij.openapi.command.WriteCommandAction +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent + +public class KtConsoleKeyListener(private val ktConsole: KotlinConsoleRunner) : KeyAdapter() { + private var historyPos = 0 + private var prevCaretOffset = -1 + private var unfinishedCommand = "" + + private enum class HistoryMove { + UP, DOWN + } + + public fun resetHistoryPosition() { + historyPos = ktConsole.history.size() + prevCaretOffset = -1 + unfinishedCommand = "" + } + + override fun keyReleased(e: KeyEvent): Unit = when (e.keyCode) { + KeyEvent.VK_UP -> moveHistoryCursor(HistoryMove.UP) + KeyEvent.VK_DOWN -> moveHistoryCursor(HistoryMove.DOWN) + } + + private fun moveHistoryCursor(move: HistoryMove) { + val history = ktConsole.history + if (history.isEmpty()) return + + val caret = ktConsole.consoleView.consoleEditor.caretModel + val document = ktConsole.consoleView.editorDocument + + val curOffset = caret.offset + val curLine = document.getLineNumber(curOffset) + val totalLines = document.lineCount + val isMultiline = totalLines > 1 + + when (move) { + HistoryMove.UP -> { + if (curLine != 0 || (isMultiline && prevCaretOffset != 0)) { + prevCaretOffset = curOffset + return + } + + if (historyPos == history.size()) { + unfinishedCommand = document.text + } + + historyPos = Math.max(historyPos - 1, 0) + WriteCommandAction.runWriteCommandAction(ktConsole.project) { + document.setText(history[historyPos]) + caret.moveToOffset(0) + prevCaretOffset = 0 + } + } + HistoryMove.DOWN -> { + if (curLine != totalLines - 1 || (isMultiline && prevCaretOffset != document.textLength)) { + prevCaretOffset = curOffset + return + } + + historyPos = Math.min(historyPos + 1, history.size()) + WriteCommandAction.runWriteCommandAction(ktConsole.project) { + document.setText(if (historyPos == history.size()) unfinishedCommand else history[historyPos]) + caret.moveToOffset(document.textLength) + prevCaretOffset = document.textLength + } + } + } + } +} \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/KtExecuteCommandAction.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/KtExecuteCommandAction.kt new file mode 100644 index 00000000000..1e272fc8b35 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/KtExecuteCommandAction.kt @@ -0,0 +1,44 @@ +/* + * 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.actions + +import com.intellij.execution.console.LanguageConsoleImpl +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.console.KotlinConsoleKeeper + +public class KtExecuteCommandAction(private val consoleFile: VirtualFile) : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return errorNotification(null, "

Cannot find project

") + val ktConsole = KotlinConsoleKeeper.getInstance(project).getConsoleByVirtualFile(consoleFile) ?: return errorNotification(project, "

Action performed in not valid console

") + + WriteCommandAction.runWriteCommandAction(project) { + val consoleView = ktConsole.consoleView + val document = consoleView.editorDocument + val command = document.text + + document.setText(command) + LanguageConsoleImpl.printWithHighlighting(consoleView, consoleView.consoleEditor, TextRange(0, command.length())) + document.setText("") + + ktConsole.submitCommand(command) + } + } +} \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/RunKotlinConsoleAction.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/RunKotlinConsoleAction.kt new file mode 100644 index 00000000000..4496514d4e3 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/RunKotlinConsoleAction.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.console.actions + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.notification.Notifications +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.console.KotlinConsoleKeeper + +fun errorNotification(project: Project?, message: String) { + val tag = "KOTLIN REPL ERROR" + val title = "Kotlin REPL Configuration Error" + Notifications.Bus.notify(Notification(tag, title, message, NotificationType.ERROR), project) +} + +fun logError(cl: Class<*>, message: String) { + val logger = Logger.getInstance(cl) + logger.error(message) +} + +public class RunKotlinConsoleAction : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return errorNotification(null, "

Project not found

") + val module = getModule(e) ?: return errorNotification(project, "

Module not found

") + + KotlinConsoleKeeper.getInstance(project).run(module) + } + + private fun getModule(e: AnActionEvent): Module? { + val project = e.project ?: return null + val file = CommonDataKeys.VIRTUAL_FILE.getData(e.dataContext) + + if (file != null) { + val moduleForFile = ModuleUtilCore.findModuleForFile(file, project) + if (moduleForFile != null) return moduleForFile + } + + return ModuleManager.getInstance(project).modules.firstOrNull() + } +} \ No newline at end of file diff --git a/idea/idea.iml b/idea/idea.iml index 725e973dd63..b77ac1495b2 100644 --- a/idea/idea.iml +++ b/idea/idea.iml @@ -51,5 +51,6 @@ +
\ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 0ea4da381f2..78f3ec38c3a 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -38,20 +38,20 @@ - - - org.jetbrains.kotlin.idea.PluginStartupComponent - + + + org.jetbrains.kotlin.idea.PluginStartupComponent + - - org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent - + + org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent + - - org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem - org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem - - + + org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem + org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem + + @@ -97,7 +97,7 @@ + text="Show Kotlin Bytecode"> @@ -108,14 +108,14 @@ - + - - + + + text="Check Partial Body Resolve"> + text="Find Implicit Nothing Calls"> @@ -147,6 +147,18 @@ + + + + + + + + + @@ -166,7 +178,7 @@ serviceImplementation="org.jetbrains.kotlin.idea.caches.FileAttributeServiceImpl"/> + serviceImplementation="org.jetbrains.kotlin.util.ImportInsertHelperImpl"/> @@ -184,7 +196,7 @@ serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.IDELightClassGenerationSupport"/> + serviceImplementation="org.jetbrains.kotlin.resolve.DummyCodeAnalyzerInitializer"/> @@ -237,6 +249,9 @@ + + @@ -920,13 +935,13 @@ - org.jetbrains.kotlin.idea.intentions.ConvertFunctionToPropertyIntention - Kotlin + org.jetbrains.kotlin.idea.intentions.ConvertFunctionToPropertyIntention + Kotlin - org.jetbrains.kotlin.idea.intentions.ConvertPropertyToFunctionIntention - Kotlin + org.jetbrains.kotlin.idea.intentions.ConvertPropertyToFunctionIntention + Kotlin @@ -989,20 +1004,20 @@ Kotlin - - org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention - Kotlin - + + org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention + Kotlin + - - org.jetbrains.kotlin.idea.intentions.RemoveArgumentNameIntention - Kotlin - + + org.jetbrains.kotlin.idea.intentions.RemoveArgumentNameIntention + Kotlin + - - org.jetbrains.kotlin.idea.intentions.IterateExpressionIntention - Kotlin - + + org.jetbrains.kotlin.idea.intentions.IterateExpressionIntention + Kotlin + org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention @@ -1014,91 +1029,91 @@ groupName="Kotlin" enabledByDefault="true" level="INFO" - /> + /> - + + /> + displayName="If-Then foldable to '?:'" + groupName="Kotlin" + enabledByDefault="true" + level="WEAK WARNING" + /> + displayName="If-Then foldable to '?.'" + groupName="Kotlin" + enabledByDefault="true" + level="WEAK WARNING" + /> - + - + - + + displayName="Simplify negated binary expression" + groupName="Kotlin" + enabledByDefault="true" + level="WEAK WARNING" + /> + displayName="Replace with operator-assignment" + groupName="Kotlin" + enabledByDefault="true" + level="WEAK WARNING" + /> + displayName="Introduce argument to 'when'" + groupName="Kotlin" + enabledByDefault="true" + level="WEAK WARNING" + /> - + - + + displayName="Unused symbol" + groupName="Kotlin" + enabledByDefault="true" + level="WARNING" + /> + displayName="Unused receiver parameter" + groupName="Kotlin" + enabledByDefault="true" + level="WARNING" + /> + displayName="Unresolved reference in KDoc" + groupName="Kotlin" + enabledByDefault="true" + level="WARNING" + /> + displayName="Package name does not match containing directory" + groupName="Kotlin" + enabledByDefault="true" + level="WARNING" + /> + shortName="KotlinDeprecation" + displayName="Usage of redundant or deprecated syntax or deprecated symbols" + groupName="Kotlin" + enabledByDefault="true" + cleanupTool="true" + level="WARNING"/> - - + + diff --git a/idea/tests/org/jetbrains/kotlin/idea/console/KotlinReplTest.kt b/idea/tests/org/jetbrains/kotlin/idea/console/KotlinReplTest.kt new file mode 100644 index 00000000000..8aab992d944 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/console/KotlinReplTest.kt @@ -0,0 +1,91 @@ +/* + * 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.idea.console + +import com.intellij.execution.impl.ConsoleViewImpl +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.PlatformTestCase +import org.jetbrains.kotlin.console.KotlinConsoleKeeper +import org.jetbrains.kotlin.console.KotlinConsoleRunner +import org.junit.Test +import kotlin.properties.Delegates +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +public class KotlinReplTest : PlatformTestCase() { + private var consoleRunner: KotlinConsoleRunner by Delegates.notNull() + + override fun setUp() { + super.setUp() + consoleRunner = KotlinConsoleKeeper.getInstance(project).run(module)!! + } + + override fun tearDown() { + val consoleView = consoleRunner.consoleView + + consoleRunner.processHandler.destroyProcess() + + // dispose RunContentDescriptor by reflection + val getNodeMethod = Disposer.getTree().javaClass.getDeclaredMethod("getNode", Object().javaClass) + getNodeMethod.isAccessible = true + val consoleNode = getNodeMethod(Disposer.getTree(), consoleView) + val getParentMethod = consoleNode.javaClass.getDeclaredMethod("getParent") + getParentMethod.isAccessible = true + val consoleParent = getParentMethod(consoleNode) + val getObjectMethod = consoleParent.javaClass.getDeclaredMethod("getObject") + getObjectMethod.isAccessible = true + val descriptor = getObjectMethod(consoleParent) as Disposable + + Disposer.dispose(descriptor) + + super.tearDown() + } + + fun checkHistoryUpdate(maxIter: Int = 20, sleepTime: Long = 500, p: (String) -> Boolean): String { + val consoleView = consoleRunner.consoleView as ConsoleViewImpl + var docHistory: String = "" + + for (i in 1..maxIter) { + docHistory = consoleRunner.consoleView.historyViewer.document.text.trim() + + if (p(docHistory)) break + + Thread.sleep(sleepTime) + consoleView.flushDeferredText() + } + + return docHistory + } + + @Test fun testOnRunPossibility() { + val allOk = { x: String -> x.contains(":help for help") } + val hasErrors = { x: String -> x.contains("Process finished with exit code 1") || x.contains("Exception in") || x.contains("Error") } + val historyText = checkHistoryUpdate { hasErrors(it) || allOk(it) } + + assertFalse(hasErrors(historyText), "Cannot run kotlin repl") + assertTrue(allOk(historyText), "Successful run should contain text: ':help for help'") + assertFalse(consoleRunner.processHandler.isProcessTerminated, "Process accidentally terminated") + } + + @Test fun testSimpleCommand() { + consoleRunner.submitCommand("1 + 1") + val docHistory = checkHistoryUpdate { x: String -> x.endsWith('2') } + + assertTrue(docHistory.endsWith('2'), "1 + 1 should be equal 2, but document history is: '$docHistory'") + } +} \ No newline at end of file