PY-73608 Jupyter(feat): Cell Manager: Rewrite event system v2

GitOrigin-RevId: 14ca6dab713368fa0dea26bddbf31d95a561464c
This commit is contained in:
Nikita.Ashihmin
2024-07-24 16:07:31 +04:00
committed by intellij-monorepo-bot
parent 979c853600
commit abc0f80620
14 changed files with 201 additions and 178 deletions

View File

@@ -86,10 +86,6 @@ interface NotebookCellInlayController {
fun createGutterRendererLineMarker(editor: EditorEx, interval: NotebookCellLines.Interval, cellView: EditorCellView) {}
/**
* This method will be called in folding batch operation
*/
fun updateCellFolding() = Unit
val shouldUpdateInlay: Boolean
get() = false

View File

@@ -20,11 +20,11 @@ import com.intellij.util.EventDispatcher
import com.intellij.util.SmartList
import com.intellij.util.concurrency.ThreadingAssertions
import org.jetbrains.plugins.notebooks.ui.isFoldingEnabledKey
import org.jetbrains.plugins.notebooks.visualization.inlay.JupyterBoundsChangeHandler
import org.jetbrains.plugins.notebooks.visualization.ui.*
import org.jetbrains.plugins.notebooks.visualization.ui.EditorCellEventListener.*
import org.jetbrains.plugins.notebooks.visualization.ui.EditorCellViewEventListener.CellViewCreated
import org.jetbrains.plugins.notebooks.visualization.ui.EditorCellViewEventListener.CellViewRemoved
import org.jetbrains.plugins.notebooks.visualization.ui.keepScrollingPositionWhile
import java.util.*
class NotebookCellInlayManager private constructor(
@@ -232,6 +232,7 @@ class NotebookCellInlayManager private constructor(
}.toMutableList()
}
JupyterBoundsChangeHandler.get(editor)?.postponeUpdates()
_cells.forEach {
it.view?.postInitInlays()
}
@@ -240,6 +241,7 @@ class NotebookCellInlayManager private constructor(
cellEventListeners.multicaster.onEditorCellEvents(_cells.map { CellCreated(it) })
inlaysChanged()
JupyterBoundsChangeHandler.get(editor)?.performPostponed()
}
private fun createCell(interval: NotebookIntervalPointer) = EditorCell(editor, this, interval) { cell ->
@@ -250,10 +252,10 @@ class NotebookCellInlayManager private constructor(
changedListener?.inlaysChanged()
}
private fun updateCellsFolding(editorCells: List<EditorCell>) = update { ctx ->
private fun updateCellsFolding(editorCells: List<EditorCell>) = update { updateContext ->
val cellsForFoldingUpdate = editorCells.filter { it.view?.shouldUpdateFolding == true }
cellsForFoldingUpdate.forEach { cell ->
cell.view?.updateCellFolding(ctx)
cell.view?.updateCellFolding(updateContext)
}
}
@@ -379,22 +381,6 @@ class NotebookCellInlayManager private constructor(
invalidationListeners.forEach { it.run() }
}
}
fun validateCells() {
if (!valid) {
_cells.forEach {
it.view?.also { view ->
view.bounds = view.calculateBounds()
view.validate()
}
}
valid = true
}
}
fun onInvalidate(function: () -> Unit) {
invalidationListeners.add(function)
}
}
class UpdateContext(val force: Boolean = false) {
@@ -410,5 +396,4 @@ class UpdateContext(val force: Boolean = false) {
foldingOperations.forEach { it() }
}
}
}

View File

@@ -0,0 +1,121 @@
package org.jetbrains.plugins.notebooks.visualization.inlay
import com.intellij.openapi.Disposable
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.editor.InlayModel
import com.intellij.openapi.editor.ex.FoldingListener
import com.intellij.openapi.editor.ex.SoftWrapChangeListener
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.util.EventDispatcher
import org.jetbrains.plugins.notebooks.visualization.NotebookCellLines
import org.jetbrains.plugins.notebooks.visualization.NotebookCellLinesEvent
import java.beans.PropertyChangeListener
class JupyterBoundsChangeHandler(val editor: EditorImpl) : Disposable {
private var isDelayed = false
private var isShouldBeRecalculated = false
private val dispatcher = EventDispatcher.create(JupyterBoundsChangeListener::class.java)
init {
editor.addPropertyChangeListener(PropertyChangeListener { _ ->
boundsChanged()
}, this)
editor.softWrapModel.addSoftWrapChangeListener(object : SoftWrapChangeListener {
override fun softWrapsChanged() {
}
override fun recalculationEnds() {
if (editor.document.isInEventsHandling)
return
boundsChanged()
}
})
editor.foldingModel.addListener(object : FoldingListener {
override fun onFoldProcessingEnd() {
boundsChanged()
}
}, this)
NotebookCellLines.get(editor).intervalListeners.addListener(object : NotebookCellLines.IntervalListener {
override fun documentChanged(event: NotebookCellLinesEvent) {
if (event.isIntervalsChanged())
boundsChanged()
}
})
editor.inlayModel.addListener(object : InlayModel.Listener {
override fun onBatchModeFinish(editor: Editor) = boundsChanged()
override fun onAdded(inlay: Inlay<*>) {
if (inlay.heightInPixels == 0)
return
recalculateIfNotBatch()
}
override fun onUpdated(inlay: Inlay<*>, changeFlags: Int) {
if ((changeFlags and InlayModel.ChangeFlags.HEIGHT_CHANGED) != 0)
recalculateIfNotBatch()
}
override fun onRemoved(inlay: Inlay<*>) = recalculateIfNotBatch()
private fun recalculateIfNotBatch() {
if (editor.inlayModel.isInBatchMode)
return
if (editor.document.isInEventsHandling)
return
boundsChanged()
}
}, this)
}
override fun dispose() {}
fun subscribe(listener: JupyterBoundsChangeListener) {
dispatcher.addListener(listener)
}
fun unsubscribe(listener: JupyterBoundsChangeListener) {
dispatcher.removeListener(listener)
}
fun boundsChanged() {
if (isDelayed) {
isShouldBeRecalculated = true
return
}
dispatcher.multicaster.boundsChanged()
}
fun postponeUpdates() {
isDelayed = true
isShouldBeRecalculated = false
}
fun performPostponed() {
isDelayed = false
if (isShouldBeRecalculated) {
boundsChanged()
}
}
companion object {
private val INSTANCE_KEY = Key<JupyterBoundsChangeHandler>("INLAYS_CHANGE_HANDLER")
fun install(editor: EditorImpl) {
val updater = JupyterBoundsChangeHandler(editor).also { Disposer.register(editor.disposable, it) }
editor.putUserData(INSTANCE_KEY, updater)
}
fun get(editor: Editor): JupyterBoundsChangeHandler? = INSTANCE_KEY.get(editor)
}
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.plugins.notebooks.visualization.inlay
import java.util.*
interface JupyterBoundsChangeListener: EventListener {
fun boundsChanged() {}
}

View File

@@ -5,33 +5,19 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.FoldRegion
import com.intellij.openapi.editor.ex.FoldingModelEx
import com.intellij.openapi.util.Disposer
import com.intellij.util.asSafely
import org.jetbrains.plugins.notebooks.visualization.NotebookCellInlayController
import org.jetbrains.plugins.notebooks.visualization.UpdateContext
import java.awt.Rectangle
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.JComponent
class ControllerEditorCellViewComponent(
internal val controller: NotebookCellInlayController,
private val parent: EditorCellInput,
private val editor: Editor,
private val cell: EditorCell,
) : EditorCellViewComponent(), HasGutterIcon {
private val listener = object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
parent.invalidate()
}
}
private var foldedRegion: FoldRegion? = null
init {
controller.inlay.renderer.asSafely<JComponent>()?.addComponentListener(listener)
}
override fun updateGutterIcons(gutterAction: AnAction?) {
val inlay = controller.inlay
inlay.putUserData(NotebookCellInlayController.GUTTER_ACTION_KEY, gutterAction)
@@ -39,7 +25,6 @@ class ControllerEditorCellViewComponent(
}
override fun doDispose() {
controller.inlay.renderer.asSafely<JComponent>()?.removeComponentListener(listener)
controller.let { controller -> Disposer.dispose(controller.inlay) }
disposeFolding()
}
@@ -57,8 +42,7 @@ class ControllerEditorCellViewComponent(
}
override fun calculateBounds(): Rectangle {
val component = controller.inlay.renderer as JComponent
return component.bounds
return controller.inlay.bounds ?: Rectangle(0, 0, 0, 0)
}
override fun updateCellFolding(updateContext: UpdateContext) {

View File

@@ -9,7 +9,6 @@ import com.intellij.openapi.editor.event.*
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.util.EditorScrollingPositionKeeper
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.editor.impl.EditorEmbeddedComponentManager.FullEditorWidthRenderer
import com.intellij.openapi.fileEditor.FileEditorState
import com.intellij.openapi.fileEditor.FileEditorStateLevel
import com.intellij.openapi.fileEditor.TextEditor
@@ -19,6 +18,7 @@ import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.plugins.notebooks.ui.editor.actions.command.mode.NotebookEditorMode
import org.jetbrains.plugins.notebooks.ui.editor.actions.command.mode.setMode
import org.jetbrains.plugins.notebooks.visualization.*
import org.jetbrains.plugins.notebooks.visualization.inlay.JupyterBoundsChangeHandler
import org.jetbrains.plugins.notebooks.visualization.ui.EditorCellViewEventListener.CellViewRemoved
import org.jetbrains.plugins.notebooks.visualization.ui.EditorCellViewEventListener.EditorCellViewEvent
import java.awt.*
@@ -49,9 +49,6 @@ private class DecoratedEditor(private val original: TextEditor, private val mana
setupEditorComponentWrapper()
manager.onInvalidate {
component.revalidate()
}
manager.addCellViewEventsListener(object : EditorCellViewEventListener {
override fun onEditorCellViewEvents(events: List<EditorCellViewEvent>) {
events.asSequence().filterIsInstance<CellViewRemoved>().forEach {
@@ -146,7 +143,7 @@ private class DecoratedEditor(private val original: TextEditor, private val mana
val scrollPane = editorEx.scrollPane
val view = scrollPane.viewport.view
scrollPane.viewport.isOpaque = false
scrollPane.viewport.view = EditorComponentWrapper(editorEx, view as EditorComponentImpl, manager)
scrollPane.viewport.view = EditorComponentWrapper(editorEx, view as EditorComponentImpl)
}
override fun getComponent(): JComponent = component
@@ -214,13 +211,9 @@ private class DecoratedEditor(private val original: TextEditor, private val mana
}
private fun getCellViewByPoint(point: Point): EditorCellView? {
val cells = manager.cells
val currentOverCell = cells.filter { it.visible }.mapNotNull { it.view }.firstOrNull {
val viewTop = it.bounds.y
val viewBottom = viewTop + it.bounds.height
point.y in viewTop..<viewBottom
}
return currentOverCell
val visualLine = editor.xyToLogicalPosition(point)
val cur = manager.cells.firstOrNull { it.interval.lines.contains(visualLine.line) }
return cur?.view
}
override fun inlayClicked(clickedCell: NotebookCellLines.Interval, ctrlPressed: Boolean, shiftPressed: Boolean) {
@@ -228,6 +221,7 @@ private class DecoratedEditor(private val original: TextEditor, private val mana
mousePressed(clickedCell, ctrlPressed, shiftPressed)
}
@Suppress("ConvertArgumentToSet")
private fun mousePressed(clickedCell: NotebookCellLines.Interval, ctrlPressed: Boolean, shiftPressed: Boolean) {
val model = editor.cellSelectionModel!!
when {
@@ -287,8 +281,7 @@ internal fun keepScrollingPositionWhile(editor: Editor, task: Runnable) {
class EditorComponentWrapper(
private val editor: Editor,
private val editorComponent: EditorComponentImpl,
private val manager: NotebookCellInlayManager,
editorComponent: EditorComponentImpl,
) : JComponent() {
init {
isOpaque = false
@@ -296,21 +289,11 @@ class EditorComponentWrapper(
add(editorComponent, BorderLayout.CENTER)
}
override fun doLayout() {
super.doLayout()
// EditorEmbeddedComponentManager breaks the Swing layout model as it expect that subcomponents will define their own bounds themselves.
// Here we invoke FullEditorWidthRenderer#validate to place inlay components correctly after doLayout.
editorComponent.components.asSequence()
.filterIsInstance<FullEditorWidthRenderer>()
.forEach {
it.validate()
}
manager.validateCells()
}
override fun validateTree() {
keepScrollingPositionWhile(editor) {
JupyterBoundsChangeHandler.get(editor)?.postponeUpdates()
super.validateTree()
JupyterBoundsChangeHandler.get(editor)?.performPostponed()
}
}
}

View File

@@ -1,11 +1,13 @@
package org.jetbrains.plugins.notebooks.visualization.ui
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.ui.ExperimentalUI
import com.intellij.ui.paint.LinePainter2D
import com.intellij.ui.paint.RectanglePainter2D
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.notebooks.ui.visualization.notebookAppearance
import org.jetbrains.plugins.notebooks.visualization.inlay.JupyterBoundsChangeHandler
import org.jetbrains.plugins.notebooks.visualization.inlay.JupyterBoundsChangeListener
import org.jetbrains.plugins.notebooks.visualization.use
import java.awt.*
import java.awt.event.MouseAdapter
@@ -14,7 +16,7 @@ import javax.swing.JComponent
@ApiStatus.Internal
class EditorCellFoldingBar(
private val editor: EditorEx,
private val editor: EditorImpl,
private val yAndHeightSupplier: () -> Pair<Int, Int>,
private val toggleListener: () -> Unit,
) {
@@ -22,6 +24,12 @@ class EditorCellFoldingBar(
// Because it is not possible to create RangeHighlighter for every single inlay, only on a chain of consequential inlays.
private var panel: JComponent? = null
private val boundsChangeListener = object : JupyterBoundsChangeListener {
override fun boundsChanged() {
updateBounds()
}
}
var visible: Boolean
get() = panel?.isVisible == true
set(value) {
@@ -31,6 +39,7 @@ class EditorCellFoldingBar(
val panel = createFoldingBar()
editor.gutterComponentEx.add(panel)
this.panel = panel
JupyterBoundsChangeHandler.get(editor)?.subscribe(boundsChangeListener)
updateBounds()
}
else {
@@ -52,6 +61,7 @@ class EditorCellFoldingBar(
remove(it)
repaint()
}
JupyterBoundsChangeHandler.get(editor)?.unsubscribe(boundsChangeListener)
panel = null
}
}

View File

@@ -3,7 +3,7 @@ package org.jetbrains.plugins.notebooks.visualization.ui
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.FoldRegion
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.plugins.notebooks.ui.visualization.notebookAppearance
@@ -11,7 +11,7 @@ import org.jetbrains.plugins.notebooks.visualization.NotebookCellLines
import java.awt.Rectangle
class EditorCellInput(
private val editor: EditorEx,
private val editor: EditorImpl,
private val componentFactory: (EditorCellInput, EditorCellViewComponent?) -> EditorCellViewComponent,
private val cell: EditorCell,
) : EditorCellViewComponent() {
@@ -44,13 +44,21 @@ class EditorCellInput(
private var gutterAction: AnAction? = null
private fun getFoldingBounds() : Pair<Int, Int> {
private fun getFoldingBounds(): Pair<Int, Int> {
//For disposed
cell
if (cell.intervalPointer.get()==null) {
return Pair(0,0)
}
val delimiterPanelSize = if (interval.ordinal == 0) {
editor.notebookAppearance.aboveFirstCellDelimiterHeight
}
else {
editor.notebookAppearance.cellBorderHeight / 2
}
val bounds = calculateBounds()
return bounds.y + delimiterPanelSize to bounds.height - delimiterPanelSize
}
@@ -63,7 +71,6 @@ class EditorCellInput(
toggleTextFolding()
inputComponentFactory(this, component)
}
invalidate()
}
private fun toggleTextFolding() {
@@ -127,17 +134,9 @@ class EditorCellInput(
return bounds
}
override fun doLayout() {
super.doLayout()
folding.updateBounds()
}
fun updateInput(force: Boolean = false) {
val oldComponent = if (force) null else component
component = componentFactory(this, oldComponent)
if (bounds != calculateBounds()) {
invalidate()
}
}
}

View File

@@ -3,30 +3,22 @@ package org.jetbrains.plugins.notebooks.visualization.ui
import com.intellij.ide.DataManager
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.DataKey
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.util.Disposer
import com.intellij.ui.AncestorListenerAdapter
import com.intellij.ui.ComponentUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.notebooks.ui.isFoldingEnabledKey
import org.jetbrains.plugins.notebooks.visualization.outputs.NotebookOutputComponentFactory.Companion.gutterPainter
import org.jetbrains.plugins.notebooks.visualization.outputs.NotebookOutputInlayShowable
import org.jetbrains.plugins.notebooks.visualization.outputs.impl.CollapsingComponent
import org.jetbrains.plugins.notebooks.visualization.outputs.impl.SurroundingComponent
import java.awt.Graphics
import java.awt.Point
import java.awt.Rectangle
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.JComponent
import javax.swing.SwingUtilities
import javax.swing.event.AncestorEvent
val NOTEBOOK_CELL_OUTPUT_DATA_KEY = DataKey.create<EditorCellOutput>("NOTEBOOK_CELL_OUTPUT")
class EditorCellOutput internal constructor(
private val editor: EditorEx,
private val editor: EditorImpl,
private val component: CollapsingComponent,
private val toDispose: Disposable?,
) : EditorCellViewComponent() {
@@ -55,8 +47,8 @@ class EditorCellOutput internal constructor(
fun getOutputComponent(): JComponent = component.mainComponent
private fun getFoldingBounds(): Pair<Int, Int> {
val inlayComponentLocation = SwingUtilities.convertPoint(component, Point(0, 0), editor.gutterComponentEx)
return inlayComponentLocation.y to component.height
val bounds = calculateBounds()
return bounds.y to bounds.height
}
override fun doDispose() {
@@ -67,7 +59,6 @@ class EditorCellOutput internal constructor(
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)
}
@@ -87,7 +78,16 @@ class EditorCellOutput internal constructor(
}
override fun calculateBounds(): Rectangle {
val location = SwingUtilities.convertPoint(component.parent, component.location, editor.contentComponent)
val allCellOutputs = parent as EditorCellOutputs
//Need validate because swing component can be invalid on update
allCellOutputs.innerComponent.validate()
val inlayBounds = allCellOutputs.inlay?.bounds ?: Rectangle(0, 0, 0, 0)
val diffBetweenInternalAndExternal = allCellOutputs.innerComponent.location
val location = component.location
location.translate(inlayBounds.x + diffBetweenInternalAndExternal.x, inlayBounds.y + diffBetweenInternalAndExternal.y)
return Rectangle(location, component.size)
}
}

View File

@@ -12,6 +12,7 @@ import com.intellij.util.asSafely
import org.jetbrains.plugins.notebooks.ui.visualization.notebookAppearance
import org.jetbrains.plugins.notebooks.visualization.NotebookCellLines
import org.jetbrains.plugins.notebooks.visualization.SwingClientProperty
import org.jetbrains.plugins.notebooks.visualization.inlay.JupyterBoundsChangeHandler
import org.jetbrains.plugins.notebooks.visualization.outputs.*
import org.jetbrains.plugins.notebooks.visualization.outputs.NotebookOutputComponentFactory.Companion.gutterPainter
import org.jetbrains.plugins.notebooks.visualization.outputs.impl.CollapsingComponent
@@ -23,8 +24,6 @@ import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Rectangle
import java.awt.Toolkit
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.JComponent
class EditorCellOutputs(
@@ -53,7 +52,7 @@ class EditorCellOutputs(
val outputs: List<EditorCellOutput>
get() = _outputs
private val innerComponent = InnerComponent().also {
internal val innerComponent = InnerComponent().also {
it.maxHeight = if (!ApplicationManager.getApplication().isUnitTestMode) {
(Toolkit.getDefaultToolkit().screenSize.height * 0.3).toInt()
}
@@ -65,7 +64,18 @@ class EditorCellOutputs(
private val outerComponent = SurroundingComponent.create(editor, innerComponent).also {
DataManager.registerDataProvider(it, NotebookCellDataProvider(editor, it, interval))
}
private var inlay: Inlay<*>? = null
internal var inlay: Inlay<*>? = null
private set(value) {
val oldHeight = field?.heightInPixels ?: 0
val newHeight = value?.heightInPixels ?: 0
val shouldUpdate = oldHeight != newHeight
field = value
if (shouldUpdate)
JupyterBoundsChangeHandler.get(editor)?.boundsChanged()
}
init {
update()
@@ -227,15 +237,6 @@ class EditorCellOutputs(
priority = editor.notebookAppearance.NOTEBOOK_OUTPUT_INLAY_PRIORITY,
offset = computeInlayOffset(editor.document, interval().lines),
).also { inlay ->
inlay.renderer.asSafely<JComponent>()?.addComponentListener(object : ComponentAdapter() {
private fun update() {
invalidate()
outputs.forEach { it.folding.updateBounds() }
}
override fun componentResized(e: ComponentEvent) = update()
override fun componentMoved(e: ComponentEvent?) = update()
})
Disposer.register(inlay) {
onInlayDisposed(this)
}

View File

@@ -33,7 +33,6 @@ import java.awt.Point
import java.awt.Rectangle
import java.time.ZonedDateTime
import javax.swing.JComponent
import javax.swing.JPanel
import kotlin.reflect.KClass
class EditorCellView(
@@ -92,7 +91,7 @@ class EditorCellView(
currentComponent
}
else {
ControllerEditorCellViewComponent(controller, parent, editor, cell)
ControllerEditorCellViewComponent(controller, editor, cell)
}
}
else {
@@ -109,9 +108,6 @@ class EditorCellView(
}
fun postInitInlays() {
_controllers.forEach {
(it.inlay.renderer as? JPanel)?.validate()
}
updateControllers()
}
@@ -152,7 +148,6 @@ class EditorCellView(
input.update(force)
updateOutputs()
updateCellHighlight()
invalidate()
}
private fun recreateControllers() {
@@ -395,31 +390,20 @@ class EditorCellView(
input.runCellButton?.visible = mouseOver || selected
}
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
)
val outputRectangle = currentOutputs?.calculateBounds()?.takeIf { !it.isEmpty }
val height = outputRectangle?.let { it.height + it.y - inputBounds.y } ?: inputBounds.height
return Rectangle(0, inputBounds.y, editor.contentSize.width, height)
}
val shouldUpdateFolding: Boolean
get() = controllers.any { it.shouldUpdateInlay }
override fun updateCellFolding(updateContext: UpdateContext) {
super.updateCellFolding(updateContext)
controllers.forEach { it.updateCellFolding() }
}
fun updateExecutionStatus(executionCount: Int?, progressStatus: ProgressStatus?, startTime: ZonedDateTime?, endTime: ZonedDateTime?) {
_controllers.filterIsInstance<CellExecutionStatusView>().firstOrNull()

View File

@@ -4,31 +4,18 @@ import org.jetbrains.plugins.notebooks.visualization.UpdateContext
import java.awt.Rectangle
abstract class EditorCellViewComponent {
var bounds: Rectangle = Rectangle(0, 0, 0, 0)
set(value) {
if (value != field) {
invalidate()
}
field = value
}
private var parent: EditorCellViewComponent? = null
protected var parent: EditorCellViewComponent? = null
private val children = mutableListOf<EditorCellViewComponent>()
private var valid = false
fun add(child: EditorCellViewComponent) {
children.add(child)
child.parent = this
invalidate()
}
fun remove(child: EditorCellViewComponent) {
children.remove(child)
child.parent = null
invalidate()
}
fun dispose() {
@@ -47,31 +34,6 @@ abstract class EditorCellViewComponent {
abstract fun calculateBounds(): Rectangle
fun isValid(): Boolean = valid
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() = Unit
open fun updateCellFolding(updateContext: UpdateContext) {
children.forEach {
it.updateCellFolding(updateContext)

View File

@@ -8,7 +8,6 @@ import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.editor.impl.EditorEmbeddedComponentManager
import com.intellij.openapi.editor.impl.EditorEmbeddedComponentManager.Properties.RendererFactory
import java.awt.Container
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.HierarchyEvent
@@ -116,11 +115,3 @@ fun JComponent.yOffsetFromEditor(editor: Editor): Int? =
SwingUtilities.convertPoint(this, 0, 0, editor.contentComponent).y
.takeIf { it >= 0 }
?.let { it + insets.top }
fun validateComponent(c: Container) {
var validationRoot = c
while (!validationRoot.isValidateRoot && validationRoot.parent != null) {
validationRoot = validationRoot.parent
}
validationRoot.validate()
}

View File

@@ -26,7 +26,7 @@ class TextEditorCellViewComponent(
override fun updateGutterIcons(gutterAction: AnAction?) {
disposeExistingHighlighter()
if (gutterAction != null && cell.view?.isValid() == true) {
if (gutterAction != null) {
val markupModel = editor.markupModel
val interval = safeInterval ?: return
val startOffset = editor.document.getLineStartOffset(interval.lines.first)