Path: blob/main/src/vs/platform/actions/browser/buttonbar.ts
5243 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ButtonBar, IButton } from '../../../base/browser/ui/button/button.js';6import { createInstantHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegateFactory.js';7import { ActionRunner, IAction, IActionRunner, SubmenuAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../base/common/actions.js';8import { Codicon } from '../../../base/common/codicons.js';9import { Emitter, Event } from '../../../base/common/event.js';10import { DisposableStore } from '../../../base/common/lifecycle.js';11import { ThemeIcon } from '../../../base/common/themables.js';12import { localize } from '../../../nls.js';13import { getActionBarActions } from './menuEntryActionViewItem.js';14import { IToolBarRenderOptions } from './toolbar.js';15import { MenuId, IMenuService, MenuItemAction, IMenuActionOptions } from '../common/actions.js';16import { IContextKeyService } from '../../contextkey/common/contextkey.js';17import { IContextMenuService } from '../../contextview/browser/contextView.js';18import { IHoverService } from '../../hover/browser/hover.js';19import { IKeybindingService } from '../../keybinding/common/keybinding.js';20import { ITelemetryService } from '../../telemetry/common/telemetry.js';2122export type IButtonConfigProvider = (action: IAction, index: number) => {23showIcon?: boolean;24showLabel?: boolean;25isSecondary?: boolean;26} | undefined;2728export interface IWorkbenchButtonBarOptions {29telemetrySource?: string;30buttonConfigProvider?: IButtonConfigProvider;31small?: boolean;32disableWhileRunning?: boolean;33}3435export class WorkbenchButtonBar extends ButtonBar {3637protected readonly _store = new DisposableStore();38protected readonly _updateStore = new DisposableStore();3940private readonly _actionRunner: IActionRunner;41private readonly _onDidChange = new Emitter<this>();42readonly onDidChange: Event<this> = this._onDidChange.event;434445constructor(46container: HTMLElement,47private readonly _options: IWorkbenchButtonBarOptions | undefined,48@IContextMenuService private readonly _contextMenuService: IContextMenuService,49@IKeybindingService private readonly _keybindingService: IKeybindingService,50@ITelemetryService telemetryService: ITelemetryService,51@IHoverService private readonly _hoverService: IHoverService,52) {53super(container);5455this._actionRunner = this._store.add(new ActionRunner());56if (_options?.telemetrySource) {57this._actionRunner.onDidRun(e => {58telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>(59'workbenchActionExecuted',60{ id: e.action.id, from: _options.telemetrySource! }61);62}, undefined, this._store);63}64}6566override dispose() {67this._onDidChange.dispose();68this._updateStore.dispose();69this._store.dispose();70super.dispose();71}7273update(actions: IAction[], secondary: IAction[]): void {7475const conifgProvider: IButtonConfigProvider = this._options?.buttonConfigProvider ?? (() => ({ showLabel: true }));7677this._updateStore.clear();78this.clear();7980// Support instamt hover between buttons81const hoverDelegate = this._updateStore.add(createInstantHoverDelegate());8283for (let i = 0; i < actions.length; i++) {8485const secondary = i > 0;86const actionOrSubmenu = actions[i];87let action: IAction;88let btn: IButton;89let tooltip = actionOrSubmenu.tooltip || actionOrSubmenu.label;90if (!(actionOrSubmenu instanceof SubmenuAction)) {91tooltip = this._keybindingService.appendKeybinding(tooltip, actionOrSubmenu.id);92}93if (actionOrSubmenu instanceof SubmenuAction && actionOrSubmenu.actions.length > 0) {94const [first, ...rest] = actionOrSubmenu.actions;95action = <MenuItemAction>first;96btn = this.addButtonWithDropdown({97secondary: conifgProvider(action, i)?.isSecondary ?? secondary,98actionRunner: this._actionRunner,99actions: rest,100contextMenuProvider: this._contextMenuService,101ariaLabel: tooltip,102supportIcons: true,103small: this._options?.small,104});105} else {106action = actionOrSubmenu;107btn = this.addButton({108secondary: conifgProvider(action, i)?.isSecondary ?? secondary,109ariaLabel: tooltip,110supportIcons: true,111small: this._options?.small,112});113}114115btn.enabled = action.enabled;116btn.checked = action.checked ?? false;117btn.element.classList.add('default-colors');118const showLabel = conifgProvider(action, i)?.showLabel ?? true;119if (showLabel) {120btn.label = action.label;121} else {122btn.element.classList.add('monaco-text-button');123}124if (conifgProvider(action, i)?.showIcon) {125if (action instanceof MenuItemAction && ThemeIcon.isThemeIcon(action.item.icon)) {126if (!showLabel) {127btn.icon = action.item.icon;128} else {129// this is REALLY hacky but combining a codicon and normal text is ugly because130// the former define a font which doesn't work for text131btn.label = `$(${action.item.icon.id}) ${action.label}`;132}133} else if (action.class) {134btn.element.classList.add(...action.class.split(' '));135}136}137138this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, tooltip));139this._updateStore.add(btn.onDidClick(async () => {140if (this._options?.disableWhileRunning) {141btn.enabled = false;142try {143await this._actionRunner.run(action);144} finally {145btn.enabled = action.enabled;146}147} else {148this._actionRunner.run(action);149}150}));151}152153if (secondary.length > 0) {154155const btn = this.addButton({156secondary: true,157ariaLabel: localize('moreActions', "More Actions"),158small: this._options?.small,159});160161btn.icon = Codicon.dropDownButton;162btn.element.classList.add('default-colors', 'monaco-text-button');163164btn.enabled = true;165this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, localize('moreActions', "More Actions")));166this._updateStore.add(btn.onDidClick(async () => {167this._contextMenuService.showContextMenu({168getAnchor: () => btn.element,169getActions: () => secondary,170actionRunner: this._actionRunner,171onHide: () => btn.element.setAttribute('aria-expanded', 'false')172});173btn.element.setAttribute('aria-expanded', 'true');174175}));176}177this._onDidChange.fire(this);178}179}180181export interface IMenuWorkbenchButtonBarOptions extends IWorkbenchButtonBarOptions {182menuOptions?: IMenuActionOptions;183184toolbarOptions?: IToolBarRenderOptions;185}186187export class MenuWorkbenchButtonBar extends WorkbenchButtonBar {188189constructor(190container: HTMLElement,191menuId: MenuId,192options: IMenuWorkbenchButtonBarOptions | undefined,193@IMenuService menuService: IMenuService,194@IContextKeyService contextKeyService: IContextKeyService,195@IContextMenuService contextMenuService: IContextMenuService,196@IKeybindingService keybindingService: IKeybindingService,197@ITelemetryService telemetryService: ITelemetryService,198@IHoverService hoverService: IHoverService,199) {200super(container, options, contextMenuService, keybindingService, telemetryService, hoverService);201202const menu = menuService.createMenu(menuId, contextKeyService);203this._store.add(menu);204205const update = () => {206207this.clear();208209const actions = getActionBarActions(210menu.getActions(options?.menuOptions),211options?.toolbarOptions?.primaryGroup212);213214super.update(actions.primary, actions.secondary);215};216this._store.add(menu.onDidChange(update));217update();218}219220override dispose() {221super.dispose();222}223224override update(_actions: IAction[]): void {225throw new Error('Use Menu or WorkbenchButtonBar');226}227}228229230