Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/fixtures/doc/issue-6406/debugModel.ts
13405 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 { distinct } from 'vs/base/common/arrays';
7
import { findLastIdx } from 'vs/base/common/arraysFind';
8
import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async';
9
import { VSBuffer, decodeBase64, encodeBase64 } from 'vs/base/common/buffer';
10
import { CancellationTokenSource } from 'vs/base/common/cancellation';
11
import { Emitter, Event } from 'vs/base/common/event';
12
import { stringHash } from 'vs/base/common/hash';
13
import { Disposable } from 'vs/base/common/lifecycle';
14
import { mixin } from 'vs/base/common/objects';
15
import { autorun } from 'vs/base/common/observable';
16
import * as resources from 'vs/base/common/resources';
17
import { isString, isUndefinedOrNull } from 'vs/base/common/types';
18
import { URI, URI as uri } from 'vs/base/common/uri';
19
import { generateUuid } from 'vs/base/common/uuid';
20
import { IRange, Range } from 'vs/editor/common/core/range';
21
import * as nls from 'vs/nls';
22
import { ILogService } from 'vs/platform/log/common/log';
23
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
24
import { IEditorPane } from 'vs/workbench/common/editor';
25
import { DEBUG_MEMORY_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugEvaluatePosition, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State, isFrameDeemphasized } from 'vs/workbench/contrib/debug/common/debug';
26
import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource';
27
import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage';
28
import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers';
29
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
30
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
31
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
32
33
interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable {
34
__vscodeVariableMenuContext?: string;
35
}
36
37
export class ExpressionContainer implements IExpressionContainer {
38
39
public static readonly allValues = new Map<string, string>();
40
// Use chunks to support variable paging #9537
41
private static readonly BASE_CHUNK_SIZE = 100;
42
43
public type: string | undefined;
44
public valueChanged = false;
45
private _value: string = '';
46
protected children?: Promise<IExpression[]>;
47
48
constructor(
49
protected session: IDebugSession | undefined,
50
protected readonly threadId: number | undefined,
51
private _reference: number | undefined,
52
private readonly id: string,
53
public namedVariables: number | undefined = 0,
54
public indexedVariables: number | undefined = 0,
55
public memoryReference: string | undefined = undefined,
56
private startOfVariables: number | undefined = 0,
57
public presentationHint: DebugProtocol.VariablePresentationHint | undefined = undefined
58
) { }
59
60
get reference(): number | undefined {
61
return this._reference;
62
}
63
64
set reference(value: number | undefined) {
65
this._reference = value;
66
this.children = undefined; // invalidate children cache
67
}
68
69
async evaluateLazy(): Promise<void> {
70
if (typeof this.reference === 'undefined') {
71
return;
72
}
73
74
const response = await this.session!.variables(this.reference, this.threadId, undefined, undefined, undefined);
75
if (!response || !response.body || !response.body.variables || response.body.variables.length !== 1) {
76
return;
77
}
78
79
const dummyVar = response.body.variables[0];
80
this.reference = dummyVar.variablesReference;
81
this._value = dummyVar.value;
82
this.namedVariables = dummyVar.namedVariables;
83
this.indexedVariables = dummyVar.indexedVariables;
84
this.memoryReference = dummyVar.memoryReference;
85
this.presentationHint = dummyVar.presentationHint;
86
// Also call overridden method to adopt subclass props
87
this.adoptLazyResponse(dummyVar);
88
}
89
90
protected adoptLazyResponse(response: DebugProtocol.Variable): void {
91
}
92
93
getChildren(): Promise<IExpression[]> {
94
if (!this.children) {
95
this.children = this.doGetChildren();
96
}
97
98
return this.children;
99
}
100
101
private async doGetChildren(): Promise<IExpression[]> {
102
if (!this.hasChildren) {
103
return [];
104
}
105
106
if (!this.getChildrenInChunks) {
107
return this.fetchVariables(undefined, undefined, undefined);
108
}
109
110
// Check if object has named variables, fetch them independent from indexed variables #9670
111
const children = this.namedVariables ? await this.fetchVariables(undefined, undefined, 'named') : [];
112
113
// Use a dynamic chunk size based on the number of elements #9774
114
let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE;
115
while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) {
116
chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE;
117
}
118
119
if (!!this.indexedVariables && this.indexedVariables > chunkSize) {
120
// There are a lot of children, create fake intermediate values that represent chunks #9537
121
const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);
122
for (let i = 0; i < numberOfChunks; i++) {
123
const start = (this.startOfVariables || 0) + i * chunkSize;
124
const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);
125
children.push(new Variable(this.session, this.threadId, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, undefined, { kind: 'virtual' }, undefined, undefined, true, start));
126
}
127
128
return children;
129
}
130
131
const variables = await this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed');
132
return children.concat(variables);
133
}
134
135
getId(): string {
136
return this.id;
137
}
138
139
getSession(): IDebugSession | undefined {
140
return this.session;
141
}
142
143
get value(): string {
144
return this._value;
145
}
146
147
get hasChildren(): boolean {
148
// only variables with reference > 0 have children.
149
return !!this.reference && this.reference > 0 && !this.presentationHint?.lazy;
150
}
151
152
private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {
153
try {
154
const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count);
155
if (!response || !response.body || !response.body.variables) {
156
return [];
157
}
158
159
const nameCount = new Map<string, number>();
160
const vars = response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => {
161
if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') {
162
const count = nameCount.get(v.name) || 0;
163
const idDuplicationIndex = count > 0 ? count.toString() : '';
164
nameCount.set(v.name, count + 1);
165
return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.memoryReference, v.presentationHint, v.type, v.__vscodeVariableMenuContext, true, 0, idDuplicationIndex);
166
}
167
return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false);
168
});
169
170
if (this.session!.autoExpandLazyVariables) {
171
await Promise.all(vars.map(v => v.presentationHint?.lazy && v.evaluateLazy()));
172
}
173
174
return vars;
175
} catch (e) {
176
return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false)];
177
}
178
}
179
180
// The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked.
181
private get getChildrenInChunks(): boolean {
182
return !!this.indexedVariables;
183
}
184
185
set value(value: string) {
186
this._value = value;
187
this.valueChanged = !!ExpressionContainer.allValues.get(this.getId()) &&
188
ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value;
189
ExpressionContainer.allValues.set(this.getId(), value);
190
}
191
192
toString(): string {
193
return this.value;
194
}
195
196
async evaluateExpression(
197
expression: string,
198
session: IDebugSession | undefined,
199
stackFrame: IStackFrame | undefined,
200
context: string,
201
keepLazyVars = false,
202
location?: IDebugEvaluatePosition,
203
): Promise<boolean> {
204
205
if (!session || (!stackFrame && context !== 'repl')) {
206
this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE;
207
this.reference = 0;
208
return false;
209
}
210
211
this.session = session;
212
try {
213
const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context, location);
214
215
if (response && response.body) {
216
this.value = response.body.result || '';
217
this.reference = response.body.variablesReference;
218
this.namedVariables = response.body.namedVariables;
219
this.indexedVariables = response.body.indexedVariables;
220
this.memoryReference = response.body.memoryReference;
221
this.type = response.body.type || this.type;
222
this.presentationHint = response.body.presentationHint;
223
224
if (!keepLazyVars && response.body.presentationHint?.lazy) {
225
await this.evaluateLazy();
226
}
227
228
return true;
229
}
230
return false;
231
} catch (e) {
232
this.value = e.message || '';
233
this.reference = 0;
234
return false;
235
}
236
}
237
}
238
239
function handleSetResponse(expression: ExpressionContainer, response: DebugProtocol.SetVariableResponse | DebugProtocol.SetExpressionResponse | undefined): void {
240
if (response && response.body) {
241
expression.value = response.body.value || '';
242
expression.type = response.body.type || expression.type;
243
expression.reference = response.body.variablesReference;
244
expression.namedVariables = response.body.namedVariables;
245
expression.indexedVariables = response.body.indexedVariables;
246
// todo @weinand: the set responses contain most properties, but not memory references. Should they?
247
}
248
}
249
250
export class VisualizedExpression implements IExpression {
251
public errorMessage?: string;
252
private readonly id = generateUuid();
253
254
evaluateLazy(): Promise<void> {
255
return Promise.resolve();
256
}
257
getChildren(): Promise<IExpression[]> {
258
return this.visualizer.getVisualizedChildren(this.treeId, this.treeItem.id);
259
}
260
261
getId(): string {
262
return this.id;
263
}
264
265
get name() {
266
return this.treeItem.label;
267
}
268
269
get value() {
270
return this.treeItem.description || '';
271
}
272
273
get hasChildren() {
274
return this.treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None;
275
}
276
277
constructor(
278
private readonly visualizer: IDebugVisualizerService,
279
public readonly treeId: string,
280
public readonly treeItem: IDebugVisualizationTreeItem,
281
public readonly original?: Variable,
282
) { }
283
284
/** Edits the value, sets the {@link errorMessage} and returns false if unsuccessful */
285
public async edit(newValue: string) {
286
try {
287
await this.visualizer.editTreeItem(this.treeId, this.treeItem, newValue);
288
return true;
289
} catch (e) {
290
this.errorMessage = e.message;
291
return false;
292
}
293
}
294
}
295
296
export class Expression extends ExpressionContainer implements IExpression {
297
static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available");
298
299
public available: boolean;
300
301
constructor(public name: string, id = generateUuid()) {
302
super(undefined, undefined, 0, id);
303
this.available = false;
304
// name is not set if the expression is just being added
305
// in that case do not set default value to prevent flashing #14499
306
if (name) {
307
this.value = Expression.DEFAULT_VALUE;
308
}
309
}
310
311
async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean, location?: IDebugEvaluatePosition): Promise<void> {
312
this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars, location);
313
}
314
315
override toString(): string {
316
return `${this.name}\n${this.value}`;
317
}
318
319
async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {
320
if (!this.session) {
321
return;
322
}
323
324
const response = await this.session.setExpression(stackFrame.frameId, this.name, value);
325
handleSetResponse(this, response);
326
}
327
}
328
329
export class Variable extends ExpressionContainer implements IExpression {
330
331
// Used to show the error message coming from the adapter when setting the value #7807
332
public errorMessage: string | undefined;
333
334
constructor(
335
session: IDebugSession | undefined,
336
threadId: number | undefined,
337
public readonly parent: IExpressionContainer,
338
reference: number | undefined,
339
public readonly name: string,
340
public evaluateName: string | undefined,
341
value: string | undefined,
342
namedVariables: number | undefined,
343
indexedVariables: number | undefined,
344
memoryReference: string | undefined,
345
presentationHint: DebugProtocol.VariablePresentationHint | undefined,
346
type: string | undefined = undefined,
347
public readonly variableMenuContext: string | undefined = undefined,
348
public readonly available = true,
349
startOfVariables = 0,
350
idDuplicationIndex = '',
351
) {
352
super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint);
353
this.value = value || '';
354
this.type = type;
355
}
356
357
getThreadId() {
358
return this.threadId;
359
}
360
361
async setVariable(value: string, stackFrame: IStackFrame): Promise<any> {
362
if (!this.session) {
363
return;
364
}
365
366
try {
367
// Send out a setExpression for debug extensions that do not support set variables https://github.com/microsoft/vscode/issues/124679#issuecomment-869844437
368
if (this.session.capabilities.supportsSetExpression && !this.session.capabilities.supportsSetVariable && this.evaluateName) {
369
return this.setExpression(value, stackFrame);
370
}
371
372
const response = await this.session.setVariable((<ExpressionContainer>this.parent).reference, this.name, value);
373
handleSetResponse(this, response);
374
} catch (err) {
375
this.errorMessage = err.message;
376
}
377
}
378
379
async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {
380
if (!this.session || !this.evaluateName) {
381
return;
382
}
383
384
const response = await this.session.setExpression(stackFrame.frameId, this.evaluateName, value);
385
handleSetResponse(this, response);
386
}
387
388
override toString(): string {
389
return this.name ? `${this.name}: ${this.value}` : this.value;
390
}
391
392
protected override adoptLazyResponse(response: DebugProtocol.Variable): void {
393
this.evaluateName = response.evaluateName;
394
}
395
396
toDebugProtocolObject(): DebugProtocol.Variable {
397
return {
398
name: this.name,
399
variablesReference: this.reference || 0,
400
memoryReference: this.memoryReference,
401
value: this.value,
402
evaluateName: this.evaluateName
403
};
404
}
405
}
406
407
export class Scope extends ExpressionContainer implements IScope {
408
409
constructor(
410
public readonly stackFrame: IStackFrame,
411
id: number,
412
public readonly name: string,
413
reference: number,
414
public expensive: boolean,
415
namedVariables?: number,
416
indexedVariables?: number,
417
public readonly range?: IRange
418
) {
419
super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${id}`, namedVariables, indexedVariables);
420
}
421
422
override toString(): string {
423
return this.name;
424
}
425
426
toDebugProtocolObject(): DebugProtocol.Scope {
427
return {
428
name: this.name,
429
variablesReference: this.reference || 0,
430
expensive: this.expensive
431
};
432
}
433
}
434
435
export class ErrorScope extends Scope {
436
437
constructor(
438
stackFrame: IStackFrame,
439
index: number,
440
message: string,
441
) {
442
super(stackFrame, index, message, 0, false);
443
}
444
445
override toString(): string {
446
return this.name;
447
}
448
}
449
450
export class StackFrame implements IStackFrame {
451
452
private scopes: Promise<Scope[]> | undefined;
453
454
constructor(
455
public readonly thread: Thread,
456
public readonly frameId: number,
457
public readonly source: Source,
458
public readonly name: string,
459
public readonly presentationHint: string | undefined,
460
public readonly range: IRange,
461
private readonly index: number,
462
public readonly canRestart: boolean,
463
public readonly instructionPointerReference?: string
464
) { }
465
466
getId(): string {
467
return `stackframe:${this.thread.getId()}:${this.index}:${this.source.name}`;
468
}
469
470
getScopes(): Promise<IScope[]> {
471
if (!this.scopes) {
472
this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => {
473
if (!response || !response.body || !response.body.scopes) {
474
return [];
475
}
476
477
const usedIds = new Set<number>();
478
return response.body.scopes.map(rs => {
479
// form the id based on the name and location so that it's the
480
// same across multiple pauses to retain expansion state
481
let id = 0;
482
do {
483
id = stringHash(`${rs.name}:${rs.line}:${rs.column}`, id);
484
} while (usedIds.has(id));
485
486
usedIds.add(id);
487
return new Scope(this, id, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,
488
rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined);
489
490
});
491
}, err => [new ErrorScope(this, 0, err.message)]);
492
}
493
494
return this.scopes;
495
}
496
497
async getMostSpecificScopes(range: IRange): Promise<IScope[]> {
498
const scopes = await this.getScopes();
499
const nonExpensiveScopes = scopes.filter(s => !s.expensive);
500
const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range);
501
if (!haveRangeInfo) {
502
return nonExpensiveScopes;
503
}
504
505
const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && Range.containsRange(scope.range, range))
506
.sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber));
507
return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes;
508
}
509
510
restart(): Promise<void> {
511
return this.thread.session.restartFrame(this.frameId, this.thread.threadId);
512
}
513
514
forgetScopes(): void {
515
this.scopes = undefined;
516
}
517
518
toString(): string {
519
const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : '';
520
const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`;
521
522
return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;
523
}
524
525
async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<IEditorPane | undefined> {
526
const threadStopReason = this.thread.stoppedDetails?.reason;
527
if (this.instructionPointerReference &&
528
(threadStopReason === 'instruction breakpoint' ||
529
(threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction') ||
530
editorService.activeEditor instanceof DisassemblyViewInput)) {
531
return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true });
532
}
533
534
if (this.source.available) {
535
return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
536
}
537
return undefined;
538
}
539
540
equals(other: IStackFrame): boolean {
541
return (this.name === other.name) && (other.thread === this.thread) && (this.frameId === other.frameId) && (other.source === this.source) && (Range.equalsRange(this.range, other.range));
542
}
543
}
544
545
const KEEP_SUBTLE_FRAME_AT_TOP_REASONS: readonly string[] = ['breakpoint', 'step', 'function breakpoint'];
546
547
export class Thread implements IThread {
548
private callStack: IStackFrame[];
549
private staleCallStack: IStackFrame[];
550
private callStackCancellationTokens: CancellationTokenSource[] = [];
551
public stoppedDetails: IRawStoppedDetails | undefined;
552
public stopped: boolean;
553
public reachedEndOfCallStack = false;
554
public lastSteppingGranularity: DebugProtocol.SteppingGranularity | undefined;
555
556
constructor(public readonly session: IDebugSession, public name: string, public readonly threadId: number) {
557
this.callStack = [];
558
this.staleCallStack = [];
559
this.stopped = false;
560
}
561
562
getId(): string {
563
return `thread:${this.session.getId()}:${this.threadId}`;
564
}
565
566
clearCallStack(): void {
567
if (this.callStack.length) {
568
this.staleCallStack = this.callStack;
569
}
570
this.callStack = [];
571
this.callStackCancellationTokens.forEach(c => c.dispose(true));
572
this.callStackCancellationTokens = [];
573
}
574
575
getCallStack(): IStackFrame[] {
576
return this.callStack;
577
}
578
579
getStaleCallStack(): ReadonlyArray<IStackFrame> {
580
return this.staleCallStack;
581
}
582
583
getTopStackFrame(): IStackFrame | undefined {
584
const callStack = this.getCallStack();
585
const stopReason = this.stoppedDetails?.reason;
586
// Allow stack frame without source and with instructionReferencePointer as top stack frame when using disassembly view.
587
const firstAvailableStackFrame = callStack.find(sf => !!(
588
((stopReason === 'instruction breakpoint' || (stopReason === 'step' && this.lastSteppingGranularity === 'instruction')) && sf.instructionPointerReference) ||
589
(sf.source && sf.source.available && (KEEP_SUBTLE_FRAME_AT_TOP_REASONS.includes(stopReason!) || !isFrameDeemphasized(sf)))));
590
return firstAvailableStackFrame;
591
}
592
593
get stateLabel(): string {
594
if (this.stoppedDetails) {
595
return this.stoppedDetails.description ||
596
(this.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", this.stoppedDetails.reason) : nls.localize('paused', "Paused"));
597
}
598
599
return nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
600
}
601
602
/**
603
* Queries the debug adapter for the callstack and returns a promise
604
* which completes once the call stack has been retrieved.
605
* If the thread is not stopped, it returns a promise to an empty array.
606
* Only fetches the first stack frame for performance reasons. Calling this method consecutive times
607
* gets the remainder of the call stack.
608
*/
609
async fetchCallStack(levels = 20): Promise<void> {
610
if (this.stopped) {
611
const start = this.callStack.length;
612
const callStack = await this.getCallStackImpl(start, levels);
613
this.reachedEndOfCallStack = callStack.length < levels;
614
if (start < this.callStack.length) {
615
// Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660
616
this.callStack.splice(start, this.callStack.length - start);
617
}
618
this.callStack = this.callStack.concat(callStack || []);
619
if (typeof this.stoppedDetails?.totalFrames === 'number' && this.stoppedDetails.totalFrames === this.callStack.length) {
620
this.reachedEndOfCallStack = true;
621
}
622
}
623
}
624
625
private async getCallStackImpl(startFrame: number, levels: number): Promise<IStackFrame[]> {
626
try {
627
const tokenSource = new CancellationTokenSource();
628
this.callStackCancellationTokens.push(tokenSource);
629
const response = await this.session.stackTrace(this.threadId, startFrame, levels, tokenSource.token);
630
if (!response || !response.body || tokenSource.token.isCancellationRequested) {
631
return [];
632
}
633
634
if (this.stoppedDetails) {
635
this.stoppedDetails.totalFrames = response.body.totalFrames;
636
}
637
638
return response.body.stackFrames.map((rsf, index) => {
639
const source = this.session.getSource(rsf.source);
640
641
return new StackFrame(this, rsf.id, source, rsf.name, rsf.presentationHint, new Range(
642
rsf.line,
643
rsf.column,
644
rsf.endLine || rsf.line,
645
rsf.endColumn || rsf.column
646
), startFrame + index, typeof rsf.canRestart === 'boolean' ? rsf.canRestart : true, rsf.instructionPointerReference);
647
});
648
} catch (err) {
649
if (this.stoppedDetails) {
650
this.stoppedDetails.framesErrorMessage = err.message;
651
}
652
653
return [];
654
}
655
}
656
657
/**
658
* Returns exception info promise if the exception was thrown, otherwise undefined
659
*/
660
get exceptionInfo(): Promise<IExceptionInfo | undefined> {
661
if (this.stoppedDetails && this.stoppedDetails.reason === 'exception') {
662
if (this.session.capabilities.supportsExceptionInfoRequest) {
663
return this.session.exceptionInfo(this.threadId);
664
}
665
return Promise.resolve({
666
description: this.stoppedDetails.text,
667
breakMode: null
668
});
669
}
670
return Promise.resolve(undefined);
671
}
672
673
next(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
674
return this.session.next(this.threadId, granularity);
675
}
676
677
stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
678
return this.session.stepIn(this.threadId, undefined, granularity);
679
}
680
681
stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
682
return this.session.stepOut(this.threadId, granularity);
683
}
684
685
stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
686
return this.session.stepBack(this.threadId, granularity);
687
}
688
689
continue(): Promise<any> {
690
return this.session.continue(this.threadId);
691
}
692
693
pause(): Promise<any> {
694
return this.session.pause(this.threadId);
695
}
696
697
terminate(): Promise<any> {
698
return this.session.terminateThreads([this.threadId]);
699
}
700
701
reverseContinue(): Promise<any> {
702
return this.session.reverseContinue(this.threadId);
703
}
704
}
705
706
/**
707
* Gets a URI to a memory in the given session ID.
708
*/
709
export const getUriForDebugMemory = (
710
sessionId: string,
711
memoryReference: string,
712
range?: { fromOffset: number; toOffset: number },
713
displayName = 'memory'
714
) => {
715
return URI.from({
716
scheme: DEBUG_MEMORY_SCHEME,
717
authority: sessionId,
718
path: '/' + encodeURIComponent(memoryReference) + `/${encodeURIComponent(displayName)}.bin`,
719
query: range ? `?range=${range.fromOffset}:${range.toOffset}` : undefined,
720
});
721
};
722
723
export class MemoryRegion extends Disposable implements IMemoryRegion {
724
private readonly invalidateEmitter = this._register(new Emitter<IMemoryInvalidationEvent>());
725
726
/** @inheritdoc */
727
public readonly onDidInvalidate = this.invalidateEmitter.event;
728
729
/** @inheritdoc */
730
public readonly writable = !!this.session.capabilities.supportsWriteMemoryRequest;
731
732
constructor(private readonly memoryReference: string, private readonly session: IDebugSession) {
733
super();
734
this._register(session.onDidInvalidateMemory(e => {
735
if (e.body.memoryReference === memoryReference) {
736
this.invalidate(e.body.offset, e.body.count - e.body.offset);
737
}
738
}));
739
}
740
741
public async read(fromOffset: number, toOffset: number): Promise<MemoryRange[]> {
742
const length = toOffset - fromOffset;
743
const offset = fromOffset;
744
const result = await this.session.readMemory(this.memoryReference, offset, length);
745
746
if (result === undefined || !result.body?.data) {
747
return [{ type: MemoryRangeType.Unreadable, offset, length }];
748
}
749
750
let data: VSBuffer;
751
try {
752
data = decodeBase64(result.body.data);
753
} catch {
754
return [{ type: MemoryRangeType.Error, offset, length, error: 'Invalid base64 data from debug adapter' }];
755
}
756
757
const unreadable = result.body.unreadableBytes || 0;
758
const dataLength = length - unreadable;
759
if (data.byteLength < dataLength) {
760
const pad = VSBuffer.alloc(dataLength - data.byteLength);
761
pad.buffer.fill(0);
762
data = VSBuffer.concat([data, pad], dataLength);
763
} else if (data.byteLength > dataLength) {
764
data = data.slice(0, dataLength);
765
}
766
767
if (!unreadable) {
768
return [{ type: MemoryRangeType.Valid, offset, length, data }];
769
}
770
771
return [
772
{ type: MemoryRangeType.Valid, offset, length: dataLength, data },
773
{ type: MemoryRangeType.Unreadable, offset: offset + dataLength, length: unreadable },
774
];
775
}
776
777
public async write(offset: number, data: VSBuffer): Promise<number> {
778
const result = await this.session.writeMemory(this.memoryReference, offset, encodeBase64(data), true);
779
const written = result?.body?.bytesWritten ?? data.byteLength;
780
this.invalidate(offset, offset + written);
781
return written;
782
}
783
784
public override dispose() {
785
super.dispose();
786
}
787
788
private invalidate(fromOffset: number, toOffset: number) {
789
this.invalidateEmitter.fire({ fromOffset, toOffset });
790
}
791
}
792
793
export class Enablement implements IEnablement {
794
constructor(
795
public enabled: boolean,
796
private readonly id: string
797
) { }
798
799
getId(): string {
800
return this.id;
801
}
802
}
803
804
interface IBreakpointSessionData extends DebugProtocol.Breakpoint {
805
supportsConditionalBreakpoints: boolean;
806
supportsHitConditionalBreakpoints: boolean;
807
supportsLogPoints: boolean;
808
supportsFunctionBreakpoints: boolean;
809
supportsDataBreakpoints: boolean;
810
supportsInstructionBreakpoints: boolean;
811
sessionId: string;
812
}
813
814
function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: DebugProtocol.Capabilities): IBreakpointSessionData {
815
return mixin({
816
supportsConditionalBreakpoints: !!capabilities.supportsConditionalBreakpoints,
817
supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints,
818
supportsLogPoints: !!capabilities.supportsLogPoints,
819
supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints,
820
supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints,
821
supportsInstructionBreakpoints: !!capabilities.supportsInstructionBreakpoints
822
}, data);
823
}
824
825
export interface IBaseBreakpointOptions {
826
enabled?: boolean;
827
hitCondition?: string;
828
condition?: string;
829
logMessage?: string;
830
mode?: string;
831
modeLabel?: string;
832
}
833
834
export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint {
835
836
private sessionData = new Map<string, IBreakpointSessionData>();
837
protected data: IBreakpointSessionData | undefined;
838
public hitCondition: string | undefined;
839
public condition: string | undefined;
840
public logMessage: string | undefined;
841
public mode: string | undefined;
842
public modeLabel: string | undefined;
843
844
constructor(
845
id: string,
846
opts: IBaseBreakpointOptions
847
) {
848
super(opts.enabled ?? true, id);
849
this.condition = opts.condition;
850
this.hitCondition = opts.hitCondition;
851
this.logMessage = opts.logMessage;
852
this.mode = opts.mode;
853
this.modeLabel = opts.modeLabel;
854
}
855
856
setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {
857
if (!data) {
858
this.sessionData.delete(sessionId);
859
} else {
860
data.sessionId = sessionId;
861
this.sessionData.set(sessionId, data);
862
}
863
864
const allData = Array.from(this.sessionData.values());
865
const verifiedData = distinct(allData.filter(d => d.verified), d => `${d.line}:${d.column}`);
866
if (verifiedData.length) {
867
// In case multiple session verified the breakpoint and they provide different data show the intial data that the user set (corner case)
868
this.data = verifiedData.length === 1 ? verifiedData[0] : undefined;
869
} else {
870
// No session verified the breakpoint
871
this.data = allData.length ? allData[0] : undefined;
872
}
873
}
874
875
get message(): string | undefined {
876
if (!this.data) {
877
return undefined;
878
}
879
880
return this.data.message;
881
}
882
883
get verified(): boolean {
884
return this.data ? this.data.verified : true;
885
}
886
887
get sessionsThatVerified() {
888
const sessionIds: string[] = [];
889
for (const [sessionId, data] of this.sessionData) {
890
if (data.verified) {
891
sessionIds.push(sessionId);
892
}
893
}
894
895
return sessionIds;
896
}
897
898
abstract get supported(): boolean;
899
900
getIdFromAdapter(sessionId: string): number | undefined {
901
const data = this.sessionData.get(sessionId);
902
return data ? data.id : undefined;
903
}
904
905
getDebugProtocolBreakpoint(sessionId: string): DebugProtocol.Breakpoint | undefined {
906
const data = this.sessionData.get(sessionId);
907
if (data) {
908
const bp: DebugProtocol.Breakpoint = {
909
id: data.id,
910
verified: data.verified,
911
message: data.message,
912
source: data.source,
913
line: data.line,
914
column: data.column,
915
endLine: data.endLine,
916
endColumn: data.endColumn,
917
instructionReference: data.instructionReference,
918
offset: data.offset
919
};
920
return bp;
921
}
922
return undefined;
923
}
924
925
toJSON(): IBaseBreakpointOptions & { id: string } {
926
return {
927
id: this.getId(),
928
enabled: this.enabled,
929
condition: this.condition,
930
hitCondition: this.hitCondition,
931
logMessage: this.logMessage,
932
mode: this.mode,
933
modeLabel: this.modeLabel,
934
};
935
}
936
}
937
938
export interface IBreakpointOptions extends IBaseBreakpointOptions {
939
uri: uri;
940
lineNumber: number;
941
column: number | undefined;
942
adapterData: any;
943
triggeredBy: string | undefined;
944
}
945
946
export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
947
private sessionsDidTrigger?: Set<string>;
948
private readonly _uri: uri;
949
private _adapterData: any;
950
private _lineNumber: number;
951
private _column: number | undefined;
952
public triggeredBy: string | undefined;
953
954
constructor(
955
opts: IBreakpointOptions,
956
private readonly textFileService: ITextFileService,
957
private readonly uriIdentityService: IUriIdentityService,
958
private readonly logService: ILogService,
959
id = generateUuid(),
960
) {
961
super(id, opts);
962
this._uri = opts.uri;
963
this._lineNumber = opts.lineNumber;
964
this._column = opts.column;
965
this._adapterData = opts.adapterData;
966
this.triggeredBy = opts.triggeredBy;
967
}
968
969
toDAP(): DebugProtocol.SourceBreakpoint {
970
return {
971
line: this.sessionAgnosticData.lineNumber,
972
column: this.sessionAgnosticData.column,
973
condition: this.condition,
974
hitCondition: this.hitCondition,
975
logMessage: this.logMessage,
976
mode: this.mode
977
};
978
}
979
980
get originalUri() {
981
return this._uri;
982
}
983
984
get lineNumber(): number {
985
return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber;
986
}
987
988
override get verified(): boolean {
989
if (this.data) {
990
return this.data.verified && !this.textFileService.isDirty(this._uri);
991
}
992
993
return true;
994
}
995
996
get pending(): boolean {
997
if (this.data) {
998
return false;
999
}
1000
return this.triggeredBy !== undefined;
1001
}
1002
1003
get uri(): uri {
1004
return this.verified && this.data && this.data.source ? getUriFromSource(this.data.source, this.data.source.path, this.data.sessionId, this.uriIdentityService, this.logService) : this._uri;
1005
}
1006
1007
get column(): number | undefined {
1008
return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column;
1009
}
1010
1011
override get message(): string | undefined {
1012
if (this.textFileService.isDirty(this.uri)) {
1013
return nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session.");
1014
}
1015
1016
return super.message;
1017
}
1018
1019
get adapterData(): any {
1020
return this.data && this.data.source && this.data.source.adapterData ? this.data.source.adapterData : this._adapterData;
1021
}
1022
1023
get endLineNumber(): number | undefined {
1024
return this.verified && this.data ? this.data.endLine : undefined;
1025
}
1026
1027
get endColumn(): number | undefined {
1028
return this.verified && this.data ? this.data.endColumn : undefined;
1029
}
1030
1031
get sessionAgnosticData(): { lineNumber: number; column: number | undefined } {
1032
return {
1033
lineNumber: this._lineNumber,
1034
column: this._column
1035
};
1036
}
1037
1038
get supported(): boolean {
1039
if (!this.data) {
1040
return true;
1041
}
1042
if (this.logMessage && !this.data.supportsLogPoints) {
1043
return false;
1044
}
1045
if (this.condition && !this.data.supportsConditionalBreakpoints) {
1046
return false;
1047
}
1048
if (this.hitCondition && !this.data.supportsHitConditionalBreakpoints) {
1049
return false;
1050
}
1051
1052
return true;
1053
}
1054
1055
override setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {
1056
super.setSessionData(sessionId, data);
1057
if (!this._adapterData) {
1058
this._adapterData = this.adapterData;
1059
}
1060
}
1061
1062
override toJSON(): IBreakpointOptions & { id: string } {
1063
return {
1064
...super.toJSON(),
1065
uri: this._uri,
1066
lineNumber: this._lineNumber,
1067
column: this._column,
1068
adapterData: this.adapterData,
1069
triggeredBy: this.triggeredBy,
1070
};
1071
}
1072
1073
override toString(): string {
1074
return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`;
1075
}
1076
1077
public setSessionDidTrigger(sessionId: string): void {
1078
this.sessionsDidTrigger ??= new Set();
1079
this.sessionsDidTrigger.add(sessionId);
1080
}
1081
1082
public getSessionDidTrigger(sessionId: string): boolean {
1083
return !!this.sessionsDidTrigger?.has(sessionId);
1084
}
1085
1086
update(data: IBreakpointUpdateData): void {
1087
if (data.hasOwnProperty('lineNumber') && !isUndefinedOrNull(data.lineNumber)) {
1088
this._lineNumber = data.lineNumber;
1089
}
1090
if (data.hasOwnProperty('column')) {
1091
this._column = data.column;
1092
}
1093
if (data.hasOwnProperty('condition')) {
1094
this.condition = data.condition;
1095
}
1096
if (data.hasOwnProperty('hitCondition')) {
1097
this.hitCondition = data.hitCondition;
1098
}
1099
if (data.hasOwnProperty('logMessage')) {
1100
this.logMessage = data.logMessage;
1101
}
1102
if (data.hasOwnProperty('mode')) {
1103
this.mode = data.mode;
1104
this.modeLabel = data.modeLabel;
1105
}
1106
if (data.hasOwnProperty('triggeredBy')) {
1107
this.triggeredBy = data.triggeredBy;
1108
this.sessionsDidTrigger = undefined;
1109
}
1110
}
1111
}
1112
1113
export interface IFunctionBreakpointOptions extends IBaseBreakpointOptions {
1114
name: string;
1115
}
1116
1117
export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint {
1118
public name: string;
1119
1120
constructor(
1121
opts: IFunctionBreakpointOptions,
1122
id = generateUuid()
1123
) {
1124
super(id, opts);
1125
this.name = opts.name;
1126
}
1127
1128
toDAP(): DebugProtocol.FunctionBreakpoint {
1129
return {
1130
name: this.name,
1131
condition: this.condition,
1132
hitCondition: this.hitCondition,
1133
};
1134
}
1135
1136
override toJSON(): IFunctionBreakpointOptions & { id: string } {
1137
return {
1138
...super.toJSON(),
1139
name: this.name,
1140
};
1141
}
1142
1143
get supported(): boolean {
1144
if (!this.data) {
1145
return true;
1146
}
1147
1148
return this.data.supportsFunctionBreakpoints;
1149
}
1150
1151
override toString(): string {
1152
return this.name;
1153
}
1154
}
1155
1156
export interface IDataBreakpointOptions extends IBaseBreakpointOptions {
1157
description: string;
1158
src: DataBreakpointSource;
1159
canPersist: boolean;
1160
initialSessionData?: { session: IDebugSession; dataId: string };
1161
accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;
1162
accessType: DebugProtocol.DataBreakpointAccessType;
1163
}
1164
1165
export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {
1166
private readonly sessionDataIdForAddr = new WeakMap<IDebugSession, string | null>();
1167
1168
public readonly description: string;
1169
public readonly src: DataBreakpointSource;
1170
public readonly canPersist: boolean;
1171
public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;
1172
public readonly accessType: DebugProtocol.DataBreakpointAccessType;
1173
1174
constructor(
1175
opts: IDataBreakpointOptions,
1176
id = generateUuid()
1177
) {
1178
super(id, opts);
1179
this.description = opts.description;
1180
if ('dataId' in opts) { // back compat with old saved variables in 1.87
1181
opts.src = { type: DataBreakpointSetType.Variable, dataId: opts.dataId as string };
1182
}
1183
this.src = opts.src;
1184
this.canPersist = opts.canPersist;
1185
this.accessTypes = opts.accessTypes;
1186
this.accessType = opts.accessType;
1187
if (opts.initialSessionData) {
1188
this.sessionDataIdForAddr.set(opts.initialSessionData.session, opts.initialSessionData.dataId);
1189
}
1190
}
1191
1192
async toDAP(session: IDebugSession): Promise<DebugProtocol.DataBreakpoint | undefined> {
1193
let dataId: string;
1194
if (this.src.type === DataBreakpointSetType.Variable) {
1195
dataId = this.src.dataId;
1196
} else {
1197
let sessionDataId = this.sessionDataIdForAddr.get(session);
1198
if (!sessionDataId) {
1199
sessionDataId = (await session.dataBytesBreakpointInfo(this.src.address, this.src.bytes))?.dataId;
1200
if (!sessionDataId) {
1201
return undefined;
1202
}
1203
this.sessionDataIdForAddr.set(session, sessionDataId);
1204
}
1205
dataId = sessionDataId;
1206
}
1207
1208
return {
1209
dataId,
1210
accessType: this.accessType,
1211
condition: this.condition,
1212
hitCondition: this.hitCondition,
1213
};
1214
}
1215
1216
override toJSON(): IDataBreakpointOptions & { id: string } {
1217
return {
1218
...super.toJSON(),
1219
description: this.description,
1220
src: this.src,
1221
accessTypes: this.accessTypes,
1222
accessType: this.accessType,
1223
canPersist: this.canPersist,
1224
};
1225
}
1226
1227
get supported(): boolean {
1228
if (!this.data) {
1229
return true;
1230
}
1231
1232
return this.data.supportsDataBreakpoints;
1233
}
1234
1235
override toString(): string {
1236
return this.description;
1237
}
1238
}
1239
1240
export interface IExceptionBreakpointOptions extends IBaseBreakpointOptions {
1241
filter: string;
1242
label: string;
1243
supportsCondition: boolean;
1244
description: string | undefined;
1245
conditionDescription: string | undefined;
1246
fallback?: boolean;
1247
}
1248
1249
export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint {
1250
1251
private supportedSessions: Set<string> = new Set();
1252
1253
public readonly filter: string;
1254
public readonly label: string;
1255
public readonly supportsCondition: boolean;
1256
public readonly description: string | undefined;
1257
public readonly conditionDescription: string | undefined;
1258
private fallback: boolean = false;
1259
1260
constructor(
1261
opts: IExceptionBreakpointOptions,
1262
id = generateUuid(),
1263
) {
1264
super(id, opts);
1265
this.filter = opts.filter;
1266
this.label = opts.label;
1267
this.supportsCondition = opts.supportsCondition;
1268
this.description = opts.description;
1269
this.conditionDescription = opts.conditionDescription;
1270
this.fallback = opts.fallback || false;
1271
}
1272
1273
override toJSON(): IExceptionBreakpointOptions & { id: string } {
1274
return {
1275
...super.toJSON(),
1276
filter: this.filter,
1277
label: this.label,
1278
enabled: this.enabled,
1279
supportsCondition: this.supportsCondition,
1280
conditionDescription: this.conditionDescription,
1281
condition: this.condition,
1282
fallback: this.fallback,
1283
description: this.description,
1284
};
1285
}
1286
1287
setSupportedSession(sessionId: string, supported: boolean): void {
1288
if (supported) {
1289
this.supportedSessions.add(sessionId);
1290
}
1291
else {
1292
this.supportedSessions.delete(sessionId);
1293
}
1294
}
1295
1296
/**
1297
* Used to specify which breakpoints to show when no session is specified.
1298
* Useful when no session is active and we want to show the exception breakpoints from the last session.
1299
*/
1300
setFallback(isFallback: boolean) {
1301
this.fallback = isFallback;
1302
}
1303
1304
get supported(): boolean {
1305
return true;
1306
}
1307
1308
/**
1309
* Checks if the breakpoint is applicable for the specified session.
1310
* If sessionId is undefined, returns true if this breakpoint is a fallback breakpoint.
1311
*/
1312
isSupportedSession(sessionId?: string): boolean {
1313
return sessionId ? this.supportedSessions.has(sessionId) : this.fallback;
1314
}
1315
1316
matches(filter: DebugProtocol.ExceptionBreakpointsFilter) {
1317
return this.filter === filter.filter
1318
&& this.label === filter.label
1319
&& this.supportsCondition === !!filter.supportsCondition
1320
&& this.conditionDescription === filter.conditionDescription
1321
&& this.description === filter.description;
1322
}
1323
1324
override toString(): string {
1325
return this.label;
1326
}
1327
}
1328
1329
export interface IInstructionBreakpointOptions extends IBaseBreakpointOptions {
1330
instructionReference: string;
1331
offset: number;
1332
canPersist: boolean;
1333
address: bigint;
1334
}
1335
1336
export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint {
1337
public readonly instructionReference: string;
1338
public readonly offset: number;
1339
public readonly canPersist: boolean;
1340
public readonly address: bigint;
1341
1342
constructor(
1343
opts: IInstructionBreakpointOptions,
1344
id = generateUuid()
1345
) {
1346
super(id, opts);
1347
this.instructionReference = opts.instructionReference;
1348
this.offset = opts.offset;
1349
this.canPersist = opts.canPersist;
1350
this.address = opts.address;
1351
}
1352
1353
toDAP(): DebugProtocol.InstructionBreakpoint {
1354
return {
1355
instructionReference: this.instructionReference,
1356
condition: this.condition,
1357
hitCondition: this.hitCondition,
1358
mode: this.mode,
1359
offset: this.offset,
1360
};
1361
}
1362
1363
override toJSON(): IInstructionBreakpointOptions & { id: string } {
1364
return {
1365
...super.toJSON(),
1366
instructionReference: this.instructionReference,
1367
offset: this.offset,
1368
canPersist: this.canPersist,
1369
address: this.address,
1370
};
1371
}
1372
1373
get supported(): boolean {
1374
if (!this.data) {
1375
return true;
1376
}
1377
1378
return this.data.supportsInstructionBreakpoints;
1379
}
1380
1381
override toString(): string {
1382
return this.instructionReference;
1383
}
1384
}
1385
1386
export class ThreadAndSessionIds implements ITreeElement {
1387
constructor(public sessionId: string, public threadId: number) { }
1388
1389
getId(): string {
1390
return `${this.sessionId}:${this.threadId}`;
1391
}
1392
}
1393
1394
interface IBreakpointModeInternal extends DebugProtocol.BreakpointMode {
1395
firstFromDebugType: string;
1396
}
1397
1398
export class DebugModel extends Disposable implements IDebugModel {
1399
1400
private sessions: IDebugSession[];
1401
private schedulers = new Map<string, { scheduler: RunOnceScheduler; completeDeferred: DeferredPromise<void> }>();
1402
private breakpointsActivated = true;
1403
private readonly _onDidChangeBreakpoints = this._register(new Emitter<IBreakpointsChangeEvent | undefined>());
1404
private readonly _onDidChangeCallStack = this._register(new Emitter<void>());
1405
private readonly _onDidChangeWatchExpressions = this._register(new Emitter<IExpression | undefined>());
1406
private readonly _breakpointModes = new Map<string, IBreakpointModeInternal>();
1407
private breakpoints!: Breakpoint[];
1408
private functionBreakpoints!: FunctionBreakpoint[];
1409
private exceptionBreakpoints!: ExceptionBreakpoint[];
1410
private dataBreakpoints!: DataBreakpoint[];
1411
private watchExpressions!: Expression[];
1412
private instructionBreakpoints: InstructionBreakpoint[];
1413
1414
constructor(
1415
debugStorage: DebugStorage,
1416
@ITextFileService private readonly textFileService: ITextFileService,
1417
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
1418
@ILogService private readonly logService: ILogService
1419
) {
1420
super();
1421
1422
this._register(autorun(reader => {
1423
this.breakpoints = debugStorage.breakpoints.read(reader);
1424
this.functionBreakpoints = debugStorage.functionBreakpoints.read(reader);
1425
this.exceptionBreakpoints = debugStorage.exceptionBreakpoints.read(reader);
1426
this.dataBreakpoints = debugStorage.dataBreakpoints.read(reader);
1427
this._onDidChangeBreakpoints.fire(undefined);
1428
}));
1429
1430
this._register(autorun(reader => {
1431
this.watchExpressions = debugStorage.watchExpressions.read(reader);
1432
this._onDidChangeWatchExpressions.fire(undefined);
1433
}));
1434
1435
this.instructionBreakpoints = [];
1436
this.sessions = [];
1437
}
1438
1439
getId(): string {
1440
return 'root';
1441
}
1442
1443
getSession(sessionId: string | undefined, includeInactive = false): IDebugSession | undefined {
1444
if (sessionId) {
1445
return this.getSessions(includeInactive).find(s => s.getId() === sessionId);
1446
}
1447
return undefined;
1448
}
1449
1450
getSessions(includeInactive = false): IDebugSession[] {
1451
// By default do not return inactive sessions.
1452
// However we are still holding onto inactive sessions due to repl and debug service session revival (eh scenario)
1453
return this.sessions.filter(s => includeInactive || s.state !== State.Inactive);
1454
}
1455
1456
addSession(session: IDebugSession): void {
1457
this.sessions = this.sessions.filter(s => {
1458
if (s.getId() === session.getId()) {
1459
// Make sure to de-dupe if a session is re-initialized. In case of EH debugging we are adding a session again after an attach.
1460
return false;
1461
}
1462
if (s.state === State.Inactive && s.configuration.name === session.configuration.name) {
1463
// Make sure to remove all inactive sessions that are using the same configuration as the new session
1464
return false;
1465
}
1466
1467
return true;
1468
});
1469
1470
let i = 1;
1471
while (this.sessions.some(s => s.getLabel() === session.getLabel())) {
1472
session.setName(`${session.configuration.name} ${++i}`);
1473
}
1474
1475
let index = -1;
1476
if (session.parentSession) {
1477
// Make sure that child sessions are placed after the parent session
1478
index = findLastIdx(this.sessions, s => s.parentSession === session.parentSession || s === session.parentSession);
1479
}
1480
if (index >= 0) {
1481
this.sessions.splice(index + 1, 0, session);
1482
} else {
1483
this.sessions.push(session);
1484
}
1485
this._onDidChangeCallStack.fire(undefined);
1486
}
1487
1488
get onDidChangeBreakpoints(): Event<IBreakpointsChangeEvent | undefined> {
1489
return this._onDidChangeBreakpoints.event;
1490
}
1491
1492
get onDidChangeCallStack(): Event<void> {
1493
return this._onDidChangeCallStack.event;
1494
}
1495
1496
get onDidChangeWatchExpressions(): Event<IExpression | undefined> {
1497
return this._onDidChangeWatchExpressions.event;
1498
}
1499
1500
rawUpdate(data: IRawModelUpdate): void {
1501
const session = this.sessions.find(p => p.getId() === data.sessionId);
1502
if (session) {
1503
session.rawUpdate(data);
1504
this._onDidChangeCallStack.fire(undefined);
1505
}
1506
}
1507
1508
clearThreads(id: string, removeThreads: boolean, reference: number | undefined = undefined): void {
1509
const session = this.sessions.find(p => p.getId() === id);
1510
this.schedulers.forEach(entry => {
1511
entry.scheduler.dispose();
1512
entry.completeDeferred.complete();
1513
});
1514
this.schedulers.clear();
1515
1516
if (session) {
1517
session.clearThreads(removeThreads, reference);
1518
this._onDidChangeCallStack.fire(undefined);
1519
}
1520
}
1521
1522
/**
1523
* Update the call stack and notify the call stack view that changes have occurred.
1524
*/
1525
async fetchCallstack(thread: IThread, levels?: number): Promise<void> {
1526
1527
if ((<Thread>thread).reachedEndOfCallStack) {
1528
return;
1529
}
1530
1531
const totalFrames = thread.stoppedDetails?.totalFrames;
1532
const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined;
1533
1534
if (!levels || (remainingFrames && levels > remainingFrames)) {
1535
levels = remainingFrames;
1536
}
1537
1538
if (levels && levels > 0) {
1539
await (<Thread>thread).fetchCallStack(levels);
1540
this._onDidChangeCallStack.fire();
1541
}
1542
1543
return;
1544
}
1545
1546
refreshTopOfCallstack(thread: Thread, fetchFullStack = true): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {
1547
if (thread.session.capabilities.supportsDelayedStackTraceLoading) {
1548
// For improved performance load the first stack frame and then load the rest async.
1549
let topCallStack = Promise.resolve();
1550
const wholeCallStack = new Promise<void>((c, e) => {
1551
topCallStack = thread.fetchCallStack(1).then(() => {
1552
if (!fetchFullStack) {
1553
c();
1554
this._onDidChangeCallStack.fire();
1555
return;
1556
}
1557
1558
if (!this.schedulers.has(thread.getId())) {
1559
const deferred = new DeferredPromise<void>();
1560
this.schedulers.set(thread.getId(), {
1561
completeDeferred: deferred,
1562
scheduler: new RunOnceScheduler(() => {
1563
thread.fetchCallStack(19).then(() => {
1564
const stale = thread.getStaleCallStack();
1565
const current = thread.getCallStack();
1566
let bottomOfCallStackChanged = stale.length !== current.length;
1567
for (let i = 1; i < stale.length && !bottomOfCallStackChanged; i++) {
1568
bottomOfCallStackChanged = !stale[i].equals(current[i]);
1569
}
1570
1571
if (bottomOfCallStackChanged) {
1572
this._onDidChangeCallStack.fire();
1573
}
1574
}).finally(() => {
1575
deferred.complete();
1576
this.schedulers.delete(thread.getId());
1577
});
1578
}, 420)
1579
});
1580
}
1581
1582
const entry = this.schedulers.get(thread.getId())!;
1583
entry.scheduler.schedule();
1584
entry.completeDeferred.p.then(c, e);
1585
this._onDidChangeCallStack.fire();
1586
});
1587
});
1588
1589
return { topCallStack, wholeCallStack };
1590
}
1591
1592
const wholeCallStack = thread.fetchCallStack();
1593
return { wholeCallStack, topCallStack: wholeCallStack };
1594
}
1595
1596
getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; triggeredOnly?: boolean }): IBreakpoint[] {
1597
if (filter) {
1598
const uriStr = filter.uri?.toString();
1599
const originalUriStr = filter.originalUri?.toString();
1600
return this.breakpoints.filter(bp => {
1601
if (uriStr && bp.uri.toString() !== uriStr) {
1602
return false;
1603
}
1604
if (originalUriStr && bp.originalUri.toString() !== originalUriStr) {
1605
return false;
1606
}
1607
if (filter.lineNumber && bp.lineNumber !== filter.lineNumber) {
1608
return false;
1609
}
1610
if (filter.column && bp.column !== filter.column) {
1611
return false;
1612
}
1613
if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) {
1614
return false;
1615
}
1616
if (filter.triggeredOnly && bp.triggeredBy === undefined) {
1617
return false;
1618
}
1619
1620
return true;
1621
});
1622
}
1623
1624
return this.breakpoints;
1625
}
1626
1627
getFunctionBreakpoints(): IFunctionBreakpoint[] {
1628
return this.functionBreakpoints;
1629
}
1630
1631
getDataBreakpoints(): IDataBreakpoint[] {
1632
return this.dataBreakpoints;
1633
}
1634
1635
getExceptionBreakpoints(): IExceptionBreakpoint[] {
1636
return this.exceptionBreakpoints;
1637
}
1638
1639
getExceptionBreakpointsForSession(sessionId?: string): IExceptionBreakpoint[] {
1640
return this.exceptionBreakpoints.filter(ebp => ebp.isSupportedSession(sessionId));
1641
}
1642
1643
getInstructionBreakpoints(): IInstructionBreakpoint[] {
1644
return this.instructionBreakpoints;
1645
}
1646
1647
setExceptionBreakpointsForSession(sessionId: string, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void {
1648
if (!filters) {
1649
return;
1650
}
1651
1652
let didChangeBreakpoints = false;
1653
filters.forEach((d) => {
1654
let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop();
1655
1656
if (!ebp) {
1657
didChangeBreakpoints = true;
1658
ebp = new ExceptionBreakpoint({
1659
filter: d.filter,
1660
label: d.label,
1661
enabled: !!d.default,
1662
supportsCondition: !!d.supportsCondition,
1663
description: d.description,
1664
conditionDescription: d.conditionDescription,
1665
});
1666
this.exceptionBreakpoints.push(ebp);
1667
}
1668
1669
ebp.setSupportedSession(sessionId, true);
1670
});
1671
1672
if (didChangeBreakpoints) {
1673
this._onDidChangeBreakpoints.fire(undefined);
1674
}
1675
}
1676
1677
removeExceptionBreakpointsForSession(sessionId: string): void {
1678
this.exceptionBreakpoints.forEach(ebp => ebp.setSupportedSession(sessionId, false));
1679
}
1680
1681
// Set last focused session as fallback session.
1682
// This is done to keep track of the exception breakpoints to show when no session is active.
1683
setExceptionBreakpointFallbackSession(sessionId: string): void {
1684
this.exceptionBreakpoints.forEach(ebp => ebp.setFallback(ebp.isSupportedSession(sessionId)));
1685
}
1686
1687
setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): void {
1688
(exceptionBreakpoint as ExceptionBreakpoint).condition = condition;
1689
this._onDidChangeBreakpoints.fire(undefined);
1690
}
1691
1692
areBreakpointsActivated(): boolean {
1693
return this.breakpointsActivated;
1694
}
1695
1696
setBreakpointsActivated(activated: boolean): void {
1697
this.breakpointsActivated = activated;
1698
this._onDidChangeBreakpoints.fire(undefined);
1699
}
1700
1701
addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] {
1702
const newBreakpoints = rawData.map(rawBp => {
1703
return new Breakpoint({
1704
uri,
1705
lineNumber: rawBp.lineNumber,
1706
column: rawBp.column,
1707
enabled: rawBp.enabled ?? true,
1708
condition: rawBp.condition,
1709
hitCondition: rawBp.hitCondition,
1710
logMessage: rawBp.logMessage,
1711
triggeredBy: rawBp.triggeredBy,
1712
adapterData: undefined,
1713
mode: rawBp.mode,
1714
modeLabel: rawBp.modeLabel,
1715
}, this.textFileService, this.uriIdentityService, this.logService, rawBp.id);
1716
});
1717
this.breakpoints = this.breakpoints.concat(newBreakpoints);
1718
this.breakpointsActivated = true;
1719
this.sortAndDeDup();
1720
1721
if (fireEvent) {
1722
this._onDidChangeBreakpoints.fire({ added: newBreakpoints, sessionOnly: false });
1723
}
1724
1725
return newBreakpoints;
1726
}
1727
1728
removeBreakpoints(toRemove: IBreakpoint[]): void {
1729
this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
1730
this._onDidChangeBreakpoints.fire({ removed: toRemove, sessionOnly: false });
1731
}
1732
1733
updateBreakpoints(data: Map<string, IBreakpointUpdateData>): void {
1734
const updated: IBreakpoint[] = [];
1735
this.breakpoints.forEach(bp => {
1736
const bpData = data.get(bp.getId());
1737
if (bpData) {
1738
bp.update(bpData);
1739
updated.push(bp);
1740
}
1741
});
1742
this.sortAndDeDup();
1743
this._onDidChangeBreakpoints.fire({ changed: updated, sessionOnly: false });
1744
}
1745
1746
setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map<string, DebugProtocol.Breakpoint> | undefined): void {
1747
this.breakpoints.forEach(bp => {
1748
if (!data) {
1749
bp.setSessionData(sessionId, undefined);
1750
} else {
1751
const bpData = data.get(bp.getId());
1752
if (bpData) {
1753
bp.setSessionData(sessionId, toBreakpointSessionData(bpData, capabilites));
1754
}
1755
}
1756
});
1757
this.functionBreakpoints.forEach(fbp => {
1758
if (!data) {
1759
fbp.setSessionData(sessionId, undefined);
1760
} else {
1761
const fbpData = data.get(fbp.getId());
1762
if (fbpData) {
1763
fbp.setSessionData(sessionId, toBreakpointSessionData(fbpData, capabilites));
1764
}
1765
}
1766
});
1767
this.dataBreakpoints.forEach(dbp => {
1768
if (!data) {
1769
dbp.setSessionData(sessionId, undefined);
1770
} else {
1771
const dbpData = data.get(dbp.getId());
1772
if (dbpData) {
1773
dbp.setSessionData(sessionId, toBreakpointSessionData(dbpData, capabilites));
1774
}
1775
}
1776
});
1777
this.exceptionBreakpoints.forEach(ebp => {
1778
if (!data) {
1779
ebp.setSessionData(sessionId, undefined);
1780
} else {
1781
const ebpData = data.get(ebp.getId());
1782
if (ebpData) {
1783
ebp.setSessionData(sessionId, toBreakpointSessionData(ebpData, capabilites));
1784
}
1785
}
1786
});
1787
this.instructionBreakpoints.forEach(ibp => {
1788
if (!data) {
1789
ibp.setSessionData(sessionId, undefined);
1790
} else {
1791
const ibpData = data.get(ibp.getId());
1792
if (ibpData) {
1793
ibp.setSessionData(sessionId, toBreakpointSessionData(ibpData, capabilites));
1794
}
1795
}
1796
});
1797
1798
this._onDidChangeBreakpoints.fire({
1799
sessionOnly: true
1800
});
1801
}
1802
1803
getDebugProtocolBreakpoint(breakpointId: string, sessionId: string): DebugProtocol.Breakpoint | undefined {
1804
const bp = this.breakpoints.find(bp => bp.getId() === breakpointId);
1805
if (bp) {
1806
return bp.getDebugProtocolBreakpoint(sessionId);
1807
}
1808
return undefined;
1809
}
1810
1811
getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[] {
1812
return [...this._breakpointModes.values()].filter(mode => mode.appliesTo.includes(forBreakpointType));
1813
}
1814
1815
registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]) {
1816
for (const mode of modes) {
1817
const key = `${mode.mode}/${mode.label}`;
1818
const rec = this._breakpointModes.get(key);
1819
if (rec) {
1820
for (const target of mode.appliesTo) {
1821
if (!rec.appliesTo.includes(target)) {
1822
rec.appliesTo.push(target);
1823
}
1824
}
1825
} else {
1826
const duplicate = [...this._breakpointModes.values()].find(r => r !== rec && r.label === mode.label);
1827
if (duplicate) {
1828
duplicate.label = `${duplicate.label} (${duplicate.firstFromDebugType})`;
1829
}
1830
1831
this._breakpointModes.set(key, {
1832
mode: mode.mode,
1833
label: duplicate ? `${mode.label} (${debugType})` : mode.label,
1834
firstFromDebugType: debugType,
1835
description: mode.description,
1836
appliesTo: mode.appliesTo.slice(), // avoid later mutations
1837
});
1838
}
1839
}
1840
}
1841
1842
private sortAndDeDup(): void {
1843
this.breakpoints = this.breakpoints.sort((first, second) => {
1844
if (first.uri.toString() !== second.uri.toString()) {
1845
return resources.basenameOrAuthority(first.uri).localeCompare(resources.basenameOrAuthority(second.uri));
1846
}
1847
if (first.lineNumber === second.lineNumber) {
1848
if (first.column && second.column) {
1849
return first.column - second.column;
1850
}
1851
return 1;
1852
}
1853
1854
return first.lineNumber - second.lineNumber;
1855
});
1856
this.breakpoints = distinct(this.breakpoints, bp => `${bp.uri.toString()}:${bp.lineNumber}:${bp.column}`);
1857
}
1858
1859
setEnablement(element: IEnablement, enable: boolean): void {
1860
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof ExceptionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint) {
1861
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];
1862
if (element.enabled !== enable && (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint)) {
1863
changed.push(element);
1864
}
1865
1866
element.enabled = enable;
1867
if (enable) {
1868
this.breakpointsActivated = true;
1869
}
1870
1871
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
1872
}
1873
}
1874
1875
enableOrDisableAllBreakpoints(enable: boolean): void {
1876
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];
1877
1878
this.breakpoints.forEach(bp => {
1879
if (bp.enabled !== enable) {
1880
changed.push(bp);
1881
}
1882
bp.enabled = enable;
1883
});
1884
this.functionBreakpoints.forEach(fbp => {
1885
if (fbp.enabled !== enable) {
1886
changed.push(fbp);
1887
}
1888
fbp.enabled = enable;
1889
});
1890
this.dataBreakpoints.forEach(dbp => {
1891
if (dbp.enabled !== enable) {
1892
changed.push(dbp);
1893
}
1894
dbp.enabled = enable;
1895
});
1896
this.instructionBreakpoints.forEach(ibp => {
1897
if (ibp.enabled !== enable) {
1898
changed.push(ibp);
1899
}
1900
ibp.enabled = enable;
1901
});
1902
1903
if (enable) {
1904
this.breakpointsActivated = true;
1905
}
1906
1907
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
1908
}
1909
1910
addFunctionBreakpoint(opts: IFunctionBreakpointOptions, id?: string): IFunctionBreakpoint {
1911
const newFunctionBreakpoint = new FunctionBreakpoint(opts, id);
1912
this.functionBreakpoints.push(newFunctionBreakpoint);
1913
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false });
1914
1915
return newFunctionBreakpoint;
1916
}
1917
1918
updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): void {
1919
const functionBreakpoint = this.functionBreakpoints.find(fbp => fbp.getId() === id);
1920
if (functionBreakpoint) {
1921
if (typeof update.name === 'string') {
1922
functionBreakpoint.name = update.name;
1923
}
1924
if (typeof update.condition === 'string') {
1925
functionBreakpoint.condition = update.condition;
1926
}
1927
if (typeof update.hitCondition === 'string') {
1928
functionBreakpoint.hitCondition = update.hitCondition;
1929
}
1930
this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false });
1931
}
1932
}
1933
1934
removeFunctionBreakpoints(id?: string): void {
1935
let removed: FunctionBreakpoint[];
1936
if (id) {
1937
removed = this.functionBreakpoints.filter(fbp => fbp.getId() === id);
1938
this.functionBreakpoints = this.functionBreakpoints.filter(fbp => fbp.getId() !== id);
1939
} else {
1940
removed = this.functionBreakpoints;
1941
this.functionBreakpoints = [];
1942
}
1943
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
1944
}
1945
1946
addDataBreakpoint(opts: IDataBreakpointOptions, id?: string): void {
1947
const newDataBreakpoint = new DataBreakpoint(opts, id);
1948
this.dataBreakpoints.push(newDataBreakpoint);
1949
this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false });
1950
}
1951
1952
updateDataBreakpoint(id: string, update: { hitCondition?: string; condition?: string }): void {
1953
const dataBreakpoint = this.dataBreakpoints.find(fbp => fbp.getId() === id);
1954
if (dataBreakpoint) {
1955
if (typeof update.condition === 'string') {
1956
dataBreakpoint.condition = update.condition;
1957
}
1958
if (typeof update.hitCondition === 'string') {
1959
dataBreakpoint.hitCondition = update.hitCondition;
1960
}
1961
this._onDidChangeBreakpoints.fire({ changed: [dataBreakpoint], sessionOnly: false });
1962
}
1963
}
1964
1965
removeDataBreakpoints(id?: string): void {
1966
let removed: DataBreakpoint[];
1967
if (id) {
1968
removed = this.dataBreakpoints.filter(fbp => fbp.getId() === id);
1969
this.dataBreakpoints = this.dataBreakpoints.filter(fbp => fbp.getId() !== id);
1970
} else {
1971
removed = this.dataBreakpoints;
1972
this.dataBreakpoints = [];
1973
}
1974
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
1975
}
1976
1977
addInstructionBreakpoint(opts: IInstructionBreakpointOptions): void {
1978
const newInstructionBreakpoint = new InstructionBreakpoint(opts);
1979
this.instructionBreakpoints.push(newInstructionBreakpoint);
1980
this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true });
1981
}
1982
1983
removeInstructionBreakpoints(instructionReference?: string, offset?: number): void {
1984
let removed: InstructionBreakpoint[] = [];
1985
if (instructionReference) {
1986
for (let i = 0; i < this.instructionBreakpoints.length; i++) {
1987
const ibp = this.instructionBreakpoints[i];
1988
if (ibp.instructionReference === instructionReference && (offset === undefined || ibp.offset === offset)) {
1989
removed.push(ibp);
1990
this.instructionBreakpoints.splice(i--, 1);
1991
}
1992
}
1993
} else {
1994
removed = this.instructionBreakpoints;
1995
this.instructionBreakpoints = [];
1996
}
1997
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
1998
}
1999
2000
getWatchExpressions(): Expression[] {
2001
return this.watchExpressions;
2002
}
2003
2004
addWatchExpression(name?: string): IExpression {
2005
const we = new Expression(name || '');
2006
this.watchExpressions.push(we);
2007
this._onDidChangeWatchExpressions.fire(we);
2008
2009
return we;
2010
}
2011
2012
renameWatchExpression(id: string, newName: string): void {
2013
const filtered = this.watchExpressions.filter(we => we.getId() === id);
2014
if (filtered.length === 1) {
2015
filtered[0].name = newName;
2016
this._onDidChangeWatchExpressions.fire(filtered[0]);
2017
}
2018
}
2019
2020
removeWatchExpressions(id: string | null = null): void {
2021
this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
2022
this._onDidChangeWatchExpressions.fire(undefined);
2023
}
2024
2025
moveWatchExpression(id: string, position: number): void {
2026
const we = this.watchExpressions.find(we => we.getId() === id);
2027
if (we) {
2028
this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id);
2029
this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position));
2030
this._onDidChangeWatchExpressions.fire(undefined);
2031
}
2032
}
2033
2034
sourceIsNotAvailable(uri: uri): void {
2035
this.sessions.forEach(s => {
2036
const source = s.getSourceForUri(uri);
2037
if (source) {
2038
source.available = false;
2039
}
2040
});
2041
this._onDidChangeCallStack.fire(undefined);
2042
}
2043
}
2044
2045