diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleCompilerHelper.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleCompilerHelper.kt new file mode 100644 index 00000000000..6b05b9c3cb6 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleCompilerHelper.kt @@ -0,0 +1,50 @@ +/* + * 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.ExecutionManager +import com.intellij.execution.Executor +import com.intellij.execution.ui.RunContentDescriptor +import com.intellij.openapi.compiler.CompileContext +import com.intellij.openapi.compiler.CompilerManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project + +public class KotlinConsoleCompilerHelper( + private val project: Project, + private val module: Module, + private val executor: Executor, + private val contentDescriptor: RunContentDescriptor +) { + + fun moduleIsUpToDate(): Boolean { + val compilerManager = CompilerManager.getInstance(project) + val compilerScope = compilerManager.createModuleCompileScope(module, true) + return compilerManager.isUpToDate(compilerScope) + } + + fun compileModule() { + if (ExecutionManager.getInstance(project).contentManager.removeRunContent(executor, contentDescriptor)) { + CompilerManager.getInstance(project).make(module) { + aborted: Boolean, errors: Int, warnings: Int, compileContext: CompileContext -> + if (!module.isDisposed) { + KotlinConsoleKeeper.getInstance(project).run(module, previousCompilationFailed = errors > 0) + } + } + } + } +} \ 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 index 0728a7d5326..60cb206dc99 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleKeeper.kt @@ -44,11 +44,11 @@ public class KotlinConsoleKeeper(val project: Project) { fun putVirtualFileToConsole(virtualFile: VirtualFile, console: KotlinConsoleRunner) = consoleMap.put(virtualFile, console) fun removeConsole(virtualFile: VirtualFile) = consoleMap.remove(virtualFile) - fun run(module: Module, previousCompilationFailed: Boolean = false, testMode: Boolean = false): KotlinConsoleRunner? { + fun run(module: Module, testMode: Boolean = false, previousCompilationFailed: Boolean = false): KotlinConsoleRunner? { val path = module.moduleFilePath val cmdLine = createCommandLine(module) ?: return errorNotification(project, "Module SDK not found") let { null } - val consoleRunner = KotlinConsoleRunner(cmdLine, module, previousCompilationFailed, testMode, project, REPL_TITLE, path) + val consoleRunner = KotlinConsoleRunner(module, cmdLine, testMode, previousCompilationFailed, project, REPL_TITLE, path) consoleRunner.initAndRun() consoleRunner.setupGutters() diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt index 80029b0b6e9..bda603a3526 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt @@ -30,7 +30,6 @@ import com.intellij.openapi.editor.markup.* import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer -import com.intellij.util.Consumer import org.jetbrains.kotlin.console.actions.BuildAndRestartConsoleAction import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction import org.jetbrains.kotlin.console.gutter.KotlinConsoleGutterContentProvider @@ -46,20 +45,21 @@ import javax.swing.Icon import kotlin.properties.Delegates public class KotlinConsoleRunner( + val module: Module, private val cmdLine: GeneralCommandLine, - private val module: Module, - private val previousCompilationFailed: Boolean, private val testMode: Boolean, + private val previousCompilationFailed: Boolean, myProject: Project, title: String, path: String? ) : AbstractConsoleRunnerWithHistory(myProject, title, path) { private val editorToIndicator = ConcurrentHashMap() private val historyManager = KotlinConsoleHistoryManager(this) - val executor = KotlinConsoleExecutor(this, historyManager) - private var disposableDescriptor: RunContentDescriptor by Delegates.notNull() + val executor = KotlinConsoleExecutor(this, historyManager) + var compilerHelper: KotlinConsoleCompilerHelper by Delegates.notNull() + override fun createProcess() = cmdLine.createProcess() override fun createConsoleView(): LanguageConsoleView? { @@ -81,7 +81,9 @@ public class KotlinConsoleRunner( } override fun createProcessHandler(process: Process): OSProcessHandler { - val processHandler = KotlinReplOutputHandler(KotlinReplOutputHighlighter(this, historyManager), process, cmdLine.commandLineString) + val processHandler = KotlinReplOutputHandler( + KotlinReplOutputHighlighter(this, historyManager, testMode, previousCompilationFailed), process, cmdLine.commandLineString + ) val consoleFile = consoleView.virtualFile val keeper = KotlinConsoleKeeper.getInstance(project) @@ -104,9 +106,10 @@ public class KotlinConsoleRunner( contentDescriptor: RunContentDescriptor ): List { disposableDescriptor = contentDescriptor + compilerHelper = KotlinConsoleCompilerHelper(project, module, defaultExecutor, contentDescriptor) val actionList = arrayListOf( - BuildAndRestartConsoleAction(project, module, defaultExecutor, contentDescriptor, previousCompilationFailed, testMode), + BuildAndRestartConsoleAction(this), createConsoleExecAction(consoleExecuteActionHandler), createCloseAction(defaultExecutor, contentDescriptor) ) diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt index d04ec2830a2..dc44d0db6df 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinReplOutputHandler.kt @@ -42,6 +42,7 @@ public class KotlinReplOutputHandler( commandLine: String ) : OSProcessHandler(process, commandLine) { + private var isBuildInfoChecked = false private val dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() override fun isSilentlyDestroyOnClose() = true @@ -56,7 +57,7 @@ public class KotlinReplOutputHandler( val content = StringUtil.replace(root.textContent, XML_REPLACEMENTS, SOURCE_CHARS) when (outputType) { - "INITIAL_PROMPT" -> outputHighlighter.printInitialPrompt(content) + "INITIAL_PROMPT" -> buildWarningIfNeededBeforeInit(content) "HELP_PROMPT" -> outputHighlighter.printHelp(content) "USER_OUTPUT" -> outputHighlighter.printUserOutput(content) "REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon(content) @@ -67,6 +68,14 @@ public class KotlinReplOutputHandler( } } + private fun buildWarningIfNeededBeforeInit(content: String) { + if (!isBuildInfoChecked) { + outputHighlighter.printBuildInfoWarningIfNeeded() + isBuildInfoChecked = true + } + outputHighlighter.printInitialPrompt(content) + } + private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding))) private fun createCompilerMessages(runtimeErrorsReport: String): List { diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/BuildAndRestartConsoleAction.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/BuildAndRestartConsoleAction.kt index 26160959a11..70619a21513 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/BuildAndRestartConsoleAction.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/actions/BuildAndRestartConsoleAction.kt @@ -16,76 +16,14 @@ package org.jetbrains.kotlin.console.actions -import com.intellij.execution.ExecutionManager -import com.intellij.execution.Executor -import com.intellij.execution.ui.RunContentDescriptor import com.intellij.icons.AllIcons -import com.intellij.notification.Notification -import com.intellij.notification.NotificationListener -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.compiler.CompileContext -import com.intellij.openapi.compiler.CompilerManager -import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project -import org.jetbrains.kotlin.console.KotlinConsoleKeeper -import javax.swing.event.HyperlinkEvent - -private fun buildAndRestartMessage(module: Module) = "Build module '${module.name}' and restart" +import org.jetbrains.kotlin.console.KotlinConsoleRunner public class BuildAndRestartConsoleAction( - private val project: Project, - private val module: Module, - private val executor: Executor, - private val contentDescriptor: RunContentDescriptor, - private val previousCompilationFailed: Boolean, - private val testMode: Boolean -) : AnAction("Build and restart", buildAndRestartMessage(module), AllIcons.Actions.Restart) { + private val runner: KotlinConsoleRunner +) : AnAction("Build and restart", "Build module '${runner.module.name}' and restart", AllIcons.Actions.Restart) { - init { - if (!testMode && !previousCompilationFailed) showOutdatedClassesNotificationIfNeeded() - } - - override fun actionPerformed(_: AnActionEvent) = compileModule() - - private fun compileModule() { - if (ExecutionManager.getInstance(project).contentManager.removeRunContent(executor, contentDescriptor)) { - CompilerManager.getInstance(project).make(module) { - aborted: Boolean, errors: Int, warnings: Int, compileContext: CompileContext -> - if (!module.isDisposed) { - val compilationFailed = aborted || errors > 0 - if (compilationFailed) warningNotification("Compilation wasn't ended properly") - - KotlinConsoleKeeper.getInstance(project).run(module, previousCompilationFailed = compilationFailed) - } - } - } - } - - private fun showOutdatedClassesNotificationIfNeeded() { - val compilerManager = CompilerManager.getInstance(project) - val compilerScope = compilerManager.createModuleCompileScope(module, true) - if (compilerManager.isUpToDate(compilerScope)) return - - val message = "You’re running the REPL with outdated classes
${buildAndRestartMessage(module)}" - val hyperlinkListener = object : NotificationListener { - override fun hyperlinkUpdate(notification: Notification, event: HyperlinkEvent) { - if (event.eventType == HyperlinkEvent.EventType.ACTIVATED) { - compileModule() - notification.expire() - } - } - } - - warningNotification(message, hyperlinkListener) - } - - private fun warningNotification(message: String, hyperlinkListener: NotificationListener? = null) { - val warningTag = "KOTLIN REPL WARNING" - val warningTitle = "Kotlin REPL Configuration Warning" - - Notifications.Bus.notify(Notification(warningTag, warningTitle, message, NotificationType.WARNING, hyperlinkListener), project) - } + override fun actionPerformed(_: AnActionEvent) = runner.compilerHelper.compileModule() } \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt index 7d61e3b77df..d3f5184c02d 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/gutter/ReplIcons.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.idea.JetIcons import javax.swing.Icon public object ReplIcons { + public val BUILD_WARNING_INDICATOR: Icon = AllIcons.General.Information public val HISTORY_INDICATOR: Icon = AllIcons.General.MessageHistory public val EDITOR_INDICATOR: Icon = JetIcons.LAUNCH public val INCOMPLETE_INDICATOR: Icon = AllIcons.Nodes.Desktop diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt index de13f13783e..881d51cdfd9 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/KotlinReplOutputHighlighter.kt @@ -47,7 +47,9 @@ enum class ReplOutputType { public class KotlinReplOutputHighlighter( private val runner: KotlinConsoleRunner, - private val historyManager: KotlinConsoleHistoryManager + private val historyManager: KotlinConsoleHistoryManager, + private val testMode: Boolean, + private val previousCompilationFailed: Boolean ) { private val project = runner.project private val consoleView = runner.consoleView as LanguageConsoleImpl @@ -65,17 +67,42 @@ public class KotlinReplOutputHighlighter( return Pair(oldLen, newLen) } - private fun printOutput(output: String, contentType: ConsoleViewContentType, icon: Icon) { + private fun printOutput(output: String, contentType: ConsoleViewContentType, icon: Icon? = null) { val (startOffset, endOffset) = textOffsets(output) consoleView.print(output, contentType) consoleView.flushDeferredText() + if (icon == null) return + historyMarkup.addRangeHighlighter( startOffset, endOffset, HighlighterLayer.LAST, null, HighlighterTargetArea.EXACT_RANGE ) apply { gutterIconRenderer = KotlinConsoleIndicatorRenderer(icon) } } + private fun printWarningMessage(message: String, isAddHyperlink: Boolean) = WriteCommandAction.runWriteCommandAction(project) { + printOutput("\n", ConsoleViewContentType.NORMAL_OUTPUT) + + printOutput(message, ReplColors.WARNING_INFO_CONTENT_TYPE, ReplIcons.BUILD_WARNING_INDICATOR) + + if (isAddHyperlink) { + consoleView.printHyperlink("Build module '${runner.module.name}' and restart") { + runner.compilerHelper.compileModule() + } + } + + printOutput("\n\n", ConsoleViewContentType.NORMAL_OUTPUT) + } + + fun printBuildInfoWarningIfNeeded() { + if (testMode) return + if (previousCompilationFailed) return printWarningMessage("There were compilation errors in module ${runner.module.name}", false) + if (runner.compilerHelper.moduleIsUpToDate()) return + + val compilerWarningMessage = "You’re running the REPL with outdated classes: " + printWarningMessage(compilerWarningMessage, true) + } + fun printInitialPrompt(command: String) = consoleView.print(command, ReplColors.INITIAL_PROMPT_CONTENT_TYPE) fun printHelp(help: String) = WriteCommandAction.runWriteCommandAction(project) { @@ -105,7 +132,7 @@ public class KotlinReplOutputHighlighter( // remove line breaks after incomplete part val historyText = historyDocument.text val historyLength = historyText.length() - val trimmedHistoryText = historyText.trim() + val trimmedHistoryText = historyText.trimEnd() val whitespaceCharsToDelete = historyLength - trimmedHistoryText.length() historyDocument.deleteString(historyLength - whitespaceCharsToDelete, historyLength) diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/ReplColors.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/ReplColors.kt index 83497edac5f..f11388bc922 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/ReplColors.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/highlight/ReplColors.kt @@ -28,6 +28,11 @@ public object ReplColors { public val EDITOR_GUTTER_COLOR: JBColor = JBColor(Gray.xCF, Gray.x31) public val PLACEHOLDER_COLOR: JBColor = JBColor.LIGHT_GRAY + public val WARNING_INFO_CONTENT_TYPE: ConsoleViewContentType = + ConsoleViewContentType( + "KOTLIN_CONSOLE_WARNING_INFO", + TextAttributes() apply { fontType = Font.ITALIC; foregroundColor = JBColor.RED } + ) public val INITIAL_PROMPT_CONTENT_TYPE: ConsoleViewContentType = ConsoleViewContentType( "KOTLIN_CONSOLE_INITIAL_PROMPT",