mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
PY-73345 Optimize cell bounds calculations
GitOrigin-RevId: a65412190555d20e08a54d6717e7224912611230
This commit is contained in:
committed by
intellij-monorepo-bot
parent
141a46b4ec
commit
d738bf447f
@@ -1,11 +1,8 @@
|
||||
package org.jetbrains.plugins.notebooks.visualization
|
||||
|
||||
import com.intellij.codeInsight.codeVision.ui.popup.layouter.bottom
|
||||
import com.intellij.codeInsight.codeVision.ui.popup.layouter.right
|
||||
import com.intellij.ide.ui.LafManagerListener
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorKind
|
||||
import com.intellij.openapi.editor.FoldRegion
|
||||
@@ -36,16 +33,14 @@ import org.jetbrains.plugins.notebooks.visualization.ui.EditorCellEventListener.
|
||||
import org.jetbrains.plugins.notebooks.visualization.ui.EditorCellView
|
||||
import org.jetbrains.plugins.notebooks.visualization.ui.keepScrollingPositionWhile
|
||||
import java.awt.Graphics
|
||||
import java.awt.Point
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class NotebookCellInlayManager private constructor(
|
||||
val editor: EditorImpl
|
||||
val editor: EditorImpl,
|
||||
) : Disposable, NotebookIntervalPointerFactory.ChangeListener {
|
||||
private val notebookCellLines = NotebookCellLines.get(editor)
|
||||
private val viewportQueue = MergingUpdateQueue("NotebookCellInlayManager Viewport Update", 100, true, null, editor.disposable, null, true)
|
||||
|
||||
/** 20 is 1000 / 50, two times faster than the eye refresh rate. Actually, the value has been chosen randomly, without experiments. */
|
||||
private val updateQueue = MergingUpdateQueue("NotebookCellInlayManager Interval Update", 20, true, null, editor.disposable, null, true)
|
||||
@@ -62,6 +57,8 @@ class NotebookCellInlayManager private constructor(
|
||||
|
||||
private val cellEventListeners = EventDispatcher.create(EditorCellEventListener::class.java)
|
||||
|
||||
private var valid = false
|
||||
|
||||
override fun dispose() {}
|
||||
|
||||
fun getCellForInterval(interval: NotebookCellLines.Interval): EditorCell =
|
||||
@@ -100,18 +97,9 @@ class NotebookCellInlayManager private constructor(
|
||||
|
||||
private fun addViewportChangeListener() {
|
||||
editor.scrollPane.viewport.addChangeListener {
|
||||
val rect = editor.scrollPane.viewport.viewRect
|
||||
val top = editor.xyToLogicalPosition(rect.location)
|
||||
val bottom = editor.xyToLogicalPosition(Point(rect.right, rect.bottom))
|
||||
scheduleUpdatePositions(top.line, bottom.line)
|
||||
viewportQueue.queue(object : Update("Viewport change") {
|
||||
override fun run() {
|
||||
if (editor.isDisposed) return
|
||||
_cells.forEach {
|
||||
it.onViewportChange()
|
||||
}
|
||||
}
|
||||
})
|
||||
_cells.forEach {
|
||||
it.onViewportChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +124,7 @@ class NotebookCellInlayManager private constructor(
|
||||
|
||||
editor.foldingModel.addListener(object : FoldingListener {
|
||||
override fun onFoldProcessingEnd() {
|
||||
scheduleUpdatePositions()
|
||||
invalidateCells()
|
||||
}
|
||||
}, editor.disposable)
|
||||
|
||||
@@ -215,24 +203,6 @@ class NotebookCellInlayManager private constructor(
|
||||
startOffset >= region.startOffset && endOffset <= region.endOffset
|
||||
}
|
||||
|
||||
private fun scheduleUpdatePositions(from: Int = 0, to: Int = 1000_000_000) {
|
||||
runInEdt {
|
||||
val fromIndex = _cells.indexOfFirst { cell ->
|
||||
val lines = cell.intervalPointer.get()?.lines
|
||||
lines != null && lines.hasIntersectionWith(from..to + 1)
|
||||
}
|
||||
if (fromIndex == -1) return@runInEdt
|
||||
val toIndex = _cells.subList(fromIndex, _cells.size).indexOfFirst { cell ->
|
||||
val lines = cell.intervalPointer.get()?.lines
|
||||
lines != null && !lines.hasIntersectionWith(from..to + 1)
|
||||
}
|
||||
val finalIndex = if (toIndex == -1) _cells.size - 1 else fromIndex + toIndex
|
||||
for (i in max(fromIndex - 1, 0)..min(finalIndex + 1, _cells.size - 1)) {
|
||||
_cells[i].updatePositions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshHighlightersLookAndFeel() {
|
||||
for (highlighter in editor.markupModel.allHighlighters) {
|
||||
if (highlighter.customRenderer === NotebookCellHighlighterRenderer) {
|
||||
@@ -257,7 +227,7 @@ class NotebookCellInlayManager private constructor(
|
||||
}
|
||||
|
||||
private fun createCell(interval: NotebookIntervalPointer) = EditorCell(editor, interval) { cell ->
|
||||
EditorCellView(editor, notebookCellLines, cell).also { Disposer.register(cell, it) }
|
||||
EditorCellView(editor, notebookCellLines, cell, this).also { Disposer.register(cell, it) }
|
||||
}.also { Disposer.register(this, it) }
|
||||
|
||||
private fun ensureInlaysAndHighlightersExist(matchingCellsBeforeChange: List<NotebookCellLines.Interval>, logicalLines: IntRange) {
|
||||
@@ -413,7 +383,7 @@ class NotebookCellInlayManager private constructor(
|
||||
}
|
||||
}
|
||||
if (needUpdatePositions) {
|
||||
scheduleUpdatePositions()
|
||||
invalidateCells()
|
||||
}
|
||||
cellEventListeners.multicaster.onEditorCellEvents(events)
|
||||
updateConsequentInlays(start..end)
|
||||
@@ -426,6 +396,22 @@ class NotebookCellInlayManager private constructor(
|
||||
fun getCell(index: Int): EditorCell {
|
||||
return cells[index]
|
||||
}
|
||||
|
||||
fun invalidateCells() {
|
||||
valid = false
|
||||
}
|
||||
|
||||
fun validateCells() {
|
||||
if (!valid) {
|
||||
_cells.forEach {
|
||||
it.view?.also { view ->
|
||||
view.bounds = view.calculateBounds()
|
||||
view.validate()
|
||||
}
|
||||
}
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,10 +441,12 @@ private object NotebookCellHighlighterRenderer : CustomHighlighterRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateInlaysTask(private val manager: NotebookCellInlayManager,
|
||||
pointers: Collection<NotebookIntervalPointer>? = null,
|
||||
private var updateAll: Boolean = false,
|
||||
private var forceUpdate: Boolean = false) : Update(Any()) {
|
||||
private class UpdateInlaysTask(
|
||||
private val manager: NotebookCellInlayManager,
|
||||
pointers: Collection<NotebookIntervalPointer>? = null,
|
||||
private var updateAll: Boolean = false,
|
||||
private var forceUpdate: Boolean = false,
|
||||
) : Update(Any()) {
|
||||
private val pointerSet = pointers?.let { SmartHashSet(pointers) } ?: SmartHashSet()
|
||||
|
||||
override fun run() {
|
||||
|
||||
@@ -2,36 +2,21 @@ package org.jetbrains.plugins.notebooks.visualization.ui
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.util.EventDispatcher
|
||||
import com.intellij.util.asSafely
|
||||
import org.jetbrains.plugins.notebooks.visualization.NotebookCellInlayController
|
||||
import java.awt.Dimension
|
||||
import java.awt.Point
|
||||
import java.awt.Rectangle
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import javax.swing.JComponent
|
||||
|
||||
class ControllerEditorCellViewComponent(
|
||||
internal val controller: NotebookCellInlayController
|
||||
) : EditorCellViewComponent {
|
||||
|
||||
private val cellEventListeners = EventDispatcher.create(EditorCellViewComponentListener::class.java)
|
||||
|
||||
override val location: Point
|
||||
get() {
|
||||
val component = controller.inlay.renderer as JComponent
|
||||
return component.location
|
||||
}
|
||||
|
||||
override val size: Dimension
|
||||
get() {
|
||||
val component = controller.inlay.renderer as JComponent
|
||||
return component.size
|
||||
}
|
||||
internal val controller: NotebookCellInlayController,
|
||||
private val parent: EditorCellInput,
|
||||
) : EditorCellViewComponent(), HasGutterIcon {
|
||||
|
||||
private val listener = object : ComponentAdapter() {
|
||||
override fun componentResized(e: ComponentEvent) {
|
||||
cellEventListeners.multicaster.componentBoundaryChanged(e.component.location, e.component.size)
|
||||
parent.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,21 +30,17 @@ class ControllerEditorCellViewComponent(
|
||||
inlay.update()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
override fun doDispose() {
|
||||
controller.inlay.renderer.asSafely<JComponent>()?.removeComponentListener(listener)
|
||||
controller.let { controller -> Disposer.dispose(controller.inlay) }
|
||||
}
|
||||
|
||||
override fun onViewportChange() {
|
||||
override fun doViewportChange() {
|
||||
controller.onViewportChange()
|
||||
}
|
||||
|
||||
|
||||
override fun addViewComponentListener(listener: EditorCellViewComponentListener) {
|
||||
cellEventListeners.addListener(listener)
|
||||
}
|
||||
|
||||
override fun updatePositions() {
|
||||
cellEventListeners.multicaster.componentBoundaryChanged(location, size)
|
||||
override fun calculateBounds(): Rectangle {
|
||||
val component = controller.inlay.renderer as JComponent
|
||||
return component.bounds
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import javax.swing.JLayer
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.plaf.LayerUI
|
||||
|
||||
private class DecoratedEditor(private val original: TextEditor) : TextEditor by original {
|
||||
private class DecoratedEditor(private val original: TextEditor, private val manager: NotebookCellInlayManager) : TextEditor by original {
|
||||
|
||||
private var mouseOverCell: EditorCellView? = null
|
||||
|
||||
@@ -64,6 +64,7 @@ private class DecoratedEditor(private val original: TextEditor) : TextEditor by
|
||||
|
||||
override fun validateTree() {
|
||||
keepScrollingPositionWhile(editor) {
|
||||
manager.validateCells()
|
||||
super.validateTree()
|
||||
}
|
||||
}
|
||||
@@ -118,12 +119,12 @@ private class DecoratedEditor(private val original: TextEditor) : TextEditor by
|
||||
})
|
||||
|
||||
private fun updateMouseOverCell(component: JComponent, point: Point) {
|
||||
val cells = NotebookCellInlayManager.get(editor)!!.cells
|
||||
val cells = manager.cells
|
||||
val currentOverCell = cells.filter { it.visible }.mapNotNull { it.view }.firstOrNull {
|
||||
val viewLeft = 0
|
||||
val viewTop = it.location.y
|
||||
val viewTop = it.bounds.y
|
||||
val viewRight = component.size.width
|
||||
val viewBottom = viewTop + it.size.height
|
||||
val viewBottom = viewTop + it.bounds.height
|
||||
viewLeft <= point.x && viewTop <= point.y && viewRight >= point.x && viewBottom >= point.y
|
||||
}
|
||||
if (mouseOverCell != currentOverCell) {
|
||||
@@ -135,8 +136,8 @@ private class DecoratedEditor(private val original: TextEditor) : TextEditor by
|
||||
|
||||
}
|
||||
|
||||
fun decorateTextEditor(textEditor: TextEditor): TextEditor {
|
||||
return DecoratedEditor(textEditor)
|
||||
fun decorateTextEditor(textEditor: TextEditor, manager: NotebookCellInlayManager): TextEditor {
|
||||
return DecoratedEditor(textEditor, manager)
|
||||
}
|
||||
|
||||
internal fun keepScrollingPositionWhile(editor: Editor, task: Runnable) {
|
||||
|
||||
@@ -68,10 +68,6 @@ class EditorCell(
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePositions() {
|
||||
view?.updatePositions()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
view?.let { Disposer.dispose(it) }
|
||||
}
|
||||
|
||||
@@ -4,20 +4,15 @@ import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.editor.FoldRegion
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.util.EventDispatcher
|
||||
import org.jetbrains.plugins.notebooks.ui.visualization.notebookAppearance
|
||||
import org.jetbrains.plugins.notebooks.visualization.NotebookCellLines
|
||||
import java.awt.Dimension
|
||||
import java.awt.Point
|
||||
import java.awt.Rectangle
|
||||
|
||||
class EditorCellInput(
|
||||
private val editor: EditorEx,
|
||||
private val componentFactory: (EditorCellViewComponent?) -> EditorCellViewComponent,
|
||||
private val cell: EditorCell
|
||||
) {
|
||||
|
||||
private val cellEventListeners = EventDispatcher.create(EditorCellViewComponentListener::class.java)
|
||||
private val componentFactory: (EditorCellInput, EditorCellViewComponent?) -> EditorCellViewComponent,
|
||||
private val cell: EditorCell,
|
||||
): EditorCellViewComponent() {
|
||||
|
||||
val interval: NotebookCellLines.Interval
|
||||
get() = cell.intervalPointer.get() ?: error("Invalid interval")
|
||||
@@ -33,60 +28,31 @@ class EditorCellInput(
|
||||
else -> editor.notebookAppearance.cellBorderHeight / 2
|
||||
}
|
||||
|
||||
val bounds: Rectangle
|
||||
get() {
|
||||
val linesRange = interval.lines
|
||||
val startOffset = editor.document.getLineStartOffset(linesRange.first)
|
||||
val endOffset = editor.document.getLineEndOffset(linesRange.last)
|
||||
val bounds = editor.inlayModel.getBlockElementsInRange(startOffset, endOffset)
|
||||
.asSequence()
|
||||
.filter { it.properties.priority > editor.notebookAppearance.NOTEBOOK_OUTPUT_INLAY_PRIORITY }
|
||||
.mapNotNull { it.bounds }
|
||||
.fold(Rectangle(_component.location, _component.size)) { b, i ->
|
||||
b.union(i)
|
||||
}
|
||||
return bounds
|
||||
}
|
||||
|
||||
private var _component: EditorCellViewComponent = componentFactory(null).also { bind(it) }
|
||||
private var _component: EditorCellViewComponent = componentFactory(this, null)
|
||||
set(value) {
|
||||
if (value != field) {
|
||||
field.dispose()
|
||||
remove(field)
|
||||
field = value
|
||||
bind(value)
|
||||
add(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bind(value: EditorCellViewComponent) {
|
||||
value.addViewComponentListener(object : EditorCellViewComponentListener {
|
||||
override fun componentBoundaryChanged(location: Point, size: Dimension) {
|
||||
cellEventListeners.multicaster.componentBoundaryChanged(bounds.location, bounds.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val component: EditorCellViewComponent
|
||||
get() = _component
|
||||
|
||||
private val folding: EditorCellFolding = EditorCellFolding(editor) {
|
||||
toggleFolding(componentFactory)
|
||||
}.also {
|
||||
cellEventListeners.addListener(object : EditorCellViewComponentListener {
|
||||
override fun componentBoundaryChanged(location: Point, size: Dimension) {
|
||||
it.updatePosition(location.y + delimiterPanelSize, size.height - delimiterPanelSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun toggleFolding(inputComponentFactory: (EditorCellViewComponent) -> EditorCellViewComponent) {
|
||||
private fun toggleFolding(inputComponentFactory: (EditorCellInput, EditorCellViewComponent) -> EditorCellViewComponent) {
|
||||
_component = if (_component is ControllerEditorCellViewComponent) {
|
||||
_component.dispose()
|
||||
toggleTextFolding()
|
||||
TextEditorCellViewComponent(editor, cell)
|
||||
}
|
||||
else {
|
||||
toggleTextFolding()
|
||||
inputComponentFactory(_component)
|
||||
inputComponentFactory(this, _component)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +82,7 @@ class EditorCellInput(
|
||||
|
||||
private var gutterAction: AnAction? = null
|
||||
|
||||
fun dispose() {
|
||||
override fun doDispose() {
|
||||
folding.dispose()
|
||||
runCellButton?.dispose()
|
||||
_component.dispose()
|
||||
@@ -124,20 +90,16 @@ class EditorCellInput(
|
||||
|
||||
fun update(force: Boolean = false) {
|
||||
val oldComponent = if (force) null else _component
|
||||
_component = componentFactory(oldComponent)
|
||||
_component = componentFactory(this, oldComponent)
|
||||
updateGutterIcons()
|
||||
}
|
||||
|
||||
private fun updateGutterIcons() {
|
||||
_component.updateGutterIcons(gutterAction)
|
||||
(_component as? HasGutterIcon)?.updateGutterIcons(gutterAction)
|
||||
}
|
||||
|
||||
fun updatePositions() {
|
||||
_component.updatePositions()
|
||||
}
|
||||
|
||||
fun onViewportChange() {
|
||||
_component.onViewportChange()
|
||||
override fun doLayout() {
|
||||
folding.updatePosition(bounds.y + delimiterPanelSize, bounds.height - delimiterPanelSize)
|
||||
}
|
||||
|
||||
fun setGutterAction(action: AnAction) {
|
||||
@@ -156,32 +118,44 @@ class EditorCellInput(
|
||||
fun showRunButton() {
|
||||
try {
|
||||
runCellButton?.showRunButton(interval)
|
||||
} catch (e: IllegalStateException) { return }
|
||||
}
|
||||
catch (e: IllegalStateException) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun hideRunButton() {
|
||||
runCellButton?.hideRunButton()
|
||||
}
|
||||
|
||||
fun addViewComponentListener(listener: EditorCellViewComponentListener) {
|
||||
cellEventListeners.addListener(listener)
|
||||
}
|
||||
|
||||
fun updatePresentation(view: EditorCellViewComponent) {
|
||||
_component.dispose()
|
||||
_component = view
|
||||
}
|
||||
|
||||
fun updateSelection(value: Boolean) {
|
||||
folding.updateSelection(value)
|
||||
}
|
||||
|
||||
override fun calculateBounds(): Rectangle {
|
||||
val linesRange = interval.lines
|
||||
val startOffset = editor.document.getLineStartOffset(linesRange.first)
|
||||
val endOffset = editor.document.getLineEndOffset(linesRange.last)
|
||||
val bounds = editor.inlayModel.getBlockElementsInRange(startOffset, endOffset)
|
||||
.asSequence()
|
||||
.filter { it.properties.priority > editor.notebookAppearance.NOTEBOOK_OUTPUT_INLAY_PRIORITY }
|
||||
.mapNotNull { it.bounds }
|
||||
.fold(_component.calculateBounds()) { b, i ->
|
||||
b.union(i)
|
||||
}
|
||||
return bounds
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.ellipsis(length: Int): String {
|
||||
return if (this.length > length) {
|
||||
substring(0, length - 1)
|
||||
}
|
||||
else {
|
||||
else {
|
||||
this
|
||||
} + "\u2026"
|
||||
}
|
||||
@@ -19,12 +19,12 @@ import javax.swing.SwingUtilities
|
||||
|
||||
val NOTEBOOK_CELL_OUTPUT_DATA_KEY = DataKey.create<EditorCellOutput>("NOTEBOOK_CELL_OUTPUT")
|
||||
|
||||
class EditorCellOutput internal constructor(private val editor: EditorEx, private val component: CollapsingComponent, private val disposable: Disposable?) {
|
||||
class EditorCellOutput internal constructor(
|
||||
private val editor: EditorEx,
|
||||
private val component: CollapsingComponent,
|
||||
private val disposable: Disposable?,
|
||||
) : EditorCellViewComponent() {
|
||||
|
||||
val location: Point
|
||||
get() = SwingUtilities.convertPoint(component.parent, component.location, editor.contentComponent)
|
||||
val size: Dimension
|
||||
get() = component.size
|
||||
var collapsed: Boolean
|
||||
get() = !component.isSeen
|
||||
set(value) {
|
||||
@@ -38,11 +38,11 @@ class EditorCellOutput internal constructor(private val editor: EditorEx, privat
|
||||
.also {
|
||||
component.addComponentListener(object : ComponentAdapter() {
|
||||
override fun componentMoved(e: ComponentEvent) {
|
||||
updatePositions()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun componentResized(e: ComponentEvent) {
|
||||
updatePositions()
|
||||
invalidate()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -58,21 +58,17 @@ class EditorCellOutput internal constructor(private val editor: EditorEx, privat
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePositions() {
|
||||
folding.updatePosition(location.y, size.height)
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
override fun doDispose() {
|
||||
folding.dispose()
|
||||
disposable?.let { Disposer.dispose(it) }
|
||||
}
|
||||
|
||||
fun onViewportChange() {
|
||||
val component = component.mainComponent as? NotebookOutputInlayShowable ?: return
|
||||
if (component !is JComponent) return
|
||||
validateComponent(component)
|
||||
val componentRect = SwingUtilities.convertRectangle(component, component.bounds, editor.scrollPane.viewport.view)
|
||||
component.shown = editor.scrollPane.viewport.viewRect.intersects(componentRect)
|
||||
override fun doViewportChange() {
|
||||
val component = component.mainComponent as? NotebookOutputInlayShowable ?: return
|
||||
if (component !is JComponent) return
|
||||
validateComponent(component)
|
||||
val componentRect = SwingUtilities.convertRectangle(component, component.bounds, editor.scrollPane.viewport.view)
|
||||
component.shown = editor.scrollPane.viewport.viewRect.intersects(componentRect)
|
||||
}
|
||||
|
||||
fun hideFolding() {
|
||||
@@ -100,4 +96,14 @@ class EditorCellOutput internal constructor(private val editor: EditorEx, privat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun calculateBounds(): Rectangle {
|
||||
val location = SwingUtilities.convertPoint(component.parent, component.location, editor.contentComponent)
|
||||
val size = component.size
|
||||
return Rectangle(location, size)
|
||||
}
|
||||
|
||||
override fun doLayout() {
|
||||
folding.updatePosition(bounds.y, bounds.height)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Inlay
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.util.EventDispatcher
|
||||
import com.intellij.util.asSafely
|
||||
import org.jetbrains.plugins.notebooks.ui.visualization.notebookAppearance
|
||||
import org.jetbrains.plugins.notebooks.visualization.NotebookCellLines
|
||||
@@ -30,12 +29,8 @@ import javax.swing.JComponent
|
||||
class EditorCellOutputs(
|
||||
private val editor: EditorImpl,
|
||||
private val interval: () -> NotebookCellLines.Interval,
|
||||
private val onInlayDisposed: (EditorCellOutputs) -> Unit = {}
|
||||
) : Disposable {
|
||||
|
||||
|
||||
|
||||
private val cellEventListeners = EventDispatcher.create(EditorCellViewComponentListener::class.java)
|
||||
private val onInlayDisposed: (EditorCellOutputs) -> Unit = {},
|
||||
) : EditorCellViewComponent(), Disposable {
|
||||
|
||||
private val _outputs = mutableListOf<EditorCellOutput>()
|
||||
val outputs
|
||||
@@ -55,30 +50,17 @@ class EditorCellOutputs(
|
||||
}
|
||||
private var inlay: Inlay<*>? = null
|
||||
|
||||
val bounds: Rectangle?
|
||||
get() {
|
||||
return inlay?.bounds
|
||||
}
|
||||
|
||||
init {
|
||||
update()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
override fun doDispose() {
|
||||
outputs.forEach { it.dispose() }
|
||||
inlay?.let { Disposer.dispose(it) }
|
||||
}
|
||||
|
||||
fun updatePositions() {
|
||||
val b = bounds
|
||||
if (b != null) {
|
||||
cellEventListeners.multicaster.componentBoundaryChanged(b.location, b.size)
|
||||
outputs.forEach { it.updatePositions() }
|
||||
}
|
||||
}
|
||||
|
||||
fun onViewportChange() {
|
||||
outputs.forEach { it.onViewportChange() }
|
||||
override fun calculateBounds(): Rectangle {
|
||||
return inlay?.bounds ?: Rectangle(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
fun updateSelection(selected: Boolean) {
|
||||
@@ -172,7 +154,9 @@ class EditorCellOutputs(
|
||||
|
||||
private fun removeOutput(idx: Int) {
|
||||
innerComponent.remove(idx)
|
||||
_outputs.removeAt(idx).dispose()
|
||||
val outputComponent = _outputs.removeAt(idx)
|
||||
outputComponent.dispose()
|
||||
remove(outputComponent)
|
||||
}
|
||||
|
||||
private fun <K : NotebookOutputDataKey> createOutputGuessingFactory(outputDataKey: K): NotebookOutputComponentFactory.CreatedComponent<*>? =
|
||||
@@ -185,8 +169,10 @@ class EditorCellOutputs(
|
||||
}
|
||||
.firstOrNull()
|
||||
|
||||
private fun <K : NotebookOutputDataKey> createOutput(factory: NotebookOutputComponentFactory<*, K>,
|
||||
outputDataKey: K): NotebookOutputComponentFactory.CreatedComponent<*>? {
|
||||
private fun <K : NotebookOutputDataKey> createOutput(
|
||||
factory: NotebookOutputComponentFactory<*, K>,
|
||||
outputDataKey: K,
|
||||
): NotebookOutputComponentFactory.CreatedComponent<*>? {
|
||||
val lines = interval().lines
|
||||
ApplicationManager.getApplication().messageBus.syncPublisher(OUTPUT_LISTENER).beforeOutputCreated(editor, lines.last)
|
||||
val result = try {
|
||||
@@ -238,7 +224,7 @@ class EditorCellOutputs(
|
||||
).also {
|
||||
it.renderer.asSafely<JComponent>()?.addComponentListener(object : ComponentAdapter() {
|
||||
override fun componentResized(e: ComponentEvent) {
|
||||
cellEventListeners.multicaster.componentBoundaryChanged(e.component.location, e.component.size)
|
||||
invalidate()
|
||||
}
|
||||
})
|
||||
Disposer.register(it) {
|
||||
@@ -263,7 +249,9 @@ class EditorCellOutputs(
|
||||
pos,
|
||||
)
|
||||
|
||||
_outputs.add(if (pos == -1) _outputs.size else pos, EditorCellOutput(editor, collapsingComponent, newComponent.disposable))
|
||||
val outputComponent = EditorCellOutput(editor, collapsingComponent, newComponent.disposable)
|
||||
_outputs.add(if (pos == -1) _outputs.size else pos, outputComponent)
|
||||
add(outputComponent)
|
||||
|
||||
// DS-1972 Without revalidation, the component would be just invalidated, and would be rendered only after anything else requests
|
||||
// for repainting the editor.
|
||||
@@ -275,9 +263,11 @@ class EditorCellOutputs(
|
||||
override fun next(): Pair<A, B> = this@zip.next() to other.next()
|
||||
}
|
||||
|
||||
fun paintGutter(editor: EditorImpl,
|
||||
g: Graphics,
|
||||
r: Rectangle) {
|
||||
fun paintGutter(
|
||||
editor: EditorImpl,
|
||||
g: Graphics,
|
||||
r: Rectangle,
|
||||
) {
|
||||
val yOffset = innerComponent.yOffsetFromEditor(editor) ?: return
|
||||
|
||||
val oldClip = g.clipBounds
|
||||
|
||||
@@ -36,8 +36,9 @@ import kotlin.reflect.KClass
|
||||
class EditorCellView(
|
||||
private val editor: EditorImpl,
|
||||
private val intervals: NotebookCellLines,
|
||||
internal var cell: EditorCell
|
||||
) : Disposable {
|
||||
internal var cell: EditorCell,
|
||||
private val cellInlayManager: NotebookCellInlayManager,
|
||||
) : EditorCellViewComponent(), Disposable {
|
||||
|
||||
private var _controllers: List<NotebookCellInlayController> = emptyList()
|
||||
private val controllers: List<NotebookCellInlayController>
|
||||
@@ -54,7 +55,7 @@ class EditorCellView(
|
||||
|
||||
val input: EditorCellInput = EditorCellInput(
|
||||
editor,
|
||||
{ currentComponent: EditorCellViewComponent? ->
|
||||
{ parent: EditorCellInput, currentComponent: EditorCellViewComponent? ->
|
||||
val currentController = (currentComponent as? ControllerEditorCellViewComponent)?.controller
|
||||
val controller = getInputFactories().firstNotNullOfOrNull { factory ->
|
||||
failSafeCompute(factory, editor, currentController?.let { listOf(it) }
|
||||
@@ -65,27 +66,16 @@ class EditorCellView(
|
||||
currentComponent
|
||||
}
|
||||
else {
|
||||
ControllerEditorCellViewComponent(controller)
|
||||
ControllerEditorCellViewComponent(controller, parent)
|
||||
}
|
||||
}
|
||||
else {
|
||||
TextEditorCellViewComponent(editor, cell)
|
||||
}
|
||||
}, cell).also {
|
||||
it.addViewComponentListener(object : EditorCellViewComponentListener {
|
||||
override fun componentBoundaryChanged(location: Point, size: Dimension) {
|
||||
updateBoundaries()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private var _location: Point = Point(0, 0)
|
||||
|
||||
val location: Point get() = _location
|
||||
|
||||
private var _size: Dimension = Dimension(0, 0)
|
||||
|
||||
val size: Dimension get() = _size
|
||||
}, cell)
|
||||
.also {
|
||||
add(it)
|
||||
}
|
||||
|
||||
private var _outputs: EditorCellOutputs? = null
|
||||
|
||||
@@ -101,23 +91,12 @@ class EditorCellView(
|
||||
updateSelection(false)
|
||||
}
|
||||
|
||||
private fun updateBoundaries() {
|
||||
val inputBounds = input.bounds
|
||||
val y = inputBounds.y
|
||||
_location = Point(0, y)
|
||||
val currentOutputs = outputs
|
||||
_size = Dimension(
|
||||
editor.contentSize.width,
|
||||
currentOutputs?.bounds?.let { it.height + it.y - y } ?: inputBounds.height
|
||||
)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
override fun doDispose() {
|
||||
_controllers.forEach { controller ->
|
||||
disposeController(controller)
|
||||
}
|
||||
input.dispose()
|
||||
|
||||
outputs?.dispose()
|
||||
removeCellHighlight()
|
||||
}
|
||||
|
||||
@@ -128,13 +107,8 @@ class EditorCellView(
|
||||
}
|
||||
|
||||
fun update(force: Boolean = false) {
|
||||
extracted(force)
|
||||
}
|
||||
|
||||
private fun extracted(force: Boolean) {
|
||||
val otherFactories = NotebookCellInlayController.Factory.EP_NAME.extensionList
|
||||
.filter { it !is NotebookCellInlayController.InputFactory }
|
||||
|
||||
val controllersToDispose = _controllers.toMutableSet()
|
||||
_controllers = if (!editor.isDisposed) {
|
||||
otherFactories.mapNotNull { factory -> failSafeCompute(factory, editor, _controllers, intervals.intervals.listIterator(interval.ordinal)) }
|
||||
@@ -157,14 +131,18 @@ class EditorCellView(
|
||||
}
|
||||
input.update(force)
|
||||
updateOutputs()
|
||||
updateBoundaries()
|
||||
updateCellHighlight()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun updateOutputs() {
|
||||
if (hasOutputs()) {
|
||||
if (_outputs == null) {
|
||||
_outputs = EditorCellOutputs(editor, { interval }).also { Disposer.register(this, it) }
|
||||
_outputs = EditorCellOutputs(editor, { interval })
|
||||
.also {
|
||||
Disposer.register(this, it)
|
||||
add(it)
|
||||
}
|
||||
updateCellHighlight()
|
||||
updateFolding()
|
||||
}
|
||||
@@ -173,7 +151,10 @@ class EditorCellView(
|
||||
}
|
||||
}
|
||||
else {
|
||||
outputs?.let { Disposer.dispose(it) }
|
||||
outputs?.let {
|
||||
Disposer.dispose(it)
|
||||
remove(it)
|
||||
}
|
||||
_outputs = null
|
||||
}
|
||||
}
|
||||
@@ -186,10 +167,12 @@ class EditorCellView(
|
||||
.filter { it is NotebookCellInlayController.InputFactory }
|
||||
}
|
||||
|
||||
private fun failSafeCompute(factory: NotebookCellInlayController.Factory,
|
||||
editor: Editor,
|
||||
controllers: Collection<NotebookCellInlayController>,
|
||||
intervalIterator: ListIterator<NotebookCellLines.Interval>): NotebookCellInlayController? {
|
||||
private fun failSafeCompute(
|
||||
factory: NotebookCellInlayController.Factory,
|
||||
editor: Editor,
|
||||
controllers: Collection<NotebookCellInlayController>,
|
||||
intervalIterator: ListIterator<NotebookCellLines.Interval>,
|
||||
): NotebookCellInlayController? {
|
||||
try {
|
||||
return factory.compute(editor as EditorImpl, controllers, intervalIterator)
|
||||
}
|
||||
@@ -199,11 +182,6 @@ class EditorCellView(
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePositions() {
|
||||
input.updatePositions()
|
||||
outputs?.updatePositions()
|
||||
}
|
||||
|
||||
fun onViewportChanges() {
|
||||
input.onViewportChange()
|
||||
outputs?.onViewportChange()
|
||||
@@ -362,6 +340,24 @@ class EditorCellView(
|
||||
}
|
||||
}
|
||||
|
||||
override fun doInvalidate() {
|
||||
cellInlayManager.invalidateCells()
|
||||
}
|
||||
|
||||
override fun calculateBounds(): Rectangle {
|
||||
val inputBounds = input.calculateBounds()
|
||||
val currentOutputs = outputs
|
||||
return Rectangle(
|
||||
0,
|
||||
inputBounds.y,
|
||||
editor.contentSize.width,
|
||||
currentOutputs?.calculateBounds()
|
||||
?.takeIf { !it.isEmpty }
|
||||
?.let { it.height + it.y - inputBounds.y }
|
||||
?: inputBounds.height
|
||||
)
|
||||
}
|
||||
|
||||
inner class NotebookGutterLineMarkerRenderer(private val interval: NotebookCellLines.Interval) : NotebookLineMarkerRenderer() {
|
||||
override fun paint(editor: Editor, g: Graphics, r: Rectangle) {
|
||||
editor as EditorImpl
|
||||
@@ -381,10 +377,12 @@ class EditorCellView(
|
||||
}
|
||||
}
|
||||
|
||||
private fun paintBackground(editor: EditorImpl,
|
||||
g: Graphics,
|
||||
r: Rectangle,
|
||||
interval: NotebookCellLines.Interval) {
|
||||
private fun paintBackground(
|
||||
editor: EditorImpl,
|
||||
g: Graphics,
|
||||
r: Rectangle,
|
||||
interval: NotebookCellLines.Interval,
|
||||
) {
|
||||
for (controller: NotebookCellInlayController in controllers) {
|
||||
controller.paintGutter(editor, g, r, interval)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,64 @@
|
||||
package org.jetbrains.plugins.notebooks.visualization.ui
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import java.awt.Dimension
|
||||
import java.awt.Point
|
||||
import java.awt.Rectangle
|
||||
|
||||
interface EditorCellViewComponent {
|
||||
val size: Dimension
|
||||
val location: Point
|
||||
abstract class EditorCellViewComponent {
|
||||
|
||||
fun updateGutterIcons(gutterAction: AnAction?)
|
||||
fun dispose()
|
||||
fun onViewportChange()
|
||||
fun addViewComponentListener(listener: EditorCellViewComponentListener)
|
||||
fun updatePositions()
|
||||
var bounds: Rectangle = Rectangle(0, 0, 0, 0)
|
||||
|
||||
private var parent: EditorCellViewComponent? = null
|
||||
|
||||
private val children = mutableListOf<EditorCellViewComponent>()
|
||||
|
||||
private var valid = false
|
||||
|
||||
fun add(child: EditorCellViewComponent) {
|
||||
children.add(child)
|
||||
child.parent = this
|
||||
}
|
||||
|
||||
fun remove(child: EditorCellViewComponent) {
|
||||
children.remove(child)
|
||||
child.parent = null
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
children.forEach { it.dispose() }
|
||||
doDispose()
|
||||
}
|
||||
|
||||
open fun doDispose() {}
|
||||
|
||||
fun onViewportChange() {
|
||||
children.forEach { it.onViewportChange() }
|
||||
doViewportChange()
|
||||
}
|
||||
|
||||
open fun doViewportChange() {}
|
||||
|
||||
abstract fun calculateBounds(): Rectangle
|
||||
|
||||
fun validate() {
|
||||
if (!valid) {
|
||||
doLayout()
|
||||
children.forEach { it.validate() }
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
|
||||
open fun doLayout() {
|
||||
children.forEach {
|
||||
it.bounds = it.calculateBounds()
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidate() {
|
||||
if (valid) {
|
||||
doInvalidate()
|
||||
valid = false
|
||||
parent?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
open fun doInvalidate() {}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.jetbrains.plugins.notebooks.visualization.ui
|
||||
|
||||
import java.awt.Dimension
|
||||
import java.awt.Point
|
||||
import java.util.EventListener
|
||||
|
||||
interface EditorCellViewComponentListener : EventListener {
|
||||
|
||||
fun componentBoundaryChanged(location: Point, size: Dimension) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.jetbrains.plugins.notebooks.visualization.ui
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
|
||||
interface HasGutterIcon {
|
||||
fun updateGutterIcons(gutterAction: AnAction?)
|
||||
|
||||
}
|
||||
@@ -6,39 +6,20 @@ import com.intellij.openapi.editor.markup.HighlighterLayer
|
||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.util.EventDispatcher
|
||||
import org.jetbrains.plugins.notebooks.visualization.NotebookCellLines
|
||||
import java.awt.Dimension
|
||||
import java.awt.Point
|
||||
import java.awt.Rectangle
|
||||
|
||||
class TextEditorCellViewComponent(
|
||||
private val editor: EditorEx,
|
||||
private val cell: EditorCell
|
||||
) : EditorCellViewComponent {
|
||||
private val cell: EditorCell,
|
||||
) : EditorCellViewComponent(), HasGutterIcon {
|
||||
|
||||
private var highlighters: List<RangeHighlighter>? = null
|
||||
|
||||
private val interval: NotebookCellLines.Interval
|
||||
get() = cell.intervalPointer.get() ?: error("Invalid interval")
|
||||
|
||||
private val cellEventListeners = EventDispatcher.create(EditorCellViewComponentListener::class.java)
|
||||
override val location: Point
|
||||
get() {
|
||||
val startOffset = editor.document.getLineStartOffset(interval.lines.first)
|
||||
return editor.offsetToXY(startOffset)
|
||||
}
|
||||
|
||||
override val size: Dimension
|
||||
get() {
|
||||
val interval = interval
|
||||
val startOffset = editor.document.getLineStartOffset(interval.lines.first)
|
||||
val endOffset = editor.document.getLineEndOffset(interval.lines.last)
|
||||
val location = editor.offsetToXY(startOffset)
|
||||
val height = editor.offsetToXY(endOffset).y + editor.lineHeight - location.y
|
||||
val width = editor.offsetToXY(endOffset).x - location.x
|
||||
return Dimension(width, height)
|
||||
}
|
||||
|
||||
override fun updateGutterIcons(gutterAction: AnAction?) {
|
||||
disposeExistingHighlighter()
|
||||
val action = gutterAction
|
||||
@@ -59,13 +40,10 @@ class TextEditorCellViewComponent(
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
override fun doDispose() {
|
||||
disposeExistingHighlighter()
|
||||
}
|
||||
|
||||
override fun onViewportChange() {
|
||||
}
|
||||
|
||||
private fun disposeExistingHighlighter() {
|
||||
if (highlighters != null) {
|
||||
highlighters?.forEach {
|
||||
@@ -75,11 +53,15 @@ class TextEditorCellViewComponent(
|
||||
}
|
||||
}
|
||||
|
||||
override fun addViewComponentListener(listener: EditorCellViewComponentListener) {
|
||||
cellEventListeners.addListener(listener)
|
||||
override fun calculateBounds(): Rectangle {
|
||||
val startOffset = editor.document.getLineStartOffset(interval.lines.first)
|
||||
val location = editor.offsetToXY(startOffset)
|
||||
val interval = interval
|
||||
val endOffset = editor.document.getLineEndOffset(interval.lines.last)
|
||||
val height = editor.offsetToXY(endOffset).y + editor.lineHeight - location.y
|
||||
val width = editor.offsetToXY(endOffset).x - location.x
|
||||
val dimension = Dimension(width, height)
|
||||
return Rectangle(location, dimension)
|
||||
}
|
||||
|
||||
override fun updatePositions() {
|
||||
cellEventListeners.multicaster.componentBoundaryChanged(location, size)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user