in src/vs/workbench/contrib/debug/browser/debugSession.ts [880:1168]
private registerListeners(): void {
if (!this.raw) {
return;
}
this.rawListeners.push(this.raw.onDidInitialize(async () => {
aria.status(localize('debuggingStarted', "Debugging started."));
const sendConfigurationDone = async () => {
if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) {
try {
await this.raw.configurationDone();
} catch (e) {
// Disconnect the debug session on configuration done error #10596
this.notificationService.error(e);
if (this.raw) {
this.raw.disconnect({});
}
}
}
return undefined;
};
// Send all breakpoints
try {
await this.debugService.sendAllBreakpoints(this);
} finally {
await sendConfigurationDone();
await this.fetchThreads();
}
}));
this.rawListeners.push(this.raw.onDidStop(async event => {
this.passFocusScheduler.cancel();
this.stoppedDetails.push(event.body);
await this.fetchThreads(event.body);
const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined;
if (thread) {
// Call fetch call stack twice, the first only return the top stack frame.
// Second retrieves the rest of the call stack. For performance reasons #25605
const promises = this.model.fetchCallStack(<Thread>thread);
const focus = async () => {
if (!event.body.preserveFocusHint && thread.getCallStack().length) {
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
if (!focusedStackFrame || focusedStackFrame.thread.session === this) {
// Only take focus if nothing is focused, or if the focus is already on the current session
await this.debugService.focusStackFrame(undefined, thread);
}
if (thread.stoppedDetails) {
if (thread.stoppedDetails.reason === 'breakpoint' && this.configurationService.getValue<IDebugConfiguration>('debug').openDebug === 'openOnDebugBreak' && !this.isSimpleUI) {
await this.viewletService.openViewlet(VIEWLET_ID);
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').focusWindowOnBreak) {
await this.hostService.focus({ force: true /* Application may not be active */ });
}
}
}
};
await promises.topCallStack;
focus();
await promises.wholeCallStack;
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
if (!focusedStackFrame || !focusedStackFrame.source || focusedStackFrame.source.presentationHint === 'deemphasize' || focusedStackFrame.presentationHint === 'deemphasize') {
// The top stack frame can be deemphesized so try to focus again #68616
focus();
}
}
this._onDidChangeState.fire();
}));
this.rawListeners.push(this.raw.onDidThread(event => {
if (event.body.reason === 'started') {
// debounce to reduce threadsRequest frequency and improve performance
if (!this.fetchThreadsScheduler) {
this.fetchThreadsScheduler = new RunOnceScheduler(() => {
this.fetchThreads();
}, 100);
this.rawListeners.push(this.fetchThreadsScheduler);
}
if (!this.fetchThreadsScheduler.isScheduled()) {
this.fetchThreadsScheduler.schedule();
}
} else if (event.body.reason === 'exited') {
this.model.clearThreads(this.getId(), true, event.body.threadId);
const viewModel = this.debugService.getViewModel();
const focusedThread = viewModel.focusedThread;
this.passFocusScheduler.cancel();
if (focusedThread && event.body.threadId === focusedThread.threadId) {
// De-focus the thread in case it was focused
this.debugService.focusStackFrame(undefined, undefined, viewModel.focusedSession, false);
}
}
}));
this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => {
aria.status(localize('debuggingStopped', "Debugging stopped."));
if (event.body && event.body.restart) {
await this.debugService.restartSession(this, event.body.restart);
} else if (this.raw) {
await this.raw.disconnect({ terminateDebuggee: false });
}
}));
this.rawListeners.push(this.raw.onDidContinued(event => {
const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId;
if (typeof threadId === 'number') {
this.stoppedDetails = this.stoppedDetails.filter(sd => sd.threadId !== threadId);
const tokens = this.cancellationMap.get(threadId);
this.cancellationMap.delete(threadId);
if (tokens) {
tokens.forEach(t => t.cancel());
}
} else {
this.stoppedDetails = [];
this.cancelAllRequests();
}
this.lastContinuedThreadId = threadId;
// We need to pass focus to other sessions / threads with a timeout in case a quick stop event occurs #130321
this.passFocusScheduler.schedule();
this.model.clearThreads(this.getId(), false, threadId);
this._onDidChangeState.fire();
}));
const outputQueue = new Queue<void>();
this.rawListeners.push(this.raw.onDidOutput(async event => {
// When a variables event is received, execute immediately to obtain the variables value #126967
if (event.body.variablesReference) {
const source = event.body.source && event.body.line ? {
lineNumber: event.body.line,
column: event.body.column ? event.body.column : 1,
source: this.getSource(event.body.source)
} : undefined;
const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());
const children = container.getChildren();
// we should put appendToRepl into queue to make sure the logs to be displayed in correct order
// see https://github.com/microsoft/vscode/issues/126967#issuecomment-874954269
outputQueue.queue(async () => {
const resolved = await children;
resolved.forEach((child) => {
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
(<any>child).name = null;
this.appendToRepl(child, severity.Info, source);
});
});
return;
}
outputQueue.queue(async () => {
if (!event.body || !this.raw) {
return;
}
const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info;
if (event.body.category === 'telemetry') {
// only log telemetry events from debug adapter if the debug extension provided the telemetry key
// and the user opted in telemetry
const telemetryEndpoint = this.raw.dbgr.getCustomTelemetryEndpoint();
if (telemetryEndpoint && this.telemetryService.isOptedIn) {
// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.
let data = event.body.data;
if (!telemetryEndpoint.sendErrorTelemetry && event.body.data) {
data = filterExceptionsFromTelemetry(event.body.data);
}
this.customEndpointTelemetryService.publicLog(telemetryEndpoint, event.body.output, data);
}
return;
}
// Make sure to append output in the correct order by properly waiting on preivous promises #33822
const source = event.body.source && event.body.line ? {
lineNumber: event.body.line,
column: event.body.column ? event.body.column : 1,
source: this.getSource(event.body.source)
} : undefined;
if (event.body.group === 'start' || event.body.group === 'startCollapsed') {
const expanded = event.body.group === 'start';
this.repl.startGroup(event.body.output || '', expanded, source);
return;
}
if (event.body.group === 'end') {
this.repl.endGroup();
if (!event.body.output) {
// Only return if the end event does not have additional output in it
return;
}
}
if (typeof event.body.output === 'string') {
this.appendToRepl(event.body.output, outputSeverity, source);
}
});
}));
this.rawListeners.push(this.raw.onDidBreakpoint(event => {
const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined;
const breakpoint = this.model.getBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);
const functionBreakpoint = this.model.getFunctionBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);
const dataBreakpoint = this.model.getDataBreakpoints().find(dbp => dbp.getIdFromAdapter(this.getId()) === id);
const exceptionBreakpoint = this.model.getExceptionBreakpoints().find(excbp => excbp.getIdFromAdapter(this.getId()) === id);
if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) {
const source = this.getSource(event.body.breakpoint.source);
const bps = this.model.addBreakpoints(source.uri, [{
column: event.body.breakpoint.column,
enabled: true,
lineNumber: event.body.breakpoint.line,
}], false);
if (bps.length === 1) {
const data = new Map<string, DebugProtocol.Breakpoint>([[bps[0].getId(), event.body.breakpoint]]);
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
}
if (event.body.reason === 'removed') {
if (breakpoint) {
this.model.removeBreakpoints([breakpoint]);
}
if (functionBreakpoint) {
this.model.removeFunctionBreakpoints(functionBreakpoint.getId());
}
if (dataBreakpoint) {
this.model.removeDataBreakpoints(dataBreakpoint.getId());
}
}
if (event.body.reason === 'changed') {
if (breakpoint) {
if (!breakpoint.column) {
event.body.breakpoint.column = undefined;
}
const data = new Map<string, DebugProtocol.Breakpoint>([[breakpoint.getId(), event.body.breakpoint]]);
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
if (functionBreakpoint) {
const data = new Map<string, DebugProtocol.Breakpoint>([[functionBreakpoint.getId(), event.body.breakpoint]]);
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
if (dataBreakpoint) {
const data = new Map<string, DebugProtocol.Breakpoint>([[dataBreakpoint.getId(), event.body.breakpoint]]);
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
if (exceptionBreakpoint) {
const data = new Map<string, DebugProtocol.Breakpoint>([[exceptionBreakpoint.getId(), event.body.breakpoint]]);
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
}
}));
this.rawListeners.push(this.raw.onDidLoadedSource(event => {
this._onDidLoadedSource.fire({
reason: event.body.reason,
source: this.getSource(event.body.source)
});
}));
this.rawListeners.push(this.raw.onDidCustomEvent(event => {
this._onDidCustomEvent.fire(event);
}));
this.rawListeners.push(this.raw.onDidProgressStart(event => {
this._onDidProgressStart.fire(event);
}));
this.rawListeners.push(this.raw.onDidProgressUpdate(event => {
this._onDidProgressUpdate.fire(event);
}));
this.rawListeners.push(this.raw.onDidProgressEnd(event => {
this._onDidProgressEnd.fire(event);
}));
this.rawListeners.push(this.raw.onDidInvalidated(async event => {
if (!(event.body.areas && event.body.areas.length === 1 && (event.body.areas[0] === 'variables' || event.body.areas[0] === 'watch'))) {
// If invalidated event only requires to update variables or watch, do that, otherwise refatch threads https://github.com/microsoft/vscode/issues/106745
this.cancelAllRequests();
this.model.clearThreads(this.getId(), true);
await this.fetchThreads(this.getStoppedDetails());
}
const viewModel = this.debugService.getViewModel();
if (viewModel.focusedSession === this) {
viewModel.updateViews();
}
}));
this.rawListeners.push(this.raw.onDidExitAdapter(event => this.onDidExitAdapter(event)));
}