Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/debugActionViewItems.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
6
import * as nls from '../../../../nls.js';
7
import { IAction } from '../../../../base/common/actions.js';
8
import { KeyCode } from '../../../../base/common/keyCodes.js';
9
import * as dom from '../../../../base/browser/dom.js';
10
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
11
import { SelectBox, ISelectOptionItem } from '../../../../base/browser/ui/selectBox/selectBox.js';
12
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
13
import { ICommandService } from '../../../../platform/commands/common/commands.js';
14
import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch, State } from '../common/debug.js';
15
import { ThemeIcon } from '../../../../base/common/themables.js';
16
import { selectBorder, selectBackground, asCssVariable } from '../../../../platform/theme/common/colorRegistry.js';
17
import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
18
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
19
import { IDisposable, dispose } from '../../../../base/common/lifecycle.js';
20
import { ADD_CONFIGURATION_ID } from './debugCommands.js';
21
import { BaseActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
22
import { debugStart } from './debugIcons.js';
23
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
24
import { defaultSelectBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
25
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
26
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
27
import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';
28
import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js';
29
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
30
import { hasNativeContextMenu } from '../../../../platform/window/common/window.js';
31
import { Gesture, EventType as TouchEventType } from '../../../../base/browser/touch.js';
32
33
const $ = dom.$;
34
35
export class StartDebugActionViewItem extends BaseActionViewItem {
36
37
private static readonly SEPARATOR = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';
38
39
private container!: HTMLElement;
40
private start!: HTMLElement;
41
private selectBox: SelectBox;
42
private debugOptions: { label: string; handler: (() => Promise<boolean>) }[] = [];
43
private toDispose: IDisposable[];
44
private selected = 0;
45
private providers: { label: string; type: string; pick: () => Promise<{ launch: ILaunch; config: IConfig } | undefined> }[] = [];
46
47
constructor(
48
private context: unknown,
49
action: IAction,
50
options: IBaseActionViewItemOptions,
51
@IDebugService private readonly debugService: IDebugService,
52
@IConfigurationService private readonly configurationService: IConfigurationService,
53
@ICommandService private readonly commandService: ICommandService,
54
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
55
@IContextViewService contextViewService: IContextViewService,
56
@IKeybindingService private readonly keybindingService: IKeybindingService,
57
@IHoverService private readonly hoverService: IHoverService,
58
@IContextKeyService private readonly contextKeyService: IContextKeyService
59
) {
60
super(context, action, options);
61
this.toDispose = [];
62
this.selectBox = new SelectBox([], -1, contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations'), useCustomDrawn: !hasNativeContextMenu(this.configurationService) });
63
this.selectBox.setFocusable(false);
64
this.toDispose.push(this.selectBox);
65
66
this.registerListeners();
67
}
68
69
private registerListeners(): void {
70
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
71
if (e.affectsConfiguration('launch')) {
72
this.updateOptions();
73
}
74
}));
75
this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => {
76
this.updateOptions();
77
}));
78
}
79
80
override render(container: HTMLElement): void {
81
this.container = container;
82
container.classList.add('start-debug-action-item');
83
this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart)));
84
const keybinding = this.keybindingService.lookupKeybinding(this.action.id)?.getLabel();
85
const keybindingLabel = keybinding ? ` (${keybinding})` : '';
86
const title = this.action.label + keybindingLabel;
87
this.toDispose.push(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.start, title));
88
this.start.setAttribute('role', 'button');
89
this._setAriaLabel(title);
90
91
this._register(Gesture.addTarget(this.start));
92
for (const event of [dom.EventType.CLICK, TouchEventType.Tap]) {
93
this.toDispose.push(dom.addDisposableListener(this.start, event, () => {
94
this.start.blur();
95
if (this.debugService.state !== State.Initializing) {
96
this.actionRunner.run(this.action, this.context);
97
}
98
}));
99
}
100
101
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => {
102
if (this.action.enabled && e.button === 0) {
103
this.start.classList.add('active');
104
}
105
}));
106
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_UP, () => {
107
this.start.classList.remove('active');
108
}));
109
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_OUT, () => {
110
this.start.classList.remove('active');
111
}));
112
113
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
114
const event = new StandardKeyboardEvent(e);
115
if (event.equals(KeyCode.RightArrow)) {
116
this.start.tabIndex = -1;
117
this.selectBox.focus();
118
event.stopPropagation();
119
}
120
}));
121
this.toDispose.push(this.selectBox.onDidSelect(async e => {
122
const target = this.debugOptions[e.index];
123
const shouldBeSelected = target.handler ? await target.handler() : false;
124
if (shouldBeSelected) {
125
this.selected = e.index;
126
} else {
127
// Some select options should not remain selected https://github.com/microsoft/vscode/issues/31526
128
this.selectBox.select(this.selected);
129
}
130
}));
131
132
const selectBoxContainer = $('.configuration');
133
this.selectBox.render(dom.append(container, selectBoxContainer));
134
this.toDispose.push(dom.addDisposableListener(selectBoxContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
135
const event = new StandardKeyboardEvent(e);
136
if (event.equals(KeyCode.LeftArrow)) {
137
this.selectBox.setFocusable(false);
138
this.start.tabIndex = 0;
139
this.start.focus();
140
event.stopPropagation();
141
event.preventDefault();
142
}
143
}));
144
this.container.style.border = `1px solid ${asCssVariable(selectBorder)}`;
145
selectBoxContainer.style.borderLeft = `1px solid ${asCssVariable(selectBorder)}`;
146
this.container.style.backgroundColor = asCssVariable(selectBackground);
147
148
const configManager = this.debugService.getConfigurationManager();
149
const updateDynamicConfigs = () => configManager.getDynamicProviders().then(providers => {
150
if (providers.length !== this.providers.length) {
151
this.providers = providers;
152
this.updateOptions();
153
}
154
});
155
156
this.toDispose.push(configManager.onDidChangeConfigurationProviders(updateDynamicConfigs));
157
updateDynamicConfigs();
158
this.updateOptions();
159
}
160
161
override setActionContext(context: any): void {
162
this.context = context;
163
}
164
165
override isEnabled(): boolean {
166
return true;
167
}
168
169
override focus(fromRight?: boolean): void {
170
if (fromRight) {
171
this.selectBox.focus();
172
} else {
173
this.start.tabIndex = 0;
174
this.start.focus();
175
}
176
}
177
178
override blur(): void {
179
this.start.tabIndex = -1;
180
this.selectBox.blur();
181
this.container.blur();
182
}
183
184
override setFocusable(focusable: boolean): void {
185
if (focusable) {
186
this.start.tabIndex = 0;
187
} else {
188
this.start.tabIndex = -1;
189
this.selectBox.setFocusable(false);
190
}
191
}
192
193
override dispose(): void {
194
this.toDispose = dispose(this.toDispose);
195
super.dispose();
196
}
197
198
private updateOptions(): void {
199
this.selected = 0;
200
this.debugOptions = [];
201
const manager = this.debugService.getConfigurationManager();
202
const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
203
let lastGroup: string | undefined;
204
const disabledIdxs: number[] = [];
205
manager.getAllConfigurations().forEach(({ launch, name, presentation }) => {
206
if (lastGroup !== presentation?.group) {
207
lastGroup = presentation?.group;
208
if (this.debugOptions.length) {
209
this.debugOptions.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) });
210
disabledIdxs.push(this.debugOptions.length - 1);
211
}
212
}
213
if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {
214
this.selected = this.debugOptions.length;
215
}
216
217
const label = inWorkspace ? `${name} (${launch.name})` : name;
218
this.debugOptions.push({
219
label, handler: async () => {
220
await manager.selectConfiguration(launch, name);
221
return true;
222
}
223
});
224
});
225
226
// Only take 3 elements from the recent dynamic configurations to not clutter the dropdown
227
manager.getRecentDynamicConfigurations().slice(0, 3).forEach(({ name, type }) => {
228
if (type === manager.selectedConfiguration.type && manager.selectedConfiguration.name === name) {
229
this.selected = this.debugOptions.length;
230
}
231
this.debugOptions.push({
232
label: name,
233
handler: async () => {
234
await manager.selectConfiguration(undefined, name, undefined, { type });
235
return true;
236
}
237
});
238
});
239
240
if (this.debugOptions.length === 0) {
241
this.debugOptions.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false });
242
}
243
244
this.debugOptions.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) });
245
disabledIdxs.push(this.debugOptions.length - 1);
246
247
this.providers.forEach(p => {
248
249
this.debugOptions.push({
250
label: `${p.label}...`,
251
handler: async () => {
252
const picked = await p.pick();
253
if (picked) {
254
await manager.selectConfiguration(picked.launch, picked.config.name, picked.config, { type: p.type });
255
return true;
256
}
257
return false;
258
}
259
});
260
});
261
262
manager.getLaunches().filter(l => !l.hidden).forEach(l => {
263
const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
264
this.debugOptions.push({
265
label, handler: async () => {
266
await this.commandService.executeCommand(ADD_CONFIGURATION_ID, l.uri.toString());
267
return false;
268
}
269
});
270
});
271
272
this.selectBox.setOptions(this.debugOptions.map((data, index): ISelectOptionItem => ({ text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1 })), this.selected);
273
}
274
275
private _setAriaLabel(title: string): void {
276
let ariaLabel = title;
277
let keybinding: string | undefined;
278
const verbose = this.configurationService.getValue(AccessibilityVerbositySettingId.Debug);
279
if (verbose) {
280
keybinding = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp, this.contextKeyService)?.getLabel() ?? undefined;
281
}
282
if (keybinding) {
283
ariaLabel = nls.localize('commentLabelWithKeybinding', "{0}, use ({1}) for accessibility help", ariaLabel, keybinding);
284
} else {
285
ariaLabel = nls.localize('commentLabelWithKeybindingNoKeybinding', "{0}, run the command Open Accessibility Help which is currently not triggerable via keybinding.", ariaLabel);
286
}
287
this.start.ariaLabel = ariaLabel;
288
}
289
}
290
291
export class FocusSessionActionViewItem extends SelectActionViewItem<IDebugSession> {
292
constructor(
293
action: IAction,
294
session: IDebugSession | undefined,
295
@IDebugService protected readonly debugService: IDebugService,
296
@IContextViewService contextViewService: IContextViewService,
297
@IConfigurationService private readonly configurationService: IConfigurationService
298
) {
299
super(null, action, [], -1, contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('debugSession', 'Debug Session'), useCustomDrawn: !hasNativeContextMenu(configurationService) });
300
301
this._register(this.debugService.getViewModel().onDidFocusSession(() => {
302
const session = this.getSelectedSession();
303
if (session) {
304
const index = this.getSessions().indexOf(session);
305
this.select(index);
306
}
307
}));
308
309
this._register(this.debugService.onDidNewSession(session => {
310
const sessionListeners: IDisposable[] = [];
311
sessionListeners.push(session.onDidChangeName(() => this.update()));
312
sessionListeners.push(session.onDidEndAdapter(() => dispose(sessionListeners)));
313
this.update();
314
}));
315
this.getSessions().forEach(session => {
316
this._register(session.onDidChangeName(() => this.update()));
317
});
318
this._register(this.debugService.onDidEndSession(() => this.update()));
319
320
const selectedSession = session ? this.mapFocusedSessionToSelected(session) : undefined;
321
this.update(selectedSession);
322
}
323
324
protected override getActionContext(_: string, index: number): IDebugSession {
325
return this.getSessions()[index];
326
}
327
328
private update(session?: IDebugSession) {
329
if (!session) {
330
session = this.getSelectedSession();
331
}
332
const sessions = this.getSessions();
333
const names = sessions.map(s => {
334
const label = s.getLabel();
335
if (s.parentSession) {
336
// Indent child sessions so they look like children
337
return `\u00A0\u00A0${label}`;
338
}
339
340
return label;
341
});
342
this.setOptions(names.map((data): ISelectOptionItem => ({ text: data })), session ? sessions.indexOf(session) : undefined);
343
}
344
345
private getSelectedSession(): IDebugSession | undefined {
346
const session = this.debugService.getViewModel().focusedSession;
347
return session ? this.mapFocusedSessionToSelected(session) : undefined;
348
}
349
350
protected getSessions(): ReadonlyArray<IDebugSession> {
351
const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;
352
const sessions = this.debugService.getModel().getSessions();
353
354
return showSubSessions ? sessions : sessions.filter(s => !s.parentSession);
355
}
356
357
protected mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession {
358
const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;
359
while (focusedSession.parentSession && !showSubSessions) {
360
focusedSession = focusedSession.parentSession;
361
}
362
return focusedSession;
363
}
364
}
365
366