diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0dceffcaaaa..78a18824a45 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -171,6 +171,7 @@ export class MenuId { static readonly NotebookCellBetween = new MenuId('NotebookCellBetween'); static readonly NotebookCellListTop = new MenuId('NotebookCellTop'); static readonly NotebookCellExecute = new MenuId('NotebookCellExecute'); + static readonly NotebookCellExecuteGoTo = new MenuId('NotebookCellExecuteGoTo'); static readonly NotebookCellExecutePrimary = new MenuId('NotebookCellExecutePrimary'); static readonly NotebookDiffCellInputTitle = new MenuId('NotebookDiffCellInputTitle'); static readonly NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index d9c99e9cd17..14af3383e84 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -11,7 +11,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; -import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -622,13 +622,21 @@ registerAction2(class InterruptNotebook extends CancelNotebook { }); +MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { + title: localize('revealRunningCellShort', "Go To"), + submenu: MenuId.NotebookCellExecuteGoTo, + group: 'navigation/execute', + order: 20, + icon: ThemeIcon.modify(icons.executingStateIcon, 'spin') +}); + registerAction2(class RevealRunningCellAction extends NotebookAction { constructor() { super({ id: REVEAL_RUNNING_CELL, title: localize('revealRunningCell', "Go to Running Cell"), tooltip: localize('revealRunningCell', "Go to Running Cell"), - shortTitle: localize('revealRunningCellShort', "Go To"), + shortTitle: localize('revealRunningCell', "Go to Running Cell"), precondition: NOTEBOOK_HAS_RUNNING_CELL, menu: [ { @@ -642,7 +650,7 @@ registerAction2(class RevealRunningCellAction extends NotebookAction { order: 0 }, { - id: MenuId.NotebookToolbar, + id: MenuId.NotebookCellExecuteGoTo, when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_HAS_RUNNING_CELL, @@ -703,7 +711,7 @@ registerAction2(class RevealLastFailedCellAction extends NotebookAction { id: REVEAL_LAST_FAILED_CELL, title: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"), tooltip: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"), - shortTitle: localize('revealLastFailedCellShort', "Go To"), + shortTitle: localize('revealLastFailedCellShort', "Go to Most Recently Failed Cell"), precondition: NOTEBOOK_LAST_CELL_FAILED, menu: [ { @@ -718,7 +726,7 @@ registerAction2(class RevealLastFailedCellAction extends NotebookAction { order: 0 }, { - id: MenuId.NotebookToolbar, + id: MenuId.NotebookCellExecuteGoTo, when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_LAST_CELL_FAILED, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index 38e1424f5f3..a50b67ca2b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -5,7 +5,14 @@ import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import * as DOM from 'vs/base/browser/dom'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuEntryActionViewItemOptions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionProvider } from 'vs/base/browser/ui/dropdown/dropdown'; +import { MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ThemeIcon } from 'vs/base/common/themables'; export class CodiconActionViewItem extends MenuEntryActionViewItem { @@ -35,3 +42,61 @@ export class ActionViewWithLabel extends MenuEntryActionViewItem { } } } +export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { + private _actionLabel?: HTMLAnchorElement; + + constructor( + action: SubmenuItemAction, + options: IMenuEntryActionViewItemOptions | undefined, + readonly renderLabel: boolean, + readonly subActionProvider: IActionProvider, + readonly subActionViewItemProvider: IActionViewItemProvider | undefined, + @IKeybindingService _keybindingService: IKeybindingService, + @IContextMenuService _contextMenuService: IContextMenuService, + @IThemeService _themeService: IThemeService + ) { + super(action, options, _keybindingService, _contextMenuService, _themeService); + } + + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('notebook-action-view-item'); + this._actionLabel = document.createElement('a'); + container.appendChild(this._actionLabel); + this.updateLabel(); + } + + protected override updateLabel() { + const actions = this.subActionProvider.getActions(); + if (this._actionLabel) { + const primaryAction = actions[0]; + + if (primaryAction && primaryAction instanceof MenuItemAction) { + const element = this.element; + + if (element && primaryAction.item.icon && ThemeIcon.isThemeIcon(primaryAction.item.icon)) { + const iconClasses = ThemeIcon.asClassNameArray(primaryAction.item.icon); + // remove all classes started with 'codicon-' + element.classList.forEach((cl) => { + if (cl.startsWith('codicon-')) { + element.classList.remove(cl); + } + }); + element.classList.add(...iconClasses); + } + + if (this.renderLabel) { + this._actionLabel.classList.add('notebook-label'); + this._actionLabel.innerText = this._action.label; + this._actionLabel.title = primaryAction.tooltip.length ? primaryAction.tooltip : primaryAction.label; + } + } else { + if (this.renderLabel) { + this._actionLabel.classList.add('notebook-label'); + this._actionLabel.innerText = this._action.label; + this._actionLabel.title = this._action.tooltip.length ? this._action.tooltip : this._action.label; + } + } + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index d7cb7317788..e2972e82e8a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -10,7 +10,7 @@ import { IAction, Separator } from 'vs/base/common/actions'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -21,13 +21,12 @@ import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controll import { NOTEBOOK_EDITOR_ID, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView'; -import { ActionViewWithLabel } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; +import { ActionViewWithLabel, UnifiedSubmenuActionView } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; -import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { disposableTimeout } from 'vs/base/common/async'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { HiddenItemStrategy, IWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; interface IActionModel { @@ -73,15 +72,28 @@ class WorkbenchAlwaysLabelStrategy implements IActionLayoutStrategy { constructor( readonly notebookEditor: INotebookEditorDelegate, readonly editorToolbar: NotebookEditorWorkbenchToolbar, + readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): ActionViewItem | undefined { + actionProvider(action: IAction): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); } - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + } + + if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } + + return undefined; } calculateActions(leftToolbarContainerMaxWidth: number): { primaryActions: IAction[]; secondaryActions: IAction[] } { @@ -100,15 +112,32 @@ class WorkbenchNeverLabelStrategy implements IActionLayoutStrategy { constructor( readonly notebookEditor: INotebookEditorDelegate, readonly editorToolbar: NotebookEditorWorkbenchToolbar, + readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): ActionViewItem | undefined { + actionProvider(action: IAction): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); } - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + } + + if (action instanceof SubmenuItemAction) { + if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } else { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + } + } + + return undefined; } calculateActions(leftToolbarContainerMaxWidth: number): { primaryActions: IAction[]; secondaryActions: IAction[] } { @@ -127,9 +156,10 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { constructor( readonly notebookEditor: INotebookEditorDelegate, readonly editorToolbar: NotebookEditorWorkbenchToolbar, + readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): ActionViewItem | undefined { + actionProvider(action: IAction): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); @@ -137,9 +167,37 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id); if (!a || a.renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + } + + if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } + + return undefined; } else { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + if (action instanceof MenuItemAction) { + this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + } + + if (action instanceof SubmenuItemAction) { + if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + getActions: () => { + return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; + } + }, this.actionProvider.bind(this)); + } else { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + } + } + + return undefined; } } @@ -160,6 +218,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { private _notebookTopLeftToolbarContainer!: HTMLElement; private _notebookTopRightToolbarContainer!: HTMLElement; private _notebookGlobalActionsMenu!: IMenu; + private _executeGoToActionsMenu!: IMenu; private _notebookLeftToolbar!: WorkbenchToolBar; private _primaryActions: IActionModel[]; get primaryActions(): IActionModel[] { @@ -251,7 +310,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { private _registerNotebookActionsToolbar() { this._notebookGlobalActionsMenu = this._register(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.notebookToolbar, this.contextKeyService)); - this._register(this._notebookGlobalActionsMenu); + this._executeGoToActionsMenu = this._register(this.menuService.createMenu(MenuId.NotebookCellExecuteGoTo, this.contextKeyService)); this._useGlobalToolbar = this.notebookOptions.getDisplayOptions().globalToolbar; this._renderLabel = this._convertConfiguration(this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel)); @@ -379,13 +438,13 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { private _updateStrategy() { switch (this._renderLabel) { case RenderLabel.Always: - this._strategy = new WorkbenchAlwaysLabelStrategy(this.notebookEditor, this, this.instantiationService); + this._strategy = new WorkbenchAlwaysLabelStrategy(this.notebookEditor, this, this._executeGoToActionsMenu, this.instantiationService); break; case RenderLabel.Never: - this._strategy = new WorkbenchNeverLabelStrategy(this.notebookEditor, this, this.instantiationService); + this._strategy = new WorkbenchNeverLabelStrategy(this.notebookEditor, this, this._executeGoToActionsMenu, this.instantiationService); break; case RenderLabel.Dynamic: - this._strategy = new WorkbenchDynamicLabelStrategy(this.notebookEditor, this, this.instantiationService); + this._strategy = new WorkbenchDynamicLabelStrategy(this.notebookEditor, this, this._executeGoToActionsMenu, this.instantiationService); break; } }