Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/debugSession.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { getActiveWindow } from '../../../../base/browser/dom.js';
7
import * as aria from '../../../../base/browser/ui/aria/aria.js';
8
import { mainWindow } from '../../../../base/browser/window.js';
9
import { distinct } from '../../../../base/common/arrays.js';
10
import { Queue, RunOnceScheduler, raceTimeout } from '../../../../base/common/async.js';
11
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
12
import { canceled } from '../../../../base/common/errors.js';
13
import { Emitter, Event } from '../../../../base/common/event.js';
14
import { normalizeDriveLetter } from '../../../../base/common/labels.js';
15
import { Disposable, DisposableMap, DisposableStore, MutableDisposable, dispose } from '../../../../base/common/lifecycle.js';
16
import { mixin } from '../../../../base/common/objects.js';
17
import * as platform from '../../../../base/common/platform.js';
18
import * as resources from '../../../../base/common/resources.js';
19
import Severity from '../../../../base/common/severity.js';
20
import { isDefined } from '../../../../base/common/types.js';
21
import { URI } from '../../../../base/common/uri.js';
22
import { generateUuid } from '../../../../base/common/uuid.js';
23
import { IPosition, Position } from '../../../../editor/common/core/position.js';
24
import { localize } from '../../../../nls.js';
25
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
26
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
27
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
28
import { ILogService } from '../../../../platform/log/common/log.js';
29
import { FocusMode } from '../../../../platform/native/common/native.js';
30
import { INotificationService } from '../../../../platform/notification/common/notification.js';
31
import { IProductService } from '../../../../platform/product/common/productService.js';
32
import { ICustomEndpointTelemetryService, ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';
33
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
34
import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';
35
import { ViewContainerLocation } from '../../../common/views.js';
36
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
37
import { IHostService } from '../../../services/host/browser/host.js';
38
import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';
39
import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';
40
import { LiveTestResult } from '../../testing/common/testResult.js';
41
import { ITestResultService } from '../../testing/common/testResultService.js';
42
import { ITestService } from '../../testing/common/testService.js';
43
import { AdapterEndEvent, IBreakpoint, IConfig, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugConfiguration, IDebugLocationReferenced, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, LoadedSourceEvent, State, VIEWLET_ID, isFrameDeemphasized } from '../common/debug.js';
44
import { DebugCompoundRoot } from '../common/debugCompoundRoot.js';
45
import { DebugModel, ExpressionContainer, MemoryRegion, Thread } from '../common/debugModel.js';
46
import { Source } from '../common/debugSource.js';
47
import { filterExceptionsFromTelemetry } from '../common/debugUtils.js';
48
import { INewReplElementData, ReplModel } from '../common/replModel.js';
49
import { RawDebugSession } from './rawDebugSession.js';
50
51
const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500;
52
53
export class DebugSession implements IDebugSession {
54
parentSession: IDebugSession | undefined;
55
rememberedCapabilities?: DebugProtocol.Capabilities;
56
57
private _subId: string | undefined;
58
raw: RawDebugSession | undefined; // used in tests
59
private initialized = false;
60
private _options: IDebugSessionOptions;
61
62
private sources = new Map<string, Source>();
63
private threads = new Map<number, Thread>();
64
private threadIds: number[] = [];
65
private cancellationMap = new Map<number, CancellationTokenSource[]>();
66
private readonly rawListeners = new DisposableStore();
67
private readonly globalDisposables = new DisposableStore();
68
private fetchThreadsScheduler: RunOnceScheduler | undefined;
69
private passFocusScheduler: RunOnceScheduler;
70
private lastContinuedThreadId: number | undefined;
71
private repl: ReplModel;
72
private stoppedDetails: IRawStoppedDetails[] = [];
73
private readonly statusQueue = this.rawListeners.add(new ThreadStatusScheduler());
74
75
/** Test run this debug session was spawned by */
76
public readonly correlatedTestRun?: LiveTestResult;
77
/** Whether we terminated the correlated run yet. Used so a 2nd terminate request goes through to the underlying session. */
78
private didTerminateTestRun?: boolean;
79
80
private readonly _onDidChangeState = new Emitter<void>();
81
private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent | undefined>();
82
83
private readonly _onDidLoadedSource = new Emitter<LoadedSourceEvent>();
84
private readonly _onDidCustomEvent = new Emitter<DebugProtocol.Event>();
85
private readonly _onDidProgressStart = new Emitter<DebugProtocol.ProgressStartEvent>();
86
private readonly _onDidProgressUpdate = new Emitter<DebugProtocol.ProgressUpdateEvent>();
87
private readonly _onDidProgressEnd = new Emitter<DebugProtocol.ProgressEndEvent>();
88
private readonly _onDidInvalidMemory = new Emitter<DebugProtocol.MemoryEvent>();
89
90
private readonly _onDidChangeREPLElements = new Emitter<IReplElement | undefined>();
91
92
private _name: string | undefined;
93
private readonly _onDidChangeName = new Emitter<string>();
94
95
/**
96
* Promise set while enabling dependent breakpoints to block the debugger
97
* from continuing from a stopped state.
98
*/
99
private _waitToResume?: Promise<unknown>;
100
101
constructor(
102
private id: string,
103
private _configuration: { resolved: IConfig; unresolved: IConfig | undefined },
104
public root: IWorkspaceFolder | undefined,
105
private model: DebugModel,
106
options: IDebugSessionOptions | undefined,
107
@IDebugService private readonly debugService: IDebugService,
108
@ITelemetryService private readonly telemetryService: ITelemetryService,
109
@IHostService private readonly hostService: IHostService,
110
@IConfigurationService private readonly configurationService: IConfigurationService,
111
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
112
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
113
@IProductService private readonly productService: IProductService,
114
@INotificationService private readonly notificationService: INotificationService,
115
@ILifecycleService lifecycleService: ILifecycleService,
116
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
117
@IInstantiationService private readonly instantiationService: IInstantiationService,
118
@ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService,
119
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
120
@ILogService private readonly logService: ILogService,
121
@ITestService private readonly testService: ITestService,
122
@ITestResultService testResultService: ITestResultService,
123
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
124
) {
125
this._options = options || {};
126
this.parentSession = this._options.parentSession;
127
if (this.hasSeparateRepl()) {
128
this.repl = new ReplModel(this.configurationService);
129
} else {
130
this.repl = (this.parentSession as DebugSession).repl;
131
}
132
133
const toDispose = this.globalDisposables;
134
const replListener = toDispose.add(new MutableDisposable());
135
replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e));
136
if (lifecycleService) {
137
toDispose.add(lifecycleService.onWillShutdown(() => {
138
this.shutdown();
139
dispose(toDispose);
140
}));
141
}
142
143
// Cast here, it's not possible to reference a hydrated result in this code path.
144
this.correlatedTestRun = options?.testRun
145
? (testResultService.getResult(options.testRun.runId) as LiveTestResult)
146
: this.parentSession?.correlatedTestRun;
147
148
if (this.correlatedTestRun) {
149
// Listen to the test completing because the user might have taken the cancel action rather than stopping the session.
150
toDispose.add(this.correlatedTestRun.onComplete(() => this.terminate()));
151
}
152
153
const compoundRoot = this._options.compoundRoot;
154
if (compoundRoot) {
155
toDispose.add(compoundRoot.onDidSessionStop(() => this.terminate()));
156
}
157
this.passFocusScheduler = new RunOnceScheduler(() => {
158
// If there is some session or thread that is stopped pass focus to it
159
if (this.debugService.getModel().getSessions().some(s => s.state === State.Stopped) || this.getAllThreads().some(t => t.stopped)) {
160
if (typeof this.lastContinuedThreadId === 'number') {
161
const thread = this.debugService.getViewModel().focusedThread;
162
if (thread && thread.threadId === this.lastContinuedThreadId && !thread.stopped) {
163
const toFocusThreadId = this.getStoppedDetails()?.threadId;
164
const toFocusThread = typeof toFocusThreadId === 'number' ? this.getThread(toFocusThreadId) : undefined;
165
this.debugService.focusStackFrame(undefined, toFocusThread);
166
}
167
} else {
168
const session = this.debugService.getViewModel().focusedSession;
169
if (session && session.getId() === this.getId() && session.state !== State.Stopped) {
170
this.debugService.focusStackFrame(undefined);
171
}
172
}
173
}
174
}, 800);
175
176
const parent = this._options.parentSession;
177
if (parent) {
178
toDispose.add(parent.onDidEndAdapter(() => {
179
// copy the parent repl and get a new detached repl for this child, and
180
// remove its parent, if it's still running
181
if (!this.hasSeparateRepl() && this.raw?.isInShutdown === false) {
182
this.repl = this.repl.clone();
183
replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e));
184
this.parentSession = undefined;
185
}
186
}));
187
}
188
}
189
190
getId(): string {
191
return this.id;
192
}
193
194
setSubId(subId: string | undefined) {
195
this._subId = subId;
196
}
197
198
getMemory(memoryReference: string): IMemoryRegion {
199
return new MemoryRegion(memoryReference, this);
200
}
201
202
get subId(): string | undefined {
203
return this._subId;
204
}
205
206
get configuration(): IConfig {
207
return this._configuration.resolved;
208
}
209
210
get unresolvedConfiguration(): IConfig | undefined {
211
return this._configuration.unresolved;
212
}
213
214
get lifecycleManagedByParent(): boolean {
215
return !!this._options.lifecycleManagedByParent;
216
}
217
218
get compact(): boolean {
219
return !!this._options.compact;
220
}
221
222
get saveBeforeRestart(): boolean {
223
return this._options.saveBeforeRestart ?? !this._options?.parentSession;
224
}
225
226
get compoundRoot(): DebugCompoundRoot | undefined {
227
return this._options.compoundRoot;
228
}
229
230
get suppressDebugStatusbar(): boolean {
231
return this._options.suppressDebugStatusbar ?? false;
232
}
233
234
get suppressDebugToolbar(): boolean {
235
return this._options.suppressDebugToolbar ?? false;
236
}
237
238
get suppressDebugView(): boolean {
239
return this._options.suppressDebugView ?? false;
240
}
241
242
243
get autoExpandLazyVariables(): boolean {
244
// This tiny helper avoids converting the entire debug model to use service injection
245
const screenReaderOptimized = this.accessibilityService.isScreenReaderOptimized();
246
const value = this.configurationService.getValue<IDebugConfiguration>('debug').autoExpandLazyVariables;
247
return value === 'auto' && screenReaderOptimized || value === 'on';
248
}
249
250
setConfiguration(configuration: { resolved: IConfig; unresolved: IConfig | undefined }) {
251
this._configuration = configuration;
252
}
253
254
getLabel(): string {
255
const includeRoot = this.workspaceContextService.getWorkspace().folders.length > 1;
256
return includeRoot && this.root ? `${this.name} (${resources.basenameOrAuthority(this.root.uri)})` : this.name;
257
}
258
259
setName(name: string): void {
260
this._name = name;
261
this._onDidChangeName.fire(name);
262
}
263
264
get name(): string {
265
return this._name || this.configuration.name;
266
}
267
268
get state(): State {
269
if (!this.initialized) {
270
return State.Initializing;
271
}
272
if (!this.raw) {
273
return State.Inactive;
274
}
275
276
const focusedThread = this.debugService.getViewModel().focusedThread;
277
if (focusedThread && focusedThread.session === this) {
278
return focusedThread.stopped ? State.Stopped : State.Running;
279
}
280
if (this.getAllThreads().some(t => t.stopped)) {
281
return State.Stopped;
282
}
283
284
return State.Running;
285
}
286
287
get capabilities(): DebugProtocol.Capabilities {
288
return this.raw ? this.raw.capabilities : Object.create(null);
289
}
290
291
//---- events
292
get onDidChangeState(): Event<void> {
293
return this._onDidChangeState.event;
294
}
295
296
get onDidEndAdapter(): Event<AdapterEndEvent | undefined> {
297
return this._onDidEndAdapter.event;
298
}
299
300
get onDidChangeReplElements(): Event<IReplElement | undefined> {
301
return this._onDidChangeREPLElements.event;
302
}
303
304
get onDidChangeName(): Event<string> {
305
return this._onDidChangeName.event;
306
}
307
308
//---- DAP events
309
310
get onDidCustomEvent(): Event<DebugProtocol.Event> {
311
return this._onDidCustomEvent.event;
312
}
313
314
get onDidLoadedSource(): Event<LoadedSourceEvent> {
315
return this._onDidLoadedSource.event;
316
}
317
318
get onDidProgressStart(): Event<DebugProtocol.ProgressStartEvent> {
319
return this._onDidProgressStart.event;
320
}
321
322
get onDidProgressUpdate(): Event<DebugProtocol.ProgressUpdateEvent> {
323
return this._onDidProgressUpdate.event;
324
}
325
326
get onDidProgressEnd(): Event<DebugProtocol.ProgressEndEvent> {
327
return this._onDidProgressEnd.event;
328
}
329
330
get onDidInvalidateMemory(): Event<DebugProtocol.MemoryEvent> {
331
return this._onDidInvalidMemory.event;
332
}
333
334
//---- DAP requests
335
336
/**
337
* create and initialize a new debug adapter for this session
338
*/
339
async initialize(dbgr: IDebugger): Promise<void> {
340
341
if (this.raw) {
342
// if there was already a connection make sure to remove old listeners
343
await this.shutdown();
344
}
345
346
try {
347
const debugAdapter = await dbgr.createDebugAdapter(this);
348
this.raw = this.instantiationService.createInstance(RawDebugSession, debugAdapter, dbgr, this.id, this.configuration.name);
349
350
await this.raw.start();
351
this.registerListeners();
352
await this.raw.initialize({
353
clientID: 'vscode',
354
clientName: this.productService.nameLong,
355
adapterID: this.configuration.type,
356
pathFormat: 'path',
357
linesStartAt1: true,
358
columnsStartAt1: true,
359
supportsVariableType: true, // #8858
360
supportsVariablePaging: true, // #9537
361
supportsRunInTerminalRequest: true, // #10574
362
locale: platform.language, // #169114
363
supportsProgressReporting: true, // #92253
364
supportsInvalidatedEvent: true, // #106745
365
supportsMemoryReferences: true, //#129684
366
supportsArgsCanBeInterpretedByShell: true, // #149910
367
supportsMemoryEvent: true, // #133643
368
supportsStartDebuggingRequest: true,
369
supportsANSIStyling: true,
370
});
371
372
this.initialized = true;
373
this._onDidChangeState.fire();
374
this.rememberedCapabilities = this.raw.capabilities;
375
this.debugService.setExceptionBreakpointsForSession(this, (this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []);
376
this.debugService.getModel().registerBreakpointModes(this.configuration.type, this.raw.capabilities.breakpointModes || []);
377
} catch (err) {
378
this.initialized = true;
379
this._onDidChangeState.fire();
380
await this.shutdown();
381
throw err;
382
}
383
}
384
385
/**
386
* launch or attach to the debuggee
387
*/
388
async launchOrAttach(config: IConfig): Promise<void> {
389
if (!this.raw) {
390
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'launch or attach'));
391
}
392
if (this.parentSession && this.parentSession.state === State.Inactive) {
393
throw canceled();
394
}
395
396
// __sessionID only used for EH debugging (but we add it always for now...)
397
config.__sessionId = this.getId();
398
try {
399
await this.raw.launchOrAttach(config);
400
} catch (err) {
401
this.shutdown();
402
throw err;
403
}
404
}
405
406
/**
407
* Terminate any linked test run.
408
*/
409
cancelCorrelatedTestRun() {
410
if (this.correlatedTestRun && !this.correlatedTestRun.completedAt) {
411
this.didTerminateTestRun = true;
412
this.testService.cancelTestRun(this.correlatedTestRun.id);
413
}
414
}
415
416
/**
417
* terminate the current debug adapter session
418
*/
419
async terminate(restart = false): Promise<void> {
420
if (!this.raw) {
421
// Adapter went down but it did not send a 'terminated' event, simulate like the event has been sent
422
this.onDidExitAdapter();
423
}
424
425
this.cancelAllRequests();
426
if (this._options.lifecycleManagedByParent && this.parentSession) {
427
await this.parentSession.terminate(restart);
428
} else if (this.correlatedTestRun && !this.correlatedTestRun.completedAt && !this.didTerminateTestRun) {
429
this.cancelCorrelatedTestRun();
430
} else if (this.raw) {
431
if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') {
432
await this.raw.terminate(restart);
433
} else {
434
await this.raw.disconnect({ restart, terminateDebuggee: true });
435
}
436
}
437
438
if (!restart) {
439
this._options.compoundRoot?.sessionStopped();
440
}
441
}
442
443
/**
444
* end the current debug adapter session
445
*/
446
async disconnect(restart = false, suspend = false): Promise<void> {
447
if (!this.raw) {
448
// Adapter went down but it did not send a 'terminated' event, simulate like the event has been sent
449
this.onDidExitAdapter();
450
}
451
452
this.cancelAllRequests();
453
if (this._options.lifecycleManagedByParent && this.parentSession) {
454
await this.parentSession.disconnect(restart, suspend);
455
} else if (this.raw) {
456
// TODO terminateDebuggee should be undefined by default?
457
await this.raw.disconnect({ restart, terminateDebuggee: false, suspendDebuggee: suspend });
458
}
459
460
if (!restart) {
461
this._options.compoundRoot?.sessionStopped();
462
}
463
}
464
465
/**
466
* restart debug adapter session
467
*/
468
async restart(): Promise<void> {
469
if (!this.raw) {
470
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'restart'));
471
}
472
473
this.cancelAllRequests();
474
if (this._options.lifecycleManagedByParent && this.parentSession) {
475
await this.parentSession.restart();
476
} else {
477
await this.raw.restart({ arguments: this.configuration });
478
}
479
}
480
481
async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> {
482
if (!this.raw) {
483
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints'));
484
}
485
486
if (!this.raw.readyForBreakpoints) {
487
return Promise.resolve(undefined);
488
}
489
490
const rawSource = this.getRawSource(modelUri);
491
if (breakpointsToSend.length && !rawSource.adapterData) {
492
rawSource.adapterData = breakpointsToSend[0].adapterData;
493
}
494
// Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959
495
if (rawSource.path) {
496
rawSource.path = normalizeDriveLetter(rawSource.path);
497
}
498
499
const response = await this.raw.setBreakpoints({
500
source: rawSource,
501
lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber),
502
breakpoints: breakpointsToSend.map(bp => bp.toDAP()),
503
sourceModified
504
});
505
if (response?.body) {
506
const data = new Map<string, DebugProtocol.Breakpoint>();
507
for (let i = 0; i < breakpointsToSend.length; i++) {
508
data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]);
509
}
510
511
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
512
}
513
}
514
515
async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> {
516
if (!this.raw) {
517
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'function breakpoints'));
518
}
519
520
if (this.raw.readyForBreakpoints) {
521
const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts.map(bp => bp.toDAP()) });
522
if (response?.body) {
523
const data = new Map<string, DebugProtocol.Breakpoint>();
524
for (let i = 0; i < fbpts.length; i++) {
525
data.set(fbpts[i].getId(), response.body.breakpoints[i]);
526
}
527
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
528
}
529
}
530
}
531
532
async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {
533
if (!this.raw) {
534
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'exception breakpoints'));
535
}
536
537
if (this.raw.readyForBreakpoints) {
538
const args: DebugProtocol.SetExceptionBreakpointsArguments = this.capabilities.supportsExceptionFilterOptions ? {
539
filters: [],
540
filterOptions: exbpts.map(exb => {
541
if (exb.condition) {
542
return { filterId: exb.filter, condition: exb.condition };
543
}
544
545
return { filterId: exb.filter };
546
})
547
} : { filters: exbpts.map(exb => exb.filter) };
548
549
const response = await this.raw.setExceptionBreakpoints(args);
550
if (response?.body && response.body.breakpoints) {
551
const data = new Map<string, DebugProtocol.Breakpoint>();
552
for (let i = 0; i < exbpts.length; i++) {
553
data.set(exbpts[i].getId(), response.body.breakpoints[i]);
554
}
555
556
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
557
}
558
}
559
}
560
561
dataBytesBreakpointInfo(address: string, bytes: number): Promise<IDataBreakpointInfoResponse | undefined> {
562
if (this.raw?.capabilities.supportsDataBreakpointBytes === false) {
563
throw new Error(localize('sessionDoesNotSupporBytesBreakpoints', "Session does not support breakpoints with bytes"));
564
}
565
566
return this._dataBreakpointInfo({ name: address, bytes, asAddress: true });
567
}
568
569
dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
570
return this._dataBreakpointInfo({ name, variablesReference });
571
}
572
573
private async _dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
574
if (!this.raw) {
575
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints info'));
576
}
577
if (!this.raw.readyForBreakpoints) {
578
throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"));
579
}
580
581
const response = await this.raw.dataBreakpointInfo(args);
582
return response?.body;
583
}
584
585
async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise<void> {
586
if (!this.raw) {
587
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints'));
588
}
589
590
if (this.raw.readyForBreakpoints) {
591
const converted = await Promise.all(dataBreakpoints.map(async bp => {
592
try {
593
const dap = await bp.toDAP(this);
594
return { dap, bp };
595
} catch (e) {
596
return { bp, message: e.message };
597
}
598
}));
599
const response = await this.raw.setDataBreakpoints({ breakpoints: converted.map(d => d.dap).filter(isDefined) });
600
if (response?.body) {
601
const data = new Map<string, DebugProtocol.Breakpoint>();
602
let i = 0;
603
for (const dap of converted) {
604
if (!dap.dap) {
605
data.set(dap.bp.getId(), dap.message);
606
} else if (i < response.body.breakpoints.length) {
607
data.set(dap.bp.getId(), response.body.breakpoints[i++]);
608
}
609
}
610
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
611
}
612
}
613
}
614
615
async sendInstructionBreakpoints(instructionBreakpoints: IInstructionBreakpoint[]): Promise<void> {
616
if (!this.raw) {
617
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'instruction breakpoints'));
618
}
619
620
if (this.raw.readyForBreakpoints) {
621
const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toDAP()) });
622
if (response?.body) {
623
const data = new Map<string, DebugProtocol.Breakpoint>();
624
for (let i = 0; i < instructionBreakpoints.length; i++) {
625
data.set(instructionBreakpoints[i].getId(), response.body.breakpoints[i]);
626
}
627
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
628
}
629
}
630
}
631
632
async breakpointsLocations(uri: URI, lineNumber: number): Promise<IPosition[]> {
633
if (!this.raw) {
634
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints locations'));
635
}
636
637
const source = this.getRawSource(uri);
638
const response = await this.raw.breakpointLocations({ source, line: lineNumber });
639
if (!response || !response.body || !response.body.breakpoints) {
640
return [];
641
}
642
643
const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 }));
644
645
return distinct(positions, p => `${p.lineNumber}:${p.column}`);
646
}
647
648
getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined {
649
return this.model.getDebugProtocolBreakpoint(breakpointId, this.getId());
650
}
651
652
customRequest(request: string, args: any): Promise<DebugProtocol.Response | undefined> {
653
if (!this.raw) {
654
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", request));
655
}
656
657
return this.raw.custom(request, args);
658
}
659
660
stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise<DebugProtocol.StackTraceResponse | undefined> {
661
if (!this.raw) {
662
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stackTrace'));
663
}
664
665
const sessionToken = this.getNewCancellationToken(threadId, token);
666
return this.raw.stackTrace({ threadId, startFrame, levels }, sessionToken);
667
}
668
669
async exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> {
670
if (!this.raw) {
671
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'exceptionInfo'));
672
}
673
674
const response = await this.raw.exceptionInfo({ threadId });
675
if (response) {
676
return {
677
id: response.body.exceptionId,
678
description: response.body.description,
679
breakMode: response.body.breakMode,
680
details: response.body.details
681
};
682
}
683
684
return undefined;
685
}
686
687
scopes(frameId: number, threadId: number): Promise<DebugProtocol.ScopesResponse | undefined> {
688
if (!this.raw) {
689
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'scopes'));
690
}
691
692
const token = this.getNewCancellationToken(threadId);
693
return this.raw.scopes({ frameId }, token);
694
}
695
696
variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse | undefined> {
697
if (!this.raw) {
698
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'variables'));
699
}
700
701
const token = threadId ? this.getNewCancellationToken(threadId) : undefined;
702
return this.raw.variables({ variablesReference, filter, start, count }, token);
703
}
704
705
evaluate(expression: string, frameId: number, context?: string, location?: { line: number; column: number; source: DebugProtocol.Source }): Promise<DebugProtocol.EvaluateResponse | undefined> {
706
if (!this.raw) {
707
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'evaluate'));
708
}
709
710
return this.raw.evaluate({ expression, frameId, context, line: location?.line, column: location?.column, source: location?.source });
711
}
712
713
async restartFrame(frameId: number, threadId: number): Promise<void> {
714
await this.waitForTriggeredBreakpoints();
715
if (!this.raw) {
716
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'restartFrame'));
717
}
718
719
await this.raw.restartFrame({ frameId }, threadId);
720
}
721
722
private setLastSteppingGranularity(threadId: number, granularity?: DebugProtocol.SteppingGranularity) {
723
const thread = this.getThread(threadId);
724
if (thread) {
725
thread.lastSteppingGranularity = granularity;
726
}
727
}
728
729
async next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
730
await this.waitForTriggeredBreakpoints();
731
if (!this.raw) {
732
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'next'));
733
}
734
735
this.setLastSteppingGranularity(threadId, granularity);
736
await this.raw.next({ threadId, granularity });
737
}
738
739
async stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
740
await this.waitForTriggeredBreakpoints();
741
if (!this.raw) {
742
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepIn'));
743
}
744
745
this.setLastSteppingGranularity(threadId, granularity);
746
await this.raw.stepIn({ threadId, targetId, granularity });
747
}
748
749
async stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
750
await this.waitForTriggeredBreakpoints();
751
if (!this.raw) {
752
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepOut'));
753
}
754
755
this.setLastSteppingGranularity(threadId, granularity);
756
await this.raw.stepOut({ threadId, granularity });
757
}
758
759
async stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
760
await this.waitForTriggeredBreakpoints();
761
if (!this.raw) {
762
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepBack'));
763
}
764
765
this.setLastSteppingGranularity(threadId, granularity);
766
await this.raw.stepBack({ threadId, granularity });
767
}
768
769
async continue(threadId: number): Promise<void> {
770
await this.waitForTriggeredBreakpoints();
771
if (!this.raw) {
772
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'continue'));
773
}
774
775
await this.raw.continue({ threadId });
776
}
777
778
async reverseContinue(threadId: number): Promise<void> {
779
await this.waitForTriggeredBreakpoints();
780
if (!this.raw) {
781
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'reverse continue'));
782
}
783
784
await this.raw.reverseContinue({ threadId });
785
}
786
787
async pause(threadId: number): Promise<void> {
788
if (!this.raw) {
789
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'pause'));
790
}
791
792
await this.raw.pause({ threadId });
793
}
794
795
async terminateThreads(threadIds?: number[]): Promise<void> {
796
if (!this.raw) {
797
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'terminateThreads'));
798
}
799
800
await this.raw.terminateThreads({ threadIds });
801
}
802
803
setVariable(variablesReference: number, name: string, value: string): Promise<DebugProtocol.SetVariableResponse | undefined> {
804
if (!this.raw) {
805
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'setVariable'));
806
}
807
808
return this.raw.setVariable({ variablesReference, name, value });
809
}
810
811
setExpression(frameId: number, expression: string, value: string): Promise<DebugProtocol.SetExpressionResponse | undefined> {
812
if (!this.raw) {
813
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'setExpression'));
814
}
815
816
return this.raw.setExpression({ expression, value, frameId });
817
}
818
819
gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise<DebugProtocol.GotoTargetsResponse | undefined> {
820
if (!this.raw) {
821
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'gotoTargets'));
822
}
823
824
return this.raw.gotoTargets({ source, line, column });
825
}
826
827
goto(threadId: number, targetId: number): Promise<DebugProtocol.GotoResponse | undefined> {
828
if (!this.raw) {
829
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'goto'));
830
}
831
832
return this.raw.goto({ threadId, targetId });
833
}
834
835
loadSource(resource: URI): Promise<DebugProtocol.SourceResponse | undefined> {
836
if (!this.raw) {
837
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'loadSource')));
838
}
839
840
const source = this.getSourceForUri(resource);
841
let rawSource: DebugProtocol.Source;
842
if (source) {
843
rawSource = source.raw;
844
} else {
845
// create a Source
846
const data = Source.getEncodedDebugData(resource);
847
rawSource = { path: data.path, sourceReference: data.sourceReference };
848
}
849
850
return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource });
851
}
852
853
async getLoadedSources(): Promise<Source[]> {
854
if (!this.raw) {
855
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'getLoadedSources')));
856
}
857
858
const response = await this.raw.loadedSources({});
859
if (response?.body && response.body.sources) {
860
return response.body.sources.map(src => this.getSource(src));
861
} else {
862
return [];
863
}
864
}
865
866
async completions(frameId: number | undefined, threadId: number, text: string, position: Position, token: CancellationToken): Promise<DebugProtocol.CompletionsResponse | undefined> {
867
if (!this.raw) {
868
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'completions')));
869
}
870
const sessionCancelationToken = this.getNewCancellationToken(threadId, token);
871
872
return this.raw.completions({
873
frameId,
874
text,
875
column: position.column,
876
line: position.lineNumber,
877
}, sessionCancelationToken);
878
}
879
880
async stepInTargets(frameId: number): Promise<{ id: number; label: string }[] | undefined> {
881
if (!this.raw) {
882
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepInTargets')));
883
}
884
885
const response = await this.raw.stepInTargets({ frameId });
886
return response?.body.targets;
887
}
888
889
async cancel(progressId: string): Promise<DebugProtocol.CancelResponse | undefined> {
890
if (!this.raw) {
891
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'cancel')));
892
}
893
894
return this.raw.cancel({ progressId });
895
}
896
897
async disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise<DebugProtocol.DisassembledInstruction[] | undefined> {
898
if (!this.raw) {
899
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble')));
900
}
901
902
const response = await this.raw.disassemble({ memoryReference, offset, instructionOffset, instructionCount, resolveSymbols: true });
903
return response?.body?.instructions;
904
}
905
906
readMemory(memoryReference: string, offset: number, count: number): Promise<DebugProtocol.ReadMemoryResponse | undefined> {
907
if (!this.raw) {
908
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'readMemory')));
909
}
910
911
return this.raw.readMemory({ count, memoryReference, offset });
912
}
913
914
writeMemory(memoryReference: string, offset: number, data: string, allowPartial?: boolean): Promise<DebugProtocol.WriteMemoryResponse | undefined> {
915
if (!this.raw) {
916
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble')));
917
}
918
919
return this.raw.writeMemory({ memoryReference, offset, allowPartial, data });
920
}
921
922
async resolveLocationReference(locationReference: number): Promise<IDebugLocationReferenced> {
923
if (!this.raw) {
924
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations'));
925
}
926
927
const location = await this.raw.locations({ locationReference });
928
if (!location?.body) {
929
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations'));
930
}
931
932
const source = this.getSource(location.body.source);
933
return { column: 1, ...location.body, source };
934
}
935
936
//---- threads
937
938
getThread(threadId: number): Thread | undefined {
939
return this.threads.get(threadId);
940
}
941
942
getAllThreads(): IThread[] {
943
const result: IThread[] = [];
944
this.threadIds.forEach((threadId) => {
945
const thread = this.threads.get(threadId);
946
if (thread) {
947
result.push(thread);
948
}
949
});
950
return result;
951
}
952
953
clearThreads(removeThreads: boolean, reference: number | undefined = undefined): void {
954
if (reference !== undefined && reference !== null) {
955
const thread = this.threads.get(reference);
956
if (thread) {
957
thread.clearCallStack();
958
thread.stoppedDetails = undefined;
959
thread.stopped = false;
960
961
if (removeThreads) {
962
this.threads.delete(reference);
963
}
964
}
965
} else {
966
this.threads.forEach(thread => {
967
thread.clearCallStack();
968
thread.stoppedDetails = undefined;
969
thread.stopped = false;
970
});
971
972
if (removeThreads) {
973
this.threads.clear();
974
this.threadIds = [];
975
ExpressionContainer.allValues.clear();
976
}
977
}
978
}
979
980
getStoppedDetails(): IRawStoppedDetails | undefined {
981
return this.stoppedDetails.length >= 1 ? this.stoppedDetails[0] : undefined;
982
}
983
984
rawUpdate(data: IRawModelUpdate): void {
985
this.threadIds = [];
986
data.threads.forEach(thread => {
987
this.threadIds.push(thread.id);
988
if (!this.threads.has(thread.id)) {
989
// A new thread came in, initialize it.
990
this.threads.set(thread.id, new Thread(this, thread.name, thread.id));
991
} else if (thread.name) {
992
// Just the thread name got updated #18244
993
const oldThread = this.threads.get(thread.id);
994
if (oldThread) {
995
oldThread.name = thread.name;
996
}
997
}
998
});
999
this.threads.forEach(t => {
1000
// Remove all old threads which are no longer part of the update #75980
1001
if (this.threadIds.indexOf(t.threadId) === -1) {
1002
this.threads.delete(t.threadId);
1003
}
1004
});
1005
1006
const stoppedDetails = data.stoppedDetails;
1007
if (stoppedDetails) {
1008
// Set the availability of the threads' callstacks depending on
1009
// whether the thread is stopped or not
1010
if (stoppedDetails.allThreadsStopped) {
1011
this.threads.forEach(thread => {
1012
thread.stoppedDetails = thread.threadId === stoppedDetails.threadId ? stoppedDetails : { reason: thread.stoppedDetails?.reason };
1013
thread.stopped = true;
1014
thread.clearCallStack();
1015
});
1016
} else {
1017
const thread = typeof stoppedDetails.threadId === 'number' ? this.threads.get(stoppedDetails.threadId) : undefined;
1018
if (thread) {
1019
// One thread is stopped, only update that thread.
1020
thread.stoppedDetails = stoppedDetails;
1021
thread.clearCallStack();
1022
thread.stopped = true;
1023
}
1024
}
1025
}
1026
}
1027
1028
private waitForTriggeredBreakpoints() {
1029
if (!this._waitToResume) {
1030
return;
1031
}
1032
1033
return raceTimeout(
1034
this._waitToResume,
1035
TRIGGERED_BREAKPOINT_MAX_DELAY
1036
);
1037
}
1038
1039
private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise<void> {
1040
if (this.raw) {
1041
const response = await this.raw.threads();
1042
if (response?.body && response.body.threads) {
1043
this.model.rawUpdate({
1044
sessionId: this.getId(),
1045
threads: response.body.threads,
1046
stoppedDetails
1047
});
1048
}
1049
}
1050
}
1051
1052
initializeForTest(raw: RawDebugSession): void {
1053
this.raw = raw;
1054
this.registerListeners();
1055
}
1056
1057
//---- private
1058
1059
private registerListeners(): void {
1060
if (!this.raw) {
1061
return;
1062
}
1063
1064
this.rawListeners.add(this.raw.onDidInitialize(async () => {
1065
aria.status(
1066
this.configuration.noDebug
1067
? localize('debuggingStartedNoDebug', "Started running without debugging.")
1068
: localize('debuggingStarted', "Debugging started.")
1069
);
1070
1071
const sendConfigurationDone = async () => {
1072
if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) {
1073
try {
1074
await this.raw.configurationDone();
1075
} catch (e) {
1076
// Disconnect the debug session on configuration done error #10596
1077
this.notificationService.error(e);
1078
this.raw?.disconnect({});
1079
}
1080
}
1081
1082
return undefined;
1083
};
1084
1085
// Send all breakpoints
1086
try {
1087
await this.debugService.sendAllBreakpoints(this);
1088
} finally {
1089
await sendConfigurationDone();
1090
await this.fetchThreads();
1091
}
1092
}));
1093
1094
1095
const statusQueue = this.statusQueue;
1096
this.rawListeners.add(this.raw.onDidStop(event => this.handleStop(event.body)));
1097
1098
this.rawListeners.add(this.raw.onDidThread(event => {
1099
statusQueue.cancel([event.body.threadId]);
1100
if (event.body.reason === 'started') {
1101
// debounce to reduce threadsRequest frequency and improve performance
1102
if (!this.fetchThreadsScheduler) {
1103
this.fetchThreadsScheduler = new RunOnceScheduler(() => {
1104
this.fetchThreads();
1105
}, 100);
1106
this.rawListeners.add(this.fetchThreadsScheduler);
1107
}
1108
if (!this.fetchThreadsScheduler.isScheduled()) {
1109
this.fetchThreadsScheduler.schedule();
1110
}
1111
} else if (event.body.reason === 'exited') {
1112
this.model.clearThreads(this.getId(), true, event.body.threadId);
1113
const viewModel = this.debugService.getViewModel();
1114
const focusedThread = viewModel.focusedThread;
1115
this.passFocusScheduler.cancel();
1116
if (focusedThread && event.body.threadId === focusedThread.threadId) {
1117
// De-focus the thread in case it was focused
1118
this.debugService.focusStackFrame(undefined, undefined, viewModel.focusedSession, { explicit: false });
1119
}
1120
}
1121
}));
1122
1123
this.rawListeners.add(this.raw.onDidTerminateDebugee(async event => {
1124
aria.status(localize('debuggingStopped', "Debugging stopped."));
1125
if (event.body && event.body.restart) {
1126
await this.debugService.restartSession(this, event.body.restart);
1127
} else if (this.raw) {
1128
await this.raw.disconnect({ terminateDebuggee: false });
1129
}
1130
}));
1131
1132
this.rawListeners.add(this.raw.onDidContinued(event => {
1133
const allThreads = event.body.allThreadsContinued !== false;
1134
1135
statusQueue.cancel(allThreads ? undefined : [event.body.threadId]);
1136
1137
const threadId = allThreads ? undefined : event.body.threadId;
1138
if (typeof threadId === 'number') {
1139
this.stoppedDetails = this.stoppedDetails.filter(sd => sd.threadId !== threadId);
1140
const tokens = this.cancellationMap.get(threadId);
1141
this.cancellationMap.delete(threadId);
1142
tokens?.forEach(t => t.dispose(true));
1143
} else {
1144
this.stoppedDetails = [];
1145
this.cancelAllRequests();
1146
}
1147
this.lastContinuedThreadId = threadId;
1148
// We need to pass focus to other sessions / threads with a timeout in case a quick stop event occurs #130321
1149
this.passFocusScheduler.schedule();
1150
this.model.clearThreads(this.getId(), false, threadId);
1151
this._onDidChangeState.fire();
1152
}));
1153
1154
const outputQueue = new Queue<void>();
1155
this.rawListeners.add(this.raw.onDidOutput(async event => {
1156
const outputSeverity = event.body.category === 'stderr' ? Severity.Error : event.body.category === 'console' ? Severity.Warning : Severity.Info;
1157
1158
// When a variables event is received, execute immediately to obtain the variables value #126967
1159
if (event.body.variablesReference) {
1160
const source = event.body.source && event.body.line ? {
1161
lineNumber: event.body.line,
1162
column: event.body.column ? event.body.column : 1,
1163
source: this.getSource(event.body.source)
1164
} : undefined;
1165
const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());
1166
const children = container.getChildren();
1167
// we should put appendToRepl into queue to make sure the logs to be displayed in correct order
1168
// see https://github.com/microsoft/vscode/issues/126967#issuecomment-874954269
1169
outputQueue.queue(async () => {
1170
const resolved = await children;
1171
// For single logged variables, try to use the output if we can so
1172
// present a better (i.e. ANSI-aware) representation of the output
1173
if (resolved.length === 1) {
1174
this.appendToRepl({ output: event.body.output, expression: resolved[0], sev: outputSeverity, source }, event.body.category === 'important');
1175
return;
1176
}
1177
1178
resolved.forEach((child) => {
1179
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
1180
(<any>child).name = null;
1181
this.appendToRepl({ output: '', expression: child, sev: outputSeverity, source }, event.body.category === 'important');
1182
});
1183
});
1184
return;
1185
}
1186
outputQueue.queue(async () => {
1187
if (!event.body || !this.raw) {
1188
return;
1189
}
1190
1191
if (event.body.category === 'telemetry') {
1192
// only log telemetry events from debug adapter if the debug extension provided the telemetry key
1193
// and the user opted in telemetry
1194
const telemetryEndpoint = this.raw.dbgr.getCustomTelemetryEndpoint();
1195
if (telemetryEndpoint && this.telemetryService.telemetryLevel !== TelemetryLevel.NONE) {
1196
// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.
1197
let data = event.body.data;
1198
if (!telemetryEndpoint.sendErrorTelemetry && event.body.data) {
1199
data = filterExceptionsFromTelemetry(event.body.data);
1200
}
1201
1202
this.customEndpointTelemetryService.publicLog(telemetryEndpoint, event.body.output, data);
1203
}
1204
1205
return;
1206
}
1207
1208
// Make sure to append output in the correct order by properly waiting on preivous promises #33822
1209
const source = event.body.source && event.body.line ? {
1210
lineNumber: event.body.line,
1211
column: event.body.column ? event.body.column : 1,
1212
source: this.getSource(event.body.source)
1213
} : undefined;
1214
1215
if (event.body.group === 'start' || event.body.group === 'startCollapsed') {
1216
const expanded = event.body.group === 'start';
1217
this.repl.startGroup(this, event.body.output || '', expanded, source);
1218
return;
1219
}
1220
if (event.body.group === 'end') {
1221
this.repl.endGroup();
1222
if (!event.body.output) {
1223
// Only return if the end event does not have additional output in it
1224
return;
1225
}
1226
}
1227
1228
if (typeof event.body.output === 'string') {
1229
this.appendToRepl({ output: event.body.output, sev: outputSeverity, source }, event.body.category === 'important');
1230
}
1231
});
1232
}));
1233
1234
this.rawListeners.add(this.raw.onDidBreakpoint(event => {
1235
const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined;
1236
const breakpoint = this.model.getBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);
1237
const functionBreakpoint = this.model.getFunctionBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);
1238
const dataBreakpoint = this.model.getDataBreakpoints().find(dbp => dbp.getIdFromAdapter(this.getId()) === id);
1239
const exceptionBreakpoint = this.model.getExceptionBreakpoints().find(excbp => excbp.getIdFromAdapter(this.getId()) === id);
1240
1241
if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) {
1242
const source = this.getSource(event.body.breakpoint.source);
1243
const bps = this.model.addBreakpoints(source.uri, [{
1244
column: event.body.breakpoint.column,
1245
enabled: true,
1246
lineNumber: event.body.breakpoint.line,
1247
}], false);
1248
if (bps.length === 1) {
1249
const data = new Map<string, DebugProtocol.Breakpoint>([[bps[0].getId(), event.body.breakpoint]]);
1250
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
1251
}
1252
}
1253
1254
if (event.body.reason === 'removed') {
1255
if (breakpoint) {
1256
this.model.removeBreakpoints([breakpoint]);
1257
}
1258
if (functionBreakpoint) {
1259
this.model.removeFunctionBreakpoints(functionBreakpoint.getId());
1260
}
1261
if (dataBreakpoint) {
1262
this.model.removeDataBreakpoints(dataBreakpoint.getId());
1263
}
1264
}
1265
1266
if (event.body.reason === 'changed') {
1267
if (breakpoint) {
1268
if (!breakpoint.column) {
1269
event.body.breakpoint.column = undefined;
1270
}
1271
const data = new Map<string, DebugProtocol.Breakpoint>([[breakpoint.getId(), event.body.breakpoint]]);
1272
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
1273
}
1274
if (functionBreakpoint) {
1275
const data = new Map<string, DebugProtocol.Breakpoint>([[functionBreakpoint.getId(), event.body.breakpoint]]);
1276
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
1277
}
1278
if (dataBreakpoint) {
1279
const data = new Map<string, DebugProtocol.Breakpoint>([[dataBreakpoint.getId(), event.body.breakpoint]]);
1280
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
1281
}
1282
if (exceptionBreakpoint) {
1283
const data = new Map<string, DebugProtocol.Breakpoint>([[exceptionBreakpoint.getId(), event.body.breakpoint]]);
1284
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
1285
}
1286
}
1287
}));
1288
1289
this.rawListeners.add(this.raw.onDidLoadedSource(event => {
1290
this._onDidLoadedSource.fire({
1291
reason: event.body.reason,
1292
source: this.getSource(event.body.source)
1293
});
1294
}));
1295
1296
this.rawListeners.add(this.raw.onDidCustomEvent(event => {
1297
this._onDidCustomEvent.fire(event);
1298
}));
1299
1300
this.rawListeners.add(this.raw.onDidProgressStart(event => {
1301
this._onDidProgressStart.fire(event);
1302
}));
1303
this.rawListeners.add(this.raw.onDidProgressUpdate(event => {
1304
this._onDidProgressUpdate.fire(event);
1305
}));
1306
this.rawListeners.add(this.raw.onDidProgressEnd(event => {
1307
this._onDidProgressEnd.fire(event);
1308
}));
1309
this.rawListeners.add(this.raw.onDidInvalidateMemory(event => {
1310
this._onDidInvalidMemory.fire(event);
1311
}));
1312
this.rawListeners.add(this.raw.onDidInvalidated(async event => {
1313
const areas = event.body.areas || ['all'];
1314
// If invalidated event only requires to update variables or watch, do that, otherwise refetch threads https://github.com/microsoft/vscode/issues/106745
1315
if (areas.includes('threads') || areas.includes('stacks') || areas.includes('all')) {
1316
this.cancelAllRequests();
1317
this.model.clearThreads(this.getId(), true);
1318
1319
const details = this.stoppedDetails;
1320
this.stoppedDetails.length = 1;
1321
await Promise.all(details.map(d => this.handleStop(d)));
1322
}
1323
1324
const viewModel = this.debugService.getViewModel();
1325
if (viewModel.focusedSession === this) {
1326
viewModel.updateViews();
1327
}
1328
}));
1329
1330
this.rawListeners.add(this.raw.onDidExitAdapter(event => this.onDidExitAdapter(event)));
1331
}
1332
1333
private async handleStop(event: IRawStoppedDetails) {
1334
this.passFocusScheduler.cancel();
1335
this.stoppedDetails.push(event);
1336
1337
// do this very eagerly if we have hitBreakpointIds, since it may take a
1338
// moment for breakpoints to set and we want to do our best to not miss
1339
// anything
1340
if (event.hitBreakpointIds) {
1341
this._waitToResume = this.enableDependentBreakpoints(event.hitBreakpointIds);
1342
}
1343
1344
this.statusQueue.run(
1345
this.fetchThreads(event).then(() => event.threadId === undefined ? this.threadIds : [event.threadId]),
1346
async (threadId, token) => {
1347
const hasLotsOfThreads = event.threadId === undefined && this.threadIds.length > 10;
1348
1349
// If the focus for the current session is on a non-existent thread, clear the focus.
1350
const focusedThread = this.debugService.getViewModel().focusedThread;
1351
const focusedThreadDoesNotExist = focusedThread !== undefined && focusedThread.session === this && !this.threads.has(focusedThread.threadId);
1352
if (focusedThreadDoesNotExist) {
1353
this.debugService.focusStackFrame(undefined, undefined);
1354
}
1355
1356
const thread = typeof threadId === 'number' ? this.getThread(threadId) : undefined;
1357
if (thread) {
1358
// Call fetch call stack twice, the first only return the top stack frame.
1359
// Second retrieves the rest of the call stack. For performance reasons #25605
1360
// Second call is only done if there's few threads that stopped in this event.
1361
const promises = this.model.refreshTopOfCallstack(<Thread>thread, /* fetchFullStack= */!hasLotsOfThreads);
1362
const focus = async () => {
1363
if (focusedThreadDoesNotExist || (!event.preserveFocusHint && thread.getCallStack().length)) {
1364
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
1365
if (!focusedStackFrame || focusedStackFrame.thread.session === this) {
1366
// Only take focus if nothing is focused, or if the focus is already on the current session
1367
const preserveFocus = !this.configurationService.getValue<IDebugConfiguration>('debug').focusEditorOnBreak;
1368
await this.debugService.focusStackFrame(undefined, thread, undefined, { preserveFocus });
1369
}
1370
1371
if (thread.stoppedDetails && !token.isCancellationRequested) {
1372
if (thread.stoppedDetails.reason === 'breakpoint' && this.configurationService.getValue<IDebugConfiguration>('debug').openDebug === 'openOnDebugBreak' && !this.suppressDebugView) {
1373
await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
1374
}
1375
1376
if (this.configurationService.getValue<IDebugConfiguration>('debug').focusWindowOnBreak && !this.workbenchEnvironmentService.extensionTestsLocationURI) {
1377
const activeWindow = getActiveWindow();
1378
if (!activeWindow.document.hasFocus()) {
1379
await this.hostService.focus(mainWindow, { mode: FocusMode.Force /* Application may not be active */ });
1380
}
1381
}
1382
}
1383
}
1384
};
1385
1386
await promises.topCallStack;
1387
1388
if (!event.hitBreakpointIds) { // if hitBreakpointIds are present, this is handled earlier on
1389
this._waitToResume = this.enableDependentBreakpoints(thread);
1390
}
1391
1392
if (token.isCancellationRequested) {
1393
return;
1394
}
1395
1396
focus();
1397
1398
await promises.wholeCallStack;
1399
if (token.isCancellationRequested) {
1400
return;
1401
}
1402
1403
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
1404
if (!focusedStackFrame || isFrameDeemphasized(focusedStackFrame)) {
1405
// The top stack frame can be deemphesized so try to focus again #68616
1406
focus();
1407
}
1408
}
1409
this._onDidChangeState.fire();
1410
},
1411
);
1412
}
1413
1414
private async enableDependentBreakpoints(hitBreakpointIdsOrThread: Thread | number[]) {
1415
let breakpoints: IBreakpoint[];
1416
if (Array.isArray(hitBreakpointIdsOrThread)) {
1417
breakpoints = this.model.getBreakpoints().filter(bp => hitBreakpointIdsOrThread.includes(bp.getIdFromAdapter(this.id)!));
1418
} else {
1419
const frame = hitBreakpointIdsOrThread.getTopStackFrame();
1420
if (frame === undefined) {
1421
return;
1422
}
1423
1424
if (hitBreakpointIdsOrThread.stoppedDetails && hitBreakpointIdsOrThread.stoppedDetails.reason !== 'breakpoint') {
1425
return;
1426
}
1427
1428
breakpoints = this.getBreakpointsAtPosition(frame.source.uri, frame.range.startLineNumber, frame.range.endLineNumber, frame.range.startColumn, frame.range.endColumn);
1429
}
1430
1431
// find the current breakpoints
1432
1433
// check if the current breakpoints are dependencies, and if so collect and send the dependents to DA
1434
const urisToResend = new Set<string>();
1435
this.model.getBreakpoints({ triggeredOnly: true, enabledOnly: true }).forEach(bp => {
1436
breakpoints.forEach(cbp => {
1437
if (bp.enabled && bp.triggeredBy === cbp.getId()) {
1438
bp.setSessionDidTrigger(this.getId());
1439
urisToResend.add(bp.uri.toString());
1440
}
1441
});
1442
});
1443
1444
const results: Promise<any>[] = [];
1445
urisToResend.forEach((uri) => results.push(this.debugService.sendBreakpoints(URI.parse(uri), undefined, this)));
1446
return Promise.all(results);
1447
}
1448
1449
private getBreakpointsAtPosition(uri: URI, startLineNumber: number, endLineNumber: number, startColumn: number, endColumn: number): IBreakpoint[] {
1450
return this.model.getBreakpoints({ uri: uri }).filter(bp => {
1451
if (bp.lineNumber < startLineNumber || bp.lineNumber > endLineNumber) {
1452
return false;
1453
}
1454
1455
if (bp.column && (bp.column < startColumn || bp.column > endColumn)) {
1456
return false;
1457
}
1458
return true;
1459
});
1460
}
1461
1462
private onDidExitAdapter(event?: AdapterEndEvent): void {
1463
this.initialized = true;
1464
this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined);
1465
this.shutdown();
1466
this._onDidEndAdapter.fire(event);
1467
}
1468
1469
// Disconnects and clears state. Session can be initialized again for a new connection.
1470
private shutdown(): void {
1471
this.rawListeners.clear();
1472
if (this.raw) {
1473
// Send out disconnect and immediatly dispose (do not wait for response) #127418
1474
this.raw.disconnect({});
1475
this.raw.dispose();
1476
this.raw = undefined;
1477
}
1478
this.fetchThreadsScheduler?.dispose();
1479
this.fetchThreadsScheduler = undefined;
1480
this.passFocusScheduler.cancel();
1481
this.passFocusScheduler.dispose();
1482
this.model.clearThreads(this.getId(), true);
1483
this._onDidChangeState.fire();
1484
}
1485
1486
public dispose() {
1487
this.cancelAllRequests();
1488
this.rawListeners.dispose();
1489
this.globalDisposables.dispose();
1490
}
1491
1492
//---- sources
1493
1494
getSourceForUri(uri: URI): Source | undefined {
1495
return this.sources.get(this.uriIdentityService.asCanonicalUri(uri).toString());
1496
}
1497
1498
getSource(raw?: DebugProtocol.Source): Source {
1499
let source = new Source(raw, this.getId(), this.uriIdentityService, this.logService);
1500
const uriKey = source.uri.toString();
1501
const found = this.sources.get(uriKey);
1502
if (found) {
1503
source = found;
1504
// merge attributes of new into existing
1505
source.raw = mixin(source.raw, raw);
1506
if (source.raw && raw) {
1507
// Always take the latest presentation hint from adapter #42139
1508
source.raw.presentationHint = raw.presentationHint;
1509
}
1510
} else {
1511
this.sources.set(uriKey, source);
1512
}
1513
1514
return source;
1515
}
1516
1517
private getRawSource(uri: URI): DebugProtocol.Source {
1518
const source = this.getSourceForUri(uri);
1519
if (source) {
1520
return source.raw;
1521
} else {
1522
const data = Source.getEncodedDebugData(uri);
1523
return { name: data.name, path: data.path, sourceReference: data.sourceReference };
1524
}
1525
}
1526
1527
private getNewCancellationToken(threadId: number, token?: CancellationToken): CancellationToken {
1528
const tokenSource = new CancellationTokenSource(token);
1529
const tokens = this.cancellationMap.get(threadId) || [];
1530
tokens.push(tokenSource);
1531
this.cancellationMap.set(threadId, tokens);
1532
1533
return tokenSource.token;
1534
}
1535
1536
private cancelAllRequests(): void {
1537
this.cancellationMap.forEach(tokens => tokens.forEach(t => t.dispose(true)));
1538
this.cancellationMap.clear();
1539
}
1540
1541
// REPL
1542
1543
getReplElements(): IReplElement[] {
1544
return this.repl.getReplElements();
1545
}
1546
1547
hasSeparateRepl(): boolean {
1548
return !this.parentSession || this._options.repl !== 'mergeWithParent';
1549
}
1550
1551
removeReplExpressions(): void {
1552
this.repl.removeReplExpressions();
1553
}
1554
1555
async addReplExpression(stackFrame: IStackFrame | undefined, expression: string): Promise<void> {
1556
await this.repl.addReplExpression(this, stackFrame, expression);
1557
// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.
1558
this.debugService.getViewModel().updateViews();
1559
}
1560
1561
appendToRepl(data: INewReplElementData, isImportant?: boolean): void {
1562
this.repl.appendToRepl(this, data);
1563
if (isImportant) {
1564
this.notificationService.notify({ message: data.output.toString(), severity: data.sev, source: this.name });
1565
}
1566
}
1567
}
1568
1569
/**
1570
* Keeps track of events for threads, and cancels any previous operations for
1571
* a thread when the thread goes into a new state. Currently, the operations a thread has are:
1572
*
1573
* - started
1574
* - stopped
1575
* - continue
1576
* - exited
1577
*
1578
* In each case, the new state preempts the old state, so we don't need to
1579
* queue work, just cancel old work. It's up to the caller to make sure that
1580
* no UI effects happen at the point when the `token` is cancelled.
1581
*/
1582
export class ThreadStatusScheduler extends Disposable {
1583
/**
1584
* An array of set of thread IDs. When a 'stopped' event is encountered, the
1585
* editor refreshes its thread IDs. In the meantime, the thread may change
1586
* state it again. So the editor puts a Set into this array when it starts
1587
* the refresh, and checks it after the refresh is finished, to see if
1588
* any of the threads it looked up should now be invalidated.
1589
*/
1590
private pendingCancellations: Set<number | undefined>[] = [];
1591
1592
/**
1593
* Cancellation tokens for currently-running operations on threads.
1594
*/
1595
private readonly threadOps = this._register(new DisposableMap<number, CancellationTokenSource>());
1596
1597
/**
1598
* Runs the operation.
1599
* If thread is undefined it affects all threads.
1600
*/
1601
public async run(threadIdsP: Promise<number[]>, operation: (threadId: number, ct: CancellationToken) => Promise<unknown>) {
1602
const cancelledWhileLookingUpThreads = new Set<number | undefined>();
1603
this.pendingCancellations.push(cancelledWhileLookingUpThreads);
1604
const threadIds = await threadIdsP;
1605
1606
// Now that we got our threads,
1607
// 1. Remove our pending set, and
1608
// 2. Cancel any slower callers who might also have found this thread
1609
for (let i = 0; i < this.pendingCancellations.length; i++) {
1610
const s = this.pendingCancellations[i];
1611
if (s === cancelledWhileLookingUpThreads) {
1612
this.pendingCancellations.splice(i, 1);
1613
break;
1614
} else {
1615
for (const threadId of threadIds) {
1616
s.add(threadId);
1617
}
1618
}
1619
}
1620
1621
if (cancelledWhileLookingUpThreads.has(undefined)) {
1622
return;
1623
}
1624
1625
await Promise.all(threadIds.map(threadId => {
1626
if (cancelledWhileLookingUpThreads.has(threadId)) {
1627
return;
1628
}
1629
this.threadOps.get(threadId)?.cancel();
1630
const cts = new CancellationTokenSource();
1631
this.threadOps.set(threadId, cts);
1632
return operation(threadId, cts.token);
1633
}));
1634
}
1635
1636
/**
1637
* Cancels all ongoing state operations on the given threads.
1638
* If threads is undefined it cancel all threads.
1639
*/
1640
public cancel(threadIds?: readonly number[]) {
1641
if (!threadIds) {
1642
for (const [_, op] of this.threadOps) {
1643
op.cancel();
1644
}
1645
this.threadOps.clearAndDisposeAll();
1646
for (const s of this.pendingCancellations) {
1647
s.add(undefined);
1648
}
1649
} else {
1650
for (const threadId of threadIds) {
1651
this.threadOps.get(threadId)?.cancel();
1652
this.threadOps.deleteAndDispose(threadId);
1653
for (const s of this.pendingCancellations) {
1654
s.add(threadId);
1655
}
1656
}
1657
}
1658
}
1659
}
1660
1661