diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css b/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css index 7433c9a7eb2fe..88bfc96b8f662 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css @@ -46,6 +46,8 @@ .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folded-hint { position: absolute; user-select: none; + display: flex; + align-items: center; } .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folded-hint-label { @@ -55,6 +57,22 @@ opacity: 0.7; } +.monaco-workbench .notebookOverlay > .cell-list-container .folded-cell-run-section-button { + position: relative; + left: 0px; + padding: 2px; + border-radius: 5px; + margin-right: 4px; + height: 16px; + width: 16px; + z-index: var(--z-index-notebook-cell-expand-part-button); +} + +.monaco-workbench .notebookOverlay > .cell-list-container .folded-cell-run-section-button:hover { + background-color: var(--vscode-editorStickyScrollHover-background); + cursor: pointer; +} + .monaco-workbench .notebookOverlay .cell-editor-container .monaco-editor .margin-view-overlays .codicon-folding-expanded, .monaco-workbench .notebookOverlay .cell-editor-container .monaco-editor .margin-view-overlays .codicon-folding-collapsed { margin-left: 0; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts index 2fe72e05af8b7..211e85e9a62e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts @@ -11,12 +11,21 @@ import { FoldingController } from 'vs/workbench/contrib/notebook/browser/control import { CellEditState, CellFoldingState, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; export class FoldedCellHint extends CellContentPart { + private readonly _runButtonListener = this._register(new MutableDisposable()); + private readonly _cellExecutionListener = this._register(new MutableDisposable()); + constructor( private readonly _notebookEditor: INotebookEditor, private readonly _container: HTMLElement, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService ) { super(); } @@ -27,20 +36,27 @@ export class FoldedCellHint extends CellContentPart { private update(element: MarkupCellViewModel) { if (!this._notebookEditor.hasModel()) { + this._cellExecutionListener.clear(); + this._runButtonListener.clear(); return; } if (element.isInputCollapsed || element.getEditState() === CellEditState.Editing) { + this._cellExecutionListener.clear(); + this._runButtonListener.clear(); DOM.hide(this._container); } else if (element.foldingState === CellFoldingState.Collapsed) { const idx = this._notebookEditor.getViewModel().getCellIndex(element); const length = this._notebookEditor.getViewModel().getFoldedLength(idx); - DOM.reset(this._container, this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element)); + + DOM.reset(this._container, this.getRunFoldedSectionButton({ start: idx, end: idx + length }), this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element)); DOM.show(this._container); const foldHintTop = element.layoutInfo.previewHeight; this._container.style.top = `${foldHintTop}px`; } else { + this._cellExecutionListener.clear(); + this._runButtonListener.clear(); DOM.hide(this._container); } } @@ -67,6 +83,40 @@ export class FoldedCellHint extends CellContentPart { return expandIcon; } + private getRunFoldedSectionButton(range: ICellRange): HTMLElement { + const runAllContainer = DOM.$('span.folded-cell-run-section-button'); + const cells = this._notebookEditor.getCellsInRange(range); + + const isRunning = cells.some(cell => { + const cellExecution = this._notebookExecutionStateService.getCellExecution(cell.uri); + return cellExecution && cellExecution.state === NotebookCellExecutionState.Executing; + }); + + const runAllIcon = isRunning ? + ThemeIcon.modify(executingStateIcon, 'spin') : + Codicon.play; + runAllContainer.classList.add(...ThemeIcon.asClassNameArray(runAllIcon)); + + this._runButtonListener.value = DOM.addDisposableListener(runAllContainer, DOM.EventType.CLICK, () => { + this._notebookEditor.executeNotebookCells(cells); + }); + + this._cellExecutionListener.value = this._notebookExecutionStateService.onDidChangeExecution(() => { + const isRunning = cells.some(cell => { + const cellExecution = this._notebookExecutionStateService.getCellExecution(cell.uri); + return cellExecution && cellExecution.state === NotebookCellExecutionState.Executing; + }); + + const runAllIcon = isRunning ? + ThemeIcon.modify(executingStateIcon, 'spin') : + Codicon.play; + runAllContainer.className = ''; + runAllContainer.classList.add('folded-cell-run-section-button', ...ThemeIcon.asClassNameArray(runAllIcon)); + }); + + return runAllContainer; + } + override updateInternalLayoutNow(element: MarkupCellViewModel) { this.update(element); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 25a1310af4371..9f6f5b8dac591 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -49,6 +49,7 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; const $ = DOM.$; @@ -109,6 +110,8 @@ abstract class AbstractCellRenderer { export class MarkupCellRenderer extends AbstractCellRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'markdown_cell'; + private _notebookExecutionStateService: INotebookExecutionStateService; + constructor( notebookEditor: INotebookEditorDelegate, dndController: CellDragAndDropController, @@ -120,8 +123,10 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService, @INotificationService notificationService: INotificationService, + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService ) { super(instantiationService, notebookEditor, contextMenuService, menuService, configurationService, keybindingService, notificationService, contextKeyServiceProvider, 'markdown', dndController); + this._notebookExecutionStateService = notebookExecutionStateService; } get templateId() { @@ -169,7 +174,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen templateDisposables.add(scopedInstaService.createInstance(CellChatPart, this.notebookEditor, cellChatPart)), templateDisposables.add(scopedInstaService.createInstance(CellEditorStatusBar, this.notebookEditor, container, editorPart, undefined)), templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)), - templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))), + templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')), this._notebookExecutionStateService)), templateDisposables.add(new CellDecorations(rootContainer, decorationContainer)), templateDisposables.add(scopedInstaService.createInstance(CellComments, this.notebookEditor, cellCommentPartContainer)), templateDisposables.add(new CollapsedCellInput(this.notebookEditor, cellInputCollapsedContainer)),