PY-74294: Make dependency on intellij.platform.images non-essential

GitOrigin-RevId: 74593ea0b9f72c9aa4ae896f277a3baf004a9f3f
This commit is contained in:
Ilya Muradyan
2024-07-30 14:19:59 +03:00
committed by intellij-monorepo-bot
parent 88d09ecffd
commit 23e7f52620
16 changed files with 176 additions and 809 deletions

1
.idea/modules.xml generated
View File

@@ -551,6 +551,7 @@
<module fileurl="file://$PROJECT_DIR$/plugins/ml-local-models/intellij.ml.models.local.iml" filepath="$PROJECT_DIR$/plugins/ml-local-models/intellij.ml.models.local.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/ml-local-models/java/intellij.ml.models.local.java.iml" filepath="$PROJECT_DIR$/plugins/ml-local-models/java/intellij.ml.models.local.java.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/built-in-server/client/node-rpc-client/intellij.nodeRpcClient.iml" filepath="$PROJECT_DIR$/platform/built-in-server/client/node-rpc-client/intellij.nodeRpcClient.iml" />
<module fileurl="file://$PROJECT_DIR$/notebooks/images/intellij.notebooks.images.iml" filepath="$PROJECT_DIR$/notebooks/images/intellij.notebooks.images.iml" />
<module fileurl="file://$PROJECT_DIR$/jupyter/intellij.notebooks.jupyter.core.iml" filepath="$PROJECT_DIR$/jupyter/intellij.notebooks.jupyter.core.iml" />
<module fileurl="file://$PROJECT_DIR$/notebooks/notebook-ui/intellij.notebooks.ui.iml" filepath="$PROJECT_DIR$/notebooks/notebook-ui/intellij.notebooks.ui.iml" />
<module fileurl="file://$PROJECT_DIR$/notebooks/visualization/intellij.notebooks.visualization.iml" filepath="$PROJECT_DIR$/notebooks/visualization/intellij.notebooks.visualization.iml" />

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.images" />
<orderEntry type="module" module-name="intellij.notebooks.visualization" />
</component>
</module>

View File

@@ -0,0 +1,17 @@
<idea-plugin package="com.intellij.notebooks.images">
<dependencies>
<plugin id="com.intellij.platform.images"/>
<module name="intellij.notebooks.visualization"/>
</dependencies>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.notebooks.images.imageEditorFactory"
interface="com.intellij.notebooks.images.ImageEditorFactory"
dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij.notebooks.images">
<imageEditorFactory implementation="com.intellij.notebooks.images.DefaultImageEditorFactory"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,12 @@
package com.intellij.notebooks.images
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.intellij.images.editor.ImageEditor
import org.intellij.images.editor.impl.ImageEditorImpl
class DefaultImageEditorFactory : ImageEditorFactory {
override fun createImageEditor(project: Project, file: VirtualFile, graphicsPanel: GraphicsPanel): ImageEditor =
ImageEditorImpl(project, file)
}

View File

@@ -2,7 +2,7 @@
* Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.plugins.notebooks.visualization.r.inlays.components
package com.intellij.notebooks.images
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl
@@ -30,7 +30,9 @@ import org.intellij.images.ui.ImageComponent
import org.jetbrains.annotations.Nls
import org.jetbrains.concurrency.runAsync
import org.jetbrains.plugins.notebooks.visualization.r.VisualizationBundle
import org.jetbrains.plugins.notebooks.visualization.r.ui.UiCustomizer
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.EmptyComponentPanel
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.GraphicsManager
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.ImageInverter
import java.awt.BorderLayout
import java.awt.Component
import java.awt.Dimension
@@ -253,7 +255,7 @@ class GraphicsPanel(private val project: Project, private val disposableParent:
return@runInEdt
}
closeEditor(VisualizationBundle.message("graphics.not.available"))
val editor = UiCustomizer.instance.createImageEditor(project, file, this)
val editor = ImageEditorFactory.instance.createImageEditor(project, file, this)
adjustImageZoom(editor.zoomModel)
removeImageInfoLabelAndActionToolBar(editor)
currentImageFile = file

View File

