Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/actionWidget/browser/actionWidget.ts
3296 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
import * as dom from '../../../base/browser/dom.js';
6
import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js';
7
import { IAnchor } from '../../../base/browser/ui/contextview/contextview.js';
8
import { IAction } from '../../../base/common/actions.js';
9
import { KeyCode, KeyMod } from '../../../base/common/keyCodes.js';
10
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js';
11
import './actionWidget.css';
12
import { localize, localize2 } from '../../../nls.js';
13
import { acceptSelectedActionCommand, ActionList, IActionListDelegate, IActionListItem, previewSelectedActionCommand } from './actionList.js';
14
import { Action2, registerAction2 } from '../../actions/common/actions.js';
15
import { IContextKeyService, RawContextKey } from '../../contextkey/common/contextkey.js';
16
import { IContextViewService } from '../../contextview/browser/contextView.js';
17
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
18
import { createDecorator, IInstantiationService, ServicesAccessor } from '../../instantiation/common/instantiation.js';
19
import { KeybindingWeight } from '../../keybinding/common/keybindingsRegistry.js';
20
import { inputActiveOptionBackground, registerColor } from '../../theme/common/colorRegistry.js';
21
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
22
import { IListAccessibilityProvider } from '../../../base/browser/ui/list/listWidget.js';
23
24
registerColor(
25
'actionBar.toggledBackground',
26
inputActiveOptionBackground,
27
localize('actionBar.toggledBackground', 'Background color for toggled action items in action bar.')
28
);
29
30
const ActionWidgetContextKeys = {
31
Visible: new RawContextKey<boolean>('codeActionMenuVisible', false, localize('codeActionMenuVisible', "Whether the action widget list is visible"))
32
};
33
34
export const IActionWidgetService = createDecorator<IActionWidgetService>('actionWidgetService');
35
36
export interface IActionWidgetService {
37
readonly _serviceBrand: undefined;
38
39
show<T>(user: string, supportsPreview: boolean, items: readonly IActionListItem<T>[], delegate: IActionListDelegate<T>, anchor: HTMLElement | StandardMouseEvent | IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], accessibilityProvider?: Partial<IListAccessibilityProvider<IActionListItem<T>>>): void;
40
41
hide(didCancel?: boolean): void;
42
43
readonly isVisible: boolean;
44
}
45
46
class ActionWidgetService extends Disposable implements IActionWidgetService {
47
declare readonly _serviceBrand: undefined;
48
49
get isVisible() {
50
return ActionWidgetContextKeys.Visible.getValue(this._contextKeyService) || false;
51
}
52
53
private readonly _list = this._register(new MutableDisposable<ActionList<unknown>>());
54
55
constructor(
56
@IContextViewService private readonly _contextViewService: IContextViewService,
57
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
58
@IInstantiationService private readonly _instantiationService: IInstantiationService
59
) {
60
super();
61
}
62
63
show<T>(user: string, supportsPreview: boolean, items: readonly IActionListItem<T>[], delegate: IActionListDelegate<T>, anchor: HTMLElement | StandardMouseEvent | IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], accessibilityProvider?: Partial<IListAccessibilityProvider<IActionListItem<T>>>): void {
64
const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService);
65
66
const list = this._instantiationService.createInstance(ActionList, user, supportsPreview, items, delegate, accessibilityProvider);
67
this._contextViewService.showContextView({
68
getAnchor: () => anchor,
69
render: (container: HTMLElement) => {
70
visibleContext.set(true);
71
return this._renderWidget(container, list, actionBarActions ?? []);
72
},
73
onHide: (didCancel) => {
74
visibleContext.reset();
75
this._onWidgetClosed(didCancel);
76
},
77
}, container, false);
78
}
79
80
acceptSelected(preview?: boolean) {
81
this._list.value?.acceptSelected(preview);
82
}
83
84
focusPrevious() {
85
this._list?.value?.focusPrevious();
86
}
87
88
focusNext() {
89
this._list?.value?.focusNext();
90
}
91
92
hide(didCancel?: boolean) {
93
this._list.value?.hide(didCancel);
94
this._list.clear();
95
}
96
97
clear() {
98
this._list.clear();
99
}
100
101
private _renderWidget(element: HTMLElement, list: ActionList<unknown>, actionBarActions: readonly IAction[]): IDisposable {
102
const widget = document.createElement('div');
103
widget.classList.add('action-widget');
104
element.appendChild(widget);
105
106
this._list.value = list;
107
if (this._list.value) {
108
widget.appendChild(this._list.value.domNode);
109
} else {
110
throw new Error('List has no value');
111
}
112
const renderDisposables = new DisposableStore();
113
114
// Invisible div to block mouse interaction in the rest of the UI
115
const menuBlock = document.createElement('div');
116
const block = element.appendChild(menuBlock);
117
block.classList.add('context-view-block');
118
renderDisposables.add(dom.addDisposableListener(block, dom.EventType.MOUSE_DOWN, e => e.stopPropagation()));
119
120
// Invisible div to block mouse interaction with the menu
121
const pointerBlockDiv = document.createElement('div');
122
const pointerBlock = element.appendChild(pointerBlockDiv);
123
pointerBlock.classList.add('context-view-pointerBlock');
124
125
// Removes block on click INSIDE widget or ANY mouse movement
126
renderDisposables.add(dom.addDisposableListener(pointerBlock, dom.EventType.POINTER_MOVE, () => pointerBlock.remove()));
127
renderDisposables.add(dom.addDisposableListener(pointerBlock, dom.EventType.MOUSE_DOWN, () => pointerBlock.remove()));
128
129
// Action bar
130
let actionBarWidth = 0;
131
if (actionBarActions.length) {
132
const actionBar = this._createActionBar('.action-widget-action-bar', actionBarActions);
133
if (actionBar) {
134
widget.appendChild(actionBar.getContainer().parentElement!);
135
renderDisposables.add(actionBar);
136
actionBarWidth = actionBar.getContainer().offsetWidth;
137
}
138
}
139
140
const width = this._list.value?.layout(actionBarWidth);
141
widget.style.width = `${width}px`;
142
143
const focusTracker = renderDisposables.add(dom.trackFocus(element));
144
renderDisposables.add(focusTracker.onDidBlur(() => this.hide(true)));
145
146
return renderDisposables;
147
}
148
149
private _createActionBar(className: string, actions: readonly IAction[]): ActionBar | undefined {
150
if (!actions.length) {
151
return undefined;
152
}
153
154
const container = dom.$(className);
155
const actionBar = new ActionBar(container);
156
actionBar.push(actions, { icon: false, label: true });
157
return actionBar;
158
}
159
160
private _onWidgetClosed(didCancel?: boolean): void {
161
this._list.value?.hide(didCancel);
162
}
163
}
164
165
registerSingleton(IActionWidgetService, ActionWidgetService, InstantiationType.Delayed);
166
167
const weight = KeybindingWeight.EditorContrib + 1000;
168
169
registerAction2(class extends Action2 {
170
constructor() {
171
super({
172
id: 'hideCodeActionWidget',
173
title: localize2('hideCodeActionWidget.title', "Hide action widget"),
174
precondition: ActionWidgetContextKeys.Visible,
175
keybinding: {
176
weight,
177
primary: KeyCode.Escape,
178
secondary: [KeyMod.Shift | KeyCode.Escape]
179
},
180
});
181
}
182
183
run(accessor: ServicesAccessor): void {
184
accessor.get(IActionWidgetService).hide(true);
185
}
186
});
187
188
registerAction2(class extends Action2 {
189
constructor() {
190
super({
191
id: 'selectPrevCodeAction',
192
title: localize2('selectPrevCodeAction.title', "Select previous action"),
193
precondition: ActionWidgetContextKeys.Visible,
194
keybinding: {
195
weight,
196
primary: KeyCode.UpArrow,
197
secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow],
198
mac: { primary: KeyCode.UpArrow, secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow, KeyMod.WinCtrl | KeyCode.KeyP] },
199
}
200
});
201
}
202
203
run(accessor: ServicesAccessor): void {
204
const widgetService = accessor.get(IActionWidgetService);
205
if (widgetService instanceof ActionWidgetService) {
206
widgetService.focusPrevious();
207
}
208
}
209
});
210
211
registerAction2(class extends Action2 {
212
constructor() {
213
super({
214
id: 'selectNextCodeAction',
215
title: localize2('selectNextCodeAction.title', "Select next action"),
216
precondition: ActionWidgetContextKeys.Visible,
217
keybinding: {
218
weight,
219
primary: KeyCode.DownArrow,
220
secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow],
221
mac: { primary: KeyCode.DownArrow, secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow, KeyMod.WinCtrl | KeyCode.KeyN] }
222
}
223
});
224
}
225
226
run(accessor: ServicesAccessor): void {
227
const widgetService = accessor.get(IActionWidgetService);
228
if (widgetService instanceof ActionWidgetService) {
229
widgetService.focusNext();
230
}
231
}
232
});
233
234
registerAction2(class extends Action2 {
235
constructor() {
236
super({
237
id: acceptSelectedActionCommand,
238
title: localize2('acceptSelected.title', "Accept selected action"),
239
precondition: ActionWidgetContextKeys.Visible,
240
keybinding: {
241
weight,
242
primary: KeyCode.Enter,
243
secondary: [KeyMod.CtrlCmd | KeyCode.Period],
244
}
245
});
246
}
247
248
run(accessor: ServicesAccessor): void {
249
const widgetService = accessor.get(IActionWidgetService);
250
if (widgetService instanceof ActionWidgetService) {
251
widgetService.acceptSelected();
252
}
253
}
254
});
255
256
registerAction2(class extends Action2 {
257
constructor() {
258
super({
259
id: previewSelectedActionCommand,
260
title: localize2('previewSelected.title', "Preview selected action"),
261
precondition: ActionWidgetContextKeys.Visible,
262
keybinding: {
263
weight,
264
primary: KeyMod.CtrlCmd | KeyCode.Enter,
265
}
266
});
267
}
268
269
run(accessor: ServicesAccessor): void {
270
const widgetService = accessor.get(IActionWidgetService);
271
if (widgetService instanceof ActionWidgetService) {
272
widgetService.acceptSelected(true);
273
}
274
}
275
});
276
277