Add kotlin console repl

This commit is contained in:
Dmitry Kovanikov
2015-07-27 17:58:38 +03:00
committed by Pavel V. Talanov
parent 80cbee83ee
commit a11f411f7d
18 changed files with 1092 additions and 141 deletions
+1
View File
@@ -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">
+1
View File
@@ -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" />
+1 -1
View File
@@ -47,4 +47,4 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
</module>
@@ -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<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 @@ 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);
@@ -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<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 class ReplInterpreter {
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 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<Pair<ScriptDescriptor, Type>> 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);
@@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-16\"?>"
private fun xmlEscape(s: String, escapeType: EscapeType): String {
val singleLine = StringUtil.escapeLineBreak(s)
return "$XML_PREFIX<output type=\"$escapeType\">${StringUtil.escapeXml(singleLine)}</output>"
}
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))
}
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="util" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -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<VirtualFile, KotlinConsoleRunner> = 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, "<p>Module SDK not found</p>")
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<KotlinConsoleKeeper>())
}
}
@@ -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<LanguageConsoleView>(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<String> = 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("<Ctrl+Enter> 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<AnAction> {
val actionList = arrayListOf<AnAction>(
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, "<p>Broken process stream</p>")
val charset = (processHandler as? BaseOSProcessHandler)?.charset ?: Charsets.UTF_8
val bytes = ("$res\n").toByteArray(charset)
processInputOS.write(bytes)
processInputOS.flush()
}
}
@@ -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<TextRange> = ConcurrentLinkedQueue()
override fun notifyTextAvailable(text: String, key: Key<*>?) {
// skip "/usr/lib/jvm/java-8-oracle/bin/java -cp ..." intro
if (!text.startsWith("<?xml version=")) return super.notifyTextAvailable(text, key)
val output = dBuilder.parse(strToSource(text))
val root = output.firstChild as Element
val outputType = root.getAttribute("type")
val content = StringUtil.unescapeStringCharacters(root.textContent).trim()
when (outputType) {
"ORDINARY" -> 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)))
}
@@ -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<TextRange> 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)
}
}
@@ -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
}
}
}
}
}
@@ -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, "<p>Cannot find project</p>")
val ktConsole = KotlinConsoleKeeper.getInstance(project).getConsoleByVirtualFile(consoleFile) ?: return errorNotification(project, "<p>Action performed in not valid console</p>")
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)
}
}
}
@@ -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, "<p>Project not found</p>")
val module = getModule(e) ?: return errorNotification(project, "<p>Module not found</p>")
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()
}
}
+1
View File
@@ -51,5 +51,6 @@
<orderEntry type="module" module-name="js.serializer" />
<orderEntry type="module" module-name="idea-js" scope="TEST" />
<orderEntry type="module" module-name="kotlinr" />
<orderEntry type="module" module-name="idea-repl" scope="TEST" />
</component>
</module>
+142 -127
View File
@@ -38,20 +38,20 @@
</component>
</project-components>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
</component>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent</implementation-class>
</component>
<component>
<interface-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</interface-class>
<implementation-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</implementation-class>
</component>
</application-components>
<component>
<interface-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</interface-class>
<implementation-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</implementation-class>
</component>
</application-components>
<module-components>
<component>
@@ -97,7 +97,7 @@
</action>
<action id="ShowKotlinBytecode" class="org.jetbrains.kotlin.idea.actions.ShowKotlinBytecodeAction"
text="Show Kotlin Bytecode">
text="Show Kotlin Bytecode">
<add-to-group group-id="KotlinToolsGroup"/>
</action>
@@ -108,14 +108,14 @@
<action id="IntroduceProperty" class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.IntroducePropertyAction"
text="P_roperty..." use-shortcut-of="IntroduceField">
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceField"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceField"/>
</action>
<action id="IntroduceLambdaParameter"
class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.IntroduceLambdaParameterAction"
text="La_mbda Parameter...">
<keyboard-shortcut keymap="$default" first-keystroke="control shift P"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceParameter"/>
<keyboard-shortcut keymap="$default" first-keystroke="control shift P"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceParameter"/>
</action>
<action id="CopyAsDiagnosticTest" class="org.jetbrains.kotlin.idea.actions.internal.CopyAsDiagnosticTestAction"
@@ -125,12 +125,12 @@
</action>
<action id="CheckPartialBodyResolve" class="org.jetbrains.kotlin.idea.actions.internal.CheckPartialBodyResolveAction"
text="Check Partial Body Resolve">
text="Check Partial Body Resolve">
<add-to-group group-id="KotlinToolsGroup" anchor="last"/>
</action>
<action id="FindImplicitNothingAction" class="org.jetbrains.kotlin.idea.actions.internal.FindImplicitNothingAction"
text="Find Implicit Nothing Calls">
text="Find Implicit Nothing Calls">
<add-to-group group-id="KotlinToolsGroup" anchor="last"/>
</action>
@@ -147,6 +147,18 @@
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift M"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunction"/>
</action>
<!-- Kotlin Console REPL-->
<action id="KotlinConsoleREPL" class="org.jetbrains.kotlin.console.actions.RunKotlinConsoleAction"
text="Kotlin console...">
<add-to-group group-id="KotlinToolsGroup" anchor="last"/>
</action>
<action id="KotlinShellExecute" class="com.intellij.openapi.actionSystem.EmptyAction"
text="Execute Kotlin Code"
description="Execute Kotlin code in console">
<keyboard-shortcut first-keystroke="control ENTER" keymap="$default"/>
</action>
</actions>
<extensions defaultExtensionNs="com.intellij">
@@ -166,7 +178,7 @@
serviceImplementation="org.jetbrains.kotlin.idea.caches.FileAttributeServiceImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.util.ImportInsertHelper"
serviceImplementation="org.jetbrains.kotlin.util.ImportInsertHelperImpl"/>
serviceImplementation="org.jetbrains.kotlin.util.ImportInsertHelperImpl"/>
<applicationService serviceInterface="org.jetbrains.kotlin.psi.KotlinDeclarationNavigationPolicy"
serviceImplementation="org.jetbrains.kotlin.idea.decompiler.navigation.KotlinDeclarationNavigationPolicyImpl"/>
@@ -184,7 +196,7 @@
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.IDELightClassGenerationSupport"/>
<projectService serviceInterface="org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer"
serviceImplementation="org.jetbrains.kotlin.resolve.DummyCodeAnalyzerInitializer"/>
serviceImplementation="org.jetbrains.kotlin.resolve.DummyCodeAnalyzerInitializer"/>
<projectService serviceInterface="org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider"
serviceImplementation="org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider"/>
@@ -237,6 +249,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.ClsJavaStubByVirtualFileCache"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.ClsJavaStubByVirtualFileCache"/>
<projectService serviceInterface="org.jetbrains.kotlin.console.KotlinConsoleKeeper"
serviceImplementation="org.jetbrains.kotlin.console.KotlinConsoleKeeper"/>
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
@@ -920,13 +935,13 @@
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertFunctionToPropertyIntention</className>
<category>Kotlin</category>
<className>org.jetbrains.kotlin.idea.intentions.ConvertFunctionToPropertyIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertPropertyToFunctionIntention</className>
<category>Kotlin</category>
<className>org.jetbrains.kotlin.idea.intentions.ConvertPropertyToFunctionIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
@@ -989,20 +1004,20 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.RemoveArgumentNameIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.RemoveArgumentNameIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.IterateExpressionIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.IterateExpressionIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention</className>
@@ -1014,91 +1029,91 @@
groupName="Kotlin"
enabledByDefault="true"
level="INFO"
/>
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ConvertToStringTemplateInspection"
displayName="Convert string concatenation to string template"
groupName="Kotlin"
enabledByDefault="true"
level="INFO"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ConvertToStringTemplateInspection"
displayName="Convert string concatenation to string template"
groupName="Kotlin"
enabledByDefault="true"
level="INFO"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.conventionNameCalls.ExplicitGetInspection"
displayName="Explicit 'get'"
groupName="Kotlin"
enabledByDefault="false"
level="WEAK WARNING"
/>
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToElvisInspection"
displayName="If-Then foldable to '?:'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
displayName="If-Then foldable to '?:'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToSafeAccessInspection"
displayName="If-Then foldable to '?.'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
displayName="If-Then foldable to '?.'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.IfNullToElvisInspection"
displayName="If-Null return/break/... foldable to '?:'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.IfNullToElvisInspection"
displayName="If-Null return/break/... foldable to '?:'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsInspection"
displayName="Type arguments are unnecessary"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsInspection"
displayName="Type arguments are unnecessary"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveExplicitSuperQualifierInspection"
displayName="Supertype qualification is unnecessary"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveExplicitSuperQualifierInspection"
displayName="Supertype qualification is unnecessary"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.SimplifyNegatedBinaryExpressionInspection"
displayName="Simplify negated binary expression"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
displayName="Simplify negated binary expression"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ReplaceWithOperatorAssignmentInspection"
displayName="Replace with operator-assignment"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
displayName="Replace with operator-assignment"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IntroduceWhenSubjectInspection"
displayName="Introduce argument to 'when'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
displayName="Introduce argument to 'when'"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveCurlyBracesFromTemplateInspection"
displayName="Remove redundant curly braces in string template"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveCurlyBracesFromTemplateInspection"
displayName="Remove redundant curly braces in string template"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.SimplifyBooleanWithConstantsInspection"
displayName="Simplify boolean expression with constants"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.SimplifyBooleanWithConstantsInspection"
displayName="Simplify boolean expression with constants"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxInspection"
displayName="Use property access syntax"
@@ -1109,18 +1124,18 @@
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection"
displayName="Unused symbol"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
displayName="Unused symbol"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnusedReceiverParameterInspection"
displayName="Unused receiver parameter"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
displayName="Unused receiver parameter"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnusedImportInspection"
displayName="Unused import directive"
@@ -1137,26 +1152,26 @@
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.kdoc.KDocUnresolvedReferenceInspection"
displayName="Unresolved reference in KDoc"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
displayName="Unresolved reference in KDoc"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.refactoring.move.changePackage.PackageDirectoryMismatchInspection"
displayName="Package name does not match containing directory"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
displayName="Package name does not match containing directory"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinCleanupInspection"
shortName="KotlinDeprecation"
displayName="Usage of redundant or deprecated syntax or deprecated symbols"
groupName="Kotlin"
enabledByDefault="true"
cleanupTool="true"
level="WARNING"/>
shortName="KotlinDeprecation"
displayName="Usage of redundant or deprecated syntax or deprecated symbols"
groupName="Kotlin"
enabledByDefault="true"
cleanupTool="true"
level="WARNING"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveForLoopIndicesInspection"
displayName="Index is unused"
@@ -1188,8 +1203,8 @@
</extensionPoints>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<xi:include href="extensions/kotlin2jvm.xml" xpointer="xpointer(/idea-plugin/extensions/*)"/>
<xi:include href="extensions/kotlin2js.xml" xpointer="xpointer(/idea-plugin/extensions/*)"/>
<xi:include href="extensions/kotlin2jvm.xml" xpointer="xpointer(/idea-plugin/extensions/*)"/>
<xi:include href="extensions/kotlin2js.xml" xpointer="xpointer(/idea-plugin/extensions/*)"/>
<quickFixContributor implementation="org.jetbrains.kotlin.idea.quickfix.QuickFixRegistrar"/>
</extensions>
@@ -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'")
}
}