Path: blob/main/src/vs/workbench/contrib/debug/browser/debugSession.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { getActiveWindow } from '../../../../base/browser/dom.js';6import * as aria from '../../../../base/browser/ui/aria/aria.js';7import { mainWindow } from '../../../../base/browser/window.js';8import { distinct } from '../../../../base/common/arrays.js';9import { Queue, RunOnceScheduler, raceTimeout } from '../../../../base/common/async.js';10import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';11import { canceled } from '../../../../base/common/errors.js';12import { Emitter, Event } from '../../../../base/common/event.js';13import { normalizeDriveLetter } from '../../../../base/common/labels.js';14import { Disposable, DisposableMap, DisposableStore, MutableDisposable, dispose } from '../../../../base/common/lifecycle.js';15import { mixin } from '../../../../base/common/objects.js';16import * as platform from '../../../../base/common/platform.js';17import * as resources from '../../../../base/common/resources.js';18import Severity from '../../../../base/common/severity.js';19import { isDefined } from '../../../../base/common/types.js';20import { URI } from '../../../../base/common/uri.js';21import { generateUuid } from '../../../../base/common/uuid.js';22import { IPosition, Position } from '../../../../editor/common/core/position.js';23import { localize } from '../../../../nls.js';24import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';25import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';26import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';27import { ILogService } from '../../../../platform/log/common/log.js';28import { FocusMode } from '../../../../platform/native/common/native.js';29import { INotificationService } from '../../../../platform/notification/common/notification.js';30import { IProductService } from '../../../../platform/product/common/productService.js';31import { ICustomEndpointTelemetryService, ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';32import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';33import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';34import { ViewContainerLocation } from '../../../common/views.js';35import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';36import { IHostService } from '../../../services/host/browser/host.js';37import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';38import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';39import { LiveTestResult } from '../../testing/common/testResult.js';40import { ITestResultService } from '../../testing/common/testResultService.js';41import { ITestService } from '../../testing/common/testService.js';42import { 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';43import { DebugCompoundRoot } from '../common/debugCompoundRoot.js';44import { DebugModel, ExpressionContainer, MemoryRegion, Thread } from '../common/debugModel.js';45import { Source } from '../common/debugSource.js';46import { filterExceptionsFromTelemetry } from '../common/debugUtils.js';47import { INewReplElementData, ReplModel } from '../common/replModel.js';48import { RawDebugSession } from './rawDebugSession.js';4950const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500;5152export class DebugSession implements IDebugSession {53parentSession: IDebugSession | undefined;54rememberedCapabilities?: DebugProtocol.Capabilities;5556private _subId: string | undefined;57raw: RawDebugSession | undefined; // used in tests58private initialized = false;59private _options: IDebugSessionOptions;6061private sources = new Map<string, Source>();62private threads = new Map<number, Thread>();63private threadIds: number[] = [];64private cancellationMap = new Map<number, CancellationTokenSource[]>();65private readonly rawListeners = new DisposableStore();66private readonly globalDisposables = new DisposableStore();67private fetchThreadsScheduler: RunOnceScheduler | undefined;68private passFocusScheduler: RunOnceScheduler;69private lastContinuedThreadId: number | undefined;70private repl: ReplModel;71private stoppedDetails: IRawStoppedDetails[] = [];72private readonly statusQueue = this.rawListeners.add(new ThreadStatusScheduler());7374/** Test run this debug session was spawned by */75public readonly correlatedTestRun?: LiveTestResult;76/** Whether we terminated the correlated run yet. Used so a 2nd terminate request goes through to the underlying session. */77private didTerminateTestRun?: boolean;7879private readonly _onDidChangeState = new Emitter<void>();80private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent | undefined>();8182private readonly _onDidLoadedSource = new Emitter<LoadedSourceEvent>();83private readonly _onDidCustomEvent = new Emitter<DebugProtocol.Event>();84private readonly _onDidProgressStart = new Emitter<DebugProtocol.ProgressStartEvent>();85private readonly _onDidProgressUpdate = new Emitter<DebugProtocol.ProgressUpdateEvent>();86private readonly _onDidProgressEnd = new Emitter<DebugProtocol.ProgressEndEvent>();87private readonly _onDidInvalidMemory = new Emitter<DebugProtocol.MemoryEvent>();8889private readonly _onDidChangeREPLElements = new Emitter<IReplElement | undefined>();9091private _name: string | undefined;92private readonly _onDidChangeName = new Emitter<string>();9394/**95* Promise set while enabling dependent breakpoints to block the debugger96* from continuing from a stopped state.97*/98private _waitToResume?: Promise<unknown>;99100constructor(101private id: string,102private _configuration: { resolved: IConfig; unresolved: IConfig | undefined },103public root: IWorkspaceFolder | undefined,104private model: DebugModel,105options: IDebugSessionOptions | undefined,106@IDebugService private readonly debugService: IDebugService,107@ITelemetryService private readonly telemetryService: ITelemetryService,108@IHostService private readonly hostService: IHostService,109@IConfigurationService private readonly configurationService: IConfigurationService,110@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,111@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,112@IProductService private readonly productService: IProductService,113@INotificationService private readonly notificationService: INotificationService,114@ILifecycleService lifecycleService: ILifecycleService,115@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,116@IInstantiationService private readonly instantiationService: IInstantiationService,117@ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService,118@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,119@ILogService private readonly logService: ILogService,120@ITestService private readonly testService: ITestService,121@ITestResultService testResultService: ITestResultService,122@IAccessibilityService private readonly accessibilityService: IAccessibilityService,123) {124this._options = options || {};125this.parentSession = this._options.parentSession;126if (this.hasSeparateRepl()) {127this.repl = new ReplModel(this.configurationService);128} else {129this.repl = (this.parentSession as DebugSession).repl;130}131132const toDispose = this.globalDisposables;133const replListener = toDispose.add(new MutableDisposable());134replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e));135if (lifecycleService) {136toDispose.add(lifecycleService.onWillShutdown(() => {137this.shutdown();138dispose(toDispose);139}));140}141142// Cast here, it's not possible to reference a hydrated result in this code path.143this.correlatedTestRun = options?.testRun144? (testResultService.getResult(options.testRun.runId) as LiveTestResult)145: this.parentSession?.correlatedTestRun;146147if (this.correlatedTestRun) {148// Listen to the test completing because the user might have taken the cancel action rather than stopping the session.149toDispose.add(this.correlatedTestRun.onComplete(() => this.terminate()));150}151152const compoundRoot = this._options.compoundRoot;153if (compoundRoot) {154toDispose.add(compoundRoot.onDidSessionStop(() => this.terminate()));155}156this.passFocusScheduler = new RunOnceScheduler(() => {157// If there is some session or thread that is stopped pass focus to it158if (this.debugService.getModel().getSessions().some(s => s.state === State.Stopped) || this.getAllThreads().some(t => t.stopped)) {159if (typeof this.lastContinuedThreadId === 'number') {160const thread = this.debugService.getViewModel().focusedThread;161if (thread && thread.threadId === this.lastContinuedThreadId && !thread.stopped) {162const toFocusThreadId = this.getStoppedDetails()?.threadId;163const toFocusThread = typeof toFocusThreadId === 'number' ? this.getThread(toFocusThreadId) : undefined;164this.debugService.focusStackFrame(undefined, toFocusThread);165}166} else {167const session = this.debugService.getViewModel().focusedSession;168if (session && session.getId() === this.getId() && session.state !== State.Stopped) {169this.debugService.focusStackFrame(undefined);170}171}172}173}, 800);174175const parent = this._options.parentSession;176if (parent) {177toDispose.add(parent.onDidEndAdapter(() => {178// copy the parent repl and get a new detached repl for this child, and179// remove its parent, if it's still running180if (!this.hasSeparateRepl() && this.raw?.isInShutdown === false) {181this.repl = this.repl.clone();182replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e));183this.parentSession = undefined;184}185}));186}187}188189getId(): string {190return this.id;191}192193setSubId(subId: string | undefined) {194this._subId = subId;195}196197getMemory(memoryReference: string): IMemoryRegion {198return new MemoryRegion(memoryReference, this);199}200201get subId(): string | undefined {202return this._subId;203}204205get configuration(): IConfig {206return this._configuration.resolved;207}208209get unresolvedConfiguration(): IConfig | undefined {210return this._configuration.unresolved;211}212213get lifecycleManagedByParent(): boolean {214return !!this._options.lifecycleManagedByParent;215}216217get compact(): boolean {218return !!this._options.compact;219}220221get saveBeforeRestart(): boolean {222return this._options.saveBeforeRestart ?? !this._options?.parentSession;223}224225get compoundRoot(): DebugCompoundRoot | undefined {226return this._options.compoundRoot;227}228229get suppressDebugStatusbar(): boolean {230return this._options.suppressDebugStatusbar ?? false;231}232233get suppressDebugToolbar(): boolean {234return this._options.suppressDebugToolbar ?? false;235}236237get suppressDebugView(): boolean {238return this._options.suppressDebugView ?? false;239}240241242get autoExpandLazyVariables(): boolean {243// This tiny helper avoids converting the entire debug model to use service injection244const screenReaderOptimized = this.accessibilityService.isScreenReaderOptimized();245const value = this.configurationService.getValue<IDebugConfiguration>('debug').autoExpandLazyVariables;246return value === 'auto' && screenReaderOptimized || value === 'on';247}248249setConfiguration(configuration: { resolved: IConfig; unresolved: IConfig | undefined }) {250this._configuration = configuration;251}252253getLabel(): string {254const includeRoot = this.workspaceContextService.getWorkspace().folders.length > 1;255return includeRoot && this.root ? `${this.name} (${resources.basenameOrAuthority(this.root.uri)})` : this.name;256}257258setName(name: string): void {259this._name = name;260this._onDidChangeName.fire(name);261}262263get name(): string {264return this._name || this.configuration.name;265}266267get state(): State {268if (!this.initialized) {269return State.Initializing;270}271if (!this.raw) {272return State.Inactive;273}274275const focusedThread = this.debugService.getViewModel().focusedThread;276if (focusedThread && focusedThread.session === this) {277return focusedThread.stopped ? State.Stopped : State.Running;278}279if (this.getAllThreads().some(t => t.stopped)) {280return State.Stopped;281}282283return State.Running;284}285286get capabilities(): DebugProtocol.Capabilities {287return this.raw ? this.raw.capabilities : Object.create(null);288}289290//---- events291get onDidChangeState(): Event<void> {292return this._onDidChangeState.event;293}294295get onDidEndAdapter(): Event<AdapterEndEvent | undefined> {296return this._onDidEndAdapter.event;297}298299get onDidChangeReplElements(): Event<IReplElement | undefined> {300return this._onDidChangeREPLElements.event;301}302303get onDidChangeName(): Event<string> {304return this._onDidChangeName.event;305}306307//---- DAP events308309get onDidCustomEvent(): Event<DebugProtocol.Event> {310return this._onDidCustomEvent.event;311}312313get onDidLoadedSource(): Event<LoadedSourceEvent> {314return this._onDidLoadedSource.event;315}316317get onDidProgressStart(): Event<DebugProtocol.ProgressStartEvent> {318return this._onDidProgressStart.event;319}320321get onDidProgressUpdate(): Event<DebugProtocol.ProgressUpdateEvent> {322return this._onDidProgressUpdate.event;323}324325get onDidProgressEnd(): Event<DebugProtocol.ProgressEndEvent> {326return this._onDidProgressEnd.event;327}328329get onDidInvalidateMemory(): Event<DebugProtocol.MemoryEvent> {330return this._onDidInvalidMemory.event;331}332333//---- DAP requests334335/**336* create and initialize a new debug adapter for this session337*/338async initialize(dbgr: IDebugger): Promise<void> {339340if (this.raw) {341// if there was already a connection make sure to remove old listeners342await this.shutdown();343}344345try {346const debugAdapter = await dbgr.createDebugAdapter(this);347this.raw = this.instantiationService.createInstance(RawDebugSession, debugAdapter, dbgr, this.id, this.configuration.name);348349await this.raw.start();350this.registerListeners();351await this.raw.initialize({352clientID: 'vscode',353clientName: this.productService.nameLong,354adapterID: this.configuration.type,355pathFormat: 'path',356linesStartAt1: true,357columnsStartAt1: true,358supportsVariableType: true, // #8858359supportsVariablePaging: true, // #9537360supportsRunInTerminalRequest: true, // #10574361locale: platform.language, // #169114362supportsProgressReporting: true, // #92253363supportsInvalidatedEvent: true, // #106745364supportsMemoryReferences: true, //#129684365supportsArgsCanBeInterpretedByShell: true, // #149910366supportsMemoryEvent: true, // #133643367supportsStartDebuggingRequest: true,368supportsANSIStyling: true,369});370371this.initialized = true;372this._onDidChangeState.fire();373this.rememberedCapabilities = this.raw.capabilities;374this.debugService.setExceptionBreakpointsForSession(this, (this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []);375this.debugService.getModel().registerBreakpointModes(this.configuration.type, this.raw.capabilities.breakpointModes || []);376} catch (err) {377this.initialized = true;378this._onDidChangeState.fire();379await this.shutdown();380throw err;381}382}383384/**385* launch or attach to the debuggee386*/387async launchOrAttach(config: IConfig): Promise<void> {388if (!this.raw) {389throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'launch or attach'));390}391if (this.parentSession && this.parentSession.state === State.Inactive) {392throw canceled();393}394395// __sessionID only used for EH debugging (but we add it always for now...)396config.__sessionId = this.getId();397try {398await this.raw.launchOrAttach(config);399} catch (err) {400this.shutdown();401throw err;402}403}404405/**406* Terminate any linked test run.407*/408cancelCorrelatedTestRun() {409if (this.correlatedTestRun && !this.correlatedTestRun.completedAt) {410this.didTerminateTestRun = true;411this.testService.cancelTestRun(this.correlatedTestRun.id);412}413}414415/**416* terminate the current debug adapter session417*/418async terminate(restart = false): Promise<void> {419if (!this.raw) {420// Adapter went down but it did not send a 'terminated' event, simulate like the event has been sent421this.onDidExitAdapter();422}423424this.cancelAllRequests();425if (this._options.lifecycleManagedByParent && this.parentSession) {426await this.parentSession.terminate(restart);427} else if (this.correlatedTestRun && !this.correlatedTestRun.completedAt && !this.didTerminateTestRun) {428this.cancelCorrelatedTestRun();429} else if (this.raw) {430if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') {431await this.raw.terminate(restart);432} else {433await this.raw.disconnect({ restart, terminateDebuggee: true });434}435}436437if (!restart) {438this._options.compoundRoot?.sessionStopped();439}440}441442/**443* end the current debug adapter session444*/445async disconnect(restart = false, suspend = false): Promise<void> {446if (!this.raw) {447// Adapter went down but it did not send a 'terminated' event, simulate like the event has been sent448this.onDidExitAdapter();449}450451this.cancelAllRequests();452if (this._options.lifecycleManagedByParent && this.parentSession) {453await this.parentSession.disconnect(restart, suspend);454} else if (this.raw) {455// TODO terminateDebuggee should be undefined by default?456await this.raw.disconnect({ restart, terminateDebuggee: false, suspendDebuggee: suspend });457}458459if (!restart) {460this._options.compoundRoot?.sessionStopped();461}462}463464/**465* restart debug adapter session466*/467async restart(): Promise<void> {468if (!this.raw) {469throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'restart'));470}471472this.cancelAllRequests();473if (this._options.lifecycleManagedByParent && this.parentSession) {474await this.parentSession.restart();475} else {476await this.raw.restart({ arguments: this.configuration });477}478}479480async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> {481if (!this.raw) {482throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints'));483}484485if (!this.raw.readyForBreakpoints) {486return Promise.resolve(undefined);487}488489const rawSource = this.getRawSource(modelUri);490if (breakpointsToSend.length && !rawSource.adapterData) {491rawSource.adapterData = breakpointsToSend[0].adapterData;492}493// Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959494if (rawSource.path) {495rawSource.path = normalizeDriveLetter(rawSource.path);496}497498const response = await this.raw.setBreakpoints({499source: rawSource,500lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber),501breakpoints: breakpointsToSend.map(bp => bp.toDAP()),502sourceModified503});504if (response?.body) {505const data = new Map<string, DebugProtocol.Breakpoint>();506for (let i = 0; i < breakpointsToSend.length; i++) {507data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]);508}509510this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);511}512}513514async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> {515if (!this.raw) {516throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'function breakpoints'));517}518519if (this.raw.readyForBreakpoints) {520const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts.map(bp => bp.toDAP()) });521if (response?.body) {522const data = new Map<string, DebugProtocol.Breakpoint>();523for (let i = 0; i < fbpts.length; i++) {524data.set(fbpts[i].getId(), response.body.breakpoints[i]);525}526this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);527}528}529}530531async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {532if (!this.raw) {533throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'exception breakpoints'));534}535536if (this.raw.readyForBreakpoints) {537const args: DebugProtocol.SetExceptionBreakpointsArguments = this.capabilities.supportsExceptionFilterOptions ? {538filters: [],539filterOptions: exbpts.map(exb => {540if (exb.condition) {541return { filterId: exb.filter, condition: exb.condition };542}543544return { filterId: exb.filter };545})546} : { filters: exbpts.map(exb => exb.filter) };547548const response = await this.raw.setExceptionBreakpoints(args);549if (response?.body && response.body.breakpoints) {550const data = new Map<string, DebugProtocol.Breakpoint>();551for (let i = 0; i < exbpts.length; i++) {552data.set(exbpts[i].getId(), response.body.breakpoints[i]);553}554555this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);556}557}558}559560dataBytesBreakpointInfo(address: string, bytes: number): Promise<IDataBreakpointInfoResponse | undefined> {561if (this.raw?.capabilities.supportsDataBreakpointBytes === false) {562throw new Error(localize('sessionDoesNotSupporBytesBreakpoints', "Session does not support breakpoints with bytes"));563}564565return this._dataBreakpointInfo({ name: address, bytes, asAddress: true });566}567568dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {569return this._dataBreakpointInfo({ name, variablesReference });570}571572private async _dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {573if (!this.raw) {574throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints info'));575}576if (!this.raw.readyForBreakpoints) {577throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"));578}579580const response = await this.raw.dataBreakpointInfo(args);581return response?.body;582}583584async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise<void> {585if (!this.raw) {586throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints'));587}588589if (this.raw.readyForBreakpoints) {590const converted = await Promise.all(dataBreakpoints.map(async bp => {591try {592const dap = await bp.toDAP(this);593return { dap, bp };594} catch (e) {595return { bp, message: e.message };596}597}));598const response = await this.raw.setDataBreakpoints({ breakpoints: converted.map(d => d.dap).filter(isDefined) });599if (response?.body) {600const data = new Map<string, DebugProtocol.Breakpoint>();601let i = 0;602for (const dap of converted) {603if (!dap.dap) {604data.set(dap.bp.getId(), dap.message);605} else if (i < response.body.breakpoints.length) {606data.set(dap.bp.getId(), response.body.breakpoints[i++]);607}608}609this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);610}611}612}613614async sendInstructionBreakpoints(instructionBreakpoints: IInstructionBreakpoint[]): Promise<void> {615if (!this.raw) {616throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'instruction breakpoints'));617}618619if (this.raw.readyForBreakpoints) {620const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toDAP()) });621if (response?.body) {622const data = new Map<string, DebugProtocol.Breakpoint>();623for (let i = 0; i < instructionBreakpoints.length; i++) {624data.set(instructionBreakpoints[i].getId(), response.body.breakpoints[i]);625}626this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);627}628}629}630631async breakpointsLocations(uri: URI, lineNumber: number): Promise<IPosition[]> {632if (!this.raw) {633throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints locations'));634}635636const source = this.getRawSource(uri);637const response = await this.raw.breakpointLocations({ source, line: lineNumber });638if (!response || !response.body || !response.body.breakpoints) {639return [];640}641642const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 }));643644return distinct(positions, p => `${p.lineNumber}:${p.column}`);645}646647getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined {648return this.model.getDebugProtocolBreakpoint(breakpointId, this.getId());649}650651customRequest(request: string, args: any): Promise<DebugProtocol.Response | undefined> {652if (!this.raw) {653throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", request));654}655656return this.raw.custom(request, args);657}658659stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise<DebugProtocol.StackTraceResponse | undefined> {660if (!this.raw) {661throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stackTrace'));662}663664const sessionToken = this.getNewCancellationToken(threadId, token);665return this.raw.stackTrace({ threadId, startFrame, levels }, sessionToken);666}667668async exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> {669if (!this.raw) {670throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'exceptionInfo'));671}672673const response = await this.raw.exceptionInfo({ threadId });674if (response) {675return {676id: response.body.exceptionId,677description: response.body.description,678breakMode: response.body.breakMode,679details: response.body.details680};681}682683return undefined;684}685686scopes(frameId: number, threadId: number): Promise<DebugProtocol.ScopesResponse | undefined> {687if (!this.raw) {688throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'scopes'));689}690691const token = this.getNewCancellationToken(threadId);692return this.raw.scopes({ frameId }, token);693}694695variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse | undefined> {696if (!this.raw) {697throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'variables'));698}699700const token = threadId ? this.getNewCancellationToken(threadId) : undefined;701return this.raw.variables({ variablesReference, filter, start, count }, token);702}703704evaluate(expression: string, frameId: number, context?: string, location?: { line: number; column: number; source: DebugProtocol.Source }): Promise<DebugProtocol.EvaluateResponse | undefined> {705if (!this.raw) {706throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'evaluate'));707}708709return this.raw.evaluate({ expression, frameId, context, line: location?.line, column: location?.column, source: location?.source });710}711712async restartFrame(frameId: number, threadId: number): Promise<void> {713await this.waitForTriggeredBreakpoints();714if (!this.raw) {715throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'restartFrame'));716}717718await this.raw.restartFrame({ frameId }, threadId);719}720721private setLastSteppingGranularity(threadId: number, granularity?: DebugProtocol.SteppingGranularity) {722const thread = this.getThread(threadId);723if (thread) {724thread.lastSteppingGranularity = granularity;725}726}727728async next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {729await this.waitForTriggeredBreakpoints();730if (!this.raw) {731throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'next'));732}733734this.setLastSteppingGranularity(threadId, granularity);735await this.raw.next({ threadId, granularity });736}737738async stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {739await this.waitForTriggeredBreakpoints();740if (!this.raw) {741throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepIn'));742}743744this.setLastSteppingGranularity(threadId, granularity);745await this.raw.stepIn({ threadId, targetId, granularity });746}747748async stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {749await this.waitForTriggeredBreakpoints();750if (!this.raw) {751throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepOut'));752}753754this.setLastSteppingGranularity(threadId, granularity);755await this.raw.stepOut({ threadId, granularity });756}757758async stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {759await this.waitForTriggeredBreakpoints();760if (!this.raw) {761throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepBack'));762}763764this.setLastSteppingGranularity(threadId, granularity);765await this.raw.stepBack({ threadId, granularity });766}767768async continue(threadId: number): Promise<void> {769await this.waitForTriggeredBreakpoints();770if (!this.raw) {771throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'continue'));772}773774await this.raw.continue({ threadId });775}776777async reverseContinue(threadId: number): Promise<void> {778await this.waitForTriggeredBreakpoints();779if (!this.raw) {780throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'reverse continue'));781}782783await this.raw.reverseContinue({ threadId });784}785786async pause(threadId: number): Promise<void> {787if (!this.raw) {788throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'pause'));789}790791await this.raw.pause({ threadId });792}793794async terminateThreads(threadIds?: number[]): Promise<void> {795if (!this.raw) {796throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'terminateThreads'));797}798799await this.raw.terminateThreads({ threadIds });800}801802setVariable(variablesReference: number, name: string, value: string): Promise<DebugProtocol.SetVariableResponse | undefined> {803if (!this.raw) {804throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'setVariable'));805}806807return this.raw.setVariable({ variablesReference, name, value });808}809810setExpression(frameId: number, expression: string, value: string): Promise<DebugProtocol.SetExpressionResponse | undefined> {811if (!this.raw) {812throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'setExpression'));813}814815return this.raw.setExpression({ expression, value, frameId });816}817818gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise<DebugProtocol.GotoTargetsResponse | undefined> {819if (!this.raw) {820throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'gotoTargets'));821}822823return this.raw.gotoTargets({ source, line, column });824}825826goto(threadId: number, targetId: number): Promise<DebugProtocol.GotoResponse | undefined> {827if (!this.raw) {828throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'goto'));829}830831return this.raw.goto({ threadId, targetId });832}833834loadSource(resource: URI): Promise<DebugProtocol.SourceResponse | undefined> {835if (!this.raw) {836return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'loadSource')));837}838839const source = this.getSourceForUri(resource);840let rawSource: DebugProtocol.Source;841if (source) {842rawSource = source.raw;843} else {844// create a Source845const data = Source.getEncodedDebugData(resource);846rawSource = { path: data.path, sourceReference: data.sourceReference };847}848849return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource });850}851852async getLoadedSources(): Promise<Source[]> {853if (!this.raw) {854return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'getLoadedSources')));855}856857const response = await this.raw.loadedSources({});858if (response?.body && response.body.sources) {859return response.body.sources.map(src => this.getSource(src));860} else {861return [];862}863}864865async completions(frameId: number | undefined, threadId: number, text: string, position: Position, token: CancellationToken): Promise<DebugProtocol.CompletionsResponse | undefined> {866if (!this.raw) {867return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'completions')));868}869const sessionCancelationToken = this.getNewCancellationToken(threadId, token);870871return this.raw.completions({872frameId,873text,874column: position.column,875line: position.lineNumber,876}, sessionCancelationToken);877}878879async stepInTargets(frameId: number): Promise<{ id: number; label: string }[] | undefined> {880if (!this.raw) {881return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepInTargets')));882}883884const response = await this.raw.stepInTargets({ frameId });885return response?.body.targets;886}887888async cancel(progressId: string): Promise<DebugProtocol.CancelResponse | undefined> {889if (!this.raw) {890return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'cancel')));891}892893return this.raw.cancel({ progressId });894}895896async disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise<DebugProtocol.DisassembledInstruction[] | undefined> {897if (!this.raw) {898return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble')));899}900901const response = await this.raw.disassemble({ memoryReference, offset, instructionOffset, instructionCount, resolveSymbols: true });902return response?.body?.instructions;903}904905readMemory(memoryReference: string, offset: number, count: number): Promise<DebugProtocol.ReadMemoryResponse | undefined> {906if (!this.raw) {907return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'readMemory')));908}909910return this.raw.readMemory({ count, memoryReference, offset });911}912913writeMemory(memoryReference: string, offset: number, data: string, allowPartial?: boolean): Promise<DebugProtocol.WriteMemoryResponse | undefined> {914if (!this.raw) {915return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble')));916}917918return this.raw.writeMemory({ memoryReference, offset, allowPartial, data });919}920921async resolveLocationReference(locationReference: number): Promise<IDebugLocationReferenced> {922if (!this.raw) {923throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations'));924}925926const location = await this.raw.locations({ locationReference });927if (!location?.body) {928throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations'));929}930931const source = this.getSource(location.body.source);932return { column: 1, ...location.body, source };933}934935//---- threads936937getThread(threadId: number): Thread | undefined {938return this.threads.get(threadId);939}940941getAllThreads(): IThread[] {942const result: IThread[] = [];943this.threadIds.forEach((threadId) => {944const thread = this.threads.get(threadId);945if (thread) {946result.push(thread);947}948});949return result;950}951952clearThreads(removeThreads: boolean, reference: number | undefined = undefined): void {953if (reference !== undefined && reference !== null) {954const thread = this.threads.get(reference);955if (thread) {956thread.clearCallStack();957thread.stoppedDetails = undefined;958thread.stopped = false;959960if (removeThreads) {961this.threads.delete(reference);962}963}964} else {965this.threads.forEach(thread => {966thread.clearCallStack();967thread.stoppedDetails = undefined;968thread.stopped = false;969});970971if (removeThreads) {972this.threads.clear();973this.threadIds = [];974ExpressionContainer.allValues.clear();975}976}977}978979getStoppedDetails(): IRawStoppedDetails | undefined {980return this.stoppedDetails.length >= 1 ? this.stoppedDetails[0] : undefined;981}982983rawUpdate(data: IRawModelUpdate): void {984this.threadIds = [];985data.threads.forEach(thread => {986this.threadIds.push(thread.id);987if (!this.threads.has(thread.id)) {988// A new thread came in, initialize it.989this.threads.set(thread.id, new Thread(this, thread.name, thread.id));990} else if (thread.name) {991// Just the thread name got updated #18244992const oldThread = this.threads.get(thread.id);993if (oldThread) {994oldThread.name = thread.name;995}996}997});998this.threads.forEach(t => {999// Remove all old threads which are no longer part of the update #759801000if (this.threadIds.indexOf(t.threadId) === -1) {1001this.threads.delete(t.threadId);1002}1003});10041005const stoppedDetails = data.stoppedDetails;1006if (stoppedDetails) {1007// Set the availability of the threads' callstacks depending on1008// whether the thread is stopped or not1009if (stoppedDetails.allThreadsStopped) {1010this.threads.forEach(thread => {1011thread.stoppedDetails = thread.threadId === stoppedDetails.threadId ? stoppedDetails : { reason: thread.stoppedDetails?.reason };1012thread.stopped = true;1013thread.clearCallStack();1014});1015} else {1016const thread = typeof stoppedDetails.threadId === 'number' ? this.threads.get(stoppedDetails.threadId) : undefined;1017if (thread) {1018// One thread is stopped, only update that thread.1019thread.stoppedDetails = stoppedDetails;1020thread.clearCallStack();1021thread.stopped = true;1022}1023}1024}1025}10261027private waitForTriggeredBreakpoints() {1028if (!this._waitToResume) {1029return;1030}10311032return raceTimeout(1033this._waitToResume,1034TRIGGERED_BREAKPOINT_MAX_DELAY1035);1036}10371038private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise<void> {1039if (this.raw) {1040const response = await this.raw.threads();1041if (response?.body && response.body.threads) {1042this.model.rawUpdate({1043sessionId: this.getId(),1044threads: response.body.threads,1045stoppedDetails1046});1047}1048}1049}10501051initializeForTest(raw: RawDebugSession): void {1052this.raw = raw;1053this.registerListeners();1054}10551056//---- private10571058private registerListeners(): void {1059if (!this.raw) {1060return;1061}10621063this.rawListeners.add(this.raw.onDidInitialize(async () => {1064aria.status(1065this.configuration.noDebug1066? localize('debuggingStartedNoDebug', "Started running without debugging.")1067: localize('debuggingStarted', "Debugging started.")1068);10691070const sendConfigurationDone = async () => {1071if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) {1072try {1073await this.raw.configurationDone();1074} catch (e) {1075// Disconnect the debug session on configuration done error #105961076this.notificationService.error(e);1077this.raw?.disconnect({});1078}1079}10801081return undefined;1082};10831084// Send all breakpoints1085try {1086await this.debugService.sendAllBreakpoints(this);1087} finally {1088await sendConfigurationDone();1089await this.fetchThreads();1090}1091}));109210931094const statusQueue = this.statusQueue;1095this.rawListeners.add(this.raw.onDidStop(event => this.handleStop(event.body)));10961097this.rawListeners.add(this.raw.onDidThread(event => {1098statusQueue.cancel([event.body.threadId]);1099if (event.body.reason === 'started') {1100// debounce to reduce threadsRequest frequency and improve performance1101if (!this.fetchThreadsScheduler) {1102this.fetchThreadsScheduler = new RunOnceScheduler(() => {1103this.fetchThreads();1104}, 100);1105this.rawListeners.add(this.fetchThreadsScheduler);1106}1107if (!this.fetchThreadsScheduler.isScheduled()) {1108this.fetchThreadsScheduler.schedule();1109}1110} else if (event.body.reason === 'exited') {1111this.model.clearThreads(this.getId(), true, event.body.threadId);1112const viewModel = this.debugService.getViewModel();1113const focusedThread = viewModel.focusedThread;1114this.passFocusScheduler.cancel();1115if (focusedThread && event.body.threadId === focusedThread.threadId) {1116// De-focus the thread in case it was focused1117this.debugService.focusStackFrame(undefined, undefined, viewModel.focusedSession, { explicit: false });1118}1119}1120}));11211122this.rawListeners.add(this.raw.onDidTerminateDebugee(async event => {1123aria.status(localize('debuggingStopped', "Debugging stopped."));1124if (event.body && event.body.restart) {1125await this.debugService.restartSession(this, event.body.restart);1126} else if (this.raw) {1127await this.raw.disconnect({ terminateDebuggee: false });1128}1129}));11301131this.rawListeners.add(this.raw.onDidContinued(event => {1132const allThreads = event.body.allThreadsContinued !== false;11331134statusQueue.cancel(allThreads ? undefined : [event.body.threadId]);11351136const threadId = allThreads ? undefined : event.body.threadId;1137if (typeof threadId === 'number') {1138this.stoppedDetails = this.stoppedDetails.filter(sd => sd.threadId !== threadId);1139const tokens = this.cancellationMap.get(threadId);1140this.cancellationMap.delete(threadId);1141tokens?.forEach(t => t.dispose(true));1142} else {1143this.stoppedDetails = [];1144this.cancelAllRequests();1145}1146this.lastContinuedThreadId = threadId;1147// We need to pass focus to other sessions / threads with a timeout in case a quick stop event occurs #1303211148this.passFocusScheduler.schedule();1149this.model.clearThreads(this.getId(), false, threadId);1150this._onDidChangeState.fire();1151}));11521153const outputQueue = new Queue<void>();1154this.rawListeners.add(this.raw.onDidOutput(async event => {1155const outputSeverity = event.body.category === 'stderr' ? Severity.Error : event.body.category === 'console' ? Severity.Warning : Severity.Info;11561157// When a variables event is received, execute immediately to obtain the variables value #1269671158if (event.body.variablesReference) {1159const source = event.body.source && event.body.line ? {1160lineNumber: event.body.line,1161column: event.body.column ? event.body.column : 1,1162source: this.getSource(event.body.source)1163} : undefined;1164const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());1165const children = container.getChildren();1166// we should put appendToRepl into queue to make sure the logs to be displayed in correct order1167// see https://github.com/microsoft/vscode/issues/126967#issuecomment-8749542691168outputQueue.queue(async () => {1169const resolved = await children;1170// For single logged variables, try to use the output if we can so1171// present a better (i.e. ANSI-aware) representation of the output1172if (resolved.length === 1) {1173this.appendToRepl({ output: event.body.output, expression: resolved[0], sev: outputSeverity, source }, event.body.category === 'important');1174return;1175}11761177resolved.forEach((child) => {1178// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)1179(<any>child).name = null;1180this.appendToRepl({ output: '', expression: child, sev: outputSeverity, source }, event.body.category === 'important');1181});1182});1183return;1184}1185outputQueue.queue(async () => {1186if (!event.body || !this.raw) {1187return;1188}11891190if (event.body.category === 'telemetry') {1191// only log telemetry events from debug adapter if the debug extension provided the telemetry key1192// and the user opted in telemetry1193const telemetryEndpoint = this.raw.dbgr.getCustomTelemetryEndpoint();1194if (telemetryEndpoint && this.telemetryService.telemetryLevel !== TelemetryLevel.NONE) {1195// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.1196let data = event.body.data;1197if (!telemetryEndpoint.sendErrorTelemetry && event.body.data) {1198data = filterExceptionsFromTelemetry(event.body.data);1199}12001201this.customEndpointTelemetryService.publicLog(telemetryEndpoint, event.body.output, data);1202}12031204return;1205}12061207// Make sure to append output in the correct order by properly waiting on preivous promises #338221208const source = event.body.source && event.body.line ? {1209lineNumber: event.body.line,1210column: event.body.column ? event.body.column : 1,1211source: this.getSource(event.body.source)1212} : undefined;12131214if (event.body.group === 'start' || event.body.group === 'startCollapsed') {1215const expanded = event.body.group === 'start';1216this.repl.startGroup(this, event.body.output || '', expanded, source);1217return;1218}1219if (event.body.group === 'end') {1220this.repl.endGroup();1221if (!event.body.output) {1222// Only return if the end event does not have additional output in it1223return;1224}1225}12261227if (typeof event.body.output === 'string') {1228this.appendToRepl({ output: event.body.output, sev: outputSeverity, source }, event.body.category === 'important');1229}1230});1231}));12321233this.rawListeners.add(this.raw.onDidBreakpoint(event => {1234const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined;1235const breakpoint = this.model.getBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);1236const functionBreakpoint = this.model.getFunctionBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);1237const dataBreakpoint = this.model.getDataBreakpoints().find(dbp => dbp.getIdFromAdapter(this.getId()) === id);1238const exceptionBreakpoint = this.model.getExceptionBreakpoints().find(excbp => excbp.getIdFromAdapter(this.getId()) === id);12391240if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) {1241const source = this.getSource(event.body.breakpoint.source);1242const bps = this.model.addBreakpoints(source.uri, [{1243column: event.body.breakpoint.column,1244enabled: true,1245lineNumber: event.body.breakpoint.line,1246}], false);1247if (bps.length === 1) {1248const data = new Map<string, DebugProtocol.Breakpoint>([[bps[0].getId(), event.body.breakpoint]]);1249this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1250}1251}12521253if (event.body.reason === 'removed') {1254if (breakpoint) {1255this.model.removeBreakpoints([breakpoint]);1256}1257if (functionBreakpoint) {1258this.model.removeFunctionBreakpoints(functionBreakpoint.getId());1259}1260if (dataBreakpoint) {1261this.model.removeDataBreakpoints(dataBreakpoint.getId());1262}1263}12641265if (event.body.reason === 'changed') {1266if (breakpoint) {1267if (!breakpoint.column) {1268event.body.breakpoint.column = undefined;1269}1270const data = new Map<string, DebugProtocol.Breakpoint>([[breakpoint.getId(), event.body.breakpoint]]);1271this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1272}1273if (functionBreakpoint) {1274const data = new Map<string, DebugProtocol.Breakpoint>([[functionBreakpoint.getId(), event.body.breakpoint]]);1275this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1276}1277if (dataBreakpoint) {1278const data = new Map<string, DebugProtocol.Breakpoint>([[dataBreakpoint.getId(), event.body.breakpoint]]);1279this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1280}1281if (exceptionBreakpoint) {1282const data = new Map<string, DebugProtocol.Breakpoint>([[exceptionBreakpoint.getId(), event.body.breakpoint]]);1283this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1284}1285}1286}));12871288this.rawListeners.add(this.raw.onDidLoadedSource(event => {1289this._onDidLoadedSource.fire({1290reason: event.body.reason,1291source: this.getSource(event.body.source)1292});1293}));12941295this.rawListeners.add(this.raw.onDidCustomEvent(event => {1296this._onDidCustomEvent.fire(event);1297}));12981299this.rawListeners.add(this.raw.onDidProgressStart(event => {1300this._onDidProgressStart.fire(event);1301}));1302this.rawListeners.add(this.raw.onDidProgressUpdate(event => {1303this._onDidProgressUpdate.fire(event);1304}));1305this.rawListeners.add(this.raw.onDidProgressEnd(event => {1306this._onDidProgressEnd.fire(event);1307}));1308this.rawListeners.add(this.raw.onDidInvalidateMemory(event => {1309this._onDidInvalidMemory.fire(event);1310}));1311this.rawListeners.add(this.raw.onDidInvalidated(async event => {1312const areas = event.body.areas || ['all'];1313// If invalidated event only requires to update variables or watch, do that, otherwise refetch threads https://github.com/microsoft/vscode/issues/1067451314if (areas.includes('threads') || areas.includes('stacks') || areas.includes('all')) {1315this.cancelAllRequests();1316this.model.clearThreads(this.getId(), true);13171318const details = this.stoppedDetails;1319this.stoppedDetails.length = 1;1320await Promise.all(details.map(d => this.handleStop(d)));1321}13221323const viewModel = this.debugService.getViewModel();1324if (viewModel.focusedSession === this) {1325viewModel.updateViews();1326}1327}));13281329this.rawListeners.add(this.raw.onDidExitAdapter(event => this.onDidExitAdapter(event)));1330}13311332private async handleStop(event: IRawStoppedDetails) {1333this.passFocusScheduler.cancel();1334this.stoppedDetails.push(event);13351336// do this very eagerly if we have hitBreakpointIds, since it may take a1337// moment for breakpoints to set and we want to do our best to not miss1338// anything1339if (event.hitBreakpointIds) {1340this._waitToResume = this.enableDependentBreakpoints(event.hitBreakpointIds);1341}13421343this.statusQueue.run(1344this.fetchThreads(event).then(() => event.threadId === undefined ? this.threadIds : [event.threadId]),1345async (threadId, token) => {1346const hasLotsOfThreads = event.threadId === undefined && this.threadIds.length > 10;13471348// If the focus for the current session is on a non-existent thread, clear the focus.1349const focusedThread = this.debugService.getViewModel().focusedThread;1350const focusedThreadDoesNotExist = focusedThread !== undefined && focusedThread.session === this && !this.threads.has(focusedThread.threadId);1351if (focusedThreadDoesNotExist) {1352this.debugService.focusStackFrame(undefined, undefined);1353}13541355const thread = typeof threadId === 'number' ? this.getThread(threadId) : undefined;1356if (thread) {1357// Call fetch call stack twice, the first only return the top stack frame.1358// Second retrieves the rest of the call stack. For performance reasons #256051359// Second call is only done if there's few threads that stopped in this event.1360const promises = this.model.refreshTopOfCallstack(<Thread>thread, /* fetchFullStack= */!hasLotsOfThreads);1361const focus = async () => {1362if (focusedThreadDoesNotExist || (!event.preserveFocusHint && thread.getCallStack().length)) {1363const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;1364if (!focusedStackFrame || focusedStackFrame.thread.session === this) {1365// Only take focus if nothing is focused, or if the focus is already on the current session1366const preserveFocus = !this.configurationService.getValue<IDebugConfiguration>('debug').focusEditorOnBreak;1367await this.debugService.focusStackFrame(undefined, thread, undefined, { preserveFocus });1368}13691370if (thread.stoppedDetails && !token.isCancellationRequested) {1371if (thread.stoppedDetails.reason === 'breakpoint' && this.configurationService.getValue<IDebugConfiguration>('debug').openDebug === 'openOnDebugBreak' && !this.suppressDebugView) {1372await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);1373}13741375if (this.configurationService.getValue<IDebugConfiguration>('debug').focusWindowOnBreak && !this.workbenchEnvironmentService.extensionTestsLocationURI) {1376const activeWindow = getActiveWindow();1377if (!activeWindow.document.hasFocus()) {1378await this.hostService.focus(mainWindow, { mode: FocusMode.Force /* Application may not be active */ });1379}1380}1381}1382}1383};13841385await promises.topCallStack;13861387if (!event.hitBreakpointIds) { // if hitBreakpointIds are present, this is handled earlier on1388this._waitToResume = this.enableDependentBreakpoints(thread);1389}13901391if (token.isCancellationRequested) {1392return;1393}13941395focus();13961397await promises.wholeCallStack;1398if (token.isCancellationRequested) {1399return;1400}14011402const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;1403if (!focusedStackFrame || isFrameDeemphasized(focusedStackFrame)) {1404// The top stack frame can be deemphesized so try to focus again #686161405focus();1406}1407}1408this._onDidChangeState.fire();1409},1410);1411}14121413private async enableDependentBreakpoints(hitBreakpointIdsOrThread: Thread | number[]) {1414let breakpoints: IBreakpoint[];1415if (Array.isArray(hitBreakpointIdsOrThread)) {1416breakpoints = this.model.getBreakpoints().filter(bp => hitBreakpointIdsOrThread.includes(bp.getIdFromAdapter(this.id)!));1417} else {1418const frame = hitBreakpointIdsOrThread.getTopStackFrame();1419if (frame === undefined) {1420return;1421}14221423if (hitBreakpointIdsOrThread.stoppedDetails && hitBreakpointIdsOrThread.stoppedDetails.reason !== 'breakpoint') {1424return;1425}14261427breakpoints = this.getBreakpointsAtPosition(frame.source.uri, frame.range.startLineNumber, frame.range.endLineNumber, frame.range.startColumn, frame.range.endColumn);1428}14291430// find the current breakpoints14311432// check if the current breakpoints are dependencies, and if so collect and send the dependents to DA1433const urisToResend = new Set<string>();1434this.model.getBreakpoints({ triggeredOnly: true, enabledOnly: true }).forEach(bp => {1435breakpoints.forEach(cbp => {1436if (bp.enabled && bp.triggeredBy === cbp.getId()) {1437bp.setSessionDidTrigger(this.getId());1438urisToResend.add(bp.uri.toString());1439}1440});1441});14421443const results: Promise<any>[] = [];1444urisToResend.forEach((uri) => results.push(this.debugService.sendBreakpoints(URI.parse(uri), undefined, this)));1445return Promise.all(results);1446}14471448private getBreakpointsAtPosition(uri: URI, startLineNumber: number, endLineNumber: number, startColumn: number, endColumn: number): IBreakpoint[] {1449return this.model.getBreakpoints({ uri: uri }).filter(bp => {1450if (bp.lineNumber < startLineNumber || bp.lineNumber > endLineNumber) {1451return false;1452}14531454if (bp.column && (bp.column < startColumn || bp.column > endColumn)) {1455return false;1456}1457return true;1458});1459}14601461private onDidExitAdapter(event?: AdapterEndEvent): void {1462this.initialized = true;1463this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined);1464this.shutdown();1465this._onDidEndAdapter.fire(event);1466}14671468// Disconnects and clears state. Session can be initialized again for a new connection.1469private shutdown(): void {1470this.rawListeners.clear();1471if (this.raw) {1472// Send out disconnect and immediatly dispose (do not wait for response) #1274181473this.raw.disconnect({});1474this.raw.dispose();1475this.raw = undefined;1476}1477this.fetchThreadsScheduler?.dispose();1478this.fetchThreadsScheduler = undefined;1479this.passFocusScheduler.cancel();1480this.passFocusScheduler.dispose();1481this.model.clearThreads(this.getId(), true);1482this._onDidChangeState.fire();1483}14841485public dispose() {1486this.cancelAllRequests();1487this.rawListeners.dispose();1488this.globalDisposables.dispose();1489}14901491//---- sources14921493getSourceForUri(uri: URI): Source | undefined {1494return this.sources.get(this.uriIdentityService.asCanonicalUri(uri).toString());1495}14961497getSource(raw?: DebugProtocol.Source): Source {1498let source = new Source(raw, this.getId(), this.uriIdentityService, this.logService);1499const uriKey = source.uri.toString();1500const found = this.sources.get(uriKey);1501if (found) {1502source = found;1503// merge attributes of new into existing1504source.raw = mixin(source.raw, raw);1505if (source.raw && raw) {1506// Always take the latest presentation hint from adapter #421391507source.raw.presentationHint = raw.presentationHint;1508}1509} else {1510this.sources.set(uriKey, source);1511}15121513return source;1514}15151516private getRawSource(uri: URI): DebugProtocol.Source {1517const source = this.getSourceForUri(uri);1518if (source) {1519return source.raw;1520} else {1521const data = Source.getEncodedDebugData(uri);1522return { name: data.name, path: data.path, sourceReference: data.sourceReference };1523}1524}15251526private getNewCancellationToken(threadId: number, token?: CancellationToken): CancellationToken {1527const tokenSource = new CancellationTokenSource(token);1528const tokens = this.cancellationMap.get(threadId) || [];1529tokens.push(tokenSource);1530this.cancellationMap.set(threadId, tokens);15311532return tokenSource.token;1533}15341535private cancelAllRequests(): void {1536this.cancellationMap.forEach(tokens => tokens.forEach(t => t.dispose(true)));1537this.cancellationMap.clear();1538}15391540// REPL15411542getReplElements(): IReplElement[] {1543return this.repl.getReplElements();1544}15451546hasSeparateRepl(): boolean {1547return !this.parentSession || this._options.repl !== 'mergeWithParent';1548}15491550removeReplExpressions(): void {1551this.repl.removeReplExpressions();1552}15531554async addReplExpression(stackFrame: IStackFrame | undefined, expression: string): Promise<void> {1555await this.repl.addReplExpression(this, stackFrame, expression);1556// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.1557this.debugService.getViewModel().updateViews();1558}15591560appendToRepl(data: INewReplElementData, isImportant?: boolean): void {1561this.repl.appendToRepl(this, data);1562if (isImportant) {1563this.notificationService.notify({ message: data.output.toString(), severity: data.sev, source: this.name });1564}1565}1566}15671568/**1569* Keeps track of events for threads, and cancels any previous operations for1570* a thread when the thread goes into a new state. Currently, the operations a thread has are:1571*1572* - started1573* - stopped1574* - continue1575* - exited1576*1577* In each case, the new state preempts the old state, so we don't need to1578* queue work, just cancel old work. It's up to the caller to make sure that1579* no UI effects happen at the point when the `token` is cancelled.1580*/1581export class ThreadStatusScheduler extends Disposable {1582/**1583* An array of set of thread IDs. When a 'stopped' event is encountered, the1584* editor refreshes its thread IDs. In the meantime, the thread may change1585* state it again. So the editor puts a Set into this array when it starts1586* the refresh, and checks it after the refresh is finished, to see if1587* any of the threads it looked up should now be invalidated.1588*/1589private pendingCancellations: Set<number | undefined>[] = [];15901591/**1592* Cancellation tokens for currently-running operations on threads.1593*/1594private readonly threadOps = this._register(new DisposableMap<number, CancellationTokenSource>());15951596/**1597* Runs the operation.1598* If thread is undefined it affects all threads.1599*/1600public async run(threadIdsP: Promise<number[]>, operation: (threadId: number, ct: CancellationToken) => Promise<unknown>) {1601const cancelledWhileLookingUpThreads = new Set<number | undefined>();1602this.pendingCancellations.push(cancelledWhileLookingUpThreads);1603const threadIds = await threadIdsP;16041605// Now that we got our threads,1606// 1. Remove our pending set, and1607// 2. Cancel any slower callers who might also have found this thread1608for (let i = 0; i < this.pendingCancellations.length; i++) {1609const s = this.pendingCancellations[i];1610if (s === cancelledWhileLookingUpThreads) {1611this.pendingCancellations.splice(i, 1);1612break;1613} else {1614for (const threadId of threadIds) {1615s.add(threadId);1616}1617}1618}16191620if (cancelledWhileLookingUpThreads.has(undefined)) {1621return;1622}16231624await Promise.all(threadIds.map(threadId => {1625if (cancelledWhileLookingUpThreads.has(threadId)) {1626return;1627}1628this.threadOps.get(threadId)?.cancel();1629const cts = new CancellationTokenSource();1630this.threadOps.set(threadId, cts);1631return operation(threadId, cts.token);1632}));1633}16341635/**1636* Cancels all ongoing state operations on the given threads.1637* If threads is undefined it cancel all threads.1638*/1639public cancel(threadIds?: readonly number[]) {1640if (!threadIds) {1641for (const [_, op] of this.threadOps) {1642op.cancel();1643}1644this.threadOps.clearAndDisposeAll();1645for (const s of this.pendingCancellations) {1646s.add(undefined);1647}1648} else {1649for (const threadId of threadIds) {1650this.threadOps.get(threadId)?.cancel();1651this.threadOps.deleteAndDispose(threadId);1652for (const s of this.pendingCancellations) {1653s.add(threadId);1654}1655}1656}1657}1658}165916601661