@@ -0,0 +1,19 @@
package com.intellij.notebooks.images
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.intellij.images.editor.ImageEditor
interface ImageEditorFactory {
fun createImageEditor(project: Project,
file: VirtualFile,
graphicsPanel: GraphicsPanel): ImageEditor
companion object {
val EP_NAME: ExtensionPointName<ImageEditorFactory> = ExtensionPointName("com.intellij.notebooks.images.imageEditorFactory")
val instance: ImageEditorFactory get() = EP_NAME.extensionList.first()
}
}

View File

@@ -0,0 +1,77 @@
package com.intellij.notebooks.images
import com.intellij.openapi.Disposable
import com.intellij.openapi.editor.Editor
import org.jetbrains.concurrency.Promise
import org.jetbrains.plugins.notebooks.visualization.r.inlays.ClipboardUtils
import org.jetbrains.plugins.notebooks.visualization.r.inlays.InlayDimensions
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.*
import org.jetbrains.plugins.notebooks.visualization.r.inlays.runAsyncInlay
import java.io.File
import javax.swing.SwingUtilities
class InlayOutputImg(parent: Disposable, editor: Editor, clearAction: () -> Unit)
: InlayOutput(parent, editor, clearAction, loadActions(CopyImageToClipboardAction.ID, SaveOutputAction.ID, ClearOutputAction.ID)), InlayOutput.WithCopyImageToClipboard, InlayOutput.WithSaveAs {
private val graphicsPanel = GraphicsPanel(project, parent).apply {
isAdvancedMode = true
}
override val isFullWidth = false
init {
toolbarPane.dataComponent = graphicsPanel.component
}
override fun addToolbar() {
super.addToolbar()
graphicsPanel.overlayComponent = toolbarPane.toolbarComponent
}
override fun addData(data: String, type: String) {
showImageAsync(data, type).onSuccess {
SwingUtilities.invokeLater {
val maxHeight = graphicsPanel.maximumSize?.height ?: 0
val maxWidth = graphicsPanel.maximumSize?.width ?: 0
val height = InlayDimensions.calculateInlayHeight(maxWidth, maxHeight, editor)
onHeightCalculated?.invoke(height)
}
}
}
private fun showImageAsync(data: String, type: String): Promise<Unit> {
return runAsyncInlay {
when (type) {
"IMGBase64" -> graphicsPanel.showImageBase64(data)
"IMGSVG" -> graphicsPanel.showSvgImage(data)
"IMG" -> graphicsPanel.showImage(File(data))
else -> Unit
}
}
}
override fun clear() {
}
override fun scrollToTop() {
}
override fun getCollapsedDescription(): String {
return "foo"
}
override fun saveAs() {
graphicsPanel.image?.let { image ->
InlayOutputUtil.saveImageWithFileChooser(project, image)
}
}
override fun acceptType(type: String): Boolean {
return type == "IMG" || type == "IMGBase64" || type == "IMGSVG"
}
override fun copyImageToClipboard() {
graphicsPanel.image?.let { image ->
ClipboardUtils.copyImageToClipboard(image)
}
}
}

View File

@@ -1,7 +1,6 @@
<idea-plugin package="org.jetbrains.plugins.notebooks.visualization">
<module value="com.intellij.modules.notebooks.visualization" />
<dependencies>
<plugin id="com.intellij.platform.images"/>
<module name="intellij.notebooks.ui"/>
</dependencies>

View File

