From b704bec09a42d431673d580c52e4660c288b6837 Mon Sep 17 00:00:00 2001 From: Bogdan Kirilenko Date: Fri, 17 May 2024 18:11:49 +0200 Subject: [PATCH] [jupyter] PY-72701 better "run cell" button UX GitOrigin-RevId: 3a14c64d4c398b14eb1c76688f0f2702d008bc61 --- .../visualization/ui/EditorCellInput.kt | 11 +++++ .../visualization/ui/EditorCellRunButton.kt | 42 ++++++++++++++++++ .../visualization/ui/EditorCellView.kt | 10 +++++ .../ui/EditorRunCellGutterIconRenderer.kt | 43 +++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellRunButton.kt create mode 100644 notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorRunCellGutterIconRenderer.kt diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellInput.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellInput.kt index ac726b48fc08..ff743f28d94f 100644 --- a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellInput.kt +++ b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellInput.kt @@ -56,6 +56,8 @@ class EditorCellInput( }) } + private val runCellButton = EditorCellRunButton(editor) + val component: EditorCellViewComponent get() = _component @@ -107,6 +109,7 @@ class EditorCellInput( fun dispose() { folding.dispose() + runCellButton.dispose() _component.dispose() } @@ -140,6 +143,14 @@ class EditorCellInput( folding.show() } + fun showRunButton() { + runCellButton.showRunButton(interval, interval.type) + } + + fun hideRunButton() { + runCellButton.hideRunButton() + } + fun addViewComponentListener(listener: EditorCellViewComponentListener) { cellEventListeners.addListener(listener) } diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellRunButton.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellRunButton.kt new file mode 100644 index 000000000000..fb9626aa0f09 --- /dev/null +++ b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellRunButton.kt @@ -0,0 +1,42 @@ +package org.jetbrains.plugins.notebooks.visualization.ui + +import com.intellij.openapi.editor.ex.EditorEx +import com.intellij.openapi.editor.markup.HighlighterLayer +import com.intellij.openapi.editor.markup.HighlighterTargetArea +import com.intellij.openapi.editor.markup.RangeHighlighter +import org.jetbrains.plugins.notebooks.visualization.NotebookCellLines + + +class EditorCellRunButton(private val editor: EditorEx) { + private var cellRangeHighlighter: RangeHighlighter? = null + + fun showRunButton(interval: NotebookCellLines.Interval, type: NotebookCellLines.CellType) { + hideRunButton() + if (type != NotebookCellLines.CellType.CODE) return + + val linesRange = interval.lines + + val startOffset = editor.document.getLineStartOffset(linesRange.first) + val endOffset = editor.document.getLineEndOffset(linesRange.last) + + cellRangeHighlighter = editor.markupModel.addRangeHighlighter( + startOffset, + endOffset, + HighlighterLayer.ERROR + 1, + null, + HighlighterTargetArea.LINES_IN_RANGE + ) + cellRangeHighlighter?.gutterIconRenderer = EditorRunCellGutterIconRenderer(linesRange) + } + + fun hideRunButton() { + cellRangeHighlighter?.let { + editor.markupModel.removeHighlighter(it) + cellRangeHighlighter = null + } + } + + fun dispose() { + hideRunButton() + } +} diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellView.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellView.kt index f7c4286e976d..5559db8adc26 100644 --- a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellView.kt +++ b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorCellView.kt @@ -203,11 +203,13 @@ class EditorCellView( fun mouseExited() { mouseOver = false updateFolding() + updateRunButton() } fun mouseEntered() { mouseOver = true updateFolding() + updateRunButton() } inline fun getExtension(): T? { @@ -296,6 +298,14 @@ class EditorCellView( } } + private fun updateRunButton() { + if (mouseOver || selected) { + input.showRunButton() + } else { + input.hideRunButton() + } + } + inner class NotebookGutterLineMarkerRenderer(private val interval: NotebookCellLines.Interval) : NotebookLineMarkerRenderer() { override fun paint(editor: Editor, g: Graphics, r: Rectangle) { editor as EditorImpl diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorRunCellGutterIconRenderer.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorRunCellGutterIconRenderer.kt new file mode 100644 index 000000000000..4eaecde9a714 --- /dev/null +++ b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/ui/EditorRunCellGutterIconRenderer.kt @@ -0,0 +1,43 @@ +package org.jetbrains.plugins.notebooks.visualization.ui + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.editor.markup.GutterIconRenderer +import org.jetbrains.annotations.ApiStatus +import javax.swing.Icon + +@ApiStatus.Internal +class EditorRunCellGutterIconRenderer(private val lines: IntRange) : GutterIconRenderer() { + // PY-72142 & PY-69788 & PY-72701 - adds "Run cell" button to the gutter + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as EditorRunCellGutterIconRenderer + return lines == other.lines + } + + override fun getClickAction(): AnAction = CellRunAction(lines, action) + override fun getTooltipText(): String? = action.templateText + override fun hashCode(): Int = lines.hashCode() + override fun isNavigateAction(): Boolean = true + override fun getIcon(): Icon = AllIcons.RunConfigurations.TestState.Run + + private class CellRunAction(private val lines: IntRange, + private val action: AnAction) : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + val editor = e.getData(CommonDataKeys.EDITOR) + editor?.caretModel?.moveToOffset(editor.document.getLineStartOffset(lines.first)) + action.actionPerformed(e) + } + } + + companion object { + private const val RUN_CELL_ACTION_ID = "NotebookRunCellAction" + private val action = ActionManager.getInstance().getAction(RUN_CELL_ACTION_ID) + } + +}