Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/floatingMenu/browser/floatingMenu.ts
5237 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { h } from '../../../../base/browser/dom.js';
7
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
8
import { getActionBarActions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
9
import { autorun, constObservable, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js';
10
import { URI } from '../../../../base/common/uri.js';
11
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
12
import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js';
13
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
14
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
15
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
16
import { ICodeEditor, OverlayWidgetPositionPreference } from '../../../browser/editorBrowser.js';
17
import { observableCodeEditor } from '../../../browser/observableCodeEditor.js';
18
import { IEditorContribution } from '../../../common/editorCommon.js';
19
20
export class FloatingEditorToolbar extends Disposable implements IEditorContribution {
21
static readonly ID = 'editor.contrib.floatingToolbar';
22
23
constructor(
24
editor: ICodeEditor,
25
@IInstantiationService instantiationService: IInstantiationService,
26
@IKeybindingService keybindingService: IKeybindingService,
27
@IMenuService menuService: IMenuService
28
) {
29
super();
30
31
const editorObs = this._register(observableCodeEditor(editor));
32
const editorUriObs = derived(reader => editorObs.model.read(reader)?.uri);
33
34
// Widget
35
const widget = this._register(instantiationService.createInstance(
36
FloatingEditorToolbarWidget,
37
MenuId.EditorContent,
38
editor.contextKeyService,
39
editorUriObs));
40
41
// Render widget
42
this._register(autorun(reader => {
43
const hasActions = widget.hasActions.read(reader);
44
if (!hasActions) {
45
return;
46
}
47
48
// Overlay widget
49
reader.store.add(editorObs.createOverlayWidget({
50
allowEditorOverflow: false,
51
domNode: widget.element,
52
minContentWidthInPx: constObservable(0),
53
position: constObservable({
54
preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER
55
})
56
}));
57
}));
58
}
59
}
60
61
export class FloatingEditorToolbarWidget extends Disposable {
62
readonly element: HTMLElement;
63
readonly hasActions: IObservable<boolean>;
64
65
constructor(
66
_menuId: MenuId,
67
_scopedContextKeyService: IContextKeyService,
68
_toolbarContext: IObservable<URI | undefined>,
69
@IInstantiationService instantiationService: IInstantiationService,
70
@IKeybindingService keybindingService: IKeybindingService,
71
@IMenuService menuService: IMenuService
72
) {
73
super();
74
75
const menu = this._register(menuService.createMenu(_menuId, _scopedContextKeyService));
76
const menuGroupsObs = observableFromEvent(this, menu.onDidChange, () => menu.getActions());
77
78
const menuPrimaryActionIdObs = derived(reader => {
79
const menuGroups = menuGroupsObs.read(reader);
80
81
const { primary } = getActionBarActions(menuGroups, () => true);
82
return primary.length > 0 ? primary[0].id : undefined;
83
});
84
85
this.hasActions = derived(reader => menuGroupsObs.read(reader).length > 0);
86
87
this.element = h('div.floating-menu-overlay-widget').root;
88
this._register(toDisposable(() => this.element.remove()));
89
90
// Set height explicitly to ensure that the floating menu element
91
// is rendered in the lower right corner at the correct position.
92
this.element.style.height = '26px';
93
94
this._register(autorun(reader => {
95
const hasActions = this.hasActions.read(reader);
96
const menuPrimaryActionId = menuPrimaryActionIdObs.read(reader);
97
98
if (!hasActions) {
99
return;
100
}
101
102
// Toolbar
103
const toolbar = instantiationService.createInstance(MenuWorkbenchToolBar, this.element, _menuId, {
104
actionViewItemProvider: (action, options) => {
105
if (!(action instanceof MenuItemAction)) {
106
return undefined;
107
}
108
109
return instantiationService.createInstance(class extends MenuEntryActionViewItem {
110
override render(container: HTMLElement): void {
111
super.render(container);
112
113
// Highlight primary action
114
if (action.id === menuPrimaryActionId) {
115
this.element?.classList.add('primary');
116
}
117
}
118
119
protected override updateLabel(): void {
120
const keybinding = keybindingService.lookupKeybinding(action.id);
121
const keybindingLabel = keybinding ? keybinding.getLabel() : undefined;
122
123
if (this.options.label && this.label) {
124
this.label.textContent = keybindingLabel
125
? `${this._commandAction.label} (${keybindingLabel})`
126
: this._commandAction.label;
127
}
128
}
129
}, action, { ...options, keybindingNotRenderedWithLabel: true });
130
},
131
hiddenItemStrategy: HiddenItemStrategy.Ignore,
132
menuOptions: {
133
shouldForwardArgs: true
134
},
135
telemetrySource: 'editor.overlayToolbar',
136
toolbarOptions: {
137
primaryGroup: () => true,
138
useSeparatorsInPrimaryActions: true
139
},
140
});
141
142
reader.store.add(toolbar);
143
reader.store.add(autorun(reader => {
144
const context = _toolbarContext.read(reader);
145
toolbar.context = context;
146
}));
147
}));
148
}
149
}
150
151