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