Repl: refactor ide code

More accurate class names
Move all shared state to KotlinConsoleRunner class
This commit is contained in:
Pavel V. Talanov
2015-10-22 19:06:29 +03:00
parent e10df1a05d
commit 0fe3ef7cf4
16 changed files with 160 additions and 130 deletions
@@ -20,29 +20,33 @@ import com.intellij.execution.process.BaseOSProcessHandler
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.console.actions.logError import org.jetbrains.kotlin.console.actions.logError
import org.jetbrains.kotlin.console.highlight.KotlinHistoryHighlighter
private val XML_PREAMBLE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" private val XML_PREAMBLE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
public class KotlinConsoleExecutor( public class CommandExecutor(private val runner: KotlinConsoleRunner) {
private val runner: KotlinConsoleRunner, private val commandHistory = runner.commandHistory
private val historyManager: KotlinConsoleHistoryManager, private val historyUpdater = HistoryUpdater(runner)
private val historyHighlighter: KotlinHistoryHighlighter
) {
fun executeCommand() = WriteCommandAction.runWriteCommandAction(runner.project) { fun executeCommand() = WriteCommandAction.runWriteCommandAction(runner.project) {
val commandText = getTrimmedCommandText()
if (commandText.isEmpty()) {
return@runWriteCommandAction
}
val historyDocumentRange = historyUpdater.printNewCommandInHistory(commandText)
commandHistory.addEntry(CommandHistory.Entry(commandText, historyDocumentRange))
sendCommandToProcess(commandText)
}
private fun getTrimmedCommandText(): String {
val consoleView = runner.consoleView val consoleView = runner.consoleView
val document = consoleView.editorDocument val document = consoleView.editorDocument
val inputText = document.text.trim() val inputText = document.text.trim()
return inputText
if (inputText.isNotEmpty()) {
historyHighlighter.printNewCommandInHistory(inputText)
submitCommand(inputText)
}
} }
private fun submitCommand(command: String) { private fun sendCommandToProcess(command: String) {
historyManager.updateHistory(command)
val processHandler = runner.processHandler val processHandler = runner.processHandler
val processInputOS = processHandler.processInput ?: return logError(javaClass, "<p>Broken process stream</p>") val processInputOS = processHandler.processInput ?: return logError(javaClass, "<p>Broken process stream</p>")
val charset = (processHandler as? BaseOSProcessHandler)?.charset ?: Charsets.UTF_8 val charset = (processHandler as? BaseOSProcessHandler)?.charset ?: Charsets.UTF_8
@@ -0,0 +1,43 @@
/*
* 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.util.TextRange
class CommandHistory {
class Entry(
val entryText: String,
val rangeInHistoryDocument: TextRange
)
private val entries = arrayListOf<Entry>()
val listeners = arrayListOf<HistoryUpdateListener>()
operator fun get(i: Int) = entries[i]
fun addEntry(entry: Entry) {
entries.add(entry)
listeners.forEach { it.onNewEntry(entry) }
}
val size: Int
get() = entries.size
}
interface HistoryUpdateListener {
fun onNewEntry(entry: CommandHistory.Entry)
}
@@ -24,7 +24,7 @@ import com.intellij.openapi.compiler.CompilerManager
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
public class KotlinConsoleCompilerHelper( public class ConsoleCompilerHelper(
private val project: Project, private val project: Project,
private val module: Module, private val module: Module,
private val executor: Executor, private val executor: Executor,
@@ -20,27 +20,20 @@ import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.project.Project
import java.awt.event.KeyAdapter import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
public class KotlinConsoleHistoryManager(private val runner: KotlinConsoleRunner) : KeyAdapter() { class HistoryKeyListener(
private val project = runner.project private val project: Project, private val consoleEditor: EditorEx, private val history: CommandHistory
private val consoleEditor: EditorEx by lazy { runner.consoleView.consoleEditor } // [consoleEditor] is null at the moment if instantiation ) : KeyAdapter(), HistoryUpdateListener {
private val history = arrayListOf<String>()
private var historyPos = 0 private var historyPos = 0
private var prevCaretOffset = -1 private var prevCaretOffset = -1
private var unfinishedCommand = "" private var unfinishedCommand = ""
val lastCommandLength: Int override fun onNewEntry(entry: CommandHistory.Entry) {
get() = history.last().length()
public fun updateHistory(command: String) {
history.add(command)
// reset history positions // reset history positions
historyPos = history.size() historyPos = history.size
prevCaretOffset = -1 prevCaretOffset = -1
unfinishedCommand = "" unfinishedCommand = ""
} }
@@ -50,13 +43,13 @@ public class KotlinConsoleHistoryManager(private val runner: KotlinConsoleRunner
} }
override fun keyReleased(e: KeyEvent): Unit = when (e.keyCode) { override fun keyReleased(e: KeyEvent): Unit = when (e.keyCode) {
KeyEvent.VK_UP -> moveHistoryCursor(HistoryMove.UP) KeyEvent.VK_UP -> moveHistoryCursor(HistoryMove.UP)
KeyEvent.VK_DOWN -> moveHistoryCursor(HistoryMove.DOWN) KeyEvent.VK_DOWN -> moveHistoryCursor(HistoryMove.DOWN)
KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT -> prevCaretOffset = consoleEditor.caretModel.offset KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT -> prevCaretOffset = consoleEditor.caretModel.offset
} }
private fun moveHistoryCursor(move: HistoryMove) { private fun moveHistoryCursor(move: HistoryMove) {
if (history.isEmpty()) return if (history.size == 0) return
if (LookupManager.getInstance(project).activeLookup != null) return if (LookupManager.getInstance(project).activeLookup != null) return
val caret = consoleEditor.caretModel val caret = consoleEditor.caretModel
@@ -74,29 +67,29 @@ public class KotlinConsoleHistoryManager(private val runner: KotlinConsoleRunner
return return
} }
if (historyPos == history.size()) { if (historyPos == history.size) {
unfinishedCommand = document.text unfinishedCommand = document.text
} }
historyPos = Math.max(historyPos - 1, 0) historyPos = Math.max(historyPos - 1, 0)
WriteCommandAction.runWriteCommandAction(project) { WriteCommandAction.runWriteCommandAction(project) {
document.setText(history[historyPos]) document.setText(history[historyPos].entryText)
EditorUtil.scrollToTheEnd(consoleEditor) EditorUtil.scrollToTheEnd(consoleEditor)
prevCaretOffset = 0 prevCaretOffset = 0
caret.moveToOffset(0) caret.moveToOffset(0)
} }
} }
HistoryMove.DOWN -> { HistoryMove.DOWN -> {
if (historyPos == history.size()) return if (historyPos == history.size) return
if (curLine != totalLines - 1 || (isMultiline && prevCaretOffset != document.textLength)) { if (curLine != totalLines - 1 || (isMultiline && prevCaretOffset != document.textLength)) {
prevCaretOffset = curOffset prevCaretOffset = curOffset
return return
} }
historyPos = Math.min(historyPos + 1, history.size()) historyPos = Math.min(historyPos + 1, history.size)
WriteCommandAction.runWriteCommandAction(project) { WriteCommandAction.runWriteCommandAction(project) {
document.setText(if (historyPos == history.size()) unfinishedCommand else history[historyPos]) document.setText(if (historyPos == history.size) unfinishedCommand else history[historyPos].entryText)
prevCaretOffset = document.textLength prevCaretOffset = document.textLength
EditorUtil.scrollToTheEnd(consoleEditor) EditorUtil.scrollToTheEnd(consoleEditor)
} }
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.console.highlight package org.jetbrains.kotlin.console
import com.intellij.execution.console.LanguageConsoleImpl import com.intellij.execution.console.LanguageConsoleImpl
import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorEx
@@ -22,28 +22,17 @@ import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.editor.markup.HighlighterLayer import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.console.KotlinConsoleRunner import org.jetbrains.kotlin.console.gutter.ConsoleIndicatorRenderer
import org.jetbrains.kotlin.console.gutter.KotlinConsoleIndicatorRenderer
import org.jetbrains.kotlin.console.gutter.ReplIcons import org.jetbrains.kotlin.console.gutter.ReplIcons
public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner ) { public class HistoryUpdater(private val runner: KotlinConsoleRunner) {
private val consoleView: LanguageConsoleImpl by lazy { runner.consoleView as LanguageConsoleImpl } private val consoleView: LanguageConsoleImpl by lazy { runner.consoleView as LanguageConsoleImpl }
var isReadLineMode: Boolean = false fun printNewCommandInHistory(trimmedCommandText: String): TextRange {
set(value) {
if (value)
runner.changeConsoleEditorIndicator(ReplIcons.EDITOR_READLINE_INDICATOR)
else
runner.changeConsoleEditorIndicator(ReplIcons.EDITOR_INDICATOR)
field = value
}
fun printNewCommandInHistory(trimmedCommandText: String) {
val historyEditor = consoleView.historyViewer val historyEditor = consoleView.historyViewer
addLineBreakIfNeeded(historyEditor) addLineBreakIfNeeded(historyEditor)
val startOffset = historyEditor.document.textLength val startOffset = historyEditor.document.textLength
val endOffset = startOffset + trimmedCommandText.length() val endOffset = startOffset + trimmedCommandText.length
addCommandTextToHistoryEditor(trimmedCommandText) addCommandTextToHistoryEditor(trimmedCommandText)
EditorUtil.scrollToTheEnd(historyEditor) EditorUtil.scrollToTheEnd(historyEditor)
@@ -52,9 +41,10 @@ public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner )
historyEditor.markupModel.addRangeHighlighter( historyEditor.markupModel.addRangeHighlighter(
startOffset, endOffset, HighlighterLayer.LAST, null, HighlighterTargetArea.EXACT_RANGE startOffset, endOffset, HighlighterLayer.LAST, null, HighlighterTargetArea.EXACT_RANGE
).apply { ).apply {
val historyMarker = if (isReadLineMode) ReplIcons.READLINE_MARKER else ReplIcons.COMMAND_MARKER val historyMarker = if (runner.isReadLineMode) ReplIcons.READLINE_MARKER else ReplIcons.COMMAND_MARKER
gutterIconRenderer = KotlinConsoleIndicatorRenderer(historyMarker) gutterIconRenderer = ConsoleIndicatorRenderer(historyMarker)
} }
return TextRange(startOffset, endOffset)
} }
private fun addCommandTextToHistoryEditor(trimmedCommandText: String) { private fun addCommandTextToHistoryEditor(trimmedCommandText: String) {
@@ -67,11 +57,11 @@ public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner )
} }
private fun addLineBreakIfNeeded(historyEditor: EditorEx) { private fun addLineBreakIfNeeded(historyEditor: EditorEx) {
if (isReadLineMode) return if (runner.isReadLineMode) return
val historyDocument = historyEditor.document val historyDocument = historyEditor.document
val historyText = historyDocument.text val historyText = historyDocument.text
val textLength = historyText.length() val textLength = historyText.length
if (!historyText.endsWith('\n')) { if (!historyText.endsWith('\n')) {
historyDocument.insertString(textLength, "\n") historyDocument.insertString(textLength, "\n")
@@ -88,7 +78,7 @@ public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner )
private fun addFoldingRegion(historyEditor: EditorEx, startOffset: Int, endOffset: Int, command: String) { private fun addFoldingRegion(historyEditor: EditorEx, startOffset: Int, endOffset: Int, command: String) {
val cmdLines = command.lines() val cmdLines = command.lines()
val linesCount = cmdLines.size() val linesCount = cmdLines.size
if (linesCount < 2) return if (linesCount < 2) return
val foldingModel = historyEditor.foldingModel val foldingModel = historyEditor.foldingModel
@@ -40,15 +40,15 @@ private val REPL_TITLE = "Kotlin REPL"
public class KotlinConsoleKeeper(val project: Project) { public class KotlinConsoleKeeper(val project: Project) {
private val consoleMap: MutableMap<VirtualFile, KotlinConsoleRunner> = ConcurrentHashMap() private val consoleMap: MutableMap<VirtualFile, KotlinConsoleRunner> = ConcurrentHashMap()
fun getConsoleByVirtualFile(virtualFile: VirtualFile) = consoleMap.get(virtualFile) fun getConsoleByVirtualFile(virtualFile: VirtualFile) = consoleMap[virtualFile]
fun putVirtualFileToConsole(virtualFile: VirtualFile, console: KotlinConsoleRunner) = consoleMap.put(virtualFile, console) fun putVirtualFileToConsole(virtualFile: VirtualFile, console: KotlinConsoleRunner) = consoleMap.put(virtualFile, console)
fun removeConsole(virtualFile: VirtualFile) = consoleMap.remove(virtualFile) fun removeConsole(virtualFile: VirtualFile) = consoleMap.remove(virtualFile)
fun run(module: Module, testMode: Boolean = false, previousCompilationFailed: Boolean = false): KotlinConsoleRunner? { fun run(module: Module, previousCompilationFailed: Boolean = false): KotlinConsoleRunner? {
val path = module.moduleFilePath val path = module.moduleFilePath
val cmdLine = createCommandLine(module) ?: return run { errorNotification(project, "Module SDK not found"); null } val cmdLine = createCommandLine(module) ?: return run { errorNotification(project, "Module SDK not found"); null }
val consoleRunner = KotlinConsoleRunner(module, cmdLine, testMode, previousCompilationFailed, project, REPL_TITLE, path) val consoleRunner = KotlinConsoleRunner(module, cmdLine, previousCompilationFailed, project, REPL_TITLE, path)
consoleRunner.initAndRun() consoleRunner.initAndRun()
consoleRunner.setupGutters() consoleRunner.setupGutters()
@@ -103,7 +103,7 @@ public class KotlinConsoleKeeper(val project: Project) {
} }
private fun addPathToCompiledOutput(paramList: ParametersList, module: Module) { private fun addPathToCompiledOutput(paramList: ParametersList, module: Module) {
val compiledModulePath = CompilerPathsEx.getOutputPaths(arrayOf(module)).join(File.pathSeparator) val compiledModulePath = CompilerPathsEx.getOutputPaths(arrayOf(module)).joinToString(File.pathSeparator)
val moduleDependencies = OrderEnumerator.orderEntries(module).recursively().pathsList.pathsString val moduleDependencies = OrderEnumerator.orderEntries(module).recursively().pathsList.pathsString
val compiledOutputClasspath = "$compiledModulePath${File.pathSeparator}$moduleDependencies" val compiledOutputClasspath = "$compiledModulePath${File.pathSeparator}$moduleDependencies"
@@ -47,12 +47,9 @@ import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.console.actions.BuildAndRestartConsoleAction import org.jetbrains.kotlin.console.actions.BuildAndRestartConsoleAction
import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction
import org.jetbrains.kotlin.console.gutter.IconWithTooltip import org.jetbrains.kotlin.console.gutter.IconWithTooltip
import org.jetbrains.kotlin.console.gutter.KotlinConsoleGutterContentProvider import org.jetbrains.kotlin.console.gutter.ConsoleGutterContentProvider
import org.jetbrains.kotlin.console.gutter.KotlinConsoleIndicatorRenderer import org.jetbrains.kotlin.console.gutter.ConsoleIndicatorRenderer
import org.jetbrains.kotlin.console.gutter.ReplIcons import org.jetbrains.kotlin.console.gutter.ReplIcons
import org.jetbrains.kotlin.console.highlight.KotlinHistoryHighlighter
import org.jetbrains.kotlin.console.highlight.KotlinReplOutputHighlighter
import org.jetbrains.kotlin.console.highlight.ReplColors
import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.completion.doNotComplete import org.jetbrains.kotlin.idea.completion.doNotComplete
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
@@ -65,26 +62,38 @@ private val KOTLIN_SHELL_EXECUTE_ACTION_ID = "KotlinShellExecute"
public class KotlinConsoleRunner( public class KotlinConsoleRunner(
val module: Module, val module: Module,
private val cmdLine: GeneralCommandLine, private val cmdLine: GeneralCommandLine,
private val testMode: Boolean, internal val previousCompilationFailed: Boolean,
private val previousCompilationFailed: Boolean,
myProject: Project, myProject: Project,
title: String, title: String,
path: String? path: String?
) : AbstractConsoleRunnerWithHistory<LanguageConsoleView>(myProject, title, path) { ) : AbstractConsoleRunnerWithHistory<LanguageConsoleView>(myProject, title, path) {
private val historyManager = KotlinConsoleHistoryManager(this) internal val commandHistory = CommandHistory()
private val historyHighlighter = KotlinHistoryHighlighter(this)
var isReadLineMode: Boolean = false
set(value) {
if (value)
changeConsoleEditorIndicator(ReplIcons.EDITOR_READLINE_INDICATOR)
else
changeConsoleEditorIndicator(ReplIcons.EDITOR_INDICATOR)
field = value
}
fun changeConsoleEditorIndicator(newIconWithTooltip: IconWithTooltip) = WriteCommandAction.runWriteCommandAction(project) {
consoleEditorHighlighter.gutterIconRenderer = ConsoleIndicatorRenderer(newIconWithTooltip)
}
private var consoleEditorHighlighter by Delegates.notNull<RangeHighlighter>() private var consoleEditorHighlighter by Delegates.notNull<RangeHighlighter>()
private var disposableDescriptor by Delegates.notNull<RunContentDescriptor>() private var disposableDescriptor by Delegates.notNull<RunContentDescriptor>()
val executor = KotlinConsoleExecutor(this, historyManager, historyHighlighter) val executor = CommandExecutor(this)
var compilerHelper: KotlinConsoleCompilerHelper by Delegates.notNull() var compilerHelper: ConsoleCompilerHelper by Delegates.notNull()
override fun createProcess() = cmdLine.createProcess() override fun createProcess() = cmdLine.createProcess()
override fun createConsoleView(): LanguageConsoleView? { override fun createConsoleView(): LanguageConsoleView? {
val consoleView = LanguageConsoleBuilder() val consoleView = LanguageConsoleBuilder()
.gutterContentProvider(KotlinConsoleGutterContentProvider()) .gutterContentProvider(ConsoleGutterContentProvider())
.build(project, KotlinLanguage.INSTANCE) .build(project, KotlinLanguage.INSTANCE)
consoleView.prompt = null consoleView.prompt = null
@@ -94,7 +103,9 @@ public class KotlinConsoleRunner(
val consoleEditor = consoleView.consoleEditor val consoleEditor = consoleView.consoleEditor
setupPlaceholder(consoleEditor) setupPlaceholder(consoleEditor)
consoleEditor.contentComponent.addKeyListener(historyManager) val historyKeyListener = HistoryKeyListener(module.project, consoleEditor, commandHistory)
consoleEditor.contentComponent.addKeyListener(historyKeyListener)
commandHistory.listeners.add(historyKeyListener)
val executeAction = KtExecuteCommandAction(consoleView.virtualFile) val executeAction = KtExecuteCommandAction(consoleView.virtualFile)
executeAction.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, consoleView.consoleEditor.component) executeAction.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, consoleView.consoleEditor.component)
@@ -103,9 +114,8 @@ public class KotlinConsoleRunner(
} }
override fun createProcessHandler(process: Process): OSProcessHandler { override fun createProcessHandler(process: Process): OSProcessHandler {
val processHandler = KotlinReplOutputHandler( val processHandler = ReplOutputHandler(
historyHighlighter, this,
KotlinReplOutputHighlighter(this, historyManager, testMode, previousCompilationFailed),
process, process,
cmdLine.commandLineString cmdLine.commandLineString
) )
@@ -131,7 +141,7 @@ public class KotlinConsoleRunner(
contentDescriptor: RunContentDescriptor contentDescriptor: RunContentDescriptor
): List<AnAction> { ): List<AnAction> {
disposableDescriptor = contentDescriptor disposableDescriptor = contentDescriptor
compilerHelper = KotlinConsoleCompilerHelper(project, module, defaultExecutor, contentDescriptor) compilerHelper = ConsoleCompilerHelper(project, module, defaultExecutor, contentDescriptor)
val actionList = arrayListOf<AnAction>( val actionList = arrayListOf<AnAction>(
BuildAndRestartConsoleAction(this), BuildAndRestartConsoleAction(this),
@@ -192,17 +202,13 @@ public class KotlinConsoleRunner(
} }
fun addGutterIndicator(editor: EditorEx, iconWithTooltip: IconWithTooltip): RangeHighlighter { fun addGutterIndicator(editor: EditorEx, iconWithTooltip: IconWithTooltip): RangeHighlighter {
val indicator = KotlinConsoleIndicatorRenderer(iconWithTooltip) val indicator = ConsoleIndicatorRenderer(iconWithTooltip)
val editorMarkup = editor.markupModel val editorMarkup = editor.markupModel
val indicatorHighlighter = editorMarkup.addRangeHighlighter( val indicatorHighlighter = editorMarkup.addRangeHighlighter(
0, editor.document.textLength, HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE 0, editor.document.textLength, HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE
) )
return indicatorHighlighter apply { gutterIconRenderer = indicator } return indicatorHighlighter.apply { gutterIconRenderer = indicator }
}
fun changeConsoleEditorIndicator(newIconWithTooltip: IconWithTooltip) = WriteCommandAction.runWriteCommandAction(project) {
consoleEditorHighlighter.gutterIconRenderer = KotlinConsoleIndicatorRenderer(newIconWithTooltip)
} }
@TestOnly fun dispose() { @TestOnly fun dispose() {
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.console.highlight package org.jetbrains.kotlin.console
import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.editor.markup.TextAttributes
@@ -31,16 +31,16 @@ public object ReplColors {
public val WARNING_INFO_CONTENT_TYPE: ConsoleViewContentType = public val WARNING_INFO_CONTENT_TYPE: ConsoleViewContentType =
ConsoleViewContentType( ConsoleViewContentType(
"KOTLIN_CONSOLE_WARNING_INFO", "KOTLIN_CONSOLE_WARNING_INFO",
TextAttributes() apply { fontType = Font.ITALIC; foregroundColor = JBColor.RED } TextAttributes().apply { fontType = Font.ITALIC; foregroundColor = JBColor.RED }
) )
public val INITIAL_PROMPT_CONTENT_TYPE: ConsoleViewContentType = public val INITIAL_PROMPT_CONTENT_TYPE: ConsoleViewContentType =
ConsoleViewContentType( ConsoleViewContentType(
"KOTLIN_CONSOLE_INITIAL_PROMPT", "KOTLIN_CONSOLE_INITIAL_PROMPT",
TextAttributes() apply { fontType = Font.BOLD } TextAttributes().apply { fontType = Font.BOLD }
) )
public val USER_OUTPUT_CONTENT_TYPE: ConsoleViewContentType = public val USER_OUTPUT_CONTENT_TYPE: ConsoleViewContentType =
ConsoleViewContentType( ConsoleViewContentType(
"KOTLIN_CONSOLE_USER_OUTPUT", "KOTLIN_CONSOLE_USER_OUTPUT",
TextAttributes() apply { fontType = Font.ITALIC; foregroundColor = Colors.DARK_GREEN } TextAttributes().apply { fontType = Font.ITALIC; foregroundColor = Colors.DARK_GREEN }
) )
} }
@@ -20,8 +20,6 @@ import com.intellij.execution.process.OSProcessHandler
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.console.highlight.KotlinHistoryHighlighter
import org.jetbrains.kotlin.console.highlight.KotlinReplOutputHighlighter
import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.diagnostics.Severity
import org.w3c.dom.Element import org.w3c.dom.Element
import org.xml.sax.InputSource import org.xml.sax.InputSource
@@ -36,15 +34,15 @@ public val SOURCE_CHARS: Array<String> = arrayOf("\n", "#")
data class SeverityDetails(val severity: Severity, val description: String, val range: TextRange) data class SeverityDetails(val severity: Severity, val description: String, val range: TextRange)
public class KotlinReplOutputHandler( class ReplOutputHandler(
private val historyHighlighter: KotlinHistoryHighlighter, private val runner: KotlinConsoleRunner,
private val outputHighlighter: KotlinReplOutputHighlighter,
process: Process, process: Process,
commandLine: String commandLine: String
) : OSProcessHandler(process, commandLine) { ) : OSProcessHandler(process, commandLine) {
private var isBuildInfoChecked = false private var isBuildInfoChecked = false
private val dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() private val dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
private val outputProcessor = ReplOutputProcessor(runner)
override fun isSilentlyDestroyOnClose() = true override fun isSilentlyDestroyOnClose() = true
@@ -62,24 +60,24 @@ public class KotlinReplOutputHandler(
when (outputType) { when (outputType) {
"INITIAL_PROMPT" -> buildWarningIfNeededBeforeInit(content) "INITIAL_PROMPT" -> buildWarningIfNeededBeforeInit(content)
"HELP_PROMPT" -> outputHighlighter.printHelp(content) "HELP_PROMPT" -> outputProcessor.printHelp(content)
"USER_OUTPUT" -> outputHighlighter.printUserOutput(content) "USER_OUTPUT" -> outputProcessor.printUserOutput(content)
"REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon(content) "REPL_RESULT" -> outputProcessor.printResultWithGutterIcon(content)
"READLINE_START" -> historyHighlighter.isReadLineMode = true "READLINE_START" -> runner.isReadLineMode = true
"READLINE_END" -> historyHighlighter.isReadLineMode = false "READLINE_END" -> runner.isReadLineMode = false
"REPL_INCOMPLETE", "REPL_INCOMPLETE",
"COMPILE_ERROR" -> outputHighlighter.highlightCompilerErrors(createCompilerMessages(content)) "COMPILE_ERROR" -> outputProcessor.highlightCompilerErrors(createCompilerMessages(content))
"RUNTIME_ERROR" -> outputHighlighter.printRuntimeError("${content.trim()}\n") "RUNTIME_ERROR" -> outputProcessor.printRuntimeError("${content.trim()}\n")
"INTERNAL_ERROR" -> outputHighlighter.printInternalErrorMessage(content) "INTERNAL_ERROR" -> outputProcessor.printInternalErrorMessage(content)
} }
} }
private fun buildWarningIfNeededBeforeInit(content: String) { private fun buildWarningIfNeededBeforeInit(content: String) {
if (!isBuildInfoChecked) { if (!isBuildInfoChecked) {
outputHighlighter.printBuildInfoWarningIfNeeded() outputProcessor.printBuildInfoWarningIfNeeded()
isBuildInfoChecked = true isBuildInfoChecked = true
} }
outputHighlighter.printInitialPrompt(content) outputProcessor.printInitialPrompt(content)
} }
private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding))) private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding)))
@@ -14,13 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.console.highlight package org.jetbrains.kotlin.console
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.HighlightInfoType import com.intellij.codeInsight.daemon.impl.HighlightInfoType
import com.intellij.execution.console.LanguageConsoleImpl import com.intellij.execution.console.LanguageConsoleImpl
import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.lang.annotation.HighlightSeverity import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.colors.CodeInsightColors import com.intellij.openapi.editor.colors.CodeInsightColors
import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.openapi.editor.colors.TextAttributesKey
@@ -28,21 +29,15 @@ import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.console.KotlinConsoleHistoryManager
import org.jetbrains.kotlin.console.KotlinConsoleRunner
import org.jetbrains.kotlin.console.SeverityDetails
import org.jetbrains.kotlin.console.actions.logError import org.jetbrains.kotlin.console.actions.logError
import org.jetbrains.kotlin.console.gutter.IconWithTooltip import org.jetbrains.kotlin.console.gutter.IconWithTooltip
import org.jetbrains.kotlin.console.gutter.KotlinConsoleErrorRenderer import org.jetbrains.kotlin.console.gutter.ConsoleErrorRenderer
import org.jetbrains.kotlin.console.gutter.KotlinConsoleIndicatorRenderer import org.jetbrains.kotlin.console.gutter.ConsoleIndicatorRenderer
import org.jetbrains.kotlin.console.gutter.ReplIcons import org.jetbrains.kotlin.console.gutter.ReplIcons
import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.diagnostics.Severity
public class KotlinReplOutputHighlighter( public class ReplOutputProcessor(
private val runner: KotlinConsoleRunner, private val runner: KotlinConsoleRunner
private val historyManager: KotlinConsoleHistoryManager,
private val testMode: Boolean,
private val previousCompilationFailed: Boolean
) { ) {
private val project = runner.project private val project = runner.project
private val consoleView = runner.consoleView as LanguageConsoleImpl private val consoleView = runner.consoleView as LanguageConsoleImpl
@@ -53,7 +48,7 @@ public class KotlinReplOutputHighlighter(
private fun textOffsets(text: String): Pair<Int, Int> { private fun textOffsets(text: String): Pair<Int, Int> {
consoleView.flushDeferredText() // flush before getting offsets to calculate them properly consoleView.flushDeferredText() // flush before getting offsets to calculate them properly
val oldLen = historyDocument.textLength val oldLen = historyDocument.textLength
val newLen = oldLen + text.length() val newLen = oldLen + text.length
return Pair(oldLen, newLen) return Pair(oldLen, newLen)
} }
@@ -68,7 +63,7 @@ public class KotlinReplOutputHighlighter(
historyMarkup.addRangeHighlighter( historyMarkup.addRangeHighlighter(
startOffset, endOffset, HighlighterLayer.LAST, null, HighlighterTargetArea.EXACT_RANGE startOffset, endOffset, HighlighterLayer.LAST, null, HighlighterTargetArea.EXACT_RANGE
) apply { gutterIconRenderer = KotlinConsoleIndicatorRenderer(iconWithTooltip) } ).apply { gutterIconRenderer = ConsoleIndicatorRenderer(iconWithTooltip) }
} }
private fun printWarningMessage(message: String, isAddHyperlink: Boolean) = WriteCommandAction.runWriteCommandAction(project) { private fun printWarningMessage(message: String, isAddHyperlink: Boolean) = WriteCommandAction.runWriteCommandAction(project) {
@@ -86,8 +81,8 @@ public class KotlinReplOutputHighlighter(
} }
fun printBuildInfoWarningIfNeeded() { fun printBuildInfoWarningIfNeeded() {
if (testMode) return if (ApplicationManager.getApplication().isUnitTestMode) return
if (previousCompilationFailed) return printWarningMessage("There were compilation errors in module ${runner.module.name}", false) if (runner.previousCompilationFailed) return printWarningMessage("There were compilation errors in module ${runner.module.name}", false)
if (runner.compilerHelper.moduleIsUpToDate()) return if (runner.compilerHelper.moduleIsUpToDate()) return
val compilerWarningMessage = "Youre running the REPL with outdated classes: " val compilerWarningMessage = "Youre running the REPL with outdated classes: "
@@ -109,7 +104,8 @@ public class KotlinReplOutputHighlighter(
} }
fun highlightCompilerErrors(compilerMessages: List<SeverityDetails>) = WriteCommandAction.runWriteCommandAction(project) { fun highlightCompilerErrors(compilerMessages: List<SeverityDetails>) = WriteCommandAction.runWriteCommandAction(project) {
val lastCommandStartOffset = historyDocument.textLength - historyManager.lastCommandLength - 1 val commandHistory = runner.commandHistory
val lastCommandStartOffset = historyDocument.textLength - commandHistory[commandHistory.size - 1].entryText.length - 1
val lastCommandStartLine = historyDocument.getLineNumber(lastCommandStartOffset) val lastCommandStartLine = historyDocument.getLineNumber(lastCommandStartOffset)
val historyCommandRunIndicator = historyMarkup.allHighlighters.filter { val historyCommandRunIndicator = historyMarkup.allHighlighters.filter {
historyDocument.getLineNumber(it.startOffset) == lastCommandStartLine && it.gutterIconRenderer != null historyDocument.getLineNumber(it.startOffset) == lastCommandStartLine && it.gutterIconRenderer != null
@@ -120,7 +116,7 @@ public class KotlinReplOutputHighlighter(
}.groupBy { message -> }.groupBy { message ->
val cmdStart = lastCommandStartOffset + message.range.startOffset val cmdStart = lastCommandStartOffset + message.range.startOffset
historyEditor.document.getLineNumber(cmdStart) historyEditor.document.getLineNumber(cmdStart)
}.values().map { messages -> }.values.map { messages ->
val highlighters = messages.map { message -> val highlighters = messages.map { message ->
val cmdStart = lastCommandStartOffset + message.range.startOffset val cmdStart = lastCommandStartOffset + message.range.startOffset
val cmdEnd = lastCommandStartOffset + Math.max(message.range.endOffset, message.range.startOffset + 1) val cmdEnd = lastCommandStartOffset + Math.max(message.range.endOffset, message.range.startOffset + 1)
@@ -135,9 +131,9 @@ public class KotlinReplOutputHighlighter(
for ((highlighter, messages) in highlighterAndMessagesByLine) { for ((highlighter, messages) in highlighterAndMessagesByLine) {
if (historyDocument.getLineNumber(highlighter.startOffset) == lastCommandStartLine) if (historyDocument.getLineNumber(highlighter.startOffset) == lastCommandStartLine)
historyCommandRunIndicator.gutterIconRenderer = KotlinConsoleErrorRenderer(messages) historyCommandRunIndicator.gutterIconRenderer = ConsoleErrorRenderer(messages)
else else
highlighter.gutterIconRenderer = KotlinConsoleErrorRenderer(messages) highlighter.gutterIconRenderer = ConsoleErrorRenderer(messages)
} }
} }
@@ -24,7 +24,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.JBPopupFactory
import org.jetbrains.kotlin.console.KotlinConsoleKeeper import org.jetbrains.kotlin.console.KotlinConsoleKeeper
public class KotlinConsoleModuleDialog(private val project: Project) { public class ConsoleModuleDialog(private val project: Project) {
private val TITLE = "Choose context module..." private val TITLE = "Choose context module..."
public fun showIfNeeded(dataContext: DataContext) { public fun showIfNeeded(dataContext: DataContext) {
@@ -34,9 +34,9 @@ public class KotlinConsoleModuleDialog(private val project: Project) {
val modules = ModuleManager.getInstance(project).modules val modules = ModuleManager.getInstance(project).modules
if (modules.isEmpty()) return errorNotification(project, "No modules were found") if (modules.isEmpty()) return errorNotification(project, "No modules were found")
if (modules.size() == 1) return runConsole(modules.first()) if (modules.size == 1) return runConsole(modules.first())
val moduleActions = modules sortedBy { it.name } map { createRunAction(it) } val moduleActions = modules.sortedBy { it.name }.map { createRunAction(it) }
val moduleGroup = DefaultActionGroup(moduleActions) val moduleGroup = DefaultActionGroup(moduleActions)
val modulePopup = JBPopupFactory.getInstance().createActionGroupPopup( val modulePopup = JBPopupFactory.getInstance().createActionGroupPopup(
@@ -40,7 +40,7 @@ public class RunKotlinConsoleAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return errorNotification(null, "Project not found") val project = e.project ?: return errorNotification(null, "Project not found")
KotlinConsoleModuleDialog(project).showIfNeeded(e.dataContext) ConsoleModuleDialog(project).showIfNeeded(e.dataContext)
} }
} }
@@ -20,7 +20,7 @@ import com.intellij.openapi.editor.markup.GutterIconRenderer
import org.jetbrains.kotlin.console.SeverityDetails import org.jetbrains.kotlin.console.SeverityDetails
import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.diagnostics.Severity
public class KotlinConsoleErrorRenderer(private val messages: List<SeverityDetails>) : GutterIconRenderer() { public class ConsoleErrorRenderer(private val messages: List<SeverityDetails>) : GutterIconRenderer() {
private fun msgType(severity: Severity) = when (severity) { private fun msgType(severity: Severity) = when (severity) {
Severity.ERROR -> "Error:" Severity.ERROR -> "Error:"
Severity.WARNING -> "Warning:" Severity.WARNING -> "Warning:"
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.console.gutter
import com.intellij.execution.console.BasicGutterContentProvider import com.intellij.execution.console.BasicGutterContentProvider
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
public class KotlinConsoleGutterContentProvider : BasicGutterContentProvider() { public class ConsoleGutterContentProvider : BasicGutterContentProvider() {
/** /**
* This method overriding is needed to prevent [BasicGutterContentProvider] from adding some strange unicode * This method overriding is needed to prevent [BasicGutterContentProvider] from adding some strange unicode
* symbols of zero width and to ease range highlighting. * symbols of zero width and to ease range highlighting.
@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.console.gutter
import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.openapi.editor.markup.GutterIconRenderer
public class KotlinConsoleIndicatorRenderer(iconWithTooltip: IconWithTooltip) : GutterIconRenderer() { public class ConsoleIndicatorRenderer(iconWithTooltip: IconWithTooltip) : GutterIconRenderer() {
private val icon = iconWithTooltip.icon private val icon = iconWithTooltip.icon
private val tooltip = iconWithTooltip.tooltip private val tooltip = iconWithTooltip.tooltip
@@ -26,5 +26,5 @@ public class KotlinConsoleIndicatorRenderer(iconWithTooltip: IconWithTooltip) :
override fun getTooltipText() = tooltip override fun getTooltipText() = tooltip
override fun hashCode() = icon.hashCode() override fun hashCode() = icon.hashCode()
override fun equals(other: Any?) = icon == (other as? KotlinConsoleIndicatorRenderer)?.icon ?: null override fun equals(other: Any?) = icon == (other as? ConsoleIndicatorRenderer)?.icon ?: null
} }
@@ -34,7 +34,7 @@ public class KotlinReplTest : PlatformTestCase() {
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
consoleRunner = KotlinConsoleKeeper.getInstance(project).run(module, testMode = true)!! consoleRunner = KotlinConsoleKeeper.getInstance(project).run(module)!!
} }
override fun tearDown() { override fun tearDown() {