@@ -1,388 +0,0 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.plugins.notebooks.visualization.r.inlays
import com.intellij.execution.process.ProcessOutputType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.editor.markup.*
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import org.jetbrains.plugins.notebooks.ui.visualization.notebookAppearance
import org.jetbrains.plugins.notebooks.visualization.NotebookIntervalPointer
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.*
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.progress.InlayProgressStatus
import org.jetbrains.plugins.notebooks.visualization.r.ui.UiCustomizer
import java.awt.BorderLayout
import java.awt.Cursor
import java.awt.Graphics
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
import javax.swing.JComponent
import kotlin.math.max
import kotlin.math.min
class NotebookInlayComponentPsi(val cell: PsiElement, editor: EditorImpl) : NotebookInlayComponent(editor) {
override fun updateCellSeparator() {
if (!UiCustomizer.instance.showUpdateCellSeparator) {
return
}
if (separatorHighlighter != null &&
separatorHighlighter!!.startOffset == cell.textRange.startOffset &&
separatorHighlighter!!.endOffset == cell.textRange.endOffset) {
return
}
if (separatorHighlighter != null) {
editor.markupModel.removeHighlighter(separatorHighlighter!!)
}
// ToDo This is half-hack. The problem is that updateCellSeparator called from PSI change
// but if we have a lot of sequential changes in editor.document (line Backspace button is hold)
// document was updated but PSI update is async and that's why we have this check.
if (cell.textRange.endOffset > editor.document.textLength) {
return
}
try {
separatorHighlighter = createSeparatorHighlighter(editor, cell.textRange)
}
catch (e: Exception) {
e.printStackTrace()
}
}
}
class NotebookInlayComponentInterval(val cell: NotebookIntervalPointer, editor: EditorImpl) : NotebookInlayComponent(editor) {
override fun updateCellSeparator() {
if (!UiCustomizer.instance.showUpdateCellSeparator) {
return
}
if (separatorHighlighter != null) {
editor.markupModel.removeHighlighter(separatorHighlighter!!)
}
try {
val interval = cell.get() ?: return
val doc = editor.document
val textRange = TextRange(doc.getLineStartOffset(interval.lines.first), doc.getLineEndOffset(interval.lines.last))
separatorHighlighter = createSeparatorHighlighter(editor, textRange)
}
catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun createSeparatorHighlighter(editor: EditorImpl, textRange: TextRange) =
editor.markupModel.addRangeHighlighter(textRange.startOffset, textRange.endOffset,
HighlighterLayer.SYNTAX - 1, null,
HighlighterTargetArea.LINES_IN_RANGE).apply {
customRenderer = NotebookInlayComponent.separatorRenderer
lineMarkerRenderer = LineMarkerRenderer { _, g, r ->
val gutterWidth = ((editor as EditorEx).gutterComponentEx as JComponent).width
val y = r.y + r.height - editor.lineHeight
g.color = editor.colorsScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR)
g.drawLine(0, y, gutterWidth + 10, y)
}
}
abstract class NotebookInlayComponent(protected val editor: EditorImpl)
: InlayComponent(), MouseListener, MouseMotionListener {
companion object {
val separatorRenderer = CustomHighlighterRenderer { editor, highlighter1, g ->
val y1 = editor.offsetToPoint2D(highlighter1.endOffset).y.toInt()
g.color = editor.colorsScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR)
g.drawLine(0, y1, editor.component.width, y1)
}
}
lateinit var beforeHeightChanged: () -> Unit
lateinit var afterHeightChanged: () -> Unit
var selected = false
private var state: NotebookInlayState? = null
private var expandedHeight = 0
/** Inlay short view, shown in collapsed state or in empty state. */
private var toolbar: NotebookInlayToolbar? = null
private var gutter: JComponent? = null
private var mouseOverNewParagraphArea = false
protected var separatorHighlighter: RangeHighlighter? = null
private val disposable = Disposer.newDisposable()
/** If the value is `false`, the component won't limit its height and apply the height passed to [adjustSize] method directly. */
private var shouldLimitMaxHeight = true
init {
cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
border = JBUI.Borders.empty(InlayDimensions.topBorderUnscaled,
InlayDimensions.leftBorderUnscaled,
InlayDimensions.bottomBorderUnscaled,
InlayDimensions.rightBorderUnscaled)
Disposer.register(editor.disposable, disposable)
addMouseListener(this)
addMouseMotionListener(this)
}
// region MouseListener
override fun mouseReleased(e: MouseEvent?) {}
override fun mouseEntered(e: MouseEvent?) {}
override fun mouseExited(e: MouseEvent?) {
if (mouseOverNewParagraphArea) {
mouseOverNewParagraphArea = false
repaint(0, height - JBUI.scale(20), width, height)
}
}
override fun mousePressed(e: MouseEvent?) {}
override fun mouseClicked(e: MouseEvent?) {}
// endregion
// region MouseMotionListener
override fun mouseMoved(e: MouseEvent) {
val value = e.y > height - JBUI.scale(20)
if (value != mouseOverNewParagraphArea) {
mouseOverNewParagraphArea = value
repaint(0, height - JBUI.scale(20), width, height)
}
}
override fun mouseDragged(e: MouseEvent?) {}
// endregion
private fun getOrAddToolbar(): NotebookInlayToolbar {
toolbar?.let { return it }
return NotebookInlayToolbar().also {
toolbar = it
add(it, BorderLayout.CENTER)
}
}
private fun removeToolbar() {
toolbar?.let { remove(it) }
toolbar = null
}
override fun paintComponent(g: Graphics) {
/** Paints rounded rect panel - background of inlay component. */
val g2d = g.create()
g2d.color = //if (selected) {
//inlay!!.editor.colorsScheme.getAttributes(RMARKDOWN_CHUNK).backgroundColor
//}
//else {
(inlay!!.editor as EditorImpl).let {
it.notebookAppearance.getInlayBackgroundColor(it.colorsScheme) ?: it.backgroundColor
}
//}
g2d.fillRect(0, 0, width, InlayDimensions.topOffset + InlayDimensions.cornerRadius)
g2d.fillRect(0, height - InlayDimensions.bottomOffset - InlayDimensions.cornerRadius, width,
InlayDimensions.bottomOffset + InlayDimensions.cornerRadius)
g2d.color = UIUtil.getLabelBackground()
g2d.fillRoundRect(0, InlayDimensions.topOffset, width,
height - InlayDimensions.bottomOffset - InlayDimensions.topOffset,
InlayDimensions.cornerRadius, InlayDimensions.cornerRadius)
g2d.dispose()
}
/**
* Draw separator line below cell. Also fills cell background
*/
protected abstract fun updateCellSeparator()
override fun assignInlay(inlay: Inlay<*>) {
super.assignInlay(inlay)
updateCellSeparator()
gutter = editor.gutter as JComponent
}
override fun disposeInlay() {
if (separatorHighlighter != null && inlay != null) {
editor.markupModel.removeHighlighter(separatorHighlighter!!)
separatorHighlighter = null
}
super.disposeInlay()
}
fun dispose() {
Disposer.dispose(disposable)
}
/** Changes size of component and also called updateSize for inlay. */
override fun deltaSize(dx: Int, dy: Int) {
if (dx == 0 && dy == 0) {
return
}
val newWidth = max(size.width + dx, InlayDimensions.minWidth)
var newHeight = size.height + dy
newHeight = max(InlayDimensions.minHeight, newHeight)
val newDx = newWidth - size.width
val newDy = newHeight - size.height
super.deltaSize(newDx, newDy)
}
/** Creates a component for displaying output. */
private fun getOrCreateOutput(): NotebookInlayOutput {
if (state !is NotebookInlayOutput) {
if (state != null) {
remove(state)
}
state = NotebookInlayOutput(editor, disposable).apply {
addToolbar()
onHeightCalculated = { height ->
ApplicationManager.getApplication().invokeLater {
adjustSize(height)
}
}
}.also { addState(it) }
}
resizable = true
return state as NotebookInlayOutput
}
fun updateProgressStatus(progressStatus: InlayProgressStatus) {
state?.updateProgressStatus(progressStatus)
}
private fun addState(state: NotebookInlayState) {
add(state, BorderLayout.CENTER)
// Resizing only if inlay has minimal size or if we have expanded size set.
if (expandedHeight != 0) {
deltaSize(0, expandedHeight - size.height)
expandedHeight = 0
}
else if (height < InlayDimensions.minHeight) {
deltaSize(0, InlayDimensions.defaultHeight - height)
}
revalidate()
repaint()
}
/** Adjusts size of notebook output. Method called when success data comes with inlay component desired height. */
private fun adjustSize(height: Int) {
beforeHeightChanged()
var desiredHeight = height + InlayDimensions.topBorder + InlayDimensions.bottomBorder
if (shouldLimitMaxHeight) {
desiredHeight = min(InlayDimensions.defaultHeight, desiredHeight)
}
deltaSize(0, desiredHeight - size.height)
afterHeightChanged()
}
/** Event from notebook with console output. This output contains intermediate data from console. */
private fun onOutput(data: String, type: String, progressStatus: InlayProgressStatus?, cleanup: () -> Unit) {
state?.clear()
val output = getOrCreateOutput()
output.clearAction = cleanup
if (output.addData(type, data, progressStatus)?.shouldLimitHeight() == false) {
shouldLimitMaxHeight = false
}
InlayStateCustomizer.customize(output)
if (UiCustomizer.instance.isResizeOutputToPreviewHeight && size.height == InlayDimensions.smallHeight) {
deltaSize(0, InlayDimensions.previewHeight - size.height)
}
}
private fun onMultiOutput(inlayOutputs: List<InlayOutput>, cleanup: () -> Unit) {
state?.clear()
removeToolbar()
shouldLimitMaxHeight = false
if (state !is NotebookInlayMultiOutput) {
if (state != null) {
remove(state)
}
state = TabbedMultiOutput(editor, disposable).also { st ->
st.onHeightCalculated = { height ->
ApplicationManager.getApplication().invokeLater {
adjustSize(height)
}
}
st.clearAction = cleanup
addState(st)
}
}
resizable = true
(state as? NotebookInlayMultiOutput)?.also { st ->
st.onOutputs(inlayOutputs)
InlayStateCustomizer.customize(st)
}
}
fun addInlayOutputs(inlayOutputs: List<InlayOutput>, cleanup: () -> Unit) {
if (inlayOutputs.size > 1) {
onMultiOutput(inlayOutputs, cleanup)
}
else {
val inlay = inlayOutputs.first()
onOutput(inlay.data, inlay.type, inlay.progressStatus, cleanup)
}
}
fun addText(message: String, outputType: Key<*>) {
getOrCreateOutput().addText(message, outputType)
if (size.height <= InlayDimensions.previewHeight * 3) {
deltaSize(0, min(InlayDimensions.lineHeight * (message.lines().size - 1), InlayDimensions.previewHeight * 3 - size.height))
}
}
fun createOutputComponent() {
getOrCreateOutput().addText("", ProcessOutputType.STDOUT)
}
fun onViewportChange(isInViewport: Boolean) {
state?.onViewportChange(isInViewport)
}
fun clearOutputs() {
state?.clear()
removeToolbar()
}
}

