Path: blob/main/src/vs/workbench/contrib/debug/browser/debugSession.ts
5236 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 { Lazy } from '../../../../base/common/lazy.js';15import { Disposable, DisposableMap, DisposableStore, MutableDisposable, dispose } from '../../../../base/common/lifecycle.js';16import { mixin } from '../../../../base/common/objects.js';17import * as platform from '../../../../base/common/platform.js';18import * as resources from '../../../../base/common/resources.js';19import Severity from '../../../../base/common/severity.js';20import { isDefined } from '../../../../base/common/types.js';21import { URI } from '../../../../base/common/uri.js';22import { generateUuid } from '../../../../base/common/uuid.js';23import { IPosition, Position } from '../../../../editor/common/core/position.js';24import { localize } from '../../../../nls.js';25import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';26import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';27import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';28import { ILogService } from '../../../../platform/log/common/log.js';29import { FocusMode } from '../../../../platform/native/common/native.js';30import { INotificationService } from '../../../../platform/notification/common/notification.js';31import { IProductService } from '../../../../platform/product/common/productService.js';32import { ICustomEndpointTelemetryService, ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';33import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';34import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';35import { ViewContainerLocation } from '../../../common/views.js';36import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';37import { IHostService } from '../../../services/host/browser/host.js';38import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';39import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';40import { LiveTestResult } from '../../testing/common/testResult.js';41import { ITestResultService } from '../../testing/common/testResultService.js';42import { ITestService } from '../../testing/common/testService.js';43import { 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';44import { DebugCompoundRoot } from '../common/debugCompoundRoot.js';45import { DebugModel, ExpressionContainer, MemoryRegion, Thread } from '../common/debugModel.js';46import { Source } from '../common/debugSource.js';47import { filterExceptionsFromTelemetry } from '../common/debugUtils.js';48import { INewReplElementData, ReplModel } from '../common/replModel.js';49import { RawDebugSession } from './rawDebugSession.js';5051const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500;5253export class DebugSession implements IDebugSession {54parentSession: IDebugSession | undefined;55rememberedCapabilities?: DebugProtocol.Capabilities;5657private _subId: string | undefined;58raw: RawDebugSession | undefined; // used in tests59private initialized = false;60private _options: IDebugSessionOptions;6162private sources = new Map<string, Source>();63private threads = new Map<number, Thread>();64private threadIds: number[] = [];65private cancellationMap = new Map<number, CancellationTokenSource[]>();66private readonly rawListeners = new DisposableStore();67private readonly globalDisposables = new DisposableStore();68private fetchThreadsScheduler = new Lazy(() => {69const inst = new RunOnceScheduler(() => {70this.fetchThreads();71}, 100);72this.rawListeners.add(inst);73return inst;74});75private passFocusScheduler: RunOnceScheduler;76private lastContinuedThreadId: number | undefined;77private repl: ReplModel;78private stoppedDetails: IRawStoppedDetails[] = [];79private readonly statusQueue = this.rawListeners.add(new ThreadStatusScheduler());8081/** Test run this debug session was spawned by */82public readonly correlatedTestRun?: LiveTestResult;83/** Whether we terminated the correlated run yet. Used so a 2nd terminate request goes through to the underlying session. */84private didTerminateTestRun?: boolean;8586private readonly _onDidChangeState = new Emitter<void>();87private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent | undefined>();8889private readonly _onDidLoadedSource = new Emitter<LoadedSourceEvent>();90private readonly _onDidCustomEvent = new Emitter<DebugProtocol.Event>();91private readonly _onDidProgressStart = new Emitter<DebugProtocol.ProgressStartEvent>();92private readonly _onDidProgressUpdate = new Emitter<DebugProtocol.ProgressUpdateEvent>();93private readonly _onDidProgressEnd = new Emitter<DebugProtocol.ProgressEndEvent>();94private readonly _onDidInvalidMemory = new Emitter<DebugProtocol.MemoryEvent>();9596private readonly _onDidChangeREPLElements = new Emitter<IReplElement | undefined>();9798private _name: string | undefined;99private readonly _onDidChangeName = new Emitter<string>();100101/**102* Promise set while enabling dependent breakpoints to block the debugger103* from continuing from a stopped state.104*/105private _waitToResume?: Promise<unknown>;106107constructor(108private id: string,109private _configuration: { resolved: IConfig; unresolved: IConfig | undefined },110public root: IWorkspaceFolder | undefined,111private model: DebugModel,112options: IDebugSessionOptions | undefined,113@IDebugService private readonly debugService: IDebugService,114@ITelemetryService private readonly telemetryService: ITelemetryService,115@IHostService private readonly hostService: IHostService,116@IConfigurationService private readonly configurationService: IConfigurationService,117@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,118@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,119@IProductService private readonly productService: IProductService,120@INotificationService private readonly notificationService: INotificationService,121@ILifecycleService lifecycleService: ILifecycleService,122@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,123@IInstantiationService private readonly instantiationService: IInstantiationService,124@ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService,125@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,126@ILogService private readonly logService: ILogService,127@ITestService private readonly testService: ITestService,128@ITestResultService testResultService: ITestResultService,129@IAccessibilityService private readonly accessibilityService: IAccessibilityService,130) {131this._options = options || {};132this.parentSession = this._options.parentSession;133if (this.hasSeparateRepl()) {134this.repl = new ReplModel(this.configurationService);135} else {136this.repl = (this.parentSession as DebugSession).repl;137}138139const toDispose = this.globalDisposables;140const replListener = toDispose.add(new MutableDisposable());141replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e));142if (lifecycleService) {143toDispose.add(lifecycleService.onWillShutdown(() => {144this.shutdown();145dispose(toDispose);146}));147}148149// Cast here, it's not possible to reference a hydrated result in this code path.150this.correlatedTestRun = options?.testRun151? (testResultService.getResult(options.testRun.runId) as LiveTestResult)152: this.parentSession?.correlatedTestRun;153154if (this.correlatedTestRun) {155// Listen to the test completing because the user might have taken the cancel action rather than stopping the session.156toDispose.add(this.correlatedTestRun.onComplete(() => this.terminate()));157}158159const compoundRoot = this._options.compoundRoot;160if (compoundRoot) {161toDispose.add(compoundRoot.onDidSessionStop(() => this.terminate()));162}163this.passFocusScheduler = new RunOnceScheduler(() => {164// If there is some session or thread that is stopped pass focus to it165if (this.debugService.getModel().getSessions().some(s => s.state === State.Stopped) || this.getAllThreads().some(t => t.stopped)) {166if (typeof this.lastContinuedThreadId === 'number') {167const thread = this.debugService.getViewModel().focusedThread;168if (thread && thread.threadId === this.lastContinuedThreadId && !thread.stopped) {169const toFocusThreadId = this.getStoppedDetails()?.threadId;170const toFocusThread = typeof toFocusThreadId === 'number' ? this.getThread(toFocusThreadId) : undefined;171this.debugService.focusStackFrame(undefined, toFocusThread);172}173} else {174const session = this.debugService.getViewModel().focusedSession;175if (session && session.getId() === this.getId() && session.state !== State.Stopped) {176this.debugService.focusStackFrame(undefined);177}178}179}180}, 800);181182const parent = this._options.parentSession;183if (parent) {184toDispose.add(parent.onDidEndAdapter(() => {185// copy the parent repl and get a new detached repl for this child, and186// remove its parent, if it's still running187if (!this.hasSeparateRepl() && this.raw?.isInShutdown === false) {188this.repl = this.repl.clone();189replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e));190this.parentSession = undefined;191}192}));193}194}195196getId(): string {197return this.id;198}199200setSubId(subId: string | undefined) {201this._subId = subId;202}203204getMemory(memoryReference: string): IMemoryRegion {205return new MemoryRegion(memoryReference, this);206}207208get subId(): string | undefined {209return this._subId;210}211212get configuration(): IConfig {213return this._configuration.resolved;214}215216get unresolvedConfiguration(): IConfig | undefined {217return this._configuration.unresolved;218}219220get lifecycleManagedByParent(): boolean {221return !!this._options.lifecycleManagedByParent;222}223224get compact(): boolean {225return !!this._options.compact;226}227228get saveBeforeRestart(): boolean {229return this._options.saveBeforeRestart ?? !this._options?.parentSession;230}231232get compoundRoot(): DebugCompoundRoot | undefined {233return this._options.compoundRoot;234}235236get suppressDebugStatusbar(): boolean {237return this._options.suppressDebugStatusbar ?? false;238}239240get suppressDebugToolbar(): boolean {241return this._options.suppressDebugToolbar ?? false;242}243244get suppressDebugView(): boolean {245return this._options.suppressDebugView ?? false;246}247248249get autoExpandLazyVariables(): boolean {250// This tiny helper avoids converting the entire debug model to use service injection251const screenReaderOptimized = this.accessibilityService.isScreenReaderOptimized();252const value = this.configurationService.getValue<IDebugConfiguration>('debug').autoExpandLazyVariables;253return value === 'auto' && screenReaderOptimized || value === 'on';254}255256setConfiguration(configuration: { resolved: IConfig; unresolved: IConfig | undefined }) {257this._configuration = configuration;258}259260getLabel(): string {261const includeRoot = this.workspaceContextService.getWorkspace().folders.length > 1;262return includeRoot && this.root ? `${this.name} (${resources.basenameOrAuthority(this.root.uri)})` : this.name;263}264265setName(name: string): void {266this._name = name;267this._onDidChangeName.fire(name);268}269270get name(): string {271return this._name || this.configuration.name;272}273274get state(): State {275if (!this.initialized) {276return State.Initializing;277}278if (!this.raw) {279return State.Inactive;280}281282const focusedThread = this.debugService.getViewModel().focusedThread;283if (focusedThread && focusedThread.session === this) {284return focusedThread.stopped ? State.Stopped : State.Running;285}286if (this.getAllThreads().some(t => t.stopped)) {287return State.Stopped;288}289290return State.Running;291}292293get capabilities(): DebugProtocol.Capabilities {294return this.raw ? this.raw.capabilities : Object.create(null);295}296297//---- events298get onDidChangeState(): Event<void> {299return this._onDidChangeState.event;300}301302get onDidEndAdapter(): Event<AdapterEndEvent | undefined> {303return this._onDidEndAdapter.event;304}305306get onDidChangeReplElements(): Event<IReplElement | undefined> {307return this._onDidChangeREPLElements.event;308}309310get onDidChangeName(): Event<string> {311return this._onDidChangeName.event;312}313314//---- DAP events315316get onDidCustomEvent(): Event<DebugProtocol.Event> {317return this._onDidCustomEvent.event;318}319320get onDidLoadedSource(): Event<LoadedSourceEvent> {321return this._onDidLoadedSource.event;322}323324get onDidProgressStart(): Event<DebugProtocol.ProgressStartEvent> {325return this._onDidProgressStart.event;326}327328get onDidProgressUpdate(): Event<DebugProtocol.ProgressUpdateEvent> {329return this._onDidProgressUpdate.event;330}331332get onDidProgressEnd(): Event<DebugProtocol.ProgressEndEvent> {333return this._onDidProgressEnd.event;334}335336get onDidInvalidateMemory(): Event<DebugProtocol.MemoryEvent> {337return this._onDidInvalidMemory.event;338}339340//---- DAP requests341342/**343* create and initialize a new debug adapter for this session344*/345async initialize(dbgr: IDebugger): Promise<void> {346347if (this.raw) {348// if there was already a connection make sure to remove old listeners349await this.shutdown();350}351352try {353const debugAdapter = await dbgr.createDebugAdapter(this);354this.raw = this.instantiationService.createInstance(RawDebugSession, debugAdapter, dbgr, this.id, this.configuration.name);355356await this.raw.start();357this.registerListeners();358await this.raw.initialize({359clientID: 'vscode',360clientName: this.productService.nameLong,361adapterID: this.configuration.type,362pathFormat: 'path',363linesStartAt1: true,364columnsStartAt1: true,365supportsVariableType: true, // #8858366supportsVariablePaging: true, // #9537367supportsRunInTerminalRequest: true, // #10574368locale: platform.language, // #169114369supportsProgressReporting: true, // #92253370supportsInvalidatedEvent: true, // #106745371supportsMemoryReferences: true, //#129684372supportsArgsCanBeInterpretedByShell: true, // #149910373supportsMemoryEvent: true, // #133643374supportsStartDebuggingRequest: true,375supportsANSIStyling: true,376});377378this.initialized = true;379this._onDidChangeState.fire();380this.rememberedCapabilities = this.raw.capabilities;381this.debugService.setExceptionBreakpointsForSession(this, (this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []);382this.debugService.getModel().registerBreakpointModes(this.configuration.type, this.raw.capabilities.breakpointModes || []);383} catch (err) {384this.initialized = true;385this._onDidChangeState.fire();386await this.shutdown();387throw err;388}389}390391/**392* launch or attach to the debuggee393*/394async launchOrAttach(config: IConfig): Promise<void> {395if (!this.raw) {396throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'launch or attach'));397}398if (this.parentSession && this.parentSession.state === State.Inactive) {399throw canceled();400}401402// __sessionID only used for EH debugging (but we add it always for now...)403config.__sessionId = this.getId();404try {405await this.raw.launchOrAttach(config);406} catch (err) {407this.shutdown();408throw err;409}410}411412/**413* Terminate any linked test run.414*/415cancelCorrelatedTestRun() {416if (this.correlatedTestRun && !this.correlatedTestRun.completedAt) {417this.didTerminateTestRun = true;418this.testService.cancelTestRun(this.correlatedTestRun.id);419}420}421422/**423* terminate the current debug adapter session424*/425async terminate(restart = false): Promise<void> {426if (!this.raw) {427// Adapter went down but it did not send a 'terminated' event, simulate like the event has been sent428this.onDidExitAdapter();429}430431this.cancelAllRequests();432if (this._options.lifecycleManagedByParent && this.parentSession) {433await this.parentSession.terminate(restart);434} else if (this.correlatedTestRun && !this.correlatedTestRun.completedAt && !this.didTerminateTestRun) {435this.cancelCorrelatedTestRun();436} else if (this.raw) {437if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') {438await this.raw.terminate(restart);439} else {440await this.raw.disconnect({ restart, terminateDebuggee: true });441}442}443444if (!restart) {445this._options.compoundRoot?.sessionStopped();446}447}448449/**450* end the current debug adapter session451*/452async disconnect(restart = false, suspend = false): Promise<void> {453if (!this.raw) {454// Adapter went down but it did not send a 'terminated' event, simulate like the event has been sent455this.onDidExitAdapter();456}457458this.cancelAllRequests();459if (this._options.lifecycleManagedByParent && this.parentSession) {460await this.parentSession.disconnect(restart, suspend);461} else if (this.raw) {462// TODO terminateDebuggee should be undefined by default?463await this.raw.disconnect({ restart, terminateDebuggee: false, suspendDebuggee: suspend });464}465466if (!restart) {467this._options.compoundRoot?.sessionStopped();468}469}470471/**472* restart debug adapter session473*/474async restart(): Promise<void> {475if (!this.raw) {476throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'restart'));477}478479this.cancelAllRequests();480if (this._options.lifecycleManagedByParent && this.parentSession) {481await this.parentSession.restart();482} else {483await this.raw.restart({ arguments: this.configuration });484}485}486487async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> {488if (!this.raw) {489throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints'));490}491492if (!this.raw.readyForBreakpoints) {493return Promise.resolve(undefined);494}495496const rawSource = this.getRawSource(modelUri);497if (breakpointsToSend.length && !rawSource.adapterData) {498rawSource.adapterData = breakpointsToSend[0].adapterData;499}500// Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959501if (rawSource.path) {502rawSource.path = normalizeDriveLetter(rawSource.path);503}504505const response = await this.raw.setBreakpoints({506source: rawSource,507lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber),508breakpoints: breakpointsToSend.map(bp => bp.toDAP()),509sourceModified510});511if (response?.body) {512const data = new Map<string, DebugProtocol.Breakpoint>();513for (let i = 0; i < breakpointsToSend.length; i++) {514data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]);515}516517this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);518}519}520521async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> {522if (!this.raw) {523throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'function breakpoints'));524}525526if (this.raw.readyForBreakpoints) {527const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts.map(bp => bp.toDAP()) });528if (response?.body) {529const data = new Map<string, DebugProtocol.Breakpoint>();530for (let i = 0; i < fbpts.length; i++) {531data.set(fbpts[i].getId(), response.body.breakpoints[i]);532}533this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);534}535}536}537538async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {539if (!this.raw) {540throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'exception breakpoints'));541}542543if (this.raw.readyForBreakpoints) {544const args: DebugProtocol.SetExceptionBreakpointsArguments = this.capabilities.supportsExceptionFilterOptions ? {545filters: [],546filterOptions: exbpts.map(exb => {547if (exb.condition) {548return { filterId: exb.filter, condition: exb.condition };549}550551return { filterId: exb.filter };552})553} : { filters: exbpts.map(exb => exb.filter) };554555const response = await this.raw.setExceptionBreakpoints(args);556if (response?.body && response.body.breakpoints) {557const data = new Map<string, DebugProtocol.Breakpoint>();558for (let i = 0; i < exbpts.length; i++) {559data.set(exbpts[i].getId(), response.body.breakpoints[i]);560}561562this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);563}564}565}566567dataBytesBreakpointInfo(address: string, bytes: number): Promise<IDataBreakpointInfoResponse | undefined> {568if (this.raw?.capabilities.supportsDataBreakpointBytes === false) {569throw new Error(localize('sessionDoesNotSupporBytesBreakpoints', "Session does not support breakpoints with bytes"));570}571572return this._dataBreakpointInfo({ name: address, bytes, asAddress: true });573}574575dataBreakpointInfo(name: string, variablesReference?: number, frameId?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {576return this._dataBreakpointInfo({ name, variablesReference, frameId });577}578579private async _dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {580if (!this.raw) {581throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints info'));582}583if (!this.raw.readyForBreakpoints) {584throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"));585}586587const response = await this.raw.dataBreakpointInfo(args);588return response?.body;589}590591async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise<void> {592if (!this.raw) {593throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints'));594}595596if (this.raw.readyForBreakpoints) {597const converted = await Promise.all(dataBreakpoints.map(async bp => {598try {599const dap = await bp.toDAP(this);600return { dap, bp };601} catch (e) {602return { bp, message: e.message };603}604}));605const response = await this.raw.setDataBreakpoints({ breakpoints: converted.map(d => d.dap).filter(isDefined) });606if (response?.body) {607const data = new Map<string, DebugProtocol.Breakpoint>();608let i = 0;609for (const dap of converted) {610if (!dap.dap) {611data.set(dap.bp.getId(), dap.message);612} else if (i < response.body.breakpoints.length) {613data.set(dap.bp.getId(), response.body.breakpoints[i++]);614}615}616this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);617}618}619}620621async sendInstructionBreakpoints(instructionBreakpoints: IInstructionBreakpoint[]): Promise<void> {622if (!this.raw) {623throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'instruction breakpoints'));624}625626if (this.raw.readyForBreakpoints) {627const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toDAP()) });628if (response?.body) {629const data = new Map<string, DebugProtocol.Breakpoint>();630for (let i = 0; i < instructionBreakpoints.length; i++) {631data.set(instructionBreakpoints[i].getId(), response.body.breakpoints[i]);632}633this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);634}635}636}637638async breakpointsLocations(uri: URI, lineNumber: number): Promise<IPosition[]> {639if (!this.raw) {640throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints locations'));641}642643const source = this.getRawSource(uri);644const response = await this.raw.breakpointLocations({ source, line: lineNumber });645if (!response || !response.body || !response.body.breakpoints) {646return [];647}648649const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 }));650651return distinct(positions, p => `${p.lineNumber}:${p.column}`);652}653654getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined {655return this.model.getDebugProtocolBreakpoint(breakpointId, this.getId());656}657658customRequest(request: string, args: any): Promise<DebugProtocol.Response | undefined> {659if (!this.raw) {660throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", request));661}662663return this.raw.custom(request, args);664}665666stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise<DebugProtocol.StackTraceResponse | undefined> {667if (!this.raw) {668throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stackTrace'));669}670671const sessionToken = this.getNewCancellationToken(threadId, token);672return this.raw.stackTrace({ threadId, startFrame, levels }, sessionToken);673}674675async exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> {676if (!this.raw) {677throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'exceptionInfo'));678}679680const response = await this.raw.exceptionInfo({ threadId });681if (response) {682return {683id: response.body.exceptionId,684description: response.body.description,685breakMode: response.body.breakMode,686details: response.body.details687};688}689690return undefined;691}692693scopes(frameId: number, threadId: number): Promise<DebugProtocol.ScopesResponse | undefined> {694if (!this.raw) {695throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'scopes'));696}697698const token = this.getNewCancellationToken(threadId);699return this.raw.scopes({ frameId }, token);700}701702variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse | undefined> {703if (!this.raw) {704throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'variables'));705}706707const token = threadId ? this.getNewCancellationToken(threadId) : undefined;708return this.raw.variables({ variablesReference, filter, start, count }, token);709}710711evaluate(expression: string, frameId: number, context?: string, location?: { line: number; column: number; source: DebugProtocol.Source }): Promise<DebugProtocol.EvaluateResponse | undefined> {712if (!this.raw) {713throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'evaluate'));714}715716return this.raw.evaluate({ expression, frameId, context, line: location?.line, column: location?.column, source: location?.source });717}718719async restartFrame(frameId: number, threadId: number): Promise<void> {720await this.waitForTriggeredBreakpoints();721if (!this.raw) {722throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'restartFrame'));723}724725await this.raw.restartFrame({ frameId }, threadId);726}727728private setLastSteppingGranularity(threadId: number, granularity?: DebugProtocol.SteppingGranularity) {729const thread = this.getThread(threadId);730if (thread) {731thread.lastSteppingGranularity = granularity;732}733}734735async next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {736await this.waitForTriggeredBreakpoints();737if (!this.raw) {738throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'next'));739}740741this.setLastSteppingGranularity(threadId, granularity);742await this.raw.next({ threadId, granularity });743}744745async stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {746await this.waitForTriggeredBreakpoints();747if (!this.raw) {748throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepIn'));749}750751this.setLastSteppingGranularity(threadId, granularity);752await this.raw.stepIn({ threadId, targetId, granularity });753}754755async stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {756await this.waitForTriggeredBreakpoints();757if (!this.raw) {758throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepOut'));759}760761this.setLastSteppingGranularity(threadId, granularity);762await this.raw.stepOut({ threadId, granularity });763}764765async stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {766await this.waitForTriggeredBreakpoints();767if (!this.raw) {768throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepBack'));769}770771this.setLastSteppingGranularity(threadId, granularity);772await this.raw.stepBack({ threadId, granularity });773}774775async continue(threadId: number): Promise<void> {776await this.waitForTriggeredBreakpoints();777if (!this.raw) {778throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'continue'));779}780781await this.raw.continue({ threadId });782}783784async reverseContinue(threadId: number): Promise<void> {785await this.waitForTriggeredBreakpoints();786if (!this.raw) {787throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'reverse continue'));788}789790await this.raw.reverseContinue({ threadId });791}792793async pause(threadId: number): Promise<void> {794if (!this.raw) {795throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'pause'));796}797798await this.raw.pause({ threadId });799}800801async terminateThreads(threadIds?: number[]): Promise<void> {802if (!this.raw) {803throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'terminateThreads'));804}805806await this.raw.terminateThreads({ threadIds });807}808809setVariable(variablesReference: number, name: string, value: string): Promise<DebugProtocol.SetVariableResponse | undefined> {810if (!this.raw) {811throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'setVariable'));812}813814return this.raw.setVariable({ variablesReference, name, value });815}816817setExpression(frameId: number, expression: string, value: string): Promise<DebugProtocol.SetExpressionResponse | undefined> {818if (!this.raw) {819throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'setExpression'));820}821822return this.raw.setExpression({ expression, value, frameId });823}824825gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise<DebugProtocol.GotoTargetsResponse | undefined> {826if (!this.raw) {827throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'gotoTargets'));828}829830return this.raw.gotoTargets({ source, line, column });831}832833goto(threadId: number, targetId: number): Promise<DebugProtocol.GotoResponse | undefined> {834if (!this.raw) {835throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'goto'));836}837838return this.raw.goto({ threadId, targetId });839}840841loadSource(resource: URI): Promise<DebugProtocol.SourceResponse | undefined> {842if (!this.raw) {843return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'loadSource')));844}845846const source = this.getSourceForUri(resource);847let rawSource: DebugProtocol.Source;848if (source) {849rawSource = source.raw;850} else {851// create a Source852const data = Source.getEncodedDebugData(resource);853rawSource = { path: data.path, sourceReference: data.sourceReference };854}855856return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource });857}858859async getLoadedSources(): Promise<Source[]> {860if (!this.raw) {861return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'getLoadedSources')));862}863864const response = await this.raw.loadedSources({});865if (response?.body && response.body.sources) {866return response.body.sources.map(src => this.getSource(src));867} else {868return [];869}870}871872async completions(frameId: number | undefined, threadId: number, text: string, position: Position, token: CancellationToken): Promise<DebugProtocol.CompletionsResponse | undefined> {873if (!this.raw) {874return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'completions')));875}876const sessionCancelationToken = this.getNewCancellationToken(threadId, token);877878return this.raw.completions({879frameId,880text,881column: position.column,882line: position.lineNumber,883}, sessionCancelationToken);884}885886async stepInTargets(frameId: number): Promise<{ id: number; label: string }[] | undefined> {887if (!this.raw) {888return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepInTargets')));889}890891const response = await this.raw.stepInTargets({ frameId });892return response?.body.targets;893}894895async cancel(progressId: string): Promise<DebugProtocol.CancelResponse | undefined> {896if (!this.raw) {897return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'cancel')));898}899900return this.raw.cancel({ progressId });901}902903async disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise<DebugProtocol.DisassembledInstruction[] | undefined> {904if (!this.raw) {905return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble')));906}907908const response = await this.raw.disassemble({ memoryReference, offset, instructionOffset, instructionCount, resolveSymbols: true });909return response?.body?.instructions;910}911912readMemory(memoryReference: string, offset: number, count: number): Promise<DebugProtocol.ReadMemoryResponse | undefined> {913if (!this.raw) {914return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'readMemory')));915}916917return this.raw.readMemory({ count, memoryReference, offset });918}919920writeMemory(memoryReference: string, offset: number, data: string, allowPartial?: boolean): Promise<DebugProtocol.WriteMemoryResponse | undefined> {921if (!this.raw) {922return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble')));923}924925return this.raw.writeMemory({ memoryReference, offset, allowPartial, data });926}927928async resolveLocationReference(locationReference: number): Promise<IDebugLocationReferenced> {929if (!this.raw) {930throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations'));931}932933const location = await this.raw.locations({ locationReference });934if (!location?.body) {935throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations'));936}937938const source = this.getSource(location.body.source);939return { column: 1, ...location.body, source };940}941942//---- threads943944getThread(threadId: number): Thread | undefined {945return this.threads.get(threadId);946}947948getAllThreads(): IThread[] {949const result: IThread[] = [];950this.threadIds.forEach((threadId) => {951const thread = this.threads.get(threadId);952if (thread) {953result.push(thread);954}955});956return result;957}958959clearThreads(removeThreads: boolean, reference: number | undefined = undefined): void {960if (reference !== undefined && reference !== null) {961const thread = this.threads.get(reference);962if (thread) {963thread.clearCallStack();964thread.stoppedDetails = undefined;965thread.stopped = false;966967if (removeThreads) {968this.threads.delete(reference);969}970}971} else {972this.threads.forEach(thread => {973thread.clearCallStack();974thread.stoppedDetails = undefined;975thread.stopped = false;976});977978if (removeThreads) {979this.threads.clear();980this.threadIds = [];981ExpressionContainer.allValues.clear();982}983}984}985986getStoppedDetails(): IRawStoppedDetails | undefined {987return this.stoppedDetails.length >= 1 ? this.stoppedDetails[0] : undefined;988}989990rawUpdate(data: IRawModelUpdate): void {991this.threadIds = [];992data.threads.forEach(thread => {993this.threadIds.push(thread.id);994if (!this.threads.has(thread.id)) {995// A new thread came in, initialize it.996this.threads.set(thread.id, new Thread(this, thread.name, thread.id));997} else if (thread.name) {998// Just the thread name got updated #18244999const oldThread = this.threads.get(thread.id);1000if (oldThread) {1001oldThread.name = thread.name;1002}1003}1004});1005this.threads.forEach(t => {1006// Remove all old threads which are no longer part of the update #759801007if (this.threadIds.indexOf(t.threadId) === -1) {1008this.threads.delete(t.threadId);1009}1010});10111012const stoppedDetails = data.stoppedDetails;1013if (stoppedDetails) {1014// Set the availability of the threads' callstacks depending on1015// whether the thread is stopped or not1016if (stoppedDetails.allThreadsStopped) {1017this.threads.forEach(thread => {1018thread.stoppedDetails = thread.threadId === stoppedDetails.threadId ? stoppedDetails : { reason: thread.stoppedDetails?.reason };1019thread.stopped = true;1020thread.clearCallStack();1021});1022} else {1023const thread = typeof stoppedDetails.threadId === 'number' ? this.threads.get(stoppedDetails.threadId) : undefined;1024if (thread) {1025// One thread is stopped, only update that thread.1026thread.stoppedDetails = stoppedDetails;1027thread.clearCallStack();1028thread.stopped = true;1029}1030}1031}1032}10331034private waitForTriggeredBreakpoints() {1035if (!this._waitToResume) {1036return;1037}10381039return raceTimeout(1040this._waitToResume,1041TRIGGERED_BREAKPOINT_MAX_DELAY1042);1043}10441045private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise<void> {1046if (this.raw) {1047const response = await this.raw.threads();1048if (response?.body && response.body.threads) {1049this.model.rawUpdate({1050sessionId: this.getId(),1051threads: response.body.threads,1052stoppedDetails1053});1054}1055}1056}10571058initializeForTest(raw: RawDebugSession): void {1059this.raw = raw;1060this.registerListeners();1061}10621063//---- private10641065private registerListeners(): void {1066if (!this.raw) {1067return;1068}10691070this.rawListeners.add(this.raw.onDidInitialize(async () => {1071aria.status(1072this.configuration.noDebug1073? localize('debuggingStartedNoDebug', "Started running without debugging.")1074: localize('debuggingStarted', "Debugging started.")1075);10761077const sendConfigurationDone = async () => {1078if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) {1079try {1080await this.raw.configurationDone();1081} catch (e) {1082// Disconnect the debug session on configuration done error #105961083this.notificationService.error(e);1084this.raw?.disconnect({});1085}1086}10871088return undefined;1089};10901091// Send all breakpoints1092try {1093await this.debugService.sendAllBreakpoints(this);1094} finally {1095await sendConfigurationDone();1096await this.fetchThreads();1097}1098}));109911001101const statusQueue = this.statusQueue;1102this.rawListeners.add(this.raw.onDidStop(event => this.handleStop(event.body)));11031104this.rawListeners.add(this.raw.onDidThread(event => {1105statusQueue.cancel([event.body.threadId]);1106if (event.body.reason === 'started') {1107if (!this.fetchThreadsScheduler.value.isScheduled()) {1108this.fetchThreadsScheduler.value.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(async event => {1132const allThreads = event.body.allThreadsContinued !== false;11331134let affectedThreads: number[] | Promise<number[]>;1135if (!allThreads) {1136affectedThreads = [event.body.threadId];1137if (this.threadIds.includes(event.body.threadId)) {1138affectedThreads = [event.body.threadId];1139} else {1140this.fetchThreadsScheduler.rawValue?.cancel();1141affectedThreads = this.fetchThreads().then(() => [event.body.threadId]);1142}1143} else if (this.fetchThreadsScheduler.value.isScheduled()) {1144this.fetchThreadsScheduler.value.cancel();1145affectedThreads = this.fetchThreads().then(() => this.threadIds);1146} else {1147affectedThreads = this.threadIds;1148}11491150statusQueue.cancel(allThreads ? undefined : [event.body.threadId]);1151await statusQueue.run(affectedThreads, threadId => {1152this.stoppedDetails = this.stoppedDetails.filter(sd => sd.threadId !== threadId);1153const tokens = this.cancellationMap.get(threadId);1154this.cancellationMap.delete(threadId);1155tokens?.forEach(t => t.dispose(true));1156this.model.clearThreads(this.getId(), false, threadId);1157return Promise.resolve();1158});11591160// We need to pass focus to other sessions / threads with a timeout in case a quick stop event occurs #1303211161this.lastContinuedThreadId = allThreads ? undefined : event.body.threadId;1162this.passFocusScheduler.schedule();1163this._onDidChangeState.fire();1164}));11651166const outputQueue = new Queue<void>();1167this.rawListeners.add(this.raw.onDidOutput(async event => {1168const outputSeverity = event.body.category === 'stderr' ? Severity.Error : event.body.category === 'console' ? Severity.Warning : Severity.Info;11691170// When a variables event is received, execute immediately to obtain the variables value #1269671171if (event.body.variablesReference) {1172const source = event.body.source && event.body.line ? {1173lineNumber: event.body.line,1174column: event.body.column ? event.body.column : 1,1175source: this.getSource(event.body.source)1176} : undefined;1177const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());1178const children = container.getChildren();1179// we should put appendToRepl into queue to make sure the logs to be displayed in correct order1180// see https://github.com/microsoft/vscode/issues/126967#issuecomment-8749542691181outputQueue.queue(async () => {1182const resolved = await children;1183// For single logged variables, try to use the output if we can so1184// present a better (i.e. ANSI-aware) representation of the output1185if (resolved.length === 1) {1186this.appendToRepl({ output: event.body.output, expression: resolved[0], sev: outputSeverity, source }, event.body.category === 'important');1187return;1188}11891190resolved.forEach((child) => {1191// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)1192// eslint-disable-next-line local/code-no-any-casts1193(<any>child).name = null;1194this.appendToRepl({ output: '', expression: child, sev: outputSeverity, source }, event.body.category === 'important');1195});1196});1197return;1198}1199outputQueue.queue(async () => {1200if (!event.body || !this.raw) {1201return;1202}12031204if (event.body.category === 'telemetry') {1205// only log telemetry events from debug adapter if the debug extension provided the telemetry key1206// and the user opted in telemetry1207const telemetryEndpoint = this.raw.dbgr.getCustomTelemetryEndpoint();1208if (telemetryEndpoint && this.telemetryService.telemetryLevel !== TelemetryLevel.NONE) {1209// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.1210let data = event.body.data;1211if (!telemetryEndpoint.sendErrorTelemetry && event.body.data) {1212data = filterExceptionsFromTelemetry(event.body.data);1213}12141215this.customEndpointTelemetryService.publicLog(telemetryEndpoint, event.body.output, data);1216}12171218return;1219}12201221// Make sure to append output in the correct order by properly waiting on preivous promises #338221222const source = event.body.source && event.body.line ? {1223lineNumber: event.body.line,1224column: event.body.column ? event.body.column : 1,1225source: this.getSource(event.body.source)1226} : undefined;12271228if (event.body.group === 'start' || event.body.group === 'startCollapsed') {1229const expanded = event.body.group === 'start';1230this.repl.startGroup(this, event.body.output || '', expanded, source);1231return;1232}1233if (event.body.group === 'end') {1234this.repl.endGroup();1235if (!event.body.output) {1236// Only return if the end event does not have additional output in it1237return;1238}1239}12401241if (typeof event.body.output === 'string') {1242this.appendToRepl({ output: event.body.output, sev: outputSeverity, source }, event.body.category === 'important');1243}1244});1245}));12461247this.rawListeners.add(this.raw.onDidBreakpoint(event => {1248const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined;1249const breakpoint = this.model.getBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);1250const functionBreakpoint = this.model.getFunctionBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id);1251const dataBreakpoint = this.model.getDataBreakpoints().find(dbp => dbp.getIdFromAdapter(this.getId()) === id);1252const exceptionBreakpoint = this.model.getExceptionBreakpoints().find(excbp => excbp.getIdFromAdapter(this.getId()) === id);12531254if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) {1255const source = this.getSource(event.body.breakpoint.source);1256const bps = this.model.addBreakpoints(source.uri, [{1257column: event.body.breakpoint.column,1258enabled: true,1259lineNumber: event.body.breakpoint.line,1260}], false);1261if (bps.length === 1) {1262const data = new Map<string, DebugProtocol.Breakpoint>([[bps[0].getId(), event.body.breakpoint]]);1263this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1264}1265}12661267if (event.body.reason === 'removed') {1268if (breakpoint) {1269this.model.removeBreakpoints([breakpoint]);1270}1271if (functionBreakpoint) {1272this.model.removeFunctionBreakpoints(functionBreakpoint.getId());1273}1274if (dataBreakpoint) {1275this.model.removeDataBreakpoints(dataBreakpoint.getId());1276}1277}12781279if (event.body.reason === 'changed') {1280if (breakpoint) {1281if (!breakpoint.column) {1282event.body.breakpoint.column = undefined;1283}1284const data = new Map<string, DebugProtocol.Breakpoint>([[breakpoint.getId(), event.body.breakpoint]]);1285this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1286}1287if (functionBreakpoint) {1288const data = new Map<string, DebugProtocol.Breakpoint>([[functionBreakpoint.getId(), event.body.breakpoint]]);1289this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1290}1291if (dataBreakpoint) {1292const data = new Map<string, DebugProtocol.Breakpoint>([[dataBreakpoint.getId(), event.body.breakpoint]]);1293this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1294}1295if (exceptionBreakpoint) {1296const data = new Map<string, DebugProtocol.Breakpoint>([[exceptionBreakpoint.getId(), event.body.breakpoint]]);1297this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);1298}1299}1300}));13011302this.rawListeners.add(this.raw.onDidLoadedSource(event => {1303this._onDidLoadedSource.fire({1304reason: event.body.reason,1305source: this.getSource(event.body.source)1306});1307}));13081309this.rawListeners.add(this.raw.onDidCustomEvent(event => {1310this._onDidCustomEvent.fire(event);1311}));13121313this.rawListeners.add(this.raw.onDidProgressStart(event => {1314this._onDidProgressStart.fire(event);1315}));1316this.rawListeners.add(this.raw.onDidProgressUpdate(event => {1317this._onDidProgressUpdate.fire(event);1318}));1319this.rawListeners.add(this.raw.onDidProgressEnd(event => {1320this._onDidProgressEnd.fire(event);1321}));1322this.rawListeners.add(this.raw.onDidInvalidateMemory(event => {1323this._onDidInvalidMemory.fire(event);1324}));1325this.rawListeners.add(this.raw.onDidInvalidated(async event => {1326const areas = event.body.areas || ['all'];1327// If invalidated event only requires to update variables or watch, do that, otherwise refetch threads https://github.com/microsoft/vscode/issues/1067451328if (areas.includes('threads') || areas.includes('stacks') || areas.includes('all')) {1329this.cancelAllRequests();1330this.model.clearThreads(this.getId(), true);13311332const details = this.stoppedDetails.slice();1333this.stoppedDetails.length = 0;1334if (details.length) {1335await Promise.all(details.map(d => this.handleStop(d)));1336} else if (!this.fetchThreadsScheduler.value.isScheduled()) {1337// threads are fetched as a side-effect of processing the stopped1338// event(s), but if there are none, schedule a thread update manually (#282777)1339this.fetchThreadsScheduler.value.schedule();1340}1341}13421343const viewModel = this.debugService.getViewModel();1344if (viewModel.focusedSession === this) {1345viewModel.updateViews();1346}1347}));13481349this.rawListeners.add(this.raw.onDidExitAdapter(event => this.onDidExitAdapter(event)));1350}13511352private async handleStop(event: IRawStoppedDetails) {1353this.passFocusScheduler.cancel();1354this.stoppedDetails.push(event);13551356// do this very eagerly if we have hitBreakpointIds, since it may take a1357// moment for breakpoints to set and we want to do our best to not miss1358// anything1359if (event.hitBreakpointIds) {1360this._waitToResume = this.enableDependentBreakpoints(event.hitBreakpointIds);1361}13621363this.statusQueue.run(1364this.fetchThreads(event).then(() => event.threadId === undefined ? this.threadIds : [event.threadId]),1365async (threadId, token) => {1366const hasLotsOfThreads = event.threadId === undefined && this.threadIds.length > 10;13671368// If the focus for the current session is on a non-existent thread, clear the focus.1369const focusedThread = this.debugService.getViewModel().focusedThread;1370const focusedThreadDoesNotExist = focusedThread !== undefined && focusedThread.session === this && !this.threads.has(focusedThread.threadId);1371if (focusedThreadDoesNotExist) {1372this.debugService.focusStackFrame(undefined, undefined);1373}13741375const thread = typeof threadId === 'number' ? this.getThread(threadId) : undefined;1376if (thread) {1377// Call fetch call stack twice, the first only return the top stack frame.1378// Second retrieves the rest of the call stack. For performance reasons #256051379// Second call is only done if there's few threads that stopped in this event.1380const promises = this.model.refreshTopOfCallstack(<Thread>thread, /* fetchFullStack= */!hasLotsOfThreads);1381const focus = async () => {1382if (focusedThreadDoesNotExist || (!event.preserveFocusHint && thread.getCallStack().length)) {1383const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;1384if (!focusedStackFrame || focusedStackFrame.thread.session === this) {1385// Only take focus if nothing is focused, or if the focus is already on the current session1386const preserveFocus = !this.configurationService.getValue<IDebugConfiguration>('debug').focusEditorOnBreak;1387await this.debugService.focusStackFrame(undefined, thread, undefined, { preserveFocus });1388}13891390if (thread.stoppedDetails && !token.isCancellationRequested) {1391if (thread.stoppedDetails.reason === 'breakpoint' && this.configurationService.getValue<IDebugConfiguration>('debug').openDebug === 'openOnDebugBreak' && !this.suppressDebugView) {1392await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);1393}13941395if (this.configurationService.getValue<IDebugConfiguration>('debug').focusWindowOnBreak && !this.workbenchEnvironmentService.extensionTestsLocationURI) {1396const activeWindow = getActiveWindow();1397if (!activeWindow.document.hasFocus()) {1398await this.hostService.focus(mainWindow, { mode: FocusMode.Force /* Application may not be active */ });1399}1400}1401}1402}1403};14041405await promises.topCallStack;14061407if (!event.hitBreakpointIds) { // if hitBreakpointIds are present, this is handled earlier on1408this._waitToResume = this.enableDependentBreakpoints(thread);1409}14101411if (token.isCancellationRequested) {1412return;1413}14141415focus();14161417await promises.wholeCallStack;1418if (token.isCancellationRequested) {1419return;1420}14211422const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;1423if (!focusedStackFrame || isFrameDeemphasized(focusedStackFrame)) {1424// The top stack frame can be deemphesized so try to focus again #686161425focus();1426}1427}1428this._onDidChangeState.fire();1429},1430);1431}14321433private async enableDependentBreakpoints(hitBreakpointIdsOrThread: Thread | number[]) {1434let breakpoints: IBreakpoint[];1435if (Array.isArray(hitBreakpointIdsOrThread)) {1436breakpoints = this.model.getBreakpoints().filter(bp => hitBreakpointIdsOrThread.includes(bp.getIdFromAdapter(this.id)!));1437} else {1438const frame = hitBreakpointIdsOrThread.getTopStackFrame();1439if (frame === undefined) {1440return;1441}14421443if (hitBreakpointIdsOrThread.stoppedDetails && hitBreakpointIdsOrThread.stoppedDetails.reason !== 'breakpoint') {1444return;1445}14461447breakpoints = this.getBreakpointsAtPosition(frame.source.uri, frame.range.startLineNumber, frame.range.endLineNumber, frame.range.startColumn, frame.range.endColumn);1448}14491450// find the current breakpoints14511452// check if the current breakpoints are dependencies, and if so collect and send the dependents to DA1453const urisToResend = new Set<string>();1454this.model.getBreakpoints({ triggeredOnly: true, enabledOnly: true }).forEach(bp => {1455breakpoints.forEach(cbp => {1456if (bp.enabled && bp.triggeredBy === cbp.getId()) {1457bp.setSessionDidTrigger(this.getId());1458urisToResend.add(bp.uri.toString());1459}1460});1461});14621463const results: Promise<any>[] = [];1464urisToResend.forEach((uri) => results.push(this.debugService.sendBreakpoints(URI.parse(uri), undefined, this)));1465return Promise.all(results);1466}14671468private getBreakpointsAtPosition(uri: URI, startLineNumber: number, endLineNumber: number, startColumn: number, endColumn: number): IBreakpoint[] {1469return this.model.getBreakpoints({ uri: uri }).filter(bp => {1470if (bp.lineNumber < startLineNumber || bp.lineNumber > endLineNumber) {1471return false;1472}14731474if (bp.column && (bp.column < startColumn || bp.column > endColumn)) {1475return false;1476}1477return true;1478});1479}14801481private onDidExitAdapter(event?: AdapterEndEvent): void {1482this.initialized = true;1483this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined);1484this.shutdown();1485this._onDidEndAdapter.fire(event);1486}14871488// Disconnects and clears state. Session can be initialized again for a new connection.1489private shutdown(): void {1490this.rawListeners.clear();1491if (this.raw) {1492// Send out disconnect and immediatly dispose (do not wait for response) #1274181493this.raw.disconnect({});1494this.raw.dispose();1495this.raw = undefined;1496}1497this.passFocusScheduler.cancel();1498this.passFocusScheduler.dispose();1499this.model.clearThreads(this.getId(), true);1500this.sources.clear();1501this.threads.clear();1502this.threadIds = [];1503this.stoppedDetails = [];1504this._onDidChangeState.fire();1505}15061507public dispose() {1508this.cancelAllRequests();1509this.rawListeners.dispose();1510this.globalDisposables.dispose();1511this._onDidChangeState.dispose();1512this._onDidEndAdapter.dispose();1513this._onDidLoadedSource.dispose();1514this._onDidCustomEvent.dispose();1515this._onDidProgressStart.dispose();1516this._onDidProgressUpdate.dispose();1517this._onDidProgressEnd.dispose();1518this._onDidInvalidMemory.dispose();1519this._onDidChangeREPLElements.dispose();1520this._onDidChangeName.dispose();1521this._waitToResume = undefined;1522}15231524//---- sources15251526getSourceForUri(uri: URI): Source | undefined {1527return this.sources.get(this.uriIdentityService.asCanonicalUri(uri).toString());1528}15291530getSource(raw?: DebugProtocol.Source): Source {1531let source = new Source(raw, this.getId(), this.uriIdentityService, this.logService);1532const uriKey = source.uri.toString();1533const found = this.sources.get(uriKey);1534if (found) {1535source = found;1536// merge attributes of new into existing1537source.raw = mixin(source.raw, raw);1538if (source.raw && raw) {1539// Always take the latest presentation hint from adapter #421391540source.raw.presentationHint = raw.presentationHint;1541}1542} else {1543this.sources.set(uriKey, source);1544}15451546return source;1547}15481549private getRawSource(uri: URI): DebugProtocol.Source {1550const source = this.getSourceForUri(uri);1551if (source) {1552return source.raw;1553} else {1554const data = Source.getEncodedDebugData(uri);1555return { name: data.name, path: data.path, sourceReference: data.sourceReference };1556}1557}15581559private getNewCancellationToken(threadId: number, token?: CancellationToken): CancellationToken {1560const tokenSource = new CancellationTokenSource(token);1561const tokens = this.cancellationMap.get(threadId) || [];1562tokens.push(tokenSource);1563this.cancellationMap.set(threadId, tokens);15641565return tokenSource.token;1566}15671568private cancelAllRequests(): void {1569this.cancellationMap.forEach(tokens => tokens.forEach(t => t.dispose(true)));1570this.cancellationMap.clear();1571}15721573// REPL15741575getReplElements(): IReplElement[] {1576return this.repl.getReplElements();1577}15781579hasSeparateRepl(): boolean {1580return !this.parentSession || this._options.repl !== 'mergeWithParent';1581}15821583removeReplExpressions(): void {1584this.repl.removeReplExpressions();1585}15861587async addReplExpression(stackFrame: IStackFrame | undefined, expression: string): Promise<void> {1588await this.repl.addReplExpression(this, stackFrame, expression);1589// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.1590this.debugService.getViewModel().updateViews();1591}15921593appendToRepl(data: INewReplElementData, isImportant?: boolean): void {1594this.repl.appendToRepl(this, data);1595if (isImportant) {1596this.notificationService.notify({ message: data.output.toString(), severity: data.sev, source: this.name });1597}1598}1599}16001601/**1602* Keeps track of events for threads, and cancels any previous operations for1603* a thread when the thread goes into a new state. Currently, the operations a thread has are:1604*1605* - started1606* - stopped1607* - continue1608* - exited1609*1610* In each case, the new state preempts the old state, so we don't need to1611* queue work, just cancel old work. It's up to the caller to make sure that1612* no UI effects happen at the point when the `token` is cancelled.1613*/1614export class ThreadStatusScheduler extends Disposable {1615/**1616* An array of set of thread IDs. When a 'stopped' event is encountered, the1617* editor refreshes its thread IDs. In the meantime, the thread may change1618* state it again. So the editor puts a Set into this array when it starts1619* the refresh, and checks it after the refresh is finished, to see if1620* any of the threads it looked up should now be invalidated.1621*/1622private pendingCancellations: Set<number | undefined>[] = [];16231624/**1625* Cancellation tokens for currently-running operations on threads.1626*/1627private readonly threadOps = this._register(new DisposableMap<number, CancellationTokenSource>());16281629/**1630* Runs the operation.1631* If thread is undefined it affects all threads.1632*/1633public async run(threadIdsP: Promise<number[]> | number[], operation: (threadId: number, ct: CancellationToken) => Promise<unknown>) {1634const cancelledWhileLookingUpThreads = new Set<number | undefined>();1635this.pendingCancellations.push(cancelledWhileLookingUpThreads);1636const threadIds = await threadIdsP;16371638// Now that we got our threads,1639// 1. Remove our pending set, and1640// 2. Cancel any slower callers who might also have found this thread1641for (let i = 0; i < this.pendingCancellations.length; i++) {1642const s = this.pendingCancellations[i];1643if (s === cancelledWhileLookingUpThreads) {1644this.pendingCancellations.splice(i, 1);1645break;1646} else {1647for (const threadId of threadIds) {1648s.add(threadId);1649}1650}1651}16521653if (cancelledWhileLookingUpThreads.has(undefined)) {1654return;1655}16561657await Promise.all(threadIds.map(threadId => {1658if (cancelledWhileLookingUpThreads.has(threadId)) {1659return;1660}1661this.threadOps.get(threadId)?.cancel();1662const cts = new CancellationTokenSource();1663this.threadOps.set(threadId, cts);1664return operation(threadId, cts.token);1665}));1666}16671668/**1669* Cancels all ongoing state operations on the given threads.1670* If threads is undefined it cancel all threads.1671*/1672public cancel(threadIds?: readonly number[]) {1673if (!threadIds) {1674for (const [_, op] of this.threadOps) {1675op.cancel();1676}1677this.threadOps.clearAndDisposeAll();1678for (const s of this.pendingCancellations) {1679s.add(undefined);1680}1681} else {1682for (const threadId of threadIds) {1683this.threadOps.get(threadId)?.cancel();1684this.threadOps.deleteAndDispose(threadId);1685for (const s of this.pendingCancellations) {1686s.add(threadId);1687}1688}1689}1690}1691}169216931694