[PyCharm] Jupyter (refactor): From EditorCell removed 'mode' and NotebookCellInlayManager lost subscription to NOTEBOOK_EDITOR_MODE. #PY-76627 Fixed

Also small refactor of HeadingInfo and a system of extensions in EditorCell.

(cherry picked from commit c7817c899947c94aa7d288d757cb6b642941414c)


(cherry picked from commit a2a93f22e6a6d264785a9ea236b1689bd69b88cf)

IJ-CR-147319

GitOrigin-RevId: e99bfb9ce00f69cf3679b0f3f12ca06e9d7d1a24
This commit is contained in:
Nikita Pavlenko
2024-10-15 17:45:51 +02:00
committed by intellij-monorepo-bot
parent 5bf8a16266
commit 8514cdbb1a
9 changed files with 68 additions and 94 deletions

View File

@@ -13,10 +13,10 @@ import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.MarkupModelEx
import com.intellij.openapi.editor.ex.RangeHighlighterEx
import com.intellij.openapi.util.Key
import com.intellij.ui.Gray
import com.intellij.util.concurrency.ThreadingAssertions
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.messages.Topic
import java.awt.Color
/**
* The Jupyter Notebook has a modal user interface.
@@ -37,7 +37,6 @@ val NOTEBOOK_EDITOR_MODE: Topic<NotebookEditorModeListener> = Topic.create("Note
@FunctionalInterface
interface NotebookEditorModeListener {
fun onModeChange(editor: Editor, mode: NotebookEditorMode)
}
@@ -150,7 +149,6 @@ fun Editor.setMode(mode: NotebookEditorMode) {
}
}
private val INVISIBLE_CARET = CaretVisualAttributes(
Color(0, 0, 0, 0),
CaretVisualAttributes.Weight.NORMAL)
private val INVISIBLE_CARET = CaretVisualAttributes(Gray.TRANSPARENT,
CaretVisualAttributes.Weight.NORMAL)

View File

@@ -1,9 +1,6 @@
package com.intellij.notebooks.visualization
import com.intellij.ide.ui.LafManagerListener
import com.intellij.notebooks.ui.editor.actions.command.mode.NOTEBOOK_EDITOR_MODE
import com.intellij.notebooks.ui.editor.actions.command.mode.NotebookEditorMode
import com.intellij.notebooks.ui.editor.actions.command.mode.NotebookEditorModeListener
import com.intellij.notebooks.ui.isFoldingEnabledKey
import com.intellij.notebooks.visualization.inlay.JupyterBoundsChangeHandler
import com.intellij.notebooks.visualization.ui.*
@@ -39,7 +36,7 @@ class NotebookCellInlayManager private constructor(
private val shouldCheckInlayOffsets: Boolean,
private val inputFactories: List<NotebookCellInlayController.InputFactory>,
private val cellExtensionFactories: List<CellExtensionFactory>,
) : Disposable, NotebookIntervalPointerFactory.ChangeListener, NotebookEditorModeListener {
) : Disposable, NotebookIntervalPointerFactory.ChangeListener {
private val notebookCellLines = NotebookCellLines.get(editor)
@@ -83,19 +80,22 @@ class NotebookCellInlayManager private constructor(
val newCtx = UpdateContext(force)
updateCtx = newCtx
try {
JupyterBoundsChangeHandler.get(editor).postponeUpdates()
val jupyterBoundsChangeHandler = JupyterBoundsChangeHandler.get(editor)
jupyterBoundsChangeHandler.postponeUpdates()
val r = keepScrollingPositionWhile(editor) {
val r = block(newCtx)
updateCtx = null
if (editorIsProcessingDocument) {
postponedUpdates.add(newCtx)
} else {
}
else {
newCtx.applyUpdates(editor)
}
r
}
inlaysChanged()
JupyterBoundsChangeHandler.get(editor).performPostponed()
jupyterBoundsChangeHandler.boundsChanged()
jupyterBoundsChangeHandler.performPostponed()
r
}
finally {
@@ -184,8 +184,6 @@ class NotebookCellInlayManager private constructor(
setupFoldingListener()
setupSelectionUI()
ApplicationManager.getApplication().messageBus.connect(this).subscribe(NOTEBOOK_EDITOR_MODE, this)
cellEventListeners.addListener(object : EditorCellEventListener {
override fun onEditorCellEvents(events: List<EditorCellEvent>) {
updateUI(events)
@@ -233,7 +231,6 @@ class NotebookCellInlayManager private constructor(
}
}
private fun createCellViewIfNecessary(cell: EditorCell, ctx: UpdateContext) {
if (views[cell] == null) {
createCellView(cell, ctx)
@@ -502,28 +499,6 @@ class NotebookCellInlayManager private constructor(
internal fun getInputFactories(): Sequence<NotebookCellInlayController.InputFactory> {
return inputFactories.asSequence()
}
override fun onModeChange(editor: Editor, mode: NotebookEditorMode) {
if (editor == this.editor) {
when (mode) {
NotebookEditorMode.EDIT -> {
editor.caretModel.allCarets.forEach { caret ->
getCellByLine(editor.document.getLineNumber(caret.offset))?.switchToEditMode()
}
}
NotebookEditorMode.COMMAND -> {
_cells.forEach {
it.switchToCommandMode()
}
}
}
}
}
fun getCellByLine(line: Int): EditorCell? {
return _cells.firstOrNull { it.interval.lines.contains(line) }
}
}
class UpdateContext(val force: Boolean = false) {

View File

@@ -15,6 +15,18 @@ import com.intellij.openapi.util.Key
import com.intellij.util.EventDispatcher
import java.beans.PropertyChangeListener
/**
* Per-editor service on which can one subscribe.
* It sends the boundsChanged event to the subscribed JupyterBoundsChangeListener.
*
* boundsChanged event will be dispatched if
* - someone directly calls JupyterBoundsChangeHandler.get(editor).boundsChanged()
* - EditorImpl property was changed
* - On soft wrap recalculation ends
* - Folding model change
* - Inlay model change
*/
// Class name does not reflect the functionality of this class.
class JupyterBoundsChangeHandler(val editor: EditorImpl) : Disposable {
private var isDelayed = false
private var isShouldBeRecalculated = false
@@ -26,10 +38,8 @@ class JupyterBoundsChangeHandler(val editor: EditorImpl) : Disposable {
boundsChanged()
}, this)
editor.softWrapModel.addSoftWrapChangeListener(object : SoftWrapChangeListener {
override fun softWrapsChanged() {
}
override fun softWrapsChanged() = Unit
override fun recalculationEnds() {
if (editor.document.isInEventsHandling)
@@ -81,7 +91,7 @@ class JupyterBoundsChangeHandler(val editor: EditorImpl) : Disposable {
}, this)
}
override fun dispose() {}
override fun dispose() = Unit
fun subscribe(listener: JupyterBoundsChangeListener) {
dispatcher.addListener(listener)
@@ -91,13 +101,14 @@ class JupyterBoundsChangeHandler(val editor: EditorImpl) : Disposable {
dispatcher.removeListener(listener)
}
fun boundsChanged() {
if (isDelayed) {
isShouldBeRecalculated = true
return
}
dispatcher.multicaster.boundsChanged()
if (!editor.isDisposed) {
dispatcher.multicaster.boundsChanged()
}
}
fun postponeUpdates() {
@@ -116,7 +127,8 @@ class JupyterBoundsChangeHandler(val editor: EditorImpl) : Disposable {
private val INSTANCE_KEY = Key<JupyterBoundsChangeHandler>("INLAYS_CHANGE_HANDLER")
fun install(editor: EditorImpl) {
val updater = JupyterBoundsChangeHandler(editor).also { Disposer.register(editor.disposable, it) }
val updater = JupyterBoundsChangeHandler(editor)
Disposer.register(editor.disposable, updater)
editor.putUserData(INSTANCE_KEY, updater)
}

View File

@@ -2,6 +2,6 @@ package com.intellij.notebooks.visualization.inlay
import java.util.*
interface JupyterBoundsChangeListener: EventListener {
fun boundsChanged() {}
interface JupyterBoundsChangeListener : EventListener {
fun boundsChanged() = Unit
}

View File

@@ -1,6 +1,5 @@
package com.intellij.notebooks.visualization.ui
import com.intellij.notebooks.ui.editor.actions.command.mode.NotebookEditorMode
import com.intellij.notebooks.visualization.NotebookCellInlayController
import com.intellij.notebooks.visualization.NotebookCellInlayManager
import com.intellij.notebooks.visualization.NotebookIntervalPointer
@@ -44,15 +43,8 @@ class EditorCell(
val executionStatus = AtomicProperty<ExecutionStatus>(ExecutionStatus())
// ToDo we should remove or rework this. Mode does not really reflects the state of markdown cells.
val mode = AtomicProperty<NotebookEditorMode>(NotebookEditorMode.COMMAND)
val outputs = AtomicProperty<List<NotebookOutputDataKey>>(getOutputs())
init {
CELL_EXTENSION_CONTAINER_KEY.set(this, mutableMapOf())
}
private fun getSource(): String {
val document = editor.document
if (interval.lines.first + 1 >= document.lineCount) return ""
@@ -151,29 +143,27 @@ class EditorCell(
}
}
fun switchToEditMode() = runInEdt {
mode.set(NotebookEditorMode.EDIT)
}
fun switchToCommandMode() = runInEdt {
mode.set(NotebookEditorMode.COMMAND)
}
fun requestCaret() {
view?.requestCaret()
}
inline fun <reified T : EditorCellExtension> getExtension(): T {
inline fun <reified T : EditorCellExtension> getExtension(): T? {
return getExtension(T::class)
}
@Suppress("UNCHECKED_CAST")
fun <T : EditorCellExtension> getExtension(cls: KClass<T>): T {
return CELL_EXTENSION_CONTAINER_KEY.get(this)!![cls] as T
fun <T : EditorCellExtension> getExtension(cls: KClass<T>): T? {
val extensions = CELL_EXTENSION_CONTAINER_KEY.get(this) ?: return null
return extensions[cls] as? T
}
fun <T : EditorCellExtension> addExtension(cls: KClass<T>, extension: T) {
CELL_EXTENSION_CONTAINER_KEY.get(this)!![cls] = extension
var map = CELL_EXTENSION_CONTAINER_KEY.get(this)
if (map == null) {
map = mutableMapOf<KClass<*>, EditorCellExtension>()
CELL_EXTENSION_CONTAINER_KEY.set(this, map)
}
map[cls] = extension
}
fun onBeforeRemove() {

View File

@@ -1,7 +1,5 @@
package com.intellij.notebooks.visualization.ui
interface EditorCellExtension {
fun onBeforeRemove() {}
fun onBeforeRemove() = Unit
}

View File

@@ -1,15 +1,15 @@
package com.intellij.notebooks.visualization.ui
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 com.intellij.notebooks.ui.visualization.NotebookEditorAppearanceUtils.isOrdinaryNotebookEditor
import com.intellij.notebooks.ui.visualization.notebookAppearance
import com.intellij.notebooks.visualization.inlay.JupyterBoundsChangeHandler
import com.intellij.notebooks.visualization.inlay.JupyterBoundsChangeListener
import com.intellij.notebooks.visualization.use
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 java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
@@ -75,7 +75,9 @@ class EditorCellFoldingBar(
panel.setBounds(editor.gutterComponentEx.extraLineMarkerFreePaintersAreaOffset + 1, yAndHeight.first, 6, yAndHeight.second)
}
private fun createFoldingBar() = object : JComponent() {
private fun createFoldingBar() = EditorCellFoldingBarComponent()
inner class EditorCellFoldingBarComponent : JComponent() {
private var mouseOver = false
init {

View File

@@ -1,12 +1,12 @@
package com.intellij.notebooks.visualization.ui
import com.intellij.codeInsight.hints.presentation.InlayPresentation
import com.intellij.notebooks.visualization.UpdateContext
import com.intellij.openapi.Disposable
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.util.Disposer
import com.intellij.notebooks.visualization.UpdateContext
import java.awt.Rectangle
import java.util.Collections
import java.util.*
abstract class EditorCellViewComponent : Disposable {
protected var parent: EditorCellViewComponent? = null
@@ -23,7 +23,7 @@ abstract class EditorCellViewComponent : Disposable {
Disposer.register(this, child)
}
/* Chile disposable will be automatically disposed. */
/* Child will be automatically disposed. */
fun remove(child: EditorCellViewComponent) {
Disposer.dispose(child)
_children.remove(child)

View File

@@ -43,7 +43,6 @@ class TextEditorCellViewComponent(
override fun mousePressed(e: MouseEvent) {
if (editor.xyToLogicalPosition(e.point).line in cell.interval.lines) {
editor.setMode(NotebookEditorMode.EDIT)
cell.switchToEditMode()
}
}
}
@@ -60,21 +59,21 @@ class TextEditorCellViewComponent(
private fun updateGutterIcons(gutterAction: AnAction?) = runInEdt {
disposeExistingHighlighter()
if (gutterAction != null) {
val markupModel = editor.markupModel
val interval = safeInterval ?: return@runInEdt
val startOffset = editor.document.getLineStartOffset(interval.lines.first)
val endOffset = editor.document.getLineEndOffset(interval.lines.last)
val highlighter = markupModel.addRangeHighlighter(
startOffset,
endOffset,
HighlighterLayer.FIRST - 100,
TextAttributes(),
HighlighterTargetArea.LINES_IN_RANGE
)
highlighter.gutterIconRenderer = ActionToGutterRendererAdapter(gutterAction)
this.highlighters = listOf(highlighter)
}
if (gutterAction == null) return@runInEdt
val markupModel = editor.markupModel
val interval = safeInterval ?: return@runInEdt
val startOffset = editor.document.getLineStartOffset(interval.lines.first)
val endOffset = editor.document.getLineEndOffset(interval.lines.last)
val highlighter = markupModel.addRangeHighlighter(
startOffset,
endOffset,
HighlighterLayer.FIRST - 100,
TextAttributes(),
HighlighterTargetArea.LINES_IN_RANGE
)
highlighter.gutterIconRenderer = ActionToGutterRendererAdapter(gutterAction)
this.highlighters = listOf(highlighter)
}
override fun dispose() = cell.manager.update { ctx ->