View File

@@ -31,10 +31,7 @@ import com.intellij.util.ui.JBUI
import org.cef.browser.CefBrowser
import org.cef.handler.CefLoadHandlerAdapter
import org.jetbrains.annotations.Nls
import org.jetbrains.concurrency.Promise
import org.jetbrains.plugins.notebooks.visualization.r.VisualizationBundle
import org.jetbrains.plugins.notebooks.visualization.r.inlays.ClipboardUtils
import org.jetbrains.plugins.notebooks.visualization.r.inlays.InlayDimensions
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.progress.InlayProgressStatus
import org.jetbrains.plugins.notebooks.visualization.r.inlays.dataframe.DataFrameCSVAdapter
import org.jetbrains.plugins.notebooks.visualization.r.inlays.runAsyncInlay
@@ -157,72 +154,6 @@ abstract class InlayOutput(
}
}
class InlayOutputImg(parent: Disposable, editor: Editor, clearAction: () -> Unit)
: InlayOutput(parent, editor, clearAction, loadActions(CopyImageToClipboardAction.ID, SaveOutputAction.ID, ClearOutputAction.ID)), InlayOutput.WithCopyImageToClipboard, InlayOutput.WithSaveAs {
private val graphicsPanel = GraphicsPanel(project, parent).apply {
isAdvancedMode = true
}
override val isFullWidth = false
init {
toolbarPane.dataComponent = graphicsPanel.component
}
override fun addToolbar() {
super.addToolbar()
graphicsPanel.overlayComponent = toolbarPane.toolbarComponent
}
override fun addData(data: String, type: String) {
showImageAsync(data, type).onSuccess {
SwingUtilities.invokeLater {
val maxHeight = graphicsPanel.maximumSize?.height ?: 0
val maxWidth = graphicsPanel.maximumSize?.width ?: 0
val height = InlayDimensions.calculateInlayHeight(maxWidth, maxHeight, editor)
onHeightCalculated?.invoke(height)
}
}
}
private fun showImageAsync(data: String, type: String): Promise<Unit> {
return runAsyncInlay {
when (type) {
"IMGBase64" -> graphicsPanel.showImageBase64(data)
"IMGSVG" -> graphicsPanel.showSvgImage(data)
"IMG" -> graphicsPanel.showImage(File(data))
else -> Unit
}
}
}
override fun clear() {
}
override fun scrollToTop() {
}
override fun getCollapsedDescription(): String {
return "foo"
}
override fun saveAs() {
graphicsPanel.image?.let { image ->
InlayOutputUtil.saveImageWithFileChooser(project, image)
}
}
override fun acceptType(type: String): Boolean {
return type == "IMG" || type == "IMGBase64" || type == "IMGSVG"
}
override fun copyImageToClipboard() {
graphicsPanel.image?.let { image ->
ClipboardUtils.copyImageToClipboard(image)
}
}
}
class InlayOutputText(parent: Disposable, editor: Editor, clearAction: () -> Unit)
: InlayOutput(parent, editor, clearAction, loadActions(SaveOutputAction.ID, ClearOutputAction.ID)), InlayOutput.WithSaveAs {

View File

@@ -28,7 +28,7 @@ class ClearOutputAction private constructor() : DumbAwareAction() {
}
internal class SaveOutputAction private constructor() : DumbAwareAction() {
class SaveOutputAction private constructor() : DumbAwareAction() {
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = getInlayOutput<InlayOutput.WithSaveAs>(e) != null
}

View File

@@ -1,147 +0,0 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.plugins.notebooks.visualization.r.inlays.components
import com.intellij.execution.process.ProcessOutputType
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.Key
import com.intellij.ui.RelativeFont
import com.intellij.util.ui.StartupUiUtil
import com.intellij.util.ui.UIUtil
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.progress.InlayProgressStatus
import java.awt.BorderLayout
import java.awt.Font
import java.awt.Rectangle
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import kotlin.math.min
class ProcessOutput(val text: String, kind: Key<*>) {
private val kindValue: Int = when(kind) {
ProcessOutputTypes.STDOUT -> 1
ProcessOutputTypes.STDERR -> 2
else -> 3
}
val kind: Key<*>
get() = when (kindValue) {
1 -> ProcessOutputType.STDOUT
2 -> ProcessOutputType.STDERR
else -> ProcessOutputType.SYSTEM
}
}
/** Notebook console logs, HTML, and table result view. */
class NotebookInlayOutput(private val editor: Editor, private val parent: Disposable) : NotebookInlayState(), ToolBarProvider {
init {
layout = BorderLayout()
}
companion object {
private const val RESIZE_TASK_NAME = "Resize graphics"
private const val RESIZE_TASK_IDENTITY = "Resizing graphics"
private const val RESIZE_TIME_SPAN = 500
private val monospacedFont = RelativeFont.NORMAL.family(Font.MONOSPACED)
private val outputFont = monospacedFont.derive(StartupUiUtil.labelFont.deriveFont(UIUtil.getFontSize(UIUtil.FontSize.SMALL)))
}
private var output: InlayOutput? = null
private fun addTableOutput() = createOutput { parent, editor, clearAction -> InlayOutputTable(parent, editor, clearAction) }
private fun addTextOutput() = createOutput { parent, editor, clearAction -> InlayOutputText(parent, editor, clearAction) }
private fun addHtmlOutput() = createOutput { parent, editor, clearAction -> InlayOutputHtml(parent, editor, clearAction) }
private fun addImgOutput() = createOutput { parent, editor, clearAction -> InlayOutputImg(parent, editor, clearAction) }
private inline fun <T: InlayOutput> createOutput(constructor: (Disposable, Editor, () -> Unit) -> T) =
constructor(parent, editor, clearAction).apply { setupOutput(this) }
private fun setupOutput(output: InlayOutput) {
this.output?.let { remove(it.getComponent()) }
this.output = output
output.onHeightCalculated = { height -> onHeightCalculated?.invoke(height) }
add(output.getComponent(), BorderLayout.CENTER)
addComponentListener(object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
if (output.isFullWidth) {
output.getComponent().bounds = Rectangle(0, 0, e.component.bounds.width, e.component.bounds.height)
} else {
output.getComponent().bounds = Rectangle(0, 0, min(output.getComponent().preferredSize.width, e.component.bounds.width),
e.component.bounds.height)
}
}
})
if (addToolbar) {
output.addToolbar()
}
}
private var addToolbar = false
fun addToolbar() {
addToolbar = true
output?.addToolbar()
}
private fun getOrAddTextOutput(): InlayOutputText {
(output as? InlayOutputText)?.let { return it }
return addTextOutput()
}
fun addData(type: String, data: String, progressStatus: InlayProgressStatus?): InlayOutputProvider? {
val provider = InlayOutputProvider.EP.extensionList.asSequence().filter { it.acceptType(type) }.firstOrNull()
val inlayOutput: InlayOutput
if (provider != null) {
inlayOutput = output.takeIf { it?.acceptType(type) == true } ?: createOutput { parent, editor, clearAction ->
provider.create(parent, editor, clearAction)
}
}
else {
inlayOutput = when (type) {
"TABLE" -> output?.takeIf { it is InlayOutputTable } ?: addTableOutput()
"HTML", "URL" -> output?.takeIf { it is InlayOutputHtml } ?: addHtmlOutput()
"IMG", "IMGBase64", "IMGSVG" -> output?.takeIf { it is InlayOutputImg } ?: addImgOutput()
else -> getOrAddTextOutput()
}
}
progressStatus?.let {
inlayOutput.updateProgressStatus(editor, it)
}
inlayOutput.addData(data, type)
return provider
}
fun addText(message: String, outputType: Key<*>) {
getOrAddTextOutput().addData(message, outputType)
}
override fun updateProgressStatus(progressStatus: InlayProgressStatus) {
output?.updateProgressStatus(editor, progressStatus)
}
override fun clear() {
output?.clear()
}
override fun getCollapsedDescription(): String {
return if (output == null) "" else output!!.getCollapsedDescription()
}
override fun onViewportChange(isInViewport: Boolean) {
output?.onViewportChange(isInViewport)
}
override fun createActions(): List<AnAction> = output?.actions?.filterNot { it is ClearOutputAction } ?: emptyList()
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.plugins.notebooks.visualization.r.inlays.components
import com.intellij.execution.process.ProcessOutputType
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.util.Key
class ProcessOutput(val text: String, kind: Key<*>) {
private val kindValue: Int = when(kind) {
ProcessOutputTypes.STDOUT -> 1
ProcessOutputTypes.STDERR -> 2
else -> 3
}
val kind: Key<*>
get() = when (kindValue) {
1 -> ProcessOutputType.STDOUT
2 -> ProcessOutputType.STDERR
else -> ProcessOutputType.SYSTEM
}
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.plugins.notebooks.visualization.r.inlays.components
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.Disposer
import com.intellij.ui.Gray
import com.intellij.ui.tabs.TabInfo
import com.intellij.ui.tabs.TabsListener
import com.intellij.ui.tabs.impl.JBTabsImpl
import org.jetbrains.plugins.notebooks.visualization.r.inlays.InlayOutput
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.progress.InlayProgressStatus
import org.jetbrains.plugins.notebooks.visualization.r.ui.ToolbarUtil
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Rectangle
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.JComponent
import javax.swing.JPanel
/** A multi-output inlay that puts outputs from different sources to separate tabbed pane tabs. */
internal class TabbedMultiOutput(private val editor: Editor, parent: Disposable) : NotebookInlayMultiOutput() {
/** Page control for result viewing. */
private val tabs: JBTabsImpl
var onChange: (() -> Unit)? = null
private val disposable = Disposer.newDisposable()
private var maxHeight: Int = -1
private val project = editor.project!!
private val tabsOutput: MutableSet<NotebookInlayOutput> = mutableSetOf()
@Volatile
private var isInViewport: Boolean = false
init {
Disposer.register(parent, disposable)
tabs = JBTabsImpl(project, disposable)
tabs.addListener(object : TabsListener {
override fun selectionChanged(oldSelection: TabInfo?, newSelection: TabInfo?) {
oldSelection?.onViewportChange(false) // Definitely false
newSelection?.onViewportChange(isInViewport) // Might be true
onChange?.invoke()
}
})
tabs.component.isOpaque = false
tabs.component.background = Gray.TRANSPARENT
add(tabs.component, BorderLayout.CENTER)
// To make it possible to use JLayeredPane as a parent of NotebookInlayState.
addComponentListener(object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
tabs.component.bounds = Rectangle(0, 0, e.component.bounds.width, e.component.bounds.height)
}
})
}
fun setCurrentPage(currentPage: String) {
val tabToSelect = tabs.tabs.find { it.text == currentPage }
if(tabToSelect != null) {
tabs.select(tabToSelect, false)
}
}
override fun onOutputs(inlayOutputs: List<InlayOutput>) {
tabs.removeAllTabs()
tabsOutput.clear()
for (inlayOutput in inlayOutputs) {
NotebookInlayOutput(editor, disposable).apply {
setupOnHeightCalculated()
addData(inlayOutput.type, inlayOutput.data, inlayOutput.progressStatus)
tabsOutput.add(this)
addTab(inlayOutput)
}
}
}
override fun updateProgressStatus(progressStatus: InlayProgressStatus) {
tabsOutput.forEach { output ->
output.updateProgressStatus(progressStatus)
}
}
override fun clear() {
}
override fun getCollapsedDescription(): String {
return "foooo"
}
override fun onViewportChange(isInViewport: Boolean) {
this.isInViewport = isInViewport
tabs.selectedInfo?.onViewportChange(isInViewport)
}
private fun TabInfo.onViewportChange(isInViewport: Boolean) {
(component as NotebookInlayState?)?.onViewportChange(isInViewport)
}
private fun NotebookInlayState.setupOnHeightCalculated() {
onHeightCalculated = {
tabs.findInfo(this)?.let { tab ->
updateMaxHeight(it + tabs.getTabLabel(tab)!!.preferredSize.height)
}
}
}
private fun NotebookInlayState.addTab(inlayOutput: InlayOutput) {
addTab(TabInfo(this).apply {
inlayOutput.preview?.let {
setIcon(it)
setText("")
}
inlayOutput.title?.let {
setText(it)
}
}).apply {
tabs.getTabLabel(this)?.apply {
if (inlayOutput.preferredWidth != 0) {
preferredSize = Dimension(inlayOutput.preferredWidth, 0)
}
}
if (tabs.selectedInfo == null) {
tabs.select(this, false)
}
}
}
private fun addTab(tabInfo: TabInfo, select: Boolean = false): TabInfo {
// We need to set empty DefaultActionGroup to move sideComponent to the right.
tabInfo.setActions(DefaultActionGroup(), ActionPlaces.UNKNOWN)
tabInfo.setSideComponent(createTabToolbar(tabInfo))
tabs.addTab(tabInfo)
if (select) {
tabs.select(tabInfo, false)
}
return tabInfo
}
private fun createTabToolbar(tabInfo: TabInfo): JComponent {
val actionGroups = createTabActionGroups(tabInfo)
val toolbar = ToolbarUtil.createActionToolbar(javaClass.simpleName, actionGroups).component
if (toolbar is ActionToolbarImpl) {
toolbar.setForceMinimumSize(true)
}
return JPanel().apply { // Align toolbar to top
add(toolbar)
}
}
private fun createTabActionGroups(tabInfo: TabInfo): List<List<AnAction>> {
return mutableListOf<List<AnAction>>().also { groups ->
(tabInfo.component as? ToolBarProvider)?.let { provider ->
groups.add(provider.createActions())
}
groups.add(listOf(createClearAction()))
}
}
private fun createClearAction(): AnAction {
return ToolbarUtil.createAnActionButton(ClearOutputAction.ID, clearAction::invoke)
}
private fun updateMaxHeight(height: Int) {
if (maxHeight < height) {
maxHeight = height
onHeightCalculated?.invoke(maxHeight)
}
}
}

View File

@@ -1,12 +1,7 @@
package org.jetbrains.plugins.notebooks.visualization.r.ui
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ui.UIUtil
import org.intellij.images.editor.ImageEditor
import org.intellij.images.editor.impl.ImageEditorImpl
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.GraphicsPanel
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.ToolbarPane
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.progress.InlayProgressStatus
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.progress.JupyterProgressStatus
@@ -14,9 +9,6 @@ import javax.swing.JComponent
import javax.swing.JPanel
class DefaultUiCustomizer : UiCustomizer {
override fun createImageEditor(project: Project, file: VirtualFile, graphicsPanel: GraphicsPanel): ImageEditor =
ImageEditorImpl(project, file)
override fun toolbarPaneProgressComponentChanged(toolbarPane: ToolbarPane, component: JComponent?): Unit = Unit
override fun toolbarPaneToolbarComponentChanged(toolbarPane: ToolbarPane, component: JComponent?): Unit = Unit

View File

@@ -2,10 +2,6 @@ package org.jetbrains.plugins.notebooks.visualization.r.ui
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.intellij.images.editor.ImageEditor
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.GraphicsPanel
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.ToolbarPane
import org.jetbrains.plugins.notebooks.visualization.r.inlays.components.progress.InlayProgressStatus
import java.awt.Color
@@ -13,9 +9,6 @@ import javax.swing.JComponent
import javax.swing.JPanel
interface UiCustomizer {
fun createImageEditor(project: Project,
file: VirtualFile,
graphicsPanel: GraphicsPanel): ImageEditor
fun toolbarPaneToolbarComponentChanged(toolbarPane: ToolbarPane, component: JComponent?)