[ide-console] Highlight repl process output
Add icons on gutter Send messages to process in xml
This commit is contained in:
committed by
Pavel V. Talanov
parent
cb1756f5fa
commit
cb5ff094bf
@@ -114,9 +114,9 @@ public class ReplFromTerminal {
|
|||||||
|
|
||||||
private void doRun() {
|
private void doRun() {
|
||||||
try {
|
try {
|
||||||
replWriter.println("Welcome to Kotlin version " + KotlinVersion.VERSION +
|
replWriter.printlnInit("Welcome to Kotlin version " + KotlinVersion.VERSION +
|
||||||
" (JRE " + System.getProperty("java.runtime.version") + ")");
|
" (JRE " + System.getProperty("java.runtime.version") + ")");
|
||||||
replWriter.println("Type :help for help, :quit for quit");
|
replWriter.printlnInit("Type :help for help, :quit for quit");
|
||||||
WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE;
|
WhatNextAfterOneLine next = WhatNextAfterOneLine.READ_LINE;
|
||||||
while (true) {
|
while (true) {
|
||||||
next = one(next);
|
next = one(next);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ fun PsiElement.getModuleInfo(): IdeaModuleInfo {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val explicitModuleInfo = containingJetFile?.moduleInfo
|
val explicitModuleInfo = containingJetFile?.moduleInfo ?: (containingJetFile?.originalFile as? JetFile)?.moduleInfo
|
||||||
if (explicitModuleInfo is IdeaModuleInfo) return explicitModuleInfo
|
if (explicitModuleInfo is IdeaModuleInfo) return explicitModuleInfo
|
||||||
|
|
||||||
if (containingJetFile is JetCodeFragment) {
|
if (containingJetFile is JetCodeFragment) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="library" name="idea-full" level="project" />
|
<orderEntry type="library" name="idea-full" level="project" />
|
||||||
<orderEntry type="module" module-name="frontend" />
|
<orderEntry type="module" module-name="frontend" />
|
||||||
|
<orderEntry type="module" module-name="idea-analysis" />
|
||||||
<orderEntry type="module" module-name="util" />
|
<orderEntry type="module" module-name="util" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.BaseOSProcessHandler
|
||||||
|
import com.intellij.openapi.command.WriteCommandAction
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import org.jetbrains.kotlin.console.actions.logError
|
||||||
|
import org.jetbrains.kotlin.console.highlight.KotlinHistoryHighlighter
|
||||||
|
|
||||||
|
public class KotlinConsoleExecutor(
|
||||||
|
private val runner: KotlinConsoleRunner,
|
||||||
|
private val historyManager: KotlinConsoleHistoryManager
|
||||||
|
) {
|
||||||
|
private val XML_PREFIX = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||||
|
private val historyHighlighter = KotlinHistoryHighlighter(runner)
|
||||||
|
|
||||||
|
fun executeCommand() = WriteCommandAction.runWriteCommandAction(runner.project) {
|
||||||
|
val consoleView = runner.consoleView
|
||||||
|
val document = consoleView.editorDocument
|
||||||
|
val inputText = document.text.trim()
|
||||||
|
|
||||||
|
if (inputText.isNotEmpty()) {
|
||||||
|
val command = "$inputText\n"
|
||||||
|
document.setText("")
|
||||||
|
|
||||||
|
historyHighlighter.addAndHighlightNewCommand(command)
|
||||||
|
submitCommand(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitCommand(command: String) {
|
||||||
|
historyManager.updateHistory(command)
|
||||||
|
|
||||||
|
val processHandler = runner.processHandler
|
||||||
|
val processInputOS = processHandler.processInput ?: return logError(javaClass, "<p>Broken process stream</p>")
|
||||||
|
val charset = (processHandler as? BaseOSProcessHandler)?.charset ?: Charsets.UTF_8
|
||||||
|
|
||||||
|
val xmlRes = "$XML_PREFIX<input>${StringUtil.escapeXml(StringUtil.escapeLineBreak(command.trim()))}</input>"
|
||||||
|
val bytes = ("$xmlRes\n").toByteArray(charset)
|
||||||
|
processInputOS.write(bytes)
|
||||||
|
processInputOS.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-10
@@ -17,31 +17,45 @@
|
|||||||
package org.jetbrains.kotlin.console
|
package org.jetbrains.kotlin.console
|
||||||
|
|
||||||
import com.intellij.openapi.command.WriteCommandAction
|
import com.intellij.openapi.command.WriteCommandAction
|
||||||
|
import org.jetbrains.kotlin.console.highlight.ReplOutputType
|
||||||
import java.awt.event.KeyAdapter
|
import java.awt.event.KeyAdapter
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
|
|
||||||
public class KtConsoleKeyListener(private val ktConsole: KotlinConsoleRunner) : KeyAdapter() {
|
public class KotlinConsoleHistoryManager(private val ktConsole: KotlinConsoleRunner) : KeyAdapter() {
|
||||||
|
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 = ""
|
||||||
|
|
||||||
|
var lastCommandType = ReplOutputType.USER_OUTPUT
|
||||||
|
|
||||||
|
val lastCommandLength: Int
|
||||||
|
get() = history.last().length()
|
||||||
|
|
||||||
|
public fun updateHistory(command: String) {
|
||||||
|
if (lastCommandType == ReplOutputType.INCOMPLETE)
|
||||||
|
history[history.lastIndex] = "${history.last()}$command"
|
||||||
|
else
|
||||||
|
history.add(command)
|
||||||
|
|
||||||
|
// reset history positions
|
||||||
|
historyPos = history.size()
|
||||||
|
prevCaretOffset = -1
|
||||||
|
unfinishedCommand = ""
|
||||||
|
}
|
||||||
|
|
||||||
private enum class HistoryMove {
|
private enum class HistoryMove {
|
||||||
UP, DOWN
|
UP, DOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun resetHistoryPosition() {
|
|
||||||
historyPos = ktConsole.history.size()
|
|
||||||
prevCaretOffset = -1
|
|
||||||
unfinishedCommand = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = ktConsole.consoleView.consoleEditor.caretModel.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun moveHistoryCursor(move: HistoryMove) {
|
private fun moveHistoryCursor(move: HistoryMove) {
|
||||||
val history = ktConsole.history
|
|
||||||
if (history.isEmpty()) return
|
if (history.isEmpty()) return
|
||||||
|
|
||||||
val caret = ktConsole.consoleView.consoleEditor.caretModel
|
val caret = ktConsole.consoleView.consoleEditor.caretModel
|
||||||
@@ -42,13 +42,9 @@ public class KotlinConsoleKeeper(val project: Project) {
|
|||||||
|
|
||||||
fun run(module: Module): KotlinConsoleRunner? {
|
fun run(module: Module): KotlinConsoleRunner? {
|
||||||
val path = module.moduleFilePath
|
val path = module.moduleFilePath
|
||||||
val cmdLine = createCommandLine(module)
|
val cmdLine = createCommandLine(module) ?: return errorNotification(project, "<p>Module SDK not found</p>") let { null }
|
||||||
if (cmdLine == null) {
|
|
||||||
errorNotification(project, "<p>Module SDK not found</p>")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val consoleRunner = KotlinConsoleRunner(path, cmdLine, project, REPL_TITLE)
|
val consoleRunner = KotlinConsoleRunner(path, cmdLine, module, project, REPL_TITLE)
|
||||||
consoleRunner.initAndRun()
|
consoleRunner.initAndRun()
|
||||||
consoleRunner.setupGutters()
|
consoleRunner.setupGutters()
|
||||||
|
|
||||||
|
|||||||
@@ -22,58 +22,53 @@ import com.intellij.execution.console.*
|
|||||||
import com.intellij.execution.process.*
|
import com.intellij.execution.process.*
|
||||||
import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory
|
import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory
|
||||||
import com.intellij.execution.ui.RunContentDescriptor
|
import com.intellij.execution.ui.RunContentDescriptor
|
||||||
import com.intellij.icons.AllIcons
|
|
||||||
import com.intellij.openapi.actionSystem.*
|
import com.intellij.openapi.actionSystem.*
|
||||||
import com.intellij.openapi.editor.colors.EditorColors
|
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.ex.EditorEx
|
||||||
import com.intellij.openapi.editor.markup.*
|
import com.intellij.openapi.editor.markup.*
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction
|
import org.jetbrains.kotlin.console.actions.KtExecuteCommandAction
|
||||||
import org.jetbrains.kotlin.console.actions.logError
|
import org.jetbrains.kotlin.console.gutter.KotlinConsoleGutterContentProvider
|
||||||
|
import org.jetbrains.kotlin.console.gutter.KotlinConsoleIndicatorRenderer
|
||||||
|
import org.jetbrains.kotlin.console.gutter.ReplIcons
|
||||||
|
import org.jetbrains.kotlin.console.highlight.KotlinReplOutputHighlighter
|
||||||
|
import org.jetbrains.kotlin.console.highlight.ReplColors
|
||||||
import org.jetbrains.kotlin.idea.JetLanguage
|
import org.jetbrains.kotlin.idea.JetLanguage
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.productionSourceInfo
|
||||||
|
import org.jetbrains.kotlin.psi.JetFile
|
||||||
|
import org.jetbrains.kotlin.psi.moduleInfo
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
|
import java.awt.Font
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
import kotlin.properties.Delegates
|
|
||||||
|
|
||||||
public class KotlinConsoleRunner(
|
public class KotlinConsoleRunner(
|
||||||
val title: String,
|
private val title: String,
|
||||||
private val cmdLine: GeneralCommandLine,
|
private val cmdLine: GeneralCommandLine,
|
||||||
|
private val module: Module,
|
||||||
myProject: Project,
|
myProject: Project,
|
||||||
path: String?
|
path: String?
|
||||||
) : AbstractConsoleRunnerWithHistory<LanguageConsoleView>(myProject, title, path) {
|
) : AbstractConsoleRunnerWithHistory<LanguageConsoleView>(myProject, title, path) {
|
||||||
companion object {
|
private val editorToIndicator = ConcurrentHashMap<EditorEx, KotlinConsoleIndicatorRenderer>()
|
||||||
private val HISTORY_GUTTER_ICON = AllIcons.Debugger.Console
|
private val historyManager = KotlinConsoleHistoryManager(this)
|
||||||
private val EDITOR_GUTTER_ICON = AllIcons.Debugger.CommandLine
|
val executor = KotlinConsoleExecutor(this, historyManager)
|
||||||
}
|
|
||||||
|
|
||||||
private val keyEventListener = KtConsoleKeyListener(this)
|
|
||||||
private var historyHighlighter: KotlinReplResultHighlighter by Delegates.notNull()
|
|
||||||
val history: MutableList<String> = arrayListOf()
|
|
||||||
|
|
||||||
override fun createProcess() = cmdLine.createProcess()
|
override fun createProcess() = cmdLine.createProcess()
|
||||||
|
|
||||||
override fun createConsoleView(): LanguageConsoleView? {
|
override fun createConsoleView(): LanguageConsoleView? {
|
||||||
val consoleView = LanguageConsoleBuilder().build(project, JetLanguage.INSTANCE)
|
val consoleView = LanguageConsoleBuilder()
|
||||||
|
.gutterContentProvider(KotlinConsoleGutterContentProvider())
|
||||||
|
.build(project, JetLanguage.INSTANCE)
|
||||||
|
|
||||||
consoleView.prompt = null
|
consoleView.prompt = null
|
||||||
|
configureModuleForConsoleFile(consoleView)
|
||||||
|
|
||||||
val historyEditor = consoleView.historyViewer
|
|
||||||
val consoleEditor = consoleView.consoleEditor
|
val consoleEditor = consoleView.consoleEditor
|
||||||
consoleEditor.contentComponent.addKeyListener(keyEventListener)
|
|
||||||
|
|
||||||
historyEditor.document.addDocumentListener(object : DocumentAdapter() {
|
setupPlaceholder(consoleEditor)
|
||||||
override fun documentChanged(e: DocumentEvent): Unit =
|
consoleEditor.contentComponent.addKeyListener(historyManager)
|
||||||
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)
|
val executeAction = KtExecuteCommandAction(consoleView.virtualFile)
|
||||||
executeAction.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, consoleView.consoleEditor.component)
|
executeAction.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, consoleView.consoleEditor.component)
|
||||||
@@ -82,25 +77,20 @@ public class KotlinConsoleRunner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun createProcessHandler(process: Process): OSProcessHandler {
|
override fun createProcessHandler(process: Process): OSProcessHandler {
|
||||||
val processHandler = KotlinReplOutputHandler(process, cmdLine.commandLineString)
|
val processHandler = KotlinReplOutputHandler(KotlinReplOutputHighlighter(this, historyManager), process, cmdLine.commandLineString)
|
||||||
historyHighlighter.rangeQueue = processHandler.rangeQueue
|
|
||||||
val consoleFile = consoleView.virtualFile
|
val consoleFile = consoleView.virtualFile
|
||||||
val keeper = KotlinConsoleKeeper.getInstance(project)
|
val keeper = KotlinConsoleKeeper.getInstance(project)
|
||||||
|
|
||||||
keeper.putVirtualFileToConsole(consoleFile, this)
|
keeper.putVirtualFileToConsole(consoleFile, this)
|
||||||
processHandler.addProcessListener(object : ProcessAdapter() {
|
processHandler.addProcessListener(object : ProcessAdapter() {
|
||||||
override fun processTerminated(event: ProcessEvent) {
|
override fun processTerminated(_: ProcessEvent) { keeper.removeConsole(consoleFile) }
|
||||||
keeper.removeConsole(consoleFile)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return processHandler
|
return processHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createExecuteActionHandler() = object : ProcessBackedConsoleExecuteActionHandler(processHandler, false) {
|
override fun createExecuteActionHandler() = object : ProcessBackedConsoleExecuteActionHandler(processHandler, false) {
|
||||||
override fun sendText(line: String) {
|
override fun runExecuteAction(_: LanguageConsoleView) = executor.executeCommand()
|
||||||
submitCommand(line)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fillToolBarActions(toolbarActions: DefaultActionGroup,
|
override fun fillToolBarActions(toolbarActions: DefaultActionGroup,
|
||||||
@@ -118,6 +108,22 @@ public class KotlinConsoleRunner(
|
|||||||
override fun createConsoleExecAction(consoleExecuteActionHandler: ProcessBackedConsoleExecuteActionHandler)
|
override fun createConsoleExecAction(consoleExecuteActionHandler: ProcessBackedConsoleExecuteActionHandler)
|
||||||
= ConsoleExecuteAction(consoleView, consoleExecuteActionHandler, "KotlinShellExecute", consoleExecuteActionHandler)
|
= ConsoleExecuteAction(consoleView, consoleExecuteActionHandler, "KotlinShellExecute", consoleExecuteActionHandler)
|
||||||
|
|
||||||
|
private fun setupPlaceholder(editor: EditorEx) {
|
||||||
|
editor.setPlaceholder("<Ctrl+Enter> to execute")
|
||||||
|
editor.setShowPlaceholderWhenFocused(true)
|
||||||
|
|
||||||
|
val placeholderAttrs = TextAttributes()
|
||||||
|
placeholderAttrs.foregroundColor = ReplColors.PLACEHOLDER_COLOR
|
||||||
|
placeholderAttrs.fontType = Font.ITALIC
|
||||||
|
editor.setPlaceholderAttributes(placeholderAttrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureModuleForConsoleFile(consoleView: LanguageConsoleView) {
|
||||||
|
val consoleFile = consoleView.virtualFile
|
||||||
|
val jetFile = PsiManager.getInstance(project).findFile(consoleFile) as JetFile
|
||||||
|
jetFile.moduleInfo = module.productionSourceInfo()
|
||||||
|
}
|
||||||
|
|
||||||
fun setupGutters() {
|
fun setupGutters() {
|
||||||
fun configureEditorGutter(editor: EditorEx, color: Color, icon: Icon) {
|
fun configureEditorGutter(editor: EditorEx, color: Color, icon: Icon) {
|
||||||
editor.settings.isLineMarkerAreaShown = true // hack to show gutter
|
editor.settings.isLineMarkerAreaShown = true // hack to show gutter
|
||||||
@@ -127,37 +133,27 @@ public class KotlinConsoleRunner(
|
|||||||
editorColorScheme.setColor(EditorColors.GUTTER_BACKGROUND, color)
|
editorColorScheme.setColor(EditorColors.GUTTER_BACKGROUND, color)
|
||||||
editor.colorsScheme = editorColorScheme
|
editor.colorsScheme = editorColorScheme
|
||||||
|
|
||||||
addGutterIcon(editor, icon)
|
addGutterIndicator(editor, icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
val consoleView = consoleView
|
configureEditorGutter(consoleView.historyViewer, ReplColors.HISTORY_GUTTER_COLOR, ReplIcons.HISTORY_INDICATOR)
|
||||||
val lightGray = Color(0xF2, 0xF2, 0xF2)
|
configureEditorGutter(consoleView.consoleEditor, ReplColors.EDITOR_GUTTER_COLOR, ReplIcons.EDITOR_INDICATOR)
|
||||||
val lightBlue = Color(0x93, 0xDE, 0xFF)
|
|
||||||
configureEditorGutter(consoleView.historyViewer, lightGray, HISTORY_GUTTER_ICON)
|
consoleView.consoleEditor.settings.isCaretRowShown = true
|
||||||
configureEditorGutter(consoleView.consoleEditor, lightBlue, EDITOR_GUTTER_ICON)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addGutterIcon(editor: EditorEx, icon: Icon) {
|
fun addGutterIndicator(editor: EditorEx, icon: Icon) {
|
||||||
|
val indicator = KotlinConsoleIndicatorRenderer(icon)
|
||||||
val editorMarkup = editor.markupModel
|
val editorMarkup = editor.markupModel
|
||||||
val highlighter = editorMarkup.addRangeHighlighter(0, editor.document.textLength, HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE)
|
val indicatorHighlighter = editorMarkup.addRangeHighlighter(
|
||||||
highlighter.gutterIconRenderer = object : GutterIconRenderer() {
|
0, editor.document.textLength, HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE
|
||||||
override fun getIcon() = icon
|
)
|
||||||
override fun hashCode() = System.identityHashCode(this)
|
|
||||||
override fun equals(other: Any?) = this === other
|
indicatorHighlighter.gutterIconRenderer = indicator
|
||||||
}
|
editorToIndicator[editor] = indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun submitCommand(command: String) {
|
fun changeEditorIndicatorIcon(editor: EditorEx, newIcon: Icon) {
|
||||||
val res = command.trim()
|
editorToIndicator[editor]?.indicatorIcon = newIcon
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,21 +20,23 @@ 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.KotlinReplOutputHighlighter
|
||||||
|
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
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
|
data class SeverityDetails(val severity: Severity, val description: String, val range: TextRange)
|
||||||
|
|
||||||
public class KotlinReplOutputHandler(
|
public class KotlinReplOutputHandler(
|
||||||
|
private val outputHighlighter: KotlinReplOutputHighlighter,
|
||||||
process: Process,
|
process: Process,
|
||||||
commandLine: String
|
commandLine: String
|
||||||
) : OSProcessHandler(process, commandLine) {
|
) : OSProcessHandler(process, commandLine) {
|
||||||
private val XML_START = "<?xml"
|
private val XML_START = "<?xml"
|
||||||
private val dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
private val dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
val rangeQueue: Queue<TextRange> = ConcurrentLinkedQueue()
|
|
||||||
|
|
||||||
override fun notifyTextAvailable(text: String, key: Key<*>?) {
|
override fun notifyTextAvailable(text: String, key: Key<*>?) {
|
||||||
// skip "/usr/lib/jvm/java-8-oracle/bin/java -cp ..." intro
|
// skip "/usr/lib/jvm/java-8-oracle/bin/java -cp ..." intro
|
||||||
@@ -46,21 +48,38 @@ public class KotlinReplOutputHandler(
|
|||||||
val content = StringUtil.unescapeStringCharacters(root.textContent).trim()
|
val content = StringUtil.unescapeStringCharacters(root.textContent).trim()
|
||||||
|
|
||||||
when (outputType) {
|
when (outputType) {
|
||||||
"ORDINARY" -> super.notifyTextAvailable("$content\n", key)
|
"INITIAL_PROMPT" -> super.notifyTextAvailable("$content\n", key)
|
||||||
"REPORT" -> {
|
"USER_OUTPUT" -> outputHighlighter.printUserOutput(content)
|
||||||
val report = dBuilder.parse(strToSource(content, Charsets.UTF_16BE))
|
"REPL_RESULT" -> outputHighlighter.printResultWithGutterIcon("$content\n")
|
||||||
|
"REPL_INCOMPLETE" -> outputHighlighter.changeIndicatorOnIncomplete()
|
||||||
|
"ERROR" -> {
|
||||||
|
val compilerMessages = arrayListOf<SeverityDetails>()
|
||||||
|
|
||||||
|
val report = dBuilder.parse(strToSource(content, Charsets.UTF_16))
|
||||||
val entries = report.getElementsByTagName("reportEntry")
|
val entries = report.getElementsByTagName("reportEntry")
|
||||||
for (i in 0..entries.length - 1) {
|
for (i in 0..entries.length - 1) {
|
||||||
val reportEntry = entries.item(i) as Element
|
val reportEntry = entries.item(i) as Element
|
||||||
|
|
||||||
|
val severityLevel = reportEntry.getAttribute("severity").toSeverity()
|
||||||
val rangeStart = reportEntry.getAttribute("rangeStart").toInt()
|
val rangeStart = reportEntry.getAttribute("rangeStart").toInt()
|
||||||
val rangeEnd = reportEntry.getAttribute("rangeEnd").toInt()
|
val rangeEnd = reportEntry.getAttribute("rangeEnd").toInt()
|
||||||
|
val description = reportEntry.textContent
|
||||||
|
|
||||||
rangeQueue.add(TextRange(rangeStart, rangeEnd))
|
compilerMessages.add(SeverityDetails(severityLevel, description, TextRange(rangeStart, rangeEnd)))
|
||||||
super.notifyTextAvailable("${StringUtil.unescapeXml(reportEntry.textContent)}\n", key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputHighlighter.highlightErrors(compilerMessages)
|
||||||
}
|
}
|
||||||
|
else -> super.notifyTextAvailable("UNEXPECTED TEXT: $content\n", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)))
|
||||||
|
|
||||||
|
private fun String.toSeverity() = when (this) {
|
||||||
|
"ERROR" -> Severity.ERROR
|
||||||
|
"WARNING" -> Severity.WARNING
|
||||||
|
"INFO" -> Severity.INFO
|
||||||
|
else -> throw IllegalArgumentException("Unsupported Severity: '$this'") // this case shouldn't occur
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+10
@@ -27,6 +27,7 @@ import com.intellij.openapi.module.Module
|
|||||||
import com.intellij.openapi.module.ModuleManager
|
import com.intellij.openapi.module.ModuleManager
|
||||||
import com.intellij.openapi.module.ModuleUtilCore
|
import com.intellij.openapi.module.ModuleUtilCore
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import org.jetbrains.kotlin.console.KotlinConsoleKeeper
|
import org.jetbrains.kotlin.console.KotlinConsoleKeeper
|
||||||
|
|
||||||
fun errorNotification(project: Project?, message: String) {
|
fun errorNotification(project: Project?, message: String) {
|
||||||
@@ -59,4 +60,13 @@ public class RunKotlinConsoleAction : AnAction() {
|
|||||||
|
|
||||||
return ModuleManager.getInstance(project).modules.firstOrNull()
|
return ModuleManager.getInstance(project).modules.firstOrNull()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 an invalid console</p>")
|
||||||
|
|
||||||
|
ktConsole.executor.executeCommand()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.gutter
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.markup.GutterIconRenderer
|
||||||
|
import org.jetbrains.kotlin.console.SeverityDetails
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Severity
|
||||||
|
|
||||||
|
public class KotlinConsoleErrorRenderer(private val messages: List<SeverityDetails>) : GutterIconRenderer() {
|
||||||
|
private fun msgType(severity: Severity) = when (severity) {
|
||||||
|
Severity.ERROR -> "Error:"
|
||||||
|
Severity.WARNING -> "Warning:"
|
||||||
|
Severity.INFO -> "Info:"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTooltipText(): String {
|
||||||
|
val htmlTooltips = messages map { "<b>${msgType(it.severity)}</b> ${it.description}" }
|
||||||
|
return "<html>${htmlTooltips.join("<hr size=1 noshade>")}</html>"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIcon() = ReplIcons.ERROR
|
||||||
|
override fun hashCode() = System.identityHashCode(this)
|
||||||
|
override fun equals(other: Any?) = this === other
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.gutter
|
||||||
|
|
||||||
|
import com.intellij.execution.console.BasicGutterContentProvider
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
|
||||||
|
public class KotlinConsoleGutterContentProvider : BasicGutterContentProvider() {
|
||||||
|
/**
|
||||||
|
* This method overriding is needed to prevent [BasicGutterContentProvider] from adding some strange unicode
|
||||||
|
* symbols of zero width and to ease range highlighting.
|
||||||
|
*/
|
||||||
|
override fun beforeEvaluate(_: Editor) = Unit
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.gutter
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.markup.GutterIconRenderer
|
||||||
|
import javax.swing.Icon
|
||||||
|
|
||||||
|
public class KotlinConsoleIndicatorRenderer(var indicatorIcon: Icon) : GutterIconRenderer() {
|
||||||
|
override fun getIcon() = indicatorIcon
|
||||||
|
override fun hashCode() = System.identityHashCode(this)
|
||||||
|
override fun equals(other: Any?) = this === other
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.gutter
|
||||||
|
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
|
import javax.swing.Icon
|
||||||
|
|
||||||
|
public object ReplIcons {
|
||||||
|
public val HISTORY_INDICATOR: Icon = AllIcons.General.MessageHistory
|
||||||
|
public val EDITOR_INDICATOR: Icon = AllIcons.Debugger.CommandLine
|
||||||
|
public val INCOMPLETE_INDICATOR: Icon = AllIcons.Nodes.Desktop
|
||||||
|
public val RESULT: Icon = AllIcons.Vcs.Equal
|
||||||
|
public val ERROR: Icon = AllIcons.General.Error
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.highlight
|
||||||
|
|
||||||
|
import com.intellij.execution.impl.ConsoleViewUtil
|
||||||
|
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||||
|
import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterLayer
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||||
|
import org.jetbrains.kotlin.console.KotlinConsoleRunner
|
||||||
|
import org.jetbrains.kotlin.console.gutter.ReplIcons
|
||||||
|
|
||||||
|
public class KotlinHistoryHighlighter(private val runner: KotlinConsoleRunner ) {
|
||||||
|
fun addAndHighlightNewCommand(command: String) {
|
||||||
|
val historyEditor = runner.consoleView.historyViewer
|
||||||
|
val historyDocument = historyEditor.document
|
||||||
|
|
||||||
|
if (historyDocument.textLength == 0) { // this will work first time after 'Clear all' action
|
||||||
|
historyDocument.setText("\n")
|
||||||
|
runner.addGutterIndicator(historyEditor, ReplIcons.HISTORY_INDICATOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
val oldHistoryLength = historyDocument.textLength
|
||||||
|
historyDocument.insertString(oldHistoryLength, command)
|
||||||
|
EditorUtil.scrollToTheEnd(historyEditor)
|
||||||
|
|
||||||
|
val inputEditor = runner.consoleView.consoleEditor
|
||||||
|
val lexerHighlighter = inputEditor.highlighter as? LexerEditorHighlighter ?: return
|
||||||
|
val syntaxHighlighter = lexerHighlighter.syntaxHighlighter ?: return
|
||||||
|
|
||||||
|
val lexer = syntaxHighlighter.highlightingLexer
|
||||||
|
lexer.start(command, 0, command.length(), 0)
|
||||||
|
|
||||||
|
val historyMarkupModel = historyEditor.markupModel
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val tokenType = lexer.tokenType ?: break
|
||||||
|
val tokenStartOffset = oldHistoryLength + lexer.tokenStart
|
||||||
|
val tokenEndOffset = oldHistoryLength + lexer.tokenEnd
|
||||||
|
|
||||||
|
val contentType = ConsoleViewUtil.getContentTypeForToken(tokenType, syntaxHighlighter)
|
||||||
|
historyMarkupModel.addRangeHighlighter(
|
||||||
|
tokenStartOffset, tokenEndOffset, HighlighterLayer.LAST,
|
||||||
|
contentType.attributes, HighlighterTargetArea.EXACT_RANGE
|
||||||
|
)
|
||||||
|
|
||||||
|
lexer.advance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* 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.highlight
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
|
||||||
|
import com.intellij.lang.annotation.HighlightSeverity
|
||||||
|
import com.intellij.openapi.command.WriteCommandAction
|
||||||
|
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||||
|
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||||
|
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterLayer
|
||||||
|
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||||
|
import com.intellij.openapi.editor.markup.TextAttributes
|
||||||
|
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.gutter.KotlinConsoleErrorRenderer
|
||||||
|
import org.jetbrains.kotlin.console.gutter.KotlinConsoleIndicatorRenderer
|
||||||
|
import org.jetbrains.kotlin.console.gutter.ReplIcons
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Severity
|
||||||
|
|
||||||
|
enum class ReplOutputType {
|
||||||
|
USER_OUTPUT,
|
||||||
|
RESULT,
|
||||||
|
INCOMPLETE,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KotlinReplOutputHighlighter(
|
||||||
|
private val runner: KotlinConsoleRunner,
|
||||||
|
private val historyManager: KotlinConsoleHistoryManager
|
||||||
|
) {
|
||||||
|
private val consoleView = runner.consoleView
|
||||||
|
private val historyEditor = consoleView.historyViewer
|
||||||
|
private val historyDocument = historyEditor.document
|
||||||
|
private val historyMarkup = historyEditor.markupModel
|
||||||
|
|
||||||
|
private fun resetConsoleEditorIndicator() = runner.changeEditorIndicatorIcon(consoleView.consoleEditor, ReplIcons.EDITOR_INDICATOR)
|
||||||
|
|
||||||
|
private fun insertText(text: String): Pair<Int, Int> {
|
||||||
|
val oldLen = historyDocument.textLength
|
||||||
|
val newLen = oldLen + text.length()
|
||||||
|
|
||||||
|
historyDocument.insertString(oldLen, text)
|
||||||
|
EditorUtil.scrollToTheEnd(historyEditor)
|
||||||
|
|
||||||
|
return oldLen to newLen
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printUserOutput(command: String) {
|
||||||
|
resetConsoleEditorIndicator()
|
||||||
|
historyManager.lastCommandType = ReplOutputType.USER_OUTPUT
|
||||||
|
consoleView.print(command, ReplColors.USER_OUTPUT_CONTENT_TYPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printResultWithGutterIcon(result: String) = WriteCommandAction.runWriteCommandAction(runner.project) {
|
||||||
|
resetConsoleEditorIndicator()
|
||||||
|
historyManager.lastCommandType = ReplOutputType.RESULT
|
||||||
|
|
||||||
|
val (startOffset, endOffset) = insertText(result)
|
||||||
|
|
||||||
|
val highlighter = historyMarkup.addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST, null, HighlighterTargetArea.EXACT_RANGE)
|
||||||
|
highlighter.gutterIconRenderer = KotlinConsoleIndicatorRenderer(ReplIcons.RESULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeIndicatorOnIncomplete() {
|
||||||
|
historyManager.lastCommandType = ReplOutputType.INCOMPLETE
|
||||||
|
runner.changeEditorIndicatorIcon(consoleView.consoleEditor, ReplIcons.INCOMPLETE_INDICATOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun highlightErrors(compilerMessages: List<SeverityDetails>) = WriteCommandAction.runWriteCommandAction(runner.project) {
|
||||||
|
resetConsoleEditorIndicator()
|
||||||
|
historyManager.lastCommandType = ReplOutputType.ERROR
|
||||||
|
|
||||||
|
val lastCommandStartOffset = historyDocument.textLength - historyManager.lastCommandLength
|
||||||
|
val highlighterAndMessagesByLine = compilerMessages.filter {
|
||||||
|
it.severity == Severity.ERROR || it.severity == Severity.WARNING
|
||||||
|
}.groupBy { message ->
|
||||||
|
val cmdStart = lastCommandStartOffset + message.range.startOffset
|
||||||
|
historyEditor.document.getLineNumber(cmdStart)
|
||||||
|
}.values().map { messages ->
|
||||||
|
val highlighters = messages.map { message ->
|
||||||
|
val cmdStart = lastCommandStartOffset + message.range.startOffset
|
||||||
|
val cmdEnd = lastCommandStartOffset + message.range.endOffset
|
||||||
|
|
||||||
|
val textAttributes = getAttributesForSeverity(cmdStart, cmdEnd, message.severity)
|
||||||
|
historyMarkup.addRangeHighlighter(
|
||||||
|
cmdStart, cmdEnd, HighlighterLayer.LAST, textAttributes, HighlighterTargetArea.EXACT_RANGE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Pair(highlighters.first(), messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((highlighter, messages) in highlighterAndMessagesByLine) {
|
||||||
|
highlighter.gutterIconRenderer = KotlinConsoleErrorRenderer(messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAttributesForSeverity(start: Int, end: Int, severity: Severity): TextAttributes {
|
||||||
|
val attributes = when (severity) {
|
||||||
|
Severity.ERROR -> getAttributesForSeverity(HighlightInfoType.ERROR, HighlightSeverity.ERROR, CodeInsightColors.ERRORS_ATTRIBUTES, start, end)
|
||||||
|
Severity.WARNING -> getAttributesForSeverity(HighlightInfoType.WARNING, HighlightSeverity.WARNING, CodeInsightColors.WARNINGS_ATTRIBUTES, start, end)
|
||||||
|
Severity.INFO -> getAttributesForSeverity(HighlightInfoType.WEAK_WARNING, HighlightSeverity.WEAK_WARNING, CodeInsightColors.WEAK_WARNING_ATTRIBUTES, start, end)
|
||||||
|
}
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAttributesForSeverity(
|
||||||
|
infoType: HighlightInfoType,
|
||||||
|
severity: HighlightSeverity,
|
||||||
|
insightColors: TextAttributesKey,
|
||||||
|
start: Int,
|
||||||
|
end: Int
|
||||||
|
): TextAttributes {
|
||||||
|
val highlightInfo = HighlightInfo.newHighlightInfo(infoType).range(start, end).severity(severity).textAttributes(insightColors).create()
|
||||||
|
|
||||||
|
val psiFile = PsiDocumentManager.getInstance(runner.project).getPsiFile(runner.consoleView.consoleEditor.document)
|
||||||
|
val colorScheme = runner.consoleView.consoleEditor.colorsScheme
|
||||||
|
|
||||||
|
return highlightInfo?.getTextAttributes(psiFile, colorScheme) ?: TextAttributes()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.highlight
|
||||||
|
|
||||||
|
import com.intellij.execution.ui.ConsoleViewContentType
|
||||||
|
import com.intellij.openapi.editor.markup.TextAttributes
|
||||||
|
import com.intellij.ui.Colors
|
||||||
|
import com.intellij.ui.Gray
|
||||||
|
import com.intellij.ui.JBColor
|
||||||
|
import java.awt.Font
|
||||||
|
|
||||||
|
public object ReplColors {
|
||||||
|
public val HISTORY_GUTTER_COLOR: JBColor = JBColor(Gray.xF2, Gray.x41)
|
||||||
|
public val EDITOR_GUTTER_COLOR: JBColor = JBColor(Gray.xCF, Gray.x31)
|
||||||
|
public val PLACEHOLDER_COLOR: JBColor = JBColor.LIGHT_GRAY
|
||||||
|
public val USER_OUTPUT_CONTENT_TYPE: ConsoleViewContentType =
|
||||||
|
ConsoleViewContentType(
|
||||||
|
"KOTLIN_CONSOLE_USER_OUTPUT",
|
||||||
|
TextAttributes() apply { fontType = Font.ITALIC; foregroundColor = Colors.DARK_GREEN }
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -83,9 +83,9 @@ public class KotlinReplTest : PlatformTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test fun testSimpleCommand() {
|
@Test fun testSimpleCommand() {
|
||||||
consoleRunner.submitCommand("1 + 1")
|
consoleRunner.pipeline.submitCommand("1 + 1")
|
||||||
val docHistory = checkHistoryUpdate { x: String -> x.endsWith('2') }
|
val docHistory = checkHistoryUpdate { x: String -> x.endsWith("2</output>") }
|
||||||
|
|
||||||
assertTrue(docHistory.endsWith('2'), "1 + 1 should be equal 2, but document history is: '$docHistory'")
|
assertTrue(docHistory.endsWith("2</output>"), "1 + 1 should be equal 2, but document history is: '$docHistory'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user