Path: blob/main/src/vs/platform/actions/browser/buttonbar.ts
3296 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;31}3233export class WorkbenchButtonBar extends ButtonBar {3435protected readonly _store = new DisposableStore();36protected readonly _updateStore = new DisposableStore();3738private readonly _actionRunner: IActionRunner;39private readonly _onDidChange = new Emitter<this>();40readonly onDidChange: Event<this> = this._onDidChange.event;414243constructor(44container: HTMLElement,45private readonly _options: IWorkbenchButtonBarOptions | undefined,46@IContextMenuService private readonly _contextMenuService: IContextMenuService,47@IKeybindingService private readonly _keybindingService: IKeybindingService,48@ITelemetryService telemetryService: ITelemetryService,49@IHoverService private readonly _hoverService: IHoverService,50) {51super(container);5253this._actionRunner = this._store.add(new ActionRunner());54if (_options?.telemetrySource) {55this._actionRunner.onDidRun(e => {56telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>(57'workbenchActionExecuted',58{ id: e.action.id, from: _options.telemetrySource! }59);60}, undefined, this._store);61}62}6364override dispose() {65this._onDidChange.dispose();66this._updateStore.dispose();67this._store.dispose();68super.dispose();69}7071update(actions: IAction[], secondary: IAction[]): void {7273const conifgProvider: IButtonConfigProvider = this._options?.buttonConfigProvider ?? (() => ({ showLabel: true }));7475this._updateStore.clear();76this.clear();7778// Support instamt hover between buttons79const hoverDelegate = this._updateStore.add(createInstantHoverDelegate());8081for (let i = 0; i < actions.length; i++) {8283const secondary = i > 0;84const actionOrSubmenu = actions[i];85let action: IAction;86let btn: IButton;87let tooltip: string = '';88const kb = actionOrSubmenu instanceof SubmenuAction ? '' : this._keybindingService.lookupKeybinding(actionOrSubmenu.id);89if (kb) {90tooltip = localize('labelWithKeybinding', "{0} ({1})", actionOrSubmenu.tooltip || actionOrSubmenu.label, kb.getLabel());91} else {92tooltip = actionOrSubmenu.tooltip || actionOrSubmenu.label;93}94if (actionOrSubmenu instanceof SubmenuAction && actionOrSubmenu.actions.length > 0) {95const [first, ...rest] = actionOrSubmenu.actions;96action = <MenuItemAction>first;97btn = this.addButtonWithDropdown({98secondary: conifgProvider(action, i)?.isSecondary ?? secondary,99actionRunner: this._actionRunner,100actions: rest,101contextMenuProvider: this._contextMenuService,102ariaLabel: tooltip,103supportIcons: true,104});105} else {106action = actionOrSubmenu;107btn = this.addButton({108secondary: conifgProvider(action, i)?.isSecondary ?? secondary,109ariaLabel: tooltip,110supportIcons: true,111});112}113114btn.enabled = action.enabled;115btn.checked = action.checked ?? false;116btn.element.classList.add('default-colors');117const showLabel = conifgProvider(action, i)?.showLabel ?? true;118if (showLabel) {119btn.label = action.label;120} else {121btn.element.classList.add('monaco-text-button');122}123if (conifgProvider(action, i)?.showIcon) {124if (action instanceof MenuItemAction && ThemeIcon.isThemeIcon(action.item.icon)) {125if (!showLabel) {126btn.icon = action.item.icon;127} else {128// this is REALLY hacky but combining a codicon and normal text is ugly because129// the former define a font which doesn't work for text130btn.label = `$(${action.item.icon.id}) ${action.label}`;131}132} else if (action.class) {133btn.element.classList.add(...action.class.split(' '));134}135}136137this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, tooltip));138this._updateStore.add(btn.onDidClick(async () => {139this._actionRunner.run(action);140}));141}142143if (secondary.length > 0) {144145const btn = this.addButton({146secondary: true,147ariaLabel: localize('moreActions', "More Actions")148});149150btn.icon = Codicon.dropDownButton;151btn.element.classList.add('default-colors', 'monaco-text-button');152153btn.enabled = true;154this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, localize('moreActions', "More Actions")));155this._updateStore.add(btn.onDidClick(async () => {156this._contextMenuService.showContextMenu({157getAnchor: () => btn.element,158getActions: () => secondary,159actionRunner: this._actionRunner,160onHide: () => btn.element.setAttribute('aria-expanded', 'false')161});162btn.element.setAttribute('aria-expanded', 'true');163164}));165}166this._onDidChange.fire(this);167}168}169170export interface IMenuWorkbenchButtonBarOptions extends IWorkbenchButtonBarOptions {171menuOptions?: IMenuActionOptions;172173toolbarOptions?: IToolBarRenderOptions;174}175176export class MenuWorkbenchButtonBar extends WorkbenchButtonBar {177178constructor(179container: HTMLElement,180menuId: MenuId,181options: IMenuWorkbenchButtonBarOptions | undefined,182@IMenuService menuService: IMenuService,183@IContextKeyService contextKeyService: IContextKeyService,184@IContextMenuService contextMenuService: IContextMenuService,185@IKeybindingService keybindingService: IKeybindingService,186@ITelemetryService telemetryService: ITelemetryService,187@IHoverService hoverService: IHoverService,188) {189super(container, options, contextMenuService, keybindingService, telemetryService, hoverService);190191const menu = menuService.createMenu(menuId, contextKeyService);192this._store.add(menu);193194const update = () => {195196this.clear();197198const actions = getActionBarActions(199menu.getActions(options?.menuOptions),200options?.toolbarOptions?.primaryGroup201);202203super.update(actions.primary, actions.secondary);204};205this._store.add(menu.onDidChange(update));206update();207}208209override dispose() {210super.dispose();211}212213override update(_actions: IAction[]): void {214throw new Error('Use Menu or WorkbenchButtonBar');215}216}217218219