Path: blob/main/extensions/copilot/test/simulation/fixtures/doc/issue-6406/debugModel.ts
13405 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 { distinct } from 'vs/base/common/arrays';6import { findLastIdx } from 'vs/base/common/arraysFind';7import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async';8import { VSBuffer, decodeBase64, encodeBase64 } from 'vs/base/common/buffer';9import { CancellationTokenSource } from 'vs/base/common/cancellation';10import { Emitter, Event } from 'vs/base/common/event';11import { stringHash } from 'vs/base/common/hash';12import { Disposable } from 'vs/base/common/lifecycle';13import { mixin } from 'vs/base/common/objects';14import { autorun } from 'vs/base/common/observable';15import * as resources from 'vs/base/common/resources';16import { isString, isUndefinedOrNull } from 'vs/base/common/types';17import { URI, URI as uri } from 'vs/base/common/uri';18import { generateUuid } from 'vs/base/common/uuid';19import { IRange, Range } from 'vs/editor/common/core/range';20import * as nls from 'vs/nls';21import { ILogService } from 'vs/platform/log/common/log';22import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';23import { IEditorPane } from 'vs/workbench/common/editor';24import { DEBUG_MEMORY_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugEvaluatePosition, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State, isFrameDeemphasized } from 'vs/workbench/contrib/debug/common/debug';25import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource';26import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage';27import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers';28import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';29import { IEditorService } from 'vs/workbench/services/editor/common/editorService';30import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';3132interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable {33__vscodeVariableMenuContext?: string;34}3536export class ExpressionContainer implements IExpressionContainer {3738public static readonly allValues = new Map<string, string>();39// Use chunks to support variable paging #953740private static readonly BASE_CHUNK_SIZE = 100;4142public type: string | undefined;43public valueChanged = false;44private _value: string = '';45protected children?: Promise<IExpression[]>;4647constructor(48protected session: IDebugSession | undefined,49protected readonly threadId: number | undefined,50private _reference: number | undefined,51private readonly id: string,52public namedVariables: number | undefined = 0,53public indexedVariables: number | undefined = 0,54public memoryReference: string | undefined = undefined,55private startOfVariables: number | undefined = 0,56public presentationHint: DebugProtocol.VariablePresentationHint | undefined = undefined57) { }5859get reference(): number | undefined {60return this._reference;61}6263set reference(value: number | undefined) {64this._reference = value;65this.children = undefined; // invalidate children cache66}6768async evaluateLazy(): Promise<void> {69if (typeof this.reference === 'undefined') {70return;71}7273const response = await this.session!.variables(this.reference, this.threadId, undefined, undefined, undefined);74if (!response || !response.body || !response.body.variables || response.body.variables.length !== 1) {75return;76}7778const dummyVar = response.body.variables[0];79this.reference = dummyVar.variablesReference;80this._value = dummyVar.value;81this.namedVariables = dummyVar.namedVariables;82this.indexedVariables = dummyVar.indexedVariables;83this.memoryReference = dummyVar.memoryReference;84this.presentationHint = dummyVar.presentationHint;85// Also call overridden method to adopt subclass props86this.adoptLazyResponse(dummyVar);87}8889protected adoptLazyResponse(response: DebugProtocol.Variable): void {90}9192getChildren(): Promise<IExpression[]> {93if (!this.children) {94this.children = this.doGetChildren();95}9697return this.children;98}99100private async doGetChildren(): Promise<IExpression[]> {101if (!this.hasChildren) {102return [];103}104105if (!this.getChildrenInChunks) {106return this.fetchVariables(undefined, undefined, undefined);107}108109// Check if object has named variables, fetch them independent from indexed variables #9670110const children = this.namedVariables ? await this.fetchVariables(undefined, undefined, 'named') : [];111112// Use a dynamic chunk size based on the number of elements #9774113let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE;114while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) {115chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE;116}117118if (!!this.indexedVariables && this.indexedVariables > chunkSize) {119// There are a lot of children, create fake intermediate values that represent chunks #9537120const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);121for (let i = 0; i < numberOfChunks; i++) {122const start = (this.startOfVariables || 0) + i * chunkSize;123const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);124children.push(new Variable(this.session, this.threadId, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, undefined, { kind: 'virtual' }, undefined, undefined, true, start));125}126127return children;128}129130const variables = await this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed');131return children.concat(variables);132}133134getId(): string {135return this.id;136}137138getSession(): IDebugSession | undefined {139return this.session;140}141142get value(): string {143return this._value;144}145146get hasChildren(): boolean {147// only variables with reference > 0 have children.148return !!this.reference && this.reference > 0 && !this.presentationHint?.lazy;149}150151private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {152try {153const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count);154if (!response || !response.body || !response.body.variables) {155return [];156}157158const nameCount = new Map<string, number>();159const vars = response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => {160if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') {161const count = nameCount.get(v.name) || 0;162const idDuplicationIndex = count > 0 ? count.toString() : '';163nameCount.set(v.name, count + 1);164return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.memoryReference, v.presentationHint, v.type, v.__vscodeVariableMenuContext, true, 0, idDuplicationIndex);165}166return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false);167});168169if (this.session!.autoExpandLazyVariables) {170await Promise.all(vars.map(v => v.presentationHint?.lazy && v.evaluateLazy()));171}172173return vars;174} catch (e) {175return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false)];176}177}178179// The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked.180private get getChildrenInChunks(): boolean {181return !!this.indexedVariables;182}183184set value(value: string) {185this._value = value;186this.valueChanged = !!ExpressionContainer.allValues.get(this.getId()) &&187ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value;188ExpressionContainer.allValues.set(this.getId(), value);189}190191toString(): string {192return this.value;193}194195async evaluateExpression(196expression: string,197session: IDebugSession | undefined,198stackFrame: IStackFrame | undefined,199context: string,200keepLazyVars = false,201location?: IDebugEvaluatePosition,202): Promise<boolean> {203204if (!session || (!stackFrame && context !== 'repl')) {205this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE;206this.reference = 0;207return false;208}209210this.session = session;211try {212const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context, location);213214if (response && response.body) {215this.value = response.body.result || '';216this.reference = response.body.variablesReference;217this.namedVariables = response.body.namedVariables;218this.indexedVariables = response.body.indexedVariables;219this.memoryReference = response.body.memoryReference;220this.type = response.body.type || this.type;221this.presentationHint = response.body.presentationHint;222223if (!keepLazyVars && response.body.presentationHint?.lazy) {224await this.evaluateLazy();225}226227return true;228}229return false;230} catch (e) {231this.value = e.message || '';232this.reference = 0;233return false;234}235}236}237238function handleSetResponse(expression: ExpressionContainer, response: DebugProtocol.SetVariableResponse | DebugProtocol.SetExpressionResponse | undefined): void {239if (response && response.body) {240expression.value = response.body.value || '';241expression.type = response.body.type || expression.type;242expression.reference = response.body.variablesReference;243expression.namedVariables = response.body.namedVariables;244expression.indexedVariables = response.body.indexedVariables;245// todo @weinand: the set responses contain most properties, but not memory references. Should they?246}247}248249export class VisualizedExpression implements IExpression {250public errorMessage?: string;251private readonly id = generateUuid();252253evaluateLazy(): Promise<void> {254return Promise.resolve();255}256getChildren(): Promise<IExpression[]> {257return this.visualizer.getVisualizedChildren(this.treeId, this.treeItem.id);258}259260getId(): string {261return this.id;262}263264get name() {265return this.treeItem.label;266}267268get value() {269return this.treeItem.description || '';270}271272get hasChildren() {273return this.treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None;274}275276constructor(277private readonly visualizer: IDebugVisualizerService,278public readonly treeId: string,279public readonly treeItem: IDebugVisualizationTreeItem,280public readonly original?: Variable,281) { }282283/** Edits the value, sets the {@link errorMessage} and returns false if unsuccessful */284public async edit(newValue: string) {285try {286await this.visualizer.editTreeItem(this.treeId, this.treeItem, newValue);287return true;288} catch (e) {289this.errorMessage = e.message;290return false;291}292}293}294295export class Expression extends ExpressionContainer implements IExpression {296static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available");297298public available: boolean;299300constructor(public name: string, id = generateUuid()) {301super(undefined, undefined, 0, id);302this.available = false;303// name is not set if the expression is just being added304// in that case do not set default value to prevent flashing #14499305if (name) {306this.value = Expression.DEFAULT_VALUE;307}308}309310async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean, location?: IDebugEvaluatePosition): Promise<void> {311this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars, location);312}313314override toString(): string {315return `${this.name}\n${this.value}`;316}317318async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {319if (!this.session) {320return;321}322323const response = await this.session.setExpression(stackFrame.frameId, this.name, value);324handleSetResponse(this, response);325}326}327328export class Variable extends ExpressionContainer implements IExpression {329330// Used to show the error message coming from the adapter when setting the value #7807331public errorMessage: string | undefined;332333constructor(334session: IDebugSession | undefined,335threadId: number | undefined,336public readonly parent: IExpressionContainer,337reference: number | undefined,338public readonly name: string,339public evaluateName: string | undefined,340value: string | undefined,341namedVariables: number | undefined,342indexedVariables: number | undefined,343memoryReference: string | undefined,344presentationHint: DebugProtocol.VariablePresentationHint | undefined,345type: string | undefined = undefined,346public readonly variableMenuContext: string | undefined = undefined,347public readonly available = true,348startOfVariables = 0,349idDuplicationIndex = '',350) {351super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint);352this.value = value || '';353this.type = type;354}355356getThreadId() {357return this.threadId;358}359360async setVariable(value: string, stackFrame: IStackFrame): Promise<any> {361if (!this.session) {362return;363}364365try {366// Send out a setExpression for debug extensions that do not support set variables https://github.com/microsoft/vscode/issues/124679#issuecomment-869844437367if (this.session.capabilities.supportsSetExpression && !this.session.capabilities.supportsSetVariable && this.evaluateName) {368return this.setExpression(value, stackFrame);369}370371const response = await this.session.setVariable((<ExpressionContainer>this.parent).reference, this.name, value);372handleSetResponse(this, response);373} catch (err) {374this.errorMessage = err.message;375}376}377378async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {379if (!this.session || !this.evaluateName) {380return;381}382383const response = await this.session.setExpression(stackFrame.frameId, this.evaluateName, value);384handleSetResponse(this, response);385}386387override toString(): string {388return this.name ? `${this.name}: ${this.value}` : this.value;389}390391protected override adoptLazyResponse(response: DebugProtocol.Variable): void {392this.evaluateName = response.evaluateName;393}394395toDebugProtocolObject(): DebugProtocol.Variable {396return {397name: this.name,398variablesReference: this.reference || 0,399memoryReference: this.memoryReference,400value: this.value,401evaluateName: this.evaluateName402};403}404}405406export class Scope extends ExpressionContainer implements IScope {407408constructor(409public readonly stackFrame: IStackFrame,410id: number,411public readonly name: string,412reference: number,413public expensive: boolean,414namedVariables?: number,415indexedVariables?: number,416public readonly range?: IRange417) {418super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${id}`, namedVariables, indexedVariables);419}420421override toString(): string {422return this.name;423}424425toDebugProtocolObject(): DebugProtocol.Scope {426return {427name: this.name,428variablesReference: this.reference || 0,429expensive: this.expensive430};431}432}433434export class ErrorScope extends Scope {435436constructor(437stackFrame: IStackFrame,438index: number,439message: string,440) {441super(stackFrame, index, message, 0, false);442}443444override toString(): string {445return this.name;446}447}448449export class StackFrame implements IStackFrame {450451private scopes: Promise<Scope[]> | undefined;452453constructor(454public readonly thread: Thread,455public readonly frameId: number,456public readonly source: Source,457public readonly name: string,458public readonly presentationHint: string | undefined,459public readonly range: IRange,460private readonly index: number,461public readonly canRestart: boolean,462public readonly instructionPointerReference?: string463) { }464465getId(): string {466return `stackframe:${this.thread.getId()}:${this.index}:${this.source.name}`;467}468469getScopes(): Promise<IScope[]> {470if (!this.scopes) {471this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => {472if (!response || !response.body || !response.body.scopes) {473return [];474}475476const usedIds = new Set<number>();477return response.body.scopes.map(rs => {478// form the id based on the name and location so that it's the479// same across multiple pauses to retain expansion state480let id = 0;481do {482id = stringHash(`${rs.name}:${rs.line}:${rs.column}`, id);483} while (usedIds.has(id));484485usedIds.add(id);486return new Scope(this, id, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,487rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined);488489});490}, err => [new ErrorScope(this, 0, err.message)]);491}492493return this.scopes;494}495496async getMostSpecificScopes(range: IRange): Promise<IScope[]> {497const scopes = await this.getScopes();498const nonExpensiveScopes = scopes.filter(s => !s.expensive);499const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range);500if (!haveRangeInfo) {501return nonExpensiveScopes;502}503504const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && Range.containsRange(scope.range, range))505.sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber));506return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes;507}508509restart(): Promise<void> {510return this.thread.session.restartFrame(this.frameId, this.thread.threadId);511}512513forgetScopes(): void {514this.scopes = undefined;515}516517toString(): string {518const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : '';519const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`;520521return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;522}523524async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<IEditorPane | undefined> {525const threadStopReason = this.thread.stoppedDetails?.reason;526if (this.instructionPointerReference &&527(threadStopReason === 'instruction breakpoint' ||528(threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction') ||529editorService.activeEditor instanceof DisassemblyViewInput)) {530return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true });531}532533if (this.source.available) {534return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);535}536return undefined;537}538539equals(other: IStackFrame): boolean {540return (this.name === other.name) && (other.thread === this.thread) && (this.frameId === other.frameId) && (other.source === this.source) && (Range.equalsRange(this.range, other.range));541}542}543544const KEEP_SUBTLE_FRAME_AT_TOP_REASONS: readonly string[] = ['breakpoint', 'step', 'function breakpoint'];545546export class Thread implements IThread {547private callStack: IStackFrame[];548private staleCallStack: IStackFrame[];549private callStackCancellationTokens: CancellationTokenSource[] = [];550public stoppedDetails: IRawStoppedDetails | undefined;551public stopped: boolean;552public reachedEndOfCallStack = false;553public lastSteppingGranularity: DebugProtocol.SteppingGranularity | undefined;554555constructor(public readonly session: IDebugSession, public name: string, public readonly threadId: number) {556this.callStack = [];557this.staleCallStack = [];558this.stopped = false;559}560561getId(): string {562return `thread:${this.session.getId()}:${this.threadId}`;563}564565clearCallStack(): void {566if (this.callStack.length) {567this.staleCallStack = this.callStack;568}569this.callStack = [];570this.callStackCancellationTokens.forEach(c => c.dispose(true));571this.callStackCancellationTokens = [];572}573574getCallStack(): IStackFrame[] {575return this.callStack;576}577578getStaleCallStack(): ReadonlyArray<IStackFrame> {579return this.staleCallStack;580}581582getTopStackFrame(): IStackFrame | undefined {583const callStack = this.getCallStack();584const stopReason = this.stoppedDetails?.reason;585// Allow stack frame without source and with instructionReferencePointer as top stack frame when using disassembly view.586const firstAvailableStackFrame = callStack.find(sf => !!(587((stopReason === 'instruction breakpoint' || (stopReason === 'step' && this.lastSteppingGranularity === 'instruction')) && sf.instructionPointerReference) ||588(sf.source && sf.source.available && (KEEP_SUBTLE_FRAME_AT_TOP_REASONS.includes(stopReason!) || !isFrameDeemphasized(sf)))));589return firstAvailableStackFrame;590}591592get stateLabel(): string {593if (this.stoppedDetails) {594return this.stoppedDetails.description ||595(this.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", this.stoppedDetails.reason) : nls.localize('paused', "Paused"));596}597598return nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");599}600601/**602* Queries the debug adapter for the callstack and returns a promise603* which completes once the call stack has been retrieved.604* If the thread is not stopped, it returns a promise to an empty array.605* Only fetches the first stack frame for performance reasons. Calling this method consecutive times606* gets the remainder of the call stack.607*/608async fetchCallStack(levels = 20): Promise<void> {609if (this.stopped) {610const start = this.callStack.length;611const callStack = await this.getCallStackImpl(start, levels);612this.reachedEndOfCallStack = callStack.length < levels;613if (start < this.callStack.length) {614// Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660615this.callStack.splice(start, this.callStack.length - start);616}617this.callStack = this.callStack.concat(callStack || []);618if (typeof this.stoppedDetails?.totalFrames === 'number' && this.stoppedDetails.totalFrames === this.callStack.length) {619this.reachedEndOfCallStack = true;620}621}622}623624private async getCallStackImpl(startFrame: number, levels: number): Promise<IStackFrame[]> {625try {626const tokenSource = new CancellationTokenSource();627this.callStackCancellationTokens.push(tokenSource);628const response = await this.session.stackTrace(this.threadId, startFrame, levels, tokenSource.token);629if (!response || !response.body || tokenSource.token.isCancellationRequested) {630return [];631}632633if (this.stoppedDetails) {634this.stoppedDetails.totalFrames = response.body.totalFrames;635}636637return response.body.stackFrames.map((rsf, index) => {638const source = this.session.getSource(rsf.source);639640return new StackFrame(this, rsf.id, source, rsf.name, rsf.presentationHint, new Range(641rsf.line,642rsf.column,643rsf.endLine || rsf.line,644rsf.endColumn || rsf.column645), startFrame + index, typeof rsf.canRestart === 'boolean' ? rsf.canRestart : true, rsf.instructionPointerReference);646});647} catch (err) {648if (this.stoppedDetails) {649this.stoppedDetails.framesErrorMessage = err.message;650}651652return [];653}654}655656/**657* Returns exception info promise if the exception was thrown, otherwise undefined658*/659get exceptionInfo(): Promise<IExceptionInfo | undefined> {660if (this.stoppedDetails && this.stoppedDetails.reason === 'exception') {661if (this.session.capabilities.supportsExceptionInfoRequest) {662return this.session.exceptionInfo(this.threadId);663}664return Promise.resolve({665description: this.stoppedDetails.text,666breakMode: null667});668}669return Promise.resolve(undefined);670}671672next(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {673return this.session.next(this.threadId, granularity);674}675676stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {677return this.session.stepIn(this.threadId, undefined, granularity);678}679680stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {681return this.session.stepOut(this.threadId, granularity);682}683684stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {685return this.session.stepBack(this.threadId, granularity);686}687688continue(): Promise<any> {689return this.session.continue(this.threadId);690}691692pause(): Promise<any> {693return this.session.pause(this.threadId);694}695696terminate(): Promise<any> {697return this.session.terminateThreads([this.threadId]);698}699700reverseContinue(): Promise<any> {701return this.session.reverseContinue(this.threadId);702}703}704705/**706* Gets a URI to a memory in the given session ID.707*/708export const getUriForDebugMemory = (709sessionId: string,710memoryReference: string,711range?: { fromOffset: number; toOffset: number },712displayName = 'memory'713) => {714return URI.from({715scheme: DEBUG_MEMORY_SCHEME,716authority: sessionId,717path: '/' + encodeURIComponent(memoryReference) + `/${encodeURIComponent(displayName)}.bin`,718query: range ? `?range=${range.fromOffset}:${range.toOffset}` : undefined,719});720};721722export class MemoryRegion extends Disposable implements IMemoryRegion {723private readonly invalidateEmitter = this._register(new Emitter<IMemoryInvalidationEvent>());724725/** @inheritdoc */726public readonly onDidInvalidate = this.invalidateEmitter.event;727728/** @inheritdoc */729public readonly writable = !!this.session.capabilities.supportsWriteMemoryRequest;730731constructor(private readonly memoryReference: string, private readonly session: IDebugSession) {732super();733this._register(session.onDidInvalidateMemory(e => {734if (e.body.memoryReference === memoryReference) {735this.invalidate(e.body.offset, e.body.count - e.body.offset);736}737}));738}739740public async read(fromOffset: number, toOffset: number): Promise<MemoryRange[]> {741const length = toOffset - fromOffset;742const offset = fromOffset;743const result = await this.session.readMemory(this.memoryReference, offset, length);744745if (result === undefined || !result.body?.data) {746return [{ type: MemoryRangeType.Unreadable, offset, length }];747}748749let data: VSBuffer;750try {751data = decodeBase64(result.body.data);752} catch {753return [{ type: MemoryRangeType.Error, offset, length, error: 'Invalid base64 data from debug adapter' }];754}755756const unreadable = result.body.unreadableBytes || 0;757const dataLength = length - unreadable;758if (data.byteLength < dataLength) {759const pad = VSBuffer.alloc(dataLength - data.byteLength);760pad.buffer.fill(0);761data = VSBuffer.concat([data, pad], dataLength);762} else if (data.byteLength > dataLength) {763data = data.slice(0, dataLength);764}765766if (!unreadable) {767return [{ type: MemoryRangeType.Valid, offset, length, data }];768}769770return [771{ type: MemoryRangeType.Valid, offset, length: dataLength, data },772{ type: MemoryRangeType.Unreadable, offset: offset + dataLength, length: unreadable },773];774}775776public async write(offset: number, data: VSBuffer): Promise<number> {777const result = await this.session.writeMemory(this.memoryReference, offset, encodeBase64(data), true);778const written = result?.body?.bytesWritten ?? data.byteLength;779this.invalidate(offset, offset + written);780return written;781}782783public override dispose() {784super.dispose();785}786787private invalidate(fromOffset: number, toOffset: number) {788this.invalidateEmitter.fire({ fromOffset, toOffset });789}790}791792export class Enablement implements IEnablement {793constructor(794public enabled: boolean,795private readonly id: string796) { }797798getId(): string {799return this.id;800}801}802803interface IBreakpointSessionData extends DebugProtocol.Breakpoint {804supportsConditionalBreakpoints: boolean;805supportsHitConditionalBreakpoints: boolean;806supportsLogPoints: boolean;807supportsFunctionBreakpoints: boolean;808supportsDataBreakpoints: boolean;809supportsInstructionBreakpoints: boolean;810sessionId: string;811}812813function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: DebugProtocol.Capabilities): IBreakpointSessionData {814return mixin({815supportsConditionalBreakpoints: !!capabilities.supportsConditionalBreakpoints,816supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints,817supportsLogPoints: !!capabilities.supportsLogPoints,818supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints,819supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints,820supportsInstructionBreakpoints: !!capabilities.supportsInstructionBreakpoints821}, data);822}823824export interface IBaseBreakpointOptions {825enabled?: boolean;826hitCondition?: string;827condition?: string;828logMessage?: string;829mode?: string;830modeLabel?: string;831}832833export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint {834835private sessionData = new Map<string, IBreakpointSessionData>();836protected data: IBreakpointSessionData | undefined;837public hitCondition: string | undefined;838public condition: string | undefined;839public logMessage: string | undefined;840public mode: string | undefined;841public modeLabel: string | undefined;842843constructor(844id: string,845opts: IBaseBreakpointOptions846) {847super(opts.enabled ?? true, id);848this.condition = opts.condition;849this.hitCondition = opts.hitCondition;850this.logMessage = opts.logMessage;851this.mode = opts.mode;852this.modeLabel = opts.modeLabel;853}854855setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {856if (!data) {857this.sessionData.delete(sessionId);858} else {859data.sessionId = sessionId;860this.sessionData.set(sessionId, data);861}862863const allData = Array.from(this.sessionData.values());864const verifiedData = distinct(allData.filter(d => d.verified), d => `${d.line}:${d.column}`);865if (verifiedData.length) {866// In case multiple session verified the breakpoint and they provide different data show the intial data that the user set (corner case)867this.data = verifiedData.length === 1 ? verifiedData[0] : undefined;868} else {869// No session verified the breakpoint870this.data = allData.length ? allData[0] : undefined;871}872}873874get message(): string | undefined {875if (!this.data) {876return undefined;877}878879return this.data.message;880}881882get verified(): boolean {883return this.data ? this.data.verified : true;884}885886get sessionsThatVerified() {887const sessionIds: string[] = [];888for (const [sessionId, data] of this.sessionData) {889if (data.verified) {890sessionIds.push(sessionId);891}892}893894return sessionIds;895}896897abstract get supported(): boolean;898899getIdFromAdapter(sessionId: string): number | undefined {900const data = this.sessionData.get(sessionId);901return data ? data.id : undefined;902}903904getDebugProtocolBreakpoint(sessionId: string): DebugProtocol.Breakpoint | undefined {905const data = this.sessionData.get(sessionId);906if (data) {907const bp: DebugProtocol.Breakpoint = {908id: data.id,909verified: data.verified,910message: data.message,911source: data.source,912line: data.line,913column: data.column,914endLine: data.endLine,915endColumn: data.endColumn,916instructionReference: data.instructionReference,917offset: data.offset918};919return bp;920}921return undefined;922}923924toJSON(): IBaseBreakpointOptions & { id: string } {925return {926id: this.getId(),927enabled: this.enabled,928condition: this.condition,929hitCondition: this.hitCondition,930logMessage: this.logMessage,931mode: this.mode,932modeLabel: this.modeLabel,933};934}935}936937export interface IBreakpointOptions extends IBaseBreakpointOptions {938uri: uri;939lineNumber: number;940column: number | undefined;941adapterData: any;942triggeredBy: string | undefined;943}944945export class Breakpoint extends BaseBreakpoint implements IBreakpoint {946private sessionsDidTrigger?: Set<string>;947private readonly _uri: uri;948private _adapterData: any;949private _lineNumber: number;950private _column: number | undefined;951public triggeredBy: string | undefined;952953constructor(954opts: IBreakpointOptions,955private readonly textFileService: ITextFileService,956private readonly uriIdentityService: IUriIdentityService,957private readonly logService: ILogService,958id = generateUuid(),959) {960super(id, opts);961this._uri = opts.uri;962this._lineNumber = opts.lineNumber;963this._column = opts.column;964this._adapterData = opts.adapterData;965this.triggeredBy = opts.triggeredBy;966}967968toDAP(): DebugProtocol.SourceBreakpoint {969return {970line: this.sessionAgnosticData.lineNumber,971column: this.sessionAgnosticData.column,972condition: this.condition,973hitCondition: this.hitCondition,974logMessage: this.logMessage,975mode: this.mode976};977}978979get originalUri() {980return this._uri;981}982983get lineNumber(): number {984return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber;985}986987override get verified(): boolean {988if (this.data) {989return this.data.verified && !this.textFileService.isDirty(this._uri);990}991992return true;993}994995get pending(): boolean {996if (this.data) {997return false;998}999return this.triggeredBy !== undefined;1000}10011002get uri(): uri {1003return this.verified && this.data && this.data.source ? getUriFromSource(this.data.source, this.data.source.path, this.data.sessionId, this.uriIdentityService, this.logService) : this._uri;1004}10051006get column(): number | undefined {1007return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column;1008}10091010override get message(): string | undefined {1011if (this.textFileService.isDirty(this.uri)) {1012return nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session.");1013}10141015return super.message;1016}10171018get adapterData(): any {1019return this.data && this.data.source && this.data.source.adapterData ? this.data.source.adapterData : this._adapterData;1020}10211022get endLineNumber(): number | undefined {1023return this.verified && this.data ? this.data.endLine : undefined;1024}10251026get endColumn(): number | undefined {1027return this.verified && this.data ? this.data.endColumn : undefined;1028}10291030get sessionAgnosticData(): { lineNumber: number; column: number | undefined } {1031return {1032lineNumber: this._lineNumber,1033column: this._column1034};1035}10361037get supported(): boolean {1038if (!this.data) {1039return true;1040}1041if (this.logMessage && !this.data.supportsLogPoints) {1042return false;1043}1044if (this.condition && !this.data.supportsConditionalBreakpoints) {1045return false;1046}1047if (this.hitCondition && !this.data.supportsHitConditionalBreakpoints) {1048return false;1049}10501051return true;1052}10531054override setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {1055super.setSessionData(sessionId, data);1056if (!this._adapterData) {1057this._adapterData = this.adapterData;1058}1059}10601061override toJSON(): IBreakpointOptions & { id: string } {1062return {1063...super.toJSON(),1064uri: this._uri,1065lineNumber: this._lineNumber,1066column: this._column,1067adapterData: this.adapterData,1068triggeredBy: this.triggeredBy,1069};1070}10711072override toString(): string {1073return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`;1074}10751076public setSessionDidTrigger(sessionId: string): void {1077this.sessionsDidTrigger ??= new Set();1078this.sessionsDidTrigger.add(sessionId);1079}10801081public getSessionDidTrigger(sessionId: string): boolean {1082return !!this.sessionsDidTrigger?.has(sessionId);1083}10841085update(data: IBreakpointUpdateData): void {1086if (data.hasOwnProperty('lineNumber') && !isUndefinedOrNull(data.lineNumber)) {1087this._lineNumber = data.lineNumber;1088}1089if (data.hasOwnProperty('column')) {1090this._column = data.column;1091}1092if (data.hasOwnProperty('condition')) {1093this.condition = data.condition;1094}1095if (data.hasOwnProperty('hitCondition')) {1096this.hitCondition = data.hitCondition;1097}1098if (data.hasOwnProperty('logMessage')) {1099this.logMessage = data.logMessage;1100}1101if (data.hasOwnProperty('mode')) {1102this.mode = data.mode;1103this.modeLabel = data.modeLabel;1104}1105if (data.hasOwnProperty('triggeredBy')) {1106this.triggeredBy = data.triggeredBy;1107this.sessionsDidTrigger = undefined;1108}1109}1110}11111112export interface IFunctionBreakpointOptions extends IBaseBreakpointOptions {1113name: string;1114}11151116export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint {1117public name: string;11181119constructor(1120opts: IFunctionBreakpointOptions,1121id = generateUuid()1122) {1123super(id, opts);1124this.name = opts.name;1125}11261127toDAP(): DebugProtocol.FunctionBreakpoint {1128return {1129name: this.name,1130condition: this.condition,1131hitCondition: this.hitCondition,1132};1133}11341135override toJSON(): IFunctionBreakpointOptions & { id: string } {1136return {1137...super.toJSON(),1138name: this.name,1139};1140}11411142get supported(): boolean {1143if (!this.data) {1144return true;1145}11461147return this.data.supportsFunctionBreakpoints;1148}11491150override toString(): string {1151return this.name;1152}1153}11541155export interface IDataBreakpointOptions extends IBaseBreakpointOptions {1156description: string;1157src: DataBreakpointSource;1158canPersist: boolean;1159initialSessionData?: { session: IDebugSession; dataId: string };1160accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;1161accessType: DebugProtocol.DataBreakpointAccessType;1162}11631164export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {1165private readonly sessionDataIdForAddr = new WeakMap<IDebugSession, string | null>();11661167public readonly description: string;1168public readonly src: DataBreakpointSource;1169public readonly canPersist: boolean;1170public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;1171public readonly accessType: DebugProtocol.DataBreakpointAccessType;11721173constructor(1174opts: IDataBreakpointOptions,1175id = generateUuid()1176) {1177super(id, opts);1178this.description = opts.description;1179if ('dataId' in opts) { // back compat with old saved variables in 1.871180opts.src = { type: DataBreakpointSetType.Variable, dataId: opts.dataId as string };1181}1182this.src = opts.src;1183this.canPersist = opts.canPersist;1184this.accessTypes = opts.accessTypes;1185this.accessType = opts.accessType;1186if (opts.initialSessionData) {1187this.sessionDataIdForAddr.set(opts.initialSessionData.session, opts.initialSessionData.dataId);1188}1189}11901191async toDAP(session: IDebugSession): Promise<DebugProtocol.DataBreakpoint | undefined> {1192let dataId: string;1193if (this.src.type === DataBreakpointSetType.Variable) {1194dataId = this.src.dataId;1195} else {1196let sessionDataId = this.sessionDataIdForAddr.get(session);1197if (!sessionDataId) {1198sessionDataId = (await session.dataBytesBreakpointInfo(this.src.address, this.src.bytes))?.dataId;1199if (!sessionDataId) {1200return undefined;1201}1202this.sessionDataIdForAddr.set(session, sessionDataId);1203}1204dataId = sessionDataId;1205}12061207return {1208dataId,1209accessType: this.accessType,1210condition: this.condition,1211hitCondition: this.hitCondition,1212};1213}12141215override toJSON(): IDataBreakpointOptions & { id: string } {1216return {1217...super.toJSON(),1218description: this.description,1219src: this.src,1220accessTypes: this.accessTypes,1221accessType: this.accessType,1222canPersist: this.canPersist,1223};1224}12251226get supported(): boolean {1227if (!this.data) {1228return true;1229}12301231return this.data.supportsDataBreakpoints;1232}12331234override toString(): string {1235return this.description;1236}1237}12381239export interface IExceptionBreakpointOptions extends IBaseBreakpointOptions {1240filter: string;1241label: string;1242supportsCondition: boolean;1243description: string | undefined;1244conditionDescription: string | undefined;1245fallback?: boolean;1246}12471248export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint {12491250private supportedSessions: Set<string> = new Set();12511252public readonly filter: string;1253public readonly label: string;1254public readonly supportsCondition: boolean;1255public readonly description: string | undefined;1256public readonly conditionDescription: string | undefined;1257private fallback: boolean = false;12581259constructor(1260opts: IExceptionBreakpointOptions,1261id = generateUuid(),1262) {1263super(id, opts);1264this.filter = opts.filter;1265this.label = opts.label;1266this.supportsCondition = opts.supportsCondition;1267this.description = opts.description;1268this.conditionDescription = opts.conditionDescription;1269this.fallback = opts.fallback || false;1270}12711272override toJSON(): IExceptionBreakpointOptions & { id: string } {1273return {1274...super.toJSON(),1275filter: this.filter,1276label: this.label,1277enabled: this.enabled,1278supportsCondition: this.supportsCondition,1279conditionDescription: this.conditionDescription,1280condition: this.condition,1281fallback: this.fallback,1282description: this.description,1283};1284}12851286setSupportedSession(sessionId: string, supported: boolean): void {1287if (supported) {1288this.supportedSessions.add(sessionId);1289}1290else {1291this.supportedSessions.delete(sessionId);1292}1293}12941295/**1296* Used to specify which breakpoints to show when no session is specified.1297* Useful when no session is active and we want to show the exception breakpoints from the last session.1298*/1299setFallback(isFallback: boolean) {1300this.fallback = isFallback;1301}13021303get supported(): boolean {1304return true;1305}13061307/**1308* Checks if the breakpoint is applicable for the specified session.1309* If sessionId is undefined, returns true if this breakpoint is a fallback breakpoint.1310*/1311isSupportedSession(sessionId?: string): boolean {1312return sessionId ? this.supportedSessions.has(sessionId) : this.fallback;1313}13141315matches(filter: DebugProtocol.ExceptionBreakpointsFilter) {1316return this.filter === filter.filter1317&& this.label === filter.label1318&& this.supportsCondition === !!filter.supportsCondition1319&& this.conditionDescription === filter.conditionDescription1320&& this.description === filter.description;1321}13221323override toString(): string {1324return this.label;1325}1326}13271328export interface IInstructionBreakpointOptions extends IBaseBreakpointOptions {1329instructionReference: string;1330offset: number;1331canPersist: boolean;1332address: bigint;1333}13341335export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint {1336public readonly instructionReference: string;1337public readonly offset: number;1338public readonly canPersist: boolean;1339public readonly address: bigint;13401341constructor(1342opts: IInstructionBreakpointOptions,1343id = generateUuid()1344) {1345super(id, opts);1346this.instructionReference = opts.instructionReference;1347this.offset = opts.offset;1348this.canPersist = opts.canPersist;1349this.address = opts.address;1350}13511352toDAP(): DebugProtocol.InstructionBreakpoint {1353return {1354instructionReference: this.instructionReference,1355condition: this.condition,1356hitCondition: this.hitCondition,1357mode: this.mode,1358offset: this.offset,1359};1360}13611362override toJSON(): IInstructionBreakpointOptions & { id: string } {1363return {1364...super.toJSON(),1365instructionReference: this.instructionReference,1366offset: this.offset,1367canPersist: this.canPersist,1368address: this.address,1369};1370}13711372get supported(): boolean {1373if (!this.data) {1374return true;1375}13761377return this.data.supportsInstructionBreakpoints;1378}13791380override toString(): string {1381return this.instructionReference;1382}1383}13841385export class ThreadAndSessionIds implements ITreeElement {1386constructor(public sessionId: string, public threadId: number) { }13871388getId(): string {1389return `${this.sessionId}:${this.threadId}`;1390}1391}13921393interface IBreakpointModeInternal extends DebugProtocol.BreakpointMode {1394firstFromDebugType: string;1395}13961397export class DebugModel extends Disposable implements IDebugModel {13981399private sessions: IDebugSession[];1400private schedulers = new Map<string, { scheduler: RunOnceScheduler; completeDeferred: DeferredPromise<void> }>();1401private breakpointsActivated = true;1402private readonly _onDidChangeBreakpoints = this._register(new Emitter<IBreakpointsChangeEvent | undefined>());1403private readonly _onDidChangeCallStack = this._register(new Emitter<void>());1404private readonly _onDidChangeWatchExpressions = this._register(new Emitter<IExpression | undefined>());1405private readonly _breakpointModes = new Map<string, IBreakpointModeInternal>();1406private breakpoints!: Breakpoint[];1407private functionBreakpoints!: FunctionBreakpoint[];1408private exceptionBreakpoints!: ExceptionBreakpoint[];1409private dataBreakpoints!: DataBreakpoint[];1410private watchExpressions!: Expression[];1411private instructionBreakpoints: InstructionBreakpoint[];14121413constructor(1414debugStorage: DebugStorage,1415@ITextFileService private readonly textFileService: ITextFileService,1416@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,1417@ILogService private readonly logService: ILogService1418) {1419super();14201421this._register(autorun(reader => {1422this.breakpoints = debugStorage.breakpoints.read(reader);1423this.functionBreakpoints = debugStorage.functionBreakpoints.read(reader);1424this.exceptionBreakpoints = debugStorage.exceptionBreakpoints.read(reader);1425this.dataBreakpoints = debugStorage.dataBreakpoints.read(reader);1426this._onDidChangeBreakpoints.fire(undefined);1427}));14281429this._register(autorun(reader => {1430this.watchExpressions = debugStorage.watchExpressions.read(reader);1431this._onDidChangeWatchExpressions.fire(undefined);1432}));14331434this.instructionBreakpoints = [];1435this.sessions = [];1436}14371438getId(): string {1439return 'root';1440}14411442getSession(sessionId: string | undefined, includeInactive = false): IDebugSession | undefined {1443if (sessionId) {1444return this.getSessions(includeInactive).find(s => s.getId() === sessionId);1445}1446return undefined;1447}14481449getSessions(includeInactive = false): IDebugSession[] {1450// By default do not return inactive sessions.1451// However we are still holding onto inactive sessions due to repl and debug service session revival (eh scenario)1452return this.sessions.filter(s => includeInactive || s.state !== State.Inactive);1453}14541455addSession(session: IDebugSession): void {1456this.sessions = this.sessions.filter(s => {1457if (s.getId() === session.getId()) {1458// Make sure to de-dupe if a session is re-initialized. In case of EH debugging we are adding a session again after an attach.1459return false;1460}1461if (s.state === State.Inactive && s.configuration.name === session.configuration.name) {1462// Make sure to remove all inactive sessions that are using the same configuration as the new session1463return false;1464}14651466return true;1467});14681469let i = 1;1470while (this.sessions.some(s => s.getLabel() === session.getLabel())) {1471session.setName(`${session.configuration.name} ${++i}`);1472}14731474let index = -1;1475if (session.parentSession) {1476// Make sure that child sessions are placed after the parent session1477index = findLastIdx(this.sessions, s => s.parentSession === session.parentSession || s === session.parentSession);1478}1479if (index >= 0) {1480this.sessions.splice(index + 1, 0, session);1481} else {1482this.sessions.push(session);1483}1484this._onDidChangeCallStack.fire(undefined);1485}14861487get onDidChangeBreakpoints(): Event<IBreakpointsChangeEvent | undefined> {1488return this._onDidChangeBreakpoints.event;1489}14901491get onDidChangeCallStack(): Event<void> {1492return this._onDidChangeCallStack.event;1493}14941495get onDidChangeWatchExpressions(): Event<IExpression | undefined> {1496return this._onDidChangeWatchExpressions.event;1497}14981499rawUpdate(data: IRawModelUpdate): void {1500const session = this.sessions.find(p => p.getId() === data.sessionId);1501if (session) {1502session.rawUpdate(data);1503this._onDidChangeCallStack.fire(undefined);1504}1505}15061507clearThreads(id: string, removeThreads: boolean, reference: number | undefined = undefined): void {1508const session = this.sessions.find(p => p.getId() === id);1509this.schedulers.forEach(entry => {1510entry.scheduler.dispose();1511entry.completeDeferred.complete();1512});1513this.schedulers.clear();15141515if (session) {1516session.clearThreads(removeThreads, reference);1517this._onDidChangeCallStack.fire(undefined);1518}1519}15201521/**1522* Update the call stack and notify the call stack view that changes have occurred.1523*/1524async fetchCallstack(thread: IThread, levels?: number): Promise<void> {15251526if ((<Thread>thread).reachedEndOfCallStack) {1527return;1528}15291530const totalFrames = thread.stoppedDetails?.totalFrames;1531const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined;15321533if (!levels || (remainingFrames && levels > remainingFrames)) {1534levels = remainingFrames;1535}15361537if (levels && levels > 0) {1538await (<Thread>thread).fetchCallStack(levels);1539this._onDidChangeCallStack.fire();1540}15411542return;1543}15441545refreshTopOfCallstack(thread: Thread, fetchFullStack = true): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {1546if (thread.session.capabilities.supportsDelayedStackTraceLoading) {1547// For improved performance load the first stack frame and then load the rest async.1548let topCallStack = Promise.resolve();1549const wholeCallStack = new Promise<void>((c, e) => {1550topCallStack = thread.fetchCallStack(1).then(() => {1551if (!fetchFullStack) {1552c();1553this._onDidChangeCallStack.fire();1554return;1555}15561557if (!this.schedulers.has(thread.getId())) {1558const deferred = new DeferredPromise<void>();1559this.schedulers.set(thread.getId(), {1560completeDeferred: deferred,1561scheduler: new RunOnceScheduler(() => {1562thread.fetchCallStack(19).then(() => {1563const stale = thread.getStaleCallStack();1564const current = thread.getCallStack();1565let bottomOfCallStackChanged = stale.length !== current.length;1566for (let i = 1; i < stale.length && !bottomOfCallStackChanged; i++) {1567bottomOfCallStackChanged = !stale[i].equals(current[i]);1568}15691570if (bottomOfCallStackChanged) {1571this._onDidChangeCallStack.fire();1572}1573}).finally(() => {1574deferred.complete();1575this.schedulers.delete(thread.getId());1576});1577}, 420)1578});1579}15801581const entry = this.schedulers.get(thread.getId())!;1582entry.scheduler.schedule();1583entry.completeDeferred.p.then(c, e);1584this._onDidChangeCallStack.fire();1585});1586});15871588return { topCallStack, wholeCallStack };1589}15901591const wholeCallStack = thread.fetchCallStack();1592return { wholeCallStack, topCallStack: wholeCallStack };1593}15941595getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; triggeredOnly?: boolean }): IBreakpoint[] {1596if (filter) {1597const uriStr = filter.uri?.toString();1598const originalUriStr = filter.originalUri?.toString();1599return this.breakpoints.filter(bp => {1600if (uriStr && bp.uri.toString() !== uriStr) {1601return false;1602}1603if (originalUriStr && bp.originalUri.toString() !== originalUriStr) {1604return false;1605}1606if (filter.lineNumber && bp.lineNumber !== filter.lineNumber) {1607return false;1608}1609if (filter.column && bp.column !== filter.column) {1610return false;1611}1612if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) {1613return false;1614}1615if (filter.triggeredOnly && bp.triggeredBy === undefined) {1616return false;1617}16181619return true;1620});1621}16221623return this.breakpoints;1624}16251626getFunctionBreakpoints(): IFunctionBreakpoint[] {1627return this.functionBreakpoints;1628}16291630getDataBreakpoints(): IDataBreakpoint[] {1631return this.dataBreakpoints;1632}16331634getExceptionBreakpoints(): IExceptionBreakpoint[] {1635return this.exceptionBreakpoints;1636}16371638getExceptionBreakpointsForSession(sessionId?: string): IExceptionBreakpoint[] {1639return this.exceptionBreakpoints.filter(ebp => ebp.isSupportedSession(sessionId));1640}16411642getInstructionBreakpoints(): IInstructionBreakpoint[] {1643return this.instructionBreakpoints;1644}16451646setExceptionBreakpointsForSession(sessionId: string, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void {1647if (!filters) {1648return;1649}16501651let didChangeBreakpoints = false;1652filters.forEach((d) => {1653let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop();16541655if (!ebp) {1656didChangeBreakpoints = true;1657ebp = new ExceptionBreakpoint({1658filter: d.filter,1659label: d.label,1660enabled: !!d.default,1661supportsCondition: !!d.supportsCondition,1662description: d.description,1663conditionDescription: d.conditionDescription,1664});1665this.exceptionBreakpoints.push(ebp);1666}16671668ebp.setSupportedSession(sessionId, true);1669});16701671if (didChangeBreakpoints) {1672this._onDidChangeBreakpoints.fire(undefined);1673}1674}16751676removeExceptionBreakpointsForSession(sessionId: string): void {1677this.exceptionBreakpoints.forEach(ebp => ebp.setSupportedSession(sessionId, false));1678}16791680// Set last focused session as fallback session.1681// This is done to keep track of the exception breakpoints to show when no session is active.1682setExceptionBreakpointFallbackSession(sessionId: string): void {1683this.exceptionBreakpoints.forEach(ebp => ebp.setFallback(ebp.isSupportedSession(sessionId)));1684}16851686setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): void {1687(exceptionBreakpoint as ExceptionBreakpoint).condition = condition;1688this._onDidChangeBreakpoints.fire(undefined);1689}16901691areBreakpointsActivated(): boolean {1692return this.breakpointsActivated;1693}16941695setBreakpointsActivated(activated: boolean): void {1696this.breakpointsActivated = activated;1697this._onDidChangeBreakpoints.fire(undefined);1698}16991700addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] {1701const newBreakpoints = rawData.map(rawBp => {1702return new Breakpoint({1703uri,1704lineNumber: rawBp.lineNumber,1705column: rawBp.column,1706enabled: rawBp.enabled ?? true,1707condition: rawBp.condition,1708hitCondition: rawBp.hitCondition,1709logMessage: rawBp.logMessage,1710triggeredBy: rawBp.triggeredBy,1711adapterData: undefined,1712mode: rawBp.mode,1713modeLabel: rawBp.modeLabel,1714}, this.textFileService, this.uriIdentityService, this.logService, rawBp.id);1715});1716this.breakpoints = this.breakpoints.concat(newBreakpoints);1717this.breakpointsActivated = true;1718this.sortAndDeDup();17191720if (fireEvent) {1721this._onDidChangeBreakpoints.fire({ added: newBreakpoints, sessionOnly: false });1722}17231724return newBreakpoints;1725}17261727removeBreakpoints(toRemove: IBreakpoint[]): void {1728this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));1729this._onDidChangeBreakpoints.fire({ removed: toRemove, sessionOnly: false });1730}17311732updateBreakpoints(data: Map<string, IBreakpointUpdateData>): void {1733const updated: IBreakpoint[] = [];1734this.breakpoints.forEach(bp => {1735const bpData = data.get(bp.getId());1736if (bpData) {1737bp.update(bpData);1738updated.push(bp);1739}1740});1741this.sortAndDeDup();1742this._onDidChangeBreakpoints.fire({ changed: updated, sessionOnly: false });1743}17441745setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map<string, DebugProtocol.Breakpoint> | undefined): void {1746this.breakpoints.forEach(bp => {1747if (!data) {1748bp.setSessionData(sessionId, undefined);1749} else {1750const bpData = data.get(bp.getId());1751if (bpData) {1752bp.setSessionData(sessionId, toBreakpointSessionData(bpData, capabilites));1753}1754}1755});1756this.functionBreakpoints.forEach(fbp => {1757if (!data) {1758fbp.setSessionData(sessionId, undefined);1759} else {1760const fbpData = data.get(fbp.getId());1761if (fbpData) {1762fbp.setSessionData(sessionId, toBreakpointSessionData(fbpData, capabilites));1763}1764}1765});1766this.dataBreakpoints.forEach(dbp => {1767if (!data) {1768dbp.setSessionData(sessionId, undefined);1769} else {1770const dbpData = data.get(dbp.getId());1771if (dbpData) {1772dbp.setSessionData(sessionId, toBreakpointSessionData(dbpData, capabilites));1773}1774}1775});1776this.exceptionBreakpoints.forEach(ebp => {1777if (!data) {1778ebp.setSessionData(sessionId, undefined);1779} else {1780const ebpData = data.get(ebp.getId());1781if (ebpData) {1782ebp.setSessionData(sessionId, toBreakpointSessionData(ebpData, capabilites));1783}1784}1785});1786this.instructionBreakpoints.forEach(ibp => {1787if (!data) {1788ibp.setSessionData(sessionId, undefined);1789} else {1790const ibpData = data.get(ibp.getId());1791if (ibpData) {1792ibp.setSessionData(sessionId, toBreakpointSessionData(ibpData, capabilites));1793}1794}1795});17961797this._onDidChangeBreakpoints.fire({1798sessionOnly: true1799});1800}18011802getDebugProtocolBreakpoint(breakpointId: string, sessionId: string): DebugProtocol.Breakpoint | undefined {1803const bp = this.breakpoints.find(bp => bp.getId() === breakpointId);1804if (bp) {1805return bp.getDebugProtocolBreakpoint(sessionId);1806}1807return undefined;1808}18091810getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[] {1811return [...this._breakpointModes.values()].filter(mode => mode.appliesTo.includes(forBreakpointType));1812}18131814registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]) {1815for (const mode of modes) {1816const key = `${mode.mode}/${mode.label}`;1817const rec = this._breakpointModes.get(key);1818if (rec) {1819for (const target of mode.appliesTo) {1820if (!rec.appliesTo.includes(target)) {1821rec.appliesTo.push(target);1822}1823}1824} else {1825const duplicate = [...this._breakpointModes.values()].find(r => r !== rec && r.label === mode.label);1826if (duplicate) {1827duplicate.label = `${duplicate.label} (${duplicate.firstFromDebugType})`;1828}18291830this._breakpointModes.set(key, {1831mode: mode.mode,1832label: duplicate ? `${mode.label} (${debugType})` : mode.label,1833firstFromDebugType: debugType,1834description: mode.description,1835appliesTo: mode.appliesTo.slice(), // avoid later mutations1836});1837}1838}1839}18401841private sortAndDeDup(): void {1842this.breakpoints = this.breakpoints.sort((first, second) => {1843if (first.uri.toString() !== second.uri.toString()) {1844return resources.basenameOrAuthority(first.uri).localeCompare(resources.basenameOrAuthority(second.uri));1845}1846if (first.lineNumber === second.lineNumber) {1847if (first.column && second.column) {1848return first.column - second.column;1849}1850return 1;1851}18521853return first.lineNumber - second.lineNumber;1854});1855this.breakpoints = distinct(this.breakpoints, bp => `${bp.uri.toString()}:${bp.lineNumber}:${bp.column}`);1856}18571858setEnablement(element: IEnablement, enable: boolean): void {1859if (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof ExceptionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint) {1860const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];1861if (element.enabled !== enable && (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint)) {1862changed.push(element);1863}18641865element.enabled = enable;1866if (enable) {1867this.breakpointsActivated = true;1868}18691870this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });1871}1872}18731874enableOrDisableAllBreakpoints(enable: boolean): void {1875const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];18761877this.breakpoints.forEach(bp => {1878if (bp.enabled !== enable) {1879changed.push(bp);1880}1881bp.enabled = enable;1882});1883this.functionBreakpoints.forEach(fbp => {1884if (fbp.enabled !== enable) {1885changed.push(fbp);1886}1887fbp.enabled = enable;1888});1889this.dataBreakpoints.forEach(dbp => {1890if (dbp.enabled !== enable) {1891changed.push(dbp);1892}1893dbp.enabled = enable;1894});1895this.instructionBreakpoints.forEach(ibp => {1896if (ibp.enabled !== enable) {1897changed.push(ibp);1898}1899ibp.enabled = enable;1900});19011902if (enable) {1903this.breakpointsActivated = true;1904}19051906this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });1907}19081909addFunctionBreakpoint(opts: IFunctionBreakpointOptions, id?: string): IFunctionBreakpoint {1910const newFunctionBreakpoint = new FunctionBreakpoint(opts, id);1911this.functionBreakpoints.push(newFunctionBreakpoint);1912this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false });19131914return newFunctionBreakpoint;1915}19161917updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): void {1918const functionBreakpoint = this.functionBreakpoints.find(fbp => fbp.getId() === id);1919if (functionBreakpoint) {1920if (typeof update.name === 'string') {1921functionBreakpoint.name = update.name;1922}1923if (typeof update.condition === 'string') {1924functionBreakpoint.condition = update.condition;1925}1926if (typeof update.hitCondition === 'string') {1927functionBreakpoint.hitCondition = update.hitCondition;1928}1929this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false });1930}1931}19321933removeFunctionBreakpoints(id?: string): void {1934let removed: FunctionBreakpoint[];1935if (id) {1936removed = this.functionBreakpoints.filter(fbp => fbp.getId() === id);1937this.functionBreakpoints = this.functionBreakpoints.filter(fbp => fbp.getId() !== id);1938} else {1939removed = this.functionBreakpoints;1940this.functionBreakpoints = [];1941}1942this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });1943}19441945addDataBreakpoint(opts: IDataBreakpointOptions, id?: string): void {1946const newDataBreakpoint = new DataBreakpoint(opts, id);1947this.dataBreakpoints.push(newDataBreakpoint);1948this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false });1949}19501951updateDataBreakpoint(id: string, update: { hitCondition?: string; condition?: string }): void {1952const dataBreakpoint = this.dataBreakpoints.find(fbp => fbp.getId() === id);1953if (dataBreakpoint) {1954if (typeof update.condition === 'string') {1955dataBreakpoint.condition = update.condition;1956}1957if (typeof update.hitCondition === 'string') {1958dataBreakpoint.hitCondition = update.hitCondition;1959}1960this._onDidChangeBreakpoints.fire({ changed: [dataBreakpoint], sessionOnly: false });1961}1962}19631964removeDataBreakpoints(id?: string): void {1965let removed: DataBreakpoint[];1966if (id) {1967removed = this.dataBreakpoints.filter(fbp => fbp.getId() === id);1968this.dataBreakpoints = this.dataBreakpoints.filter(fbp => fbp.getId() !== id);1969} else {1970removed = this.dataBreakpoints;1971this.dataBreakpoints = [];1972}1973this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });1974}19751976addInstructionBreakpoint(opts: IInstructionBreakpointOptions): void {1977const newInstructionBreakpoint = new InstructionBreakpoint(opts);1978this.instructionBreakpoints.push(newInstructionBreakpoint);1979this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true });1980}19811982removeInstructionBreakpoints(instructionReference?: string, offset?: number): void {1983let removed: InstructionBreakpoint[] = [];1984if (instructionReference) {1985for (let i = 0; i < this.instructionBreakpoints.length; i++) {1986const ibp = this.instructionBreakpoints[i];1987if (ibp.instructionReference === instructionReference && (offset === undefined || ibp.offset === offset)) {1988removed.push(ibp);1989this.instructionBreakpoints.splice(i--, 1);1990}1991}1992} else {1993removed = this.instructionBreakpoints;1994this.instructionBreakpoints = [];1995}1996this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });1997}19981999getWatchExpressions(): Expression[] {2000return this.watchExpressions;2001}20022003addWatchExpression(name?: string): IExpression {2004const we = new Expression(name || '');2005this.watchExpressions.push(we);2006this._onDidChangeWatchExpressions.fire(we);20072008return we;2009}20102011renameWatchExpression(id: string, newName: string): void {2012const filtered = this.watchExpressions.filter(we => we.getId() === id);2013if (filtered.length === 1) {2014filtered[0].name = newName;2015this._onDidChangeWatchExpressions.fire(filtered[0]);2016}2017}20182019removeWatchExpressions(id: string | null = null): void {2020this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];2021this._onDidChangeWatchExpressions.fire(undefined);2022}20232024moveWatchExpression(id: string, position: number): void {2025const we = this.watchExpressions.find(we => we.getId() === id);2026if (we) {2027this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id);2028this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position));2029this._onDidChangeWatchExpressions.fire(undefined);2030}2031}20322033sourceIsNotAvailable(uri: uri): void {2034this.sessions.forEach(s => {2035const source = s.getSourceForUri(uri);2036if (source) {2037source.available = false;2038}2039});2040this._onDidChangeCallStack.fire(undefined);2041}2042}204320442045