diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css index b363b557896..aeb837a6da7 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.css +++ b/src/vs/base/browser/ui/dropdown/dropdown.css @@ -37,3 +37,10 @@ line-height: 16px; margin-left: -4px; } + +.monaco-dropdown-with-primary > .dropdown-action-container > .monaco-dropdown > .dropdown-label > .action-label { + display: block; + background-size: 16px; + background-position: center center; + background-repeat: no-repeat; +} diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index fceca7212f6..8d72d50d9ff 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -554,3 +554,37 @@ export function mapFind(array: Iterable, mapFn: (value: T) => R | undef return undefined; } + +/** + * Like Math.min with a delegate, and returns the winning index + */ +export function minIndex(array: readonly T[], fn: (value: T) => number): number { + let minValue = Number.MAX_SAFE_INTEGER; + let minIdx = 0; + array.forEach((value, i) => { + const thisValue = fn(value); + if (thisValue < minValue) { + minValue = thisValue; + minIdx = i; + } + }); + + return minIdx; +} + +/** + * Like Math.max with a delegate, and returns the winning index + */ +export function maxIndex(array: readonly T[], fn: (value: T) => number): number { + let minValue = Number.MIN_SAFE_INTEGER; + let maxIdx = 0; + array.forEach((value, i) => { + const thisValue = fn(value); + if (thisValue > minValue) { + minValue = thisValue; + maxIdx = i; + } + }); + + return maxIdx; +} diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index e30a841551c..08afd92a2b4 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -295,4 +295,20 @@ suite('Arrays', () => { remove(); assert.strictEqual(array.length, 0); }); + + test('minIndex', () => { + const array = ['a', 'b', 'c']; + assert.strictEqual(arrays.minIndex(array, value => array.indexOf(value)), 0); + assert.strictEqual(arrays.minIndex(array, value => -array.indexOf(value)), 2); + assert.strictEqual(arrays.minIndex(array, _value => 0), 0); + assert.strictEqual(arrays.minIndex(array, value => value === 'b' ? 0 : 5), 1); + }); + + test('maxIndex', () => { + const array = ['a', 'b', 'c']; + assert.strictEqual(arrays.maxIndex(array, value => array.indexOf(value)), 2); + assert.strictEqual(arrays.maxIndex(array, value => -array.indexOf(value)), 0); + assert.strictEqual(arrays.maxIndex(array, _value => 0), 0); + assert.strictEqual(arrays.maxIndex(array, value => value === 'b' ? 5 : 0), 1); + }); }); diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index 4f2b75c7593..5be87731aaf 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -4,15 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; +import * as DOM from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IAction } from 'vs/base/common/actions'; -import * as DOM from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -23,6 +24,10 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { private _dropdownContainer: HTMLElement | null = null; private toDispose: IDisposable[]; + get onDidChangeDropdownVisibility(): Event { + return this._dropdown.onDidChangeVisibility; + } + constructor( primaryAction: MenuItemAction, dropdownAction: IAction, @@ -35,11 +40,18 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { super(null, primaryAction); this._primaryAction = new MenuEntryActionViewItem(primaryAction, _keybindingService, _notificationService); this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, { - menuAsChild: true + menuAsChild: true, + classNames: ['codicon', 'codicon-chevron-down'] }); this.toDispose = []; } + override setActionContext(newContext: unknown): void { + super.setActionContext(newContext); + this._primaryAction.setActionContext(newContext); + this._dropdown.setActionContext(newContext); + } + override render(container: HTMLElement): void { this._container = container; super.render(this._container); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 978cf8314dd..4e90ff03a3e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -34,7 +34,7 @@ import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } f import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { Iterable } from 'vs/base/common/iterator'; -import { flatten } from 'vs/base/common/arrays'; +import { flatten, maxIndex, minIndex } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; // Kernel Command @@ -62,6 +62,8 @@ const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete'; const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution'; const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow'; const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; +const EXECUTE_CELL_AND_BELOW = 'notebook.cell.executeCellAndBelow'; +const EXECUTE_CELLS_ABOVE = 'notebook.cell.executeCellsAbove'; const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; const CENTER_ACTIVE_CELL = 'notebook.centerActiveCell'; @@ -410,6 +412,74 @@ function parseMultiCellExecutionArgs(accessor: ServicesAccessor, ...args: any[]) return context; } +registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction { + constructor() { + super({ + id: EXECUTE_CELLS_ABOVE, + precondition: executeCellCondition, + title: localize('notebookActions.executeAbove', "Execute Above Cells"), + menu: { + id: MenuId.NotebookCellExecute, + when: executeCellCondition + }, + icon: icons.executeAboveIcon + }); + } + + parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookActionContext | undefined { + return parseMultiCellExecutionArgs(accessor, ...args); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + let endCellIdx: number | undefined = undefined; + if (context.ui && context.cell) { + endCellIdx = context.notebookEditor.viewModel.getCellIndex(context.cell); + } else if (context.selectedCells) { + endCellIdx = maxIndex(context.selectedCells, cell => context.notebookEditor.viewModel.getCellIndex(cell)); + } + + if (typeof endCellIdx === 'number') { + const range = { start: 0, end: endCellIdx }; + const cells = context.notebookEditor.viewModel.getCells(range); + context.notebookEditor.executeNotebookCells(cells); + } + } +}); + +registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction { + constructor() { + super({ + id: EXECUTE_CELL_AND_BELOW, + precondition: executeCellCondition, + title: localize('notebookActions.executeBelow', "Execute Cell and Below"), + menu: { + id: MenuId.NotebookCellExecute, + when: executeCellCondition, + }, + icon: icons.executeBelowIcon + }); + } + + parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookActionContext | undefined { + return parseMultiCellExecutionArgs(accessor, ...args); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + let startCellIdx: number | undefined = undefined; + if (context.ui && context.cell) { + startCellIdx = context.notebookEditor.viewModel.getCellIndex(context.cell); + } else if (context.selectedCells) { + startCellIdx = minIndex(context.selectedCells, cell => context.notebookEditor.viewModel.getCellIndex(cell)); + } + + if (typeof startCellIdx === 'number') { + const range = { start: startCellIdx, end: context.notebookEditor.viewModel.viewCells.length }; + const cells = context.notebookEditor.viewModel.getCells(range); + context.notebookEditor.executeNotebookCells(cells); + } + } +}); + registerAction2(class ExecuteCell extends NotebookMultiCellAction { constructor() { super({ diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 889385b437c..683e6a7e604 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -498,7 +498,7 @@ height: initial; } -.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar .codicon { +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar .action-item:not(.monaco-dropdown-with-primary) .codicon { padding: 6px; } @@ -508,6 +508,7 @@ .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .run-button-container .monaco-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .run-button-container .monaco-toolbar, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-run-toolbar-dropdown-active .run-button-container .monaco-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-output-hover .run-button-container .monaco-toolbar { visibility: visible; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 5114543cfa5..7e4d553dfad 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -30,7 +30,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; -import { CellKind, CellToolbarLocKey, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, getCellUndoRedoComparisonKey, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBarKey, CompactView, FocusIndicator, InsertToolbarPosition, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, ShowCellStatusBarAfterExecuteKey, NotebookCellEditorOptionsCustomizations } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellToolbarLocKey, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, getCellUndoRedoComparisonKey, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBarKey, CompactView, FocusIndicator, InsertToolbarPosition, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, ShowCellStatusBarAfterExecuteKey, NotebookCellEditorOptionsCustomizations, ConsolidatedRunButton } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; @@ -694,6 +694,12 @@ configurationRegistry.registerConfiguration({ default: true, tags: ['notebookLayout'] }, + [ConsolidatedRunButton]: { + description: nls.localize('notebook.consolidatedRunButton.description', "Control whether extra actions are shown in a dropdown next to the run button."), + type: 'boolean', + default: true, + tags: ['notebookLayout'] + }, [NotebookCellEditorOptionsCustomizations]: editorOptionsCustomizationSchema } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts index edeff4ff744..3fe6bdbd6c5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts @@ -11,6 +11,8 @@ export const configureKernelIcon = registerIcon('notebook-kernel-configure', Cod export const selectKernelIcon = registerIcon('notebook-kernel-select', Codicon.serverEnvironment, localize('selectKernelIcon', 'Configure icon to select a kernel in notebook editors.')); export const executeIcon = registerIcon('notebook-execute', Codicon.play, localize('executeIcon', 'Icon to execute in notebook editors.')); +export const executeAboveIcon = registerIcon('notebook-execute-above', Codicon.runAbove, localize('executeAboveIcon', 'Icon to execute above cells in notebook editors.')); +export const executeBelowIcon = registerIcon('notebook-execute-below', Codicon.runBelow, localize('executeBelowIcon', 'Icon to execute below cells in notebook editors.')); export const stopIcon = registerIcon('notebook-stop', Codicon.primitiveSquare, localize('stopIcon', 'Icon to stop an execution in notebook editors.')); export const deleteCellIcon = registerIcon('notebook-delete-cell', Codicon.trash, localize('deleteCellIcon', 'Icon to delete a cell in notebook editors.')); export const executeAllIcon = registerIcon('notebook-execute-all', Codicon.runAll, localize('executeAllIcon', 'Icon to execute all cells in notebook editors.')); 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 a9b5358ca89..a6f96e37bdf 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as Codicons from 'vs/base/common/codicons'; import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; -import * as aria from 'vs/base/browser/ui/aria/aria'; import { domEvent } from 'vs/base/browser/event'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IAction } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; +import * as Codicons from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; @@ -26,6 +26,7 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { localize } from 'vs/nls'; +import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { createActionViewItem, createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -35,10 +36,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { syncing } from 'vs/platform/theme/common/iconRegistry'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { DeleteCellAction, INotebookActionContext, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { BaseCellRenderTemplate, CellEditState, CodeCellLayoutInfo, CodeCellRenderTemplate, EXPAND_CELL_INPUT_COMMAND_ID, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { errorStateIcon, successStateIcon, unfoldIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd'; +import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; @@ -46,12 +52,7 @@ import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellExecutionState, NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { errorStateIcon, successStateIcon, unfoldIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { syncing } from 'vs/platform/theme/common/iconRegistry'; -import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions'; +import { CellEditType, CellKind, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; const $ = DOM.$; @@ -94,11 +95,11 @@ abstract class AbstractCellRenderer { protected readonly notebookEditor: INotebookEditor, protected readonly contextMenuService: IContextMenuService, configurationService: IConfigurationService, - private readonly keybindingService: IKeybindingService, - private readonly notificationService: INotificationService, + protected readonly keybindingService: IKeybindingService, + protected readonly notificationService: INotificationService, protected readonly contextKeyServiceProvider: (container: HTMLElement) => IContextKeyService, language: string, - protected dndController: CellDragAndDropController | undefined, + protected dndController: CellDragAndDropController | undefined ) { this.editorOptions = new CellEditorOptions(notebookEditor.notebookOptions, configurationService, language); this.cellMenus = this.instantiationService.createInstance(CellMenus); @@ -700,7 +701,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const cellContainer = DOM.append(container, $('.cell.code')); const runButtonContainer = DOM.append(cellContainer, $('.run-button-container')); - const runToolbar = disposables.add(this.setupRunToolbar(runButtonContainer, contextKeyService, disposables)); + const runToolbar = this.setupRunToolbar(runButtonContainer, container, contextKeyService, disposables); const executionOrderLabel = DOM.append(cellContainer, $('div.execution-count-label')); const editorPart = DOM.append(cellContainer, $('.cell-editor-part')); @@ -831,18 +832,59 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende return combinedDisposable(dragHandleListener, collapsedPartListener); } - private setupRunToolbar(runButtonContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar { - const runToolbar = this.createToolbar(runButtonContainer); - const runMenu = this.cellMenus.getCellExecuteMenu(contextKeyService); - const update = () => { - const actions = this.getCellToolbarActions(runMenu); - runToolbar.setActions(actions.primary, actions.secondary); - }; - disposables.add(runMenu); - disposables.add(runMenu.onDidChange(() => { - update(); + private createRunCellToolbar(container: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar { + const actionViewItemDisposables = disposables.add(new DisposableStore()); + const dropdownAction = disposables.add(new Action('notebook.moreRunActions', localize('notebook.moreRunActionsLabel', "More..."), 'codicon-chevron-down', true)); + + const toolbar = disposables.add(new ToolBar(container, this.contextMenuService, { + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), + actionViewItemProvider: _action => { + actionViewItemDisposables.clear(); + + const actions = this.getCellToolbarActions(this.cellMenus.getCellExecuteMenu(contextKeyService)); + const primary = actions.primary[0]; + if (!(primary instanceof MenuItemAction)) { + return undefined; + } + + if (!actions.secondary.length) { + return undefined; + } + + if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().consolidatedRunButton) { + return undefined; + } + + const item = new DropdownWithPrimaryActionViewItem( + primary, + dropdownAction, + actions.secondary, + 'notebook-cell-run-toolbar', + this.contextMenuService, + this.keybindingService, + this.notificationService); + actionViewItemDisposables.add(item.onDidChangeDropdownVisibility(visible => { + cellContainer.classList.toggle('cell-run-toolbar-dropdown-active', visible); + })); + + return item; + }, + renderDropdownAsChildElement: true })); - update(); + + return toolbar; + } + + private setupRunToolbar(runButtonContainer: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar { + const menu = this.cellMenus.getCellExecuteMenu(contextKeyService); + const runToolbar = this.createRunCellToolbar(runButtonContainer, cellContainer, contextKeyService, disposables); + const updateActions = () => { + const actions = this.getCellToolbarActions(this.cellMenus.getCellExecuteMenu(contextKeyService)); + runToolbar.setActions(actions.primary); + }; + updateActions(); + disposables.add(menu.onDidChange(updateActions)); + disposables.add(this.notebookEditor.notebookOptions.onDidChangeOptions(updateActions)); return runToolbar; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 96973ca5a9a..3b2c7327d64 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -921,6 +921,7 @@ export const ConsolidatedOutputButton = 'notebook.consolidatedOutputButton'; export const ShowFoldingControls = 'notebook.showFoldingControls'; export const DragAndDropEnabled = 'notebook.dragAndDropEnabled'; export const NotebookCellEditorOptionsCustomizations = 'notebook.editorOptionsCustomizations'; +export const ConsolidatedRunButton = 'notebook.consolidatedRunButton'; export const enum CellStatusbarAlignment { Left = 1, diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts index 58f43fec488..80929816de1 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts @@ -6,7 +6,7 @@ import { Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CellToolbarLocKey, CellToolbarVisibility, CompactView, ConsolidatedOutputButton, DragAndDropEnabled, ExperimentalInsertToolbarAlignment, FocusIndicator, GlobalToolbar, InsertToolbarPosition, NotebookCellEditorOptionsCustomizations, ShowCellStatusBarAfterExecuteKey, ShowCellStatusBarKey, ShowFoldingControls } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellToolbarLocKey, CellToolbarVisibility, CompactView, ConsolidatedOutputButton, ConsolidatedRunButton, DragAndDropEnabled, ExperimentalInsertToolbarAlignment, FocusIndicator, GlobalToolbar, InsertToolbarPosition, NotebookCellEditorOptionsCustomizations, ShowCellStatusBarAfterExecuteKey, ShowCellStatusBarKey, ShowFoldingControls } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const SCROLLABLE_ELEMENT_PADDING_TOP = 18; @@ -54,6 +54,7 @@ export interface NotebookLayoutConfiguration { insertToolbarAlignment: 'left' | 'center'; globalToolbar: boolean; consolidatedOutputButton: boolean; + consolidatedRunButton: boolean; showFoldingControls: 'always' | 'mouseover'; dragAndDropEnabled: boolean; fontSize: number; @@ -74,6 +75,7 @@ interface NotebookOptionsChangeEvent { globalToolbar?: boolean; showFoldingControls?: boolean; consolidatedOutputButton?: boolean; + consolidatedRunButton?: boolean; dragAndDropEnabled?: boolean; fontSize?: boolean; editorOptionsCustomizations?: boolean; @@ -110,6 +112,7 @@ export class NotebookOptions { const showCellStatusBarAfterExecute = this.configurationService.getValue(ShowCellStatusBarAfterExecuteKey); const globalToolbar = this.configurationService.getValue(GlobalToolbar) ?? false; const consolidatedOutputButton = this.configurationService.getValue(ConsolidatedOutputButton) ?? true; + const consolidatedRunButton = this.configurationService.getValue(ConsolidatedRunButton) ?? true; const dragAndDropEnabled = this.configurationService.getValue(DragAndDropEnabled) ?? true; const cellToolbarLocation = this.configurationService.getValue(CellToolbarLocKey) ?? { 'default': 'right' }; const cellToolbarInteraction = this.configurationService.getValue(CellToolbarVisibility); @@ -142,6 +145,7 @@ export class NotebookOptions { showCellStatusBarAfterExecute, globalToolbar, consolidatedOutputButton, + consolidatedRunButton, dragAndDropEnabled, cellToolbarLocation, cellToolbarInteraction, @@ -177,6 +181,7 @@ export class NotebookOptions { const insertToolbarAlignment = e.affectsConfiguration(ExperimentalInsertToolbarAlignment); const globalToolbar = e.affectsConfiguration(GlobalToolbar); const consolidatedOutputButton = e.affectsConfiguration(ConsolidatedOutputButton); + const consolidatedRunButton = e.affectsConfiguration(ConsolidatedRunButton); const showFoldingControls = e.affectsConfiguration(ShowFoldingControls); const dragAndDropEnabled = e.affectsConfiguration(DragAndDropEnabled); const fontSize = e.affectsConfiguration('editor.fontSize'); @@ -193,6 +198,7 @@ export class NotebookOptions { && !insertToolbarAlignment && !globalToolbar && !consolidatedOutputButton + && !consolidatedRunButton && !showFoldingControls && !dragAndDropEnabled && !fontSize @@ -246,6 +252,10 @@ export class NotebookOptions { configuration.consolidatedOutputButton = this.configurationService.getValue(ConsolidatedOutputButton) ?? true; } + if (consolidatedRunButton) { + configuration.consolidatedRunButton = this.configurationService.getValue(ConsolidatedRunButton) ?? true; + } + if (showFoldingControls) { configuration.showFoldingControls = this._computeShowFoldingControlsOption(); } @@ -277,6 +287,7 @@ export class NotebookOptions { globalToolbar, showFoldingControls, consolidatedOutputButton, + consolidatedRunButton, dragAndDropEnabled, fontSize, editorOptionsCustomizations