Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/watchExpressionsView.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 { IDragAndDropData } from '../../../../base/browser/dnd.js';
7
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
8
import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
9
import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from '../../../../base/browser/ui/list/list.js';
10
import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js';
11
import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';
12
import { ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeMouseEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
13
import { RunOnceScheduler } from '../../../../base/common/async.js';
14
import { Codicon } from '../../../../base/common/codicons.js';
15
import { FuzzyScore } from '../../../../base/common/filters.js';
16
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
17
import { localize } from '../../../../nls.js';
18
import { getContextMenuActions, } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
19
import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
20
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
21
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
22
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
23
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
24
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
25
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
26
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
27
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
28
import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js';
29
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
30
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
31
import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js';
32
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
33
import { FocusedViewContext } from '../../../common/contextkeys.js';
34
import { IViewDescriptorService } from '../../../common/views.js';
35
import { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_EXPRESSION_SELECTED, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_VARIABLE_TYPE, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugConfiguration, IDebugService, IDebugViewWithVariables, IExpression, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, WATCH_VIEW_ID, CONTEXT_DEBUG_TYPE } from '../common/debug.js';
36
import { Expression, Variable, VisualizedExpression } from '../common/debugModel.js';
37
import { AbstractExpressionDataSource, AbstractExpressionsRenderer, expressionAndScopeLabelProvider, IExpressionTemplateData, IInputBoxOptions, renderViewTree } from './baseDebugView.js';
38
import { COPY_WATCH_EXPRESSION_COMMAND_ID, setDataBreakpointInfoResponse } from './debugCommands.js';
39
import { DebugExpressionRenderer } from './debugExpressionRenderer.js';
40
import { watchExpressionsAdd, watchExpressionsRemoveAll } from './debugIcons.js';
41
import { VariablesRenderer, VisualizedVariableRenderer } from './variablesView.js';
42
43
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
44
let ignoreViewUpdates = false;
45
let useCachedEvaluation = false;
46
47
export class WatchExpressionsView extends ViewPane implements IDebugViewWithVariables {
48
49
private watchExpressionsUpdatedScheduler: RunOnceScheduler;
50
private needsRefresh = false;
51
private tree!: WorkbenchAsyncDataTree<IDebugService | IExpression, IExpression, FuzzyScore>;
52
private watchExpressionsExist: IContextKey<boolean>;
53
private expressionRenderer: DebugExpressionRenderer;
54
55
public get treeSelection() {
56
return this.tree.getSelection();
57
}
58
59
constructor(
60
options: IViewletViewOptions,
61
@IContextMenuService contextMenuService: IContextMenuService,
62
@IDebugService private readonly debugService: IDebugService,
63
@IKeybindingService keybindingService: IKeybindingService,
64
@IInstantiationService instantiationService: IInstantiationService,
65
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
66
@IConfigurationService configurationService: IConfigurationService,
67
@IContextKeyService contextKeyService: IContextKeyService,
68
@IOpenerService openerService: IOpenerService,
69
@IThemeService themeService: IThemeService,
70
@IHoverService hoverService: IHoverService,
71
@IMenuService private readonly menuService: IMenuService
72
) {
73
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
74
75
this.watchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
76
this.needsRefresh = false;
77
this.tree.updateChildren();
78
}, 50);
79
this.watchExpressionsExist = CONTEXT_WATCH_EXPRESSIONS_EXIST.bindTo(contextKeyService);
80
this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0);
81
this.expressionRenderer = instantiationService.createInstance(DebugExpressionRenderer);
82
}
83
84
protected override renderBody(container: HTMLElement): void {
85
super.renderBody(container);
86
87
this.element.classList.add('debug-pane');
88
container.classList.add('debug-watch');
89
const treeContainer = renderViewTree(container);
90
91
const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer, this.expressionRenderer);
92
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree<IDebugService | IExpression, IExpression, FuzzyScore>, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(),
93
[
94
expressionsRenderer,
95
this.instantiationService.createInstance(VariablesRenderer, this.expressionRenderer),
96
this.instantiationService.createInstance(VisualizedVariableRenderer, this.expressionRenderer),
97
],
98
this.instantiationService.createInstance(WatchExpressionsDataSource), {
99
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
100
identityProvider: { getId: (element: IExpression) => element.getId() },
101
keyboardNavigationLabelProvider: {
102
getKeyboardNavigationLabel: (e: IExpression) => {
103
if (e === this.debugService.getViewModel().getSelectedExpression()?.expression) {
104
// Don't filter input box
105
return undefined;
106
}
107
108
return expressionAndScopeLabelProvider.getKeyboardNavigationLabel(e);
109
}
110
},
111
dnd: new WatchExpressionsDragAndDrop(this.debugService),
112
overrideStyles: this.getLocationBasedColors().listOverrideStyles
113
});
114
this._register(this.tree);
115
this.tree.setInput(this.debugService);
116
CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
117
118
this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree));
119
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
120
this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));
121
this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => {
122
this.watchExpressionsExist.set(this.debugService.getModel().getWatchExpressions().length > 0);
123
if (!this.isBodyVisible()) {
124
this.needsRefresh = true;
125
} else {
126
if (we && !we.name) {
127
// We are adding a new input box, no need to re-evaluate watch expressions
128
useCachedEvaluation = true;
129
}
130
await this.tree.updateChildren();
131
useCachedEvaluation = false;
132
if (we instanceof Expression) {
133
this.tree.reveal(we);
134
}
135
}
136
}));
137
this._register(this.debugService.getViewModel().onDidFocusStackFrame(() => {
138
if (!this.isBodyVisible()) {
139
this.needsRefresh = true;
140
return;
141
}
142
143
if (!this.watchExpressionsUpdatedScheduler.isScheduled()) {
144
this.watchExpressionsUpdatedScheduler.schedule();
145
}
146
}));
147
this._register(this.debugService.getViewModel().onWillUpdateViews(() => {
148
if (!ignoreViewUpdates) {
149
this.tree.updateChildren();
150
}
151
}));
152
153
this._register(this.onDidChangeBodyVisibility(visible => {
154
if (visible && this.needsRefresh) {
155
this.watchExpressionsUpdatedScheduler.schedule();
156
}
157
}));
158
let horizontalScrolling: boolean | undefined;
159
this._register(this.debugService.getViewModel().onDidSelectExpression(e => {
160
const expression = e?.expression;
161
if (expression && this.tree.hasNode(expression)) {
162
horizontalScrolling = this.tree.options.horizontalScrolling;
163
if (horizontalScrolling) {
164
this.tree.updateOptions({ horizontalScrolling: false });
165
}
166
167
if (expression.name) {
168
// Only rerender if the input is already done since otherwise the tree is not yet aware of the new element
169
this.tree.rerender(expression);
170
}
171
} else if (!expression && horizontalScrolling !== undefined) {
172
this.tree.updateOptions({ horizontalScrolling: horizontalScrolling });
173
horizontalScrolling = undefined;
174
}
175
}));
176
177
this._register(this.debugService.getViewModel().onDidEvaluateLazyExpression(async e => {
178
if (e instanceof Variable && this.tree.hasNode(e)) {
179
await this.tree.updateChildren(e, false, true);
180
await this.tree.expand(e);
181
}
182
}));
183
}
184
185
protected override layoutBody(height: number, width: number): void {
186
super.layoutBody(height, width);
187
this.tree.layout(height, width);
188
}
189
190
override focus(): void {
191
super.focus();
192
this.tree.domFocus();
193
}
194
195
collapseAll(): void {
196
this.tree.collapseAll();
197
}
198
199
private onMouseDblClick(e: ITreeMouseEvent<IExpression>): void {
200
if ((e.browserEvent.target as HTMLElement).className.indexOf('twistie') >= 0) {
201
// Ignore double click events on twistie
202
return;
203
}
204
205
const element = e.element;
206
// double click on primitive value: open input box to be able to select and copy value.
207
const selectedExpression = this.debugService.getViewModel().getSelectedExpression();
208
if ((element instanceof Expression && element !== selectedExpression?.expression) || (element instanceof VisualizedExpression && element.treeItem.canEdit)) {
209
this.debugService.getViewModel().setSelectedExpression(element, false);
210
} else if (!element) {
211
// Double click in watch panel triggers to add a new watch expression
212
this.debugService.addWatchExpression();
213
}
214
}
215
216
private async onContextMenu(e: ITreeContextMenuEvent<IExpression>): Promise<void> {
217
const element = e.element;
218
if (!element) {
219
return;
220
}
221
222
const selection = this.tree.getSelection();
223
224
const contextKeyService = element && await getContextForWatchExpressionMenuWithDataAccess(this.contextKeyService, element);
225
const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: element, shouldForwardArgs: false });
226
const { secondary } = getContextMenuActions(menu, 'inline');
227
228
// const actions = getFlatContextMenuActions(this.menu.getActions({ arg: element, shouldForwardArgs: true }));
229
this.contextMenuService.showContextMenu({
230
getAnchor: () => e.anchor,
231
getActions: () => secondary,
232
getActionsContext: () => element && selection.includes(element) ? selection : element ? [element] : [],
233
});
234
}
235
}
236
237
class WatchExpressionsDelegate implements IListVirtualDelegate<IExpression> {
238
239
getHeight(_element: IExpression): number {
240
return 22;
241
}
242
243
getTemplateId(element: IExpression): string {
244
if (element instanceof Expression) {
245
return WatchExpressionsRenderer.ID;
246
}
247
248
if (element instanceof VisualizedExpression) {
249
return VisualizedVariableRenderer.ID;
250
}
251
252
// Variable
253
return VariablesRenderer.ID;
254
}
255
}
256
257
function isDebugService(element: any): element is IDebugService {
258
return typeof element.getConfigurationManager === 'function';
259
}
260
261
class WatchExpressionsDataSource extends AbstractExpressionDataSource<IDebugService, IExpression> {
262
263
public override hasChildren(element: IExpression | IDebugService): boolean {
264
return isDebugService(element) || element.hasChildren;
265
}
266
267
protected override doGetChildren(element: IDebugService | IExpression): Promise<Array<IExpression>> {
268
if (isDebugService(element)) {
269
const debugService = element as IDebugService;
270
const watchExpressions = debugService.getModel().getWatchExpressions();
271
const viewModel = debugService.getViewModel();
272
return Promise.all(watchExpressions.map(we => !!we.name && !useCachedEvaluation
273
? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we)
274
: Promise.resolve(we)));
275
}
276
277
return element.getChildren();
278
}
279
}
280
281
282
export class WatchExpressionsRenderer extends AbstractExpressionsRenderer {
283
284
static readonly ID = 'watchexpression';
285
286
constructor(
287
private readonly expressionRenderer: DebugExpressionRenderer,
288
@IMenuService private readonly menuService: IMenuService,
289
@IContextKeyService private readonly contextKeyService: IContextKeyService,
290
@IDebugService debugService: IDebugService,
291
@IContextViewService contextViewService: IContextViewService,
292
@IHoverService hoverService: IHoverService,
293
@IConfigurationService private configurationService: IConfigurationService,
294
) {
295
super(debugService, contextViewService, hoverService);
296
}
297
298
get templateId() {
299
return WatchExpressionsRenderer.ID;
300
}
301
302
public override renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {
303
data.elementDisposable.clear();
304
data.elementDisposable.add(this.configurationService.onDidChangeConfiguration(e => {
305
if (e.affectsConfiguration('debug.showVariableTypes')) {
306
super.renderExpressionElement(node.element, node, data);
307
}
308
}));
309
super.renderExpressionElement(node.element, node, data);
310
}
311
312
protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {
313
let text: string;
314
data.type.textContent = '';
315
const showType = this.configurationService.getValue<IDebugConfiguration>('debug').showVariableTypes;
316
if (showType && expression.type) {
317
text = typeof expression.value === 'string' ? `${expression.name}: ` : expression.name;
318
//render type
319
data.type.textContent = expression.type + ' =';
320
} else {
321
text = typeof expression.value === 'string' ? `${expression.name} =` : expression.name;
322
}
323
324
let title: string;
325
if (expression.type) {
326
if (showType) {
327
title = `${expression.name}`;
328
} else {
329
title = expression.type === expression.value ?
330
expression.type :
331
`${expression.type}`;
332
}
333
} else {
334
title = expression.value;
335
}
336
337
data.label.set(text, highlights, title);
338
data.elementDisposable.add(this.expressionRenderer.renderValue(data.value, expression, {
339
showChanged: true,
340
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
341
colorize: true,
342
session: expression.getSession(),
343
}));
344
}
345
346
protected getInputBoxOptions(expression: IExpression, settingValue: boolean): IInputBoxOptions {
347
if (settingValue) {
348
return {
349
initialValue: expression.value,
350
ariaLabel: localize('typeNewValue', "Type new value"),
351
onFinish: async (value: string, success: boolean) => {
352
if (success && value) {
353
const focusedFrame = this.debugService.getViewModel().focusedStackFrame;
354
if (focusedFrame && (expression instanceof Variable || expression instanceof Expression)) {
355
await expression.setExpression(value, focusedFrame);
356
this.debugService.getViewModel().updateViews();
357
}
358
}
359
}
360
};
361
}
362
363
return {
364
initialValue: expression.name ? expression.name : '',
365
ariaLabel: localize('watchExpressionInputAriaLabel', "Type watch expression"),
366
placeholder: localize('watchExpressionPlaceholder', "Expression to watch"),
367
onFinish: (value: string, success: boolean) => {
368
if (success && value) {
369
this.debugService.renameWatchExpression(expression.getId(), value);
370
ignoreViewUpdates = true;
371
this.debugService.getViewModel().updateViews();
372
ignoreViewUpdates = false;
373
} else if (!expression.name) {
374
this.debugService.removeWatchExpressions(expression.getId());
375
}
376
}
377
};
378
}
379
380
protected override renderActionBar(actionBar: ActionBar, expression: IExpression) {
381
const contextKeyService = getContextForWatchExpressionMenu(this.contextKeyService, expression);
382
const context = expression;
383
const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: context, shouldForwardArgs: false });
384
385
const { primary } = getContextMenuActions(menu, 'inline');
386
387
actionBar.clear();
388
actionBar.context = context;
389
actionBar.push(primary, { icon: true, label: false });
390
}
391
}
392
393
/**
394
* Gets a context key overlay that has context for the given expression.
395
*/
396
function getContextForWatchExpressionMenu(parentContext: IContextKeyService, expression: IExpression, additionalContext: [string, unknown][] = []) {
397
const session = expression.getSession();
398
return parentContext.createOverlay([
399
[CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT.key, 'evaluateName' in expression],
400
[CONTEXT_WATCH_ITEM_TYPE.key, expression instanceof Expression ? 'expression' : expression instanceof Variable ? 'variable' : undefined],
401
[CONTEXT_CAN_VIEW_MEMORY.key, !!session?.capabilities.supportsReadMemoryRequest && expression.memoryReference !== undefined],
402
[CONTEXT_VARIABLE_IS_READONLY.key, !!expression.presentationHint?.attributes?.includes('readOnly') || expression.presentationHint?.lazy],
403
[CONTEXT_VARIABLE_TYPE.key, expression.type],
404
[CONTEXT_DEBUG_TYPE.key, session?.configuration.type],
405
...additionalContext
406
]);
407
}
408
409
/**
410
* Gets a context key overlay that has context for the given expression, including data access info.
411
*/
412
async function getContextForWatchExpressionMenuWithDataAccess(parentContext: IContextKeyService, expression: IExpression) {
413
const session = expression.getSession();
414
if (!session || !session.capabilities.supportsDataBreakpoints) {
415
return getContextForWatchExpressionMenu(parentContext, expression);
416
}
417
418
const contextKeys: [string, unknown][] = [];
419
const dataBreakpointInfoResponse = await session.dataBreakpointInfo('evaluateName' in expression ? expression.evaluateName as string : expression.name);
420
const dataBreakpointId = dataBreakpointInfoResponse?.dataId;
421
const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes;
422
setDataBreakpointInfoResponse(dataBreakpointInfoResponse);
423
424
if (!dataBreakpointAccessTypes) {
425
contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);
426
} else {
427
for (const accessType of dataBreakpointAccessTypes) {
428
switch (accessType) {
429
case 'read':
430
contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.key, !!dataBreakpointId]);
431
break;
432
case 'write':
433
contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);
434
break;
435
case 'readWrite':
436
contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.key, !!dataBreakpointId]);
437
break;
438
}
439
}
440
}
441
442
return getContextForWatchExpressionMenu(parentContext, expression, contextKeys);
443
}
444
445
446
class WatchExpressionsAccessibilityProvider implements IListAccessibilityProvider<IExpression> {
447
448
getWidgetAriaLabel(): string {
449
return localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions");
450
}
451
452
getAriaLabel(element: IExpression): string {
453
if (element instanceof Expression) {
454
return localize('watchExpressionAriaLabel', "{0}, value {1}", (<Expression>element).name, (<Expression>element).value);
455
}
456
457
// Variable
458
return localize('watchVariableAriaLabel', "{0}, value {1}", (<Variable>element).name, (<Variable>element).value);
459
}
460
}
461
462
class WatchExpressionsDragAndDrop implements ITreeDragAndDrop<IExpression> {
463
464
constructor(private debugService: IDebugService) { }
465
onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void {
466
if (data instanceof ElementsDragAndDropData) {
467
originalEvent.dataTransfer!.setData('text/plain', data.elements[0].name);
468
}
469
}
470
471
onDragOver(data: IDragAndDropData, targetElement: IExpression | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
472
if (!(data instanceof ElementsDragAndDropData)) {
473
return false;
474
}
475
476
const expressions = (data as ElementsDragAndDropData<IExpression>).elements;
477
if (!(expressions.length > 0 && expressions[0] instanceof Expression)) {
478
return false;
479
}
480
481
let dropEffectPosition: ListDragOverEffectPosition | undefined = undefined;
482
if (targetIndex === undefined) {
483
// Hovering over the list
484
dropEffectPosition = ListDragOverEffectPosition.After;
485
targetIndex = -1;
486
} else {
487
// Hovering over an element
488
switch (targetSector) {
489
case ListViewTargetSector.TOP:
490
case ListViewTargetSector.CENTER_TOP:
491
dropEffectPosition = ListDragOverEffectPosition.Before; break;
492
case ListViewTargetSector.CENTER_BOTTOM:
493
case ListViewTargetSector.BOTTOM:
494
dropEffectPosition = ListDragOverEffectPosition.After; break;
495
}
496
}
497
498
return { accept: true, effect: { type: ListDragOverEffectType.Move, position: dropEffectPosition }, feedback: [targetIndex] } satisfies ITreeDragOverReaction;
499
}
500
501
getDragURI(element: IExpression): string | null {
502
if (!(element instanceof Expression) || element === this.debugService.getViewModel().getSelectedExpression()?.expression) {
503
return null;
504
}
505
506
return element.getId();
507
}
508
509
getDragLabel(elements: IExpression[]): string | undefined {
510
if (elements.length === 1) {
511
return elements[0].name;
512
}
513
514
return undefined;
515
}
516
517
drop(data: IDragAndDropData, targetElement: IExpression, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void {
518
if (!(data instanceof ElementsDragAndDropData)) {
519
return;
520
}
521
522
const draggedElement = (data as ElementsDragAndDropData<IExpression>).elements[0];
523
if (!(draggedElement instanceof Expression)) {
524
throw new Error('Invalid dragged element');
525
}
526
527
const watches = this.debugService.getModel().getWatchExpressions();
528
const sourcePosition = watches.indexOf(draggedElement);
529
530
let targetPosition;
531
if (targetElement instanceof Expression) {
532
targetPosition = watches.indexOf(targetElement);
533
534
switch (targetSector) {
535
case ListViewTargetSector.BOTTOM:
536
case ListViewTargetSector.CENTER_BOTTOM:
537
targetPosition++; break;
538
}
539
540
if (sourcePosition < targetPosition) {
541
targetPosition--;
542
}
543
} else {
544
targetPosition = watches.length - 1;
545
}
546
547
this.debugService.moveWatchExpression(draggedElement.getId(), targetPosition);
548
}
549
550
dispose(): void { }
551
}
552
553
registerAction2(class Collapse extends ViewAction<WatchExpressionsView> {
554
constructor() {
555
super({
556
id: 'watch.collapse',
557
viewId: WATCH_VIEW_ID,
558
title: localize('collapse', "Collapse All"),
559
f1: false,
560
icon: Codicon.collapseAll,
561
precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST,
562
menu: {
563
id: MenuId.ViewTitle,
564
order: 30,
565
group: 'navigation',
566
when: ContextKeyExpr.equals('view', WATCH_VIEW_ID)
567
}
568
});
569
}
570
571
runInView(_accessor: ServicesAccessor, view: WatchExpressionsView) {
572
view.collapseAll();
573
}
574
});
575
576
export const ADD_WATCH_ID = 'workbench.debug.viewlet.action.addWatchExpression'; // Use old and long id for backwards compatibility
577
export const ADD_WATCH_LABEL = localize('addWatchExpression', "Add Expression");
578
579
registerAction2(class AddWatchExpressionAction extends Action2 {
580
constructor() {
581
super({
582
id: ADD_WATCH_ID,
583
title: ADD_WATCH_LABEL,
584
f1: false,
585
icon: watchExpressionsAdd,
586
menu: {
587
id: MenuId.ViewTitle,
588
group: 'navigation',
589
when: ContextKeyExpr.equals('view', WATCH_VIEW_ID)
590
}
591
});
592
}
593
594
run(accessor: ServicesAccessor): void {
595
const debugService = accessor.get(IDebugService);
596
debugService.addWatchExpression();
597
}
598
});
599
600
export const REMOVE_WATCH_EXPRESSIONS_COMMAND_ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions';
601
export const REMOVE_WATCH_EXPRESSIONS_LABEL = localize('removeAllWatchExpressions', "Remove All Expressions");
602
registerAction2(class RemoveAllWatchExpressionsAction extends Action2 {
603
constructor() {
604
super({
605
id: REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, // Use old and long id for backwards compatibility
606
title: REMOVE_WATCH_EXPRESSIONS_LABEL,
607
f1: false,
608
icon: watchExpressionsRemoveAll,
609
precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST,
610
menu: {
611
id: MenuId.ViewTitle,
612
order: 20,
613
group: 'navigation',
614
when: ContextKeyExpr.equals('view', WATCH_VIEW_ID)
615
}
616
});
617
}
618
619
run(accessor: ServicesAccessor): void {
620
const debugService = accessor.get(IDebugService);
621
debugService.removeWatchExpressions();
622
}
623
});
624
625
registerAction2(class CopyExpression extends ViewAction<WatchExpressionsView> {
626
constructor() {
627
super({
628
id: COPY_WATCH_EXPRESSION_COMMAND_ID,
629
title: localize('copyWatchExpression', "Copy Expression"),
630
f1: false,
631
viewId: WATCH_VIEW_ID,
632
precondition: CONTEXT_WATCH_EXPRESSIONS_EXIST,
633
keybinding: {
634
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC,
635
weight: KeybindingWeight.WorkbenchContrib,
636
when: ContextKeyExpr.and(
637
FocusedViewContext.isEqualTo(WATCH_VIEW_ID),
638
CONTEXT_EXPRESSION_SELECTED.negate(),
639
),
640
},
641
menu: {
642
id: MenuId.DebugWatchContext,
643
order: 20,
644
group: '3_modification',
645
when: CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression')
646
}
647
});
648
}
649
650
runInView(accessor: ServicesAccessor, view: WatchExpressionsView, value?: IExpression): void {
651
const clipboardService = accessor.get(IClipboardService);
652
if (!value) {
653
value = view.treeSelection.at(-1);
654
}
655
if (value) {
656
clipboardService.writeText(value.name);
657
}
658
}
659
});
660
661