Path: blob/main/src/vs/editor/contrib/floatingMenu/browser/floatingMenu.ts
5237 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 { h } from '../../../../base/browser/dom.js';6import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';7import { getActionBarActions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';8import { autorun, constObservable, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js';9import { URI } from '../../../../base/common/uri.js';10import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';11import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js';12import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';13import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';14import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';15import { ICodeEditor, OverlayWidgetPositionPreference } from '../../../browser/editorBrowser.js';16import { observableCodeEditor } from '../../../browser/observableCodeEditor.js';17import { IEditorContribution } from '../../../common/editorCommon.js';1819export class FloatingEditorToolbar extends Disposable implements IEditorContribution {20static readonly ID = 'editor.contrib.floatingToolbar';2122constructor(23editor: ICodeEditor,24@IInstantiationService instantiationService: IInstantiationService,25@IKeybindingService keybindingService: IKeybindingService,26@IMenuService menuService: IMenuService27) {28super();2930const editorObs = this._register(observableCodeEditor(editor));31const editorUriObs = derived(reader => editorObs.model.read(reader)?.uri);3233// Widget34const widget = this._register(instantiationService.createInstance(35FloatingEditorToolbarWidget,36MenuId.EditorContent,37editor.contextKeyService,38editorUriObs));3940// Render widget41this._register(autorun(reader => {42const hasActions = widget.hasActions.read(reader);43if (!hasActions) {44return;45}4647// Overlay widget48reader.store.add(editorObs.createOverlayWidget({49allowEditorOverflow: false,50domNode: widget.element,51minContentWidthInPx: constObservable(0),52position: constObservable({53preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER54})55}));56}));57}58}5960export class FloatingEditorToolbarWidget extends Disposable {61readonly element: HTMLElement;62readonly hasActions: IObservable<boolean>;6364constructor(65_menuId: MenuId,66_scopedContextKeyService: IContextKeyService,67_toolbarContext: IObservable<URI | undefined>,68@IInstantiationService instantiationService: IInstantiationService,69@IKeybindingService keybindingService: IKeybindingService,70@IMenuService menuService: IMenuService71) {72super();7374const menu = this._register(menuService.createMenu(_menuId, _scopedContextKeyService));75const menuGroupsObs = observableFromEvent(this, menu.onDidChange, () => menu.getActions());7677const menuPrimaryActionIdObs = derived(reader => {78const menuGroups = menuGroupsObs.read(reader);7980const { primary } = getActionBarActions(menuGroups, () => true);81return primary.length > 0 ? primary[0].id : undefined;82});8384this.hasActions = derived(reader => menuGroupsObs.read(reader).length > 0);8586this.element = h('div.floating-menu-overlay-widget').root;87this._register(toDisposable(() => this.element.remove()));8889// Set height explicitly to ensure that the floating menu element90// is rendered in the lower right corner at the correct position.91this.element.style.height = '26px';9293this._register(autorun(reader => {94const hasActions = this.hasActions.read(reader);95const menuPrimaryActionId = menuPrimaryActionIdObs.read(reader);9697if (!hasActions) {98return;99}100101// Toolbar102const toolbar = instantiationService.createInstance(MenuWorkbenchToolBar, this.element, _menuId, {103actionViewItemProvider: (action, options) => {104if (!(action instanceof MenuItemAction)) {105return undefined;106}107108return instantiationService.createInstance(class extends MenuEntryActionViewItem {109override render(container: HTMLElement): void {110super.render(container);111112// Highlight primary action113if (action.id === menuPrimaryActionId) {114this.element?.classList.add('primary');115}116}117118protected override updateLabel(): void {119const keybinding = keybindingService.lookupKeybinding(action.id);120const keybindingLabel = keybinding ? keybinding.getLabel() : undefined;121122if (this.options.label && this.label) {123this.label.textContent = keybindingLabel124? `${this._commandAction.label} (${keybindingLabel})`125: this._commandAction.label;126}127}128}, action, { ...options, keybindingNotRenderedWithLabel: true });129},130hiddenItemStrategy: HiddenItemStrategy.Ignore,131menuOptions: {132shouldForwardArgs: true133},134telemetrySource: 'editor.overlayToolbar',135toolbarOptions: {136primaryGroup: () => true,137useSeparatorsInPrimaryActions: true138},139});140141reader.store.add(toolbar);142reader.store.add(autorun(reader => {143const context = _toolbarContext.read(reader);144toolbar.context = context;145}));146}));147}148}149150151