src/vs/workbench/contrib/debug/common/debugModel.ts

2158 LOC · 810 covered · 1348 uncovered · 210 ranges · 15 concepts · 11 introducers · 9 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/base/common/event.ts · 1964 LOCcommon/event.tssrc/vs/base/test/common/mock.ts · 47 LOCcommon/mock.tssrc/vs/workbench/contrib/debug/common/debugSource.ts · 165 LOCcommon/debugSource.tssrc/vs/workbench/contrib/debug/common/debugStorage.ts · 189 LOCcommon/debugStorage.tssrc/vs/workbench/contrib/debug/common/disassemblyViewInput.ts · 45 LOCcommon/disassemblyViewIn…src/vs/workbench/contrib/debug/test/common/mockDebug.ts · 697 LOCcommon/mockDebug.tsdebugModel.ts ×5 · 39 introduced LOCdebugModel.ts ×5debugModel.ts ×1 · 13 introduced LOCdebugModel.ts ×1debugModel.ts ×1 · 8 introduced LOCdebugModel.ts ×1debugModel.ts ×6 · 20 introduced LOCdebugModel.ts ×6debugStorage.ts ×17 · 98 introduced LOCdebugStorage.ts ×17debugModel.ts ×3 · 29 introduced LOCdebugModel.ts ×3debugModel.ts ×2 · 11 introduced LOCdebugModel.ts ×2debugModel.ts ×2 · 12 introduced LOCdebugModel.ts ×2debugModel.ts ×2 · 13 introduced LOCdebugModel.ts ×2mockDebug.ts ×1 · 2 introduced LOCmockDebug.ts ×1mockDebug.ts ×1 · 2 introduced LOCmockDebug.ts ×1mockDebug.ts ×7 · 30 introduced LOCmockDebug.ts ×7abstractDebugAdapter.ts ×4 · 18 introduced LOCabstractDebugAdapter.ts …debugModel.ts ×1 · 1 introduced LOCdebugModel.ts ×1debugModel.ts ×185 · 988 introduced LOCdebugModel.ts ×185abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering does not insert boundaries between events|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering does not insert boundaries between events|occurrence=1abstractDebugAdapter.tes…abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary after response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary after response|occurrence=1abstractDebugAdapter.tes…abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary before response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test|title=Debug - AbstractDebugAdapter event ordering inserts task boundary before response|occurrence=1abstractDebugAdapter.tes…debugModel.test|title=DebugModel DebugModel refreshTopOfCallstack resolves all returned promises when called multiple times|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel DebugModel refreshTopOfCallstack resolves all returned promises when called multiple times|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel ExceptionBreakpoint Restored matches new|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel ExceptionBreakpoint Restored matches new|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel FunctionBreakpoint Id is saved|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel FunctionBreakpoint Id is saved|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints falls back to instructionReference+offset when address not supplied|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints falls back to instructionReference+offset when address not supplied|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints prefers address match when instructionReference has changed|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints prefers address match when instructionReference has changed|occurrence=1debugModel.test|title=De…debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints with only address removes the matching entry and leaves others|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/common/debugModel.test|title=DebugModel InstructionBreakpoint removeInstructionBreakpoints with only address removes the matching entry and leaves others|occurrence=1debugModel.test|title=De…Focused file · src/vs/workbench/contrib/debug/common/debugModel.ts · 2158 LOCcommon/debugModel.ts

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

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