/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import severity from 'vs/base/common/severity'; import { Event, Emitter } from 'vs/base/common/event'; import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes'; import { Position, IPosition } from 'vs/editor/common/core/position'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { mixin } from 'vs/base/common/objects'; import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { Range } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; export class DebugSession implements IDebugSession { private id: string; private _subId: string | undefined; private raw: RawDebugSession | undefined; private initialized = false; private _options: IDebugSessionOptions; private sources = new Map(); private threads = new Map(); private cancellationMap = new Map(); private rawListeners: IDisposable[] = []; private fetchThreadsScheduler: RunOnceScheduler | undefined; private repl: ReplModel; private readonly _onDidChangeState = new Emitter(); private readonly _onDidEndAdapter = new Emitter(); private readonly _onDidLoadedSource = new Emitter(); private readonly _onDidCustomEvent = new Emitter(); private readonly _onDidChangeREPLElements = new Emitter(); private name: string | undefined; private readonly _onDidChangeName = new Emitter(); constructor( private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, public root: IWorkspaceFolder, private model: DebugModel, options: IDebugSessionOptions | undefined, @IDebugService private readonly debugService: IDebugService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, @IViewletService private readonly viewletService: IViewletService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, @IProductService private readonly productService: IProductService, @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IOpenerService private readonly openerService: IOpenerService ) { this.id = generateUuid(); this._options = options || {}; if (this.hasSeparateRepl()) { this.repl = new ReplModel(); } else { this.repl = (this.parentSession as DebugSession).repl; } } getId(): string { return this.id; } setSubId(subId: string | undefined) { this._subId = subId; } get subId(): string | undefined { return this._subId; } get configuration(): IConfig { return this._configuration.resolved; } get unresolvedConfiguration(): IConfig | undefined { return this._configuration.unresolved; } get parentSession(): IDebugSession | undefined { return this._options.parentSession; } setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }) { this._configuration = configuration; } getLabel(): string { const includeRoot = this.workspaceContextService.getWorkspace().folders.length > 1; const name = this.name || this.configuration.name; return includeRoot && this.root ? `${name} (${resources.basenameOrAuthority(this.root.uri)})` : name; } setName(name: string): void { this.name = name; this._onDidChangeName.fire(name); } get state(): State { if (!this.initialized) { return State.Initializing; } if (!this.raw) { return State.Inactive; } const focusedThread = this.debugService.getViewModel().focusedThread; if (focusedThread && focusedThread.session === this) { return focusedThread.stopped ? State.Stopped : State.Running; } if (this.getAllThreads().some(t => t.stopped)) { return State.Stopped; } return State.Running; } get capabilities(): DebugProtocol.Capabilities { return this.raw ? this.raw.capabilities : Object.create(null); } //---- events get onDidChangeState(): Event { return this._onDidChangeState.event; } get onDidEndAdapter(): Event { return this._onDidEndAdapter.event; } get onDidChangeReplElements(): Event { return this._onDidChangeREPLElements.event; } get onDidChangeName(): Event { return this._onDidChangeName.event; } //---- DAP events get onDidCustomEvent(): Event { return this._onDidCustomEvent.event; } get onDidLoadedSource(): Event { return this._onDidLoadedSource.event; } //---- DAP requests /** * create and initialize a new debug adapter for this session */ initialize(dbgr: IDebugger): Promise { if (this.raw) { // if there was already a connection make sure to remove old listeners this.shutdown(); } return dbgr.getCustomTelemetryService().then(customTelemetryService => { return dbgr.createDebugAdapter(this).then(debugAdapter => { this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService); return this.raw.start().then(() => { this.registerListeners(); return this.raw!.initialize({ clientID: 'vscode', clientName: this.productService.nameLong, adapterID: this.configuration.type, pathFormat: 'path', linesStartAt1: true, columnsStartAt1: true, supportsVariableType: true, // #8858 supportsVariablePaging: true, // #9537 supportsRunInTerminalRequest: true, // #10574 locale: platform.locale }).then(() => { this.initialized = true; this._onDidChangeState.fire(); this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []); }); }); }); }).then(undefined, err => { this.initialized = true; this._onDidChangeState.fire(); return Promise.reject(err); }); } /** * launch or attach to the debuggee */ launchOrAttach(config: IConfig): Promise { if (this.raw) { // __sessionID only used for EH debugging (but we add it always for now...) config.__sessionId = this.getId(); return this.raw.launchOrAttach(config).then(result => { return undefined; }); } return Promise.reject(new Error('no debug adapter')); } /** * end the current debug adapter session */ terminate(restart = false): Promise { if (this.raw) { this.cancelAllRequests(); if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') { return this.raw.terminate(restart).then(response => { return undefined; }); } return this.raw.disconnect(restart).then(response => { return undefined; }); } return Promise.reject(new Error('no debug adapter')); } /** * end the current debug adapter session */ disconnect(restart = false): Promise { if (this.raw) { this.cancelAllRequests(); return this.raw.disconnect(restart).then(response => { return undefined; }); } return Promise.reject(new Error('no debug adapter')); } /** * restart debug adapter session */ restart(): Promise { if (this.raw) { this.cancelAllRequests(); return this.raw.restart().then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise { if (!this.raw) { return Promise.reject(new Error('no debug adapter')); } if (!this.raw.readyForBreakpoints) { return Promise.resolve(undefined); } const rawSource = this.getRawSource(modelUri); if (breakpointsToSend.length && !rawSource.adapterData) { rawSource.adapterData = breakpointsToSend[0].adapterData; } // Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959 if (rawSource.path) { rawSource.path = normalizeDriveLetter(rawSource.path); } return this.raw.setBreakpoints({ source: rawSource, lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber), breakpoints: breakpointsToSend.map(bp => ({ line: bp.sessionAgnosticData.lineNumber, column: bp.sessionAgnosticData.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })), sourceModified }).then(response => { if (response && response.body) { const data = new Map(); for (let i = 0; i < breakpointsToSend.length; i++) { data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]); } this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } }); } sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise { if (this.raw) { if (this.raw.readyForBreakpoints) { return this.raw.setFunctionBreakpoints({ breakpoints: fbpts }).then(response => { if (response && response.body) { const data = new Map(); for (let i = 0; i < fbpts.length; i++) { data.set(fbpts[i].getId(), response.body.breakpoints[i]); } this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } }); } return Promise.resolve(undefined); } return Promise.reject(new Error('no debug adapter')); } sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise { if (this.raw) { if (this.raw.readyForBreakpoints) { return this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) }).then(() => undefined); } return Promise.resolve(undefined); } return Promise.reject(new Error('no debug adapter')); } dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> { if (this.raw) { if (this.raw.readyForBreakpoints) { return this.raw.dataBreakpointInfo({ name, variablesReference }).then(response => response.body); } return Promise.reject(new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"))); } return Promise.reject(new Error('no debug adapter')); } sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { if (this.raw) { if (this.raw.readyForBreakpoints) { return this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints }).then(response => { if (response && response.body) { const data = new Map(); for (let i = 0; i < dataBreakpoints.length; i++) { data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]); } this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } }); } return Promise.resolve(undefined); } return Promise.reject(new Error('no debug adapter')); } async breakpointsLocations(uri: URI, lineNumber: number): Promise { if (this.raw) { const source = this.getRawSource(uri); const response = await this.raw.breakpointLocations({ source, line: lineNumber }); const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 })); return distinct(positions, p => `${p.lineNumber}:${p.column}`); } return Promise.reject(new Error('no debug adapter')); } customRequest(request: string, args: any): Promise { if (this.raw) { return this.raw.custom(request, args); } return Promise.reject(new Error('no debug adapter')); } stackTrace(threadId: number, startFrame: number, levels: number): Promise { if (this.raw) { const token = this.getNewCancellationToken(threadId); return this.raw.stackTrace({ threadId, startFrame, levels }, token); } return Promise.reject(new Error('no debug adapter')); } exceptionInfo(threadId: number): Promise { if (this.raw) { return this.raw.exceptionInfo({ threadId }).then(response => { if (response) { return { id: response.body.exceptionId, description: response.body.description, breakMode: response.body.breakMode, details: response.body.details }; } return undefined; }); } return Promise.reject(new Error('no debug adapter')); } scopes(frameId: number, threadId: number): Promise { if (this.raw) { const token = this.getNewCancellationToken(threadId); return this.raw.scopes({ frameId }, token); } return Promise.reject(new Error('no debug adapter')); } variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { if (this.raw) { const token = threadId ? this.getNewCancellationToken(threadId) : undefined; return this.raw.variables({ variablesReference, filter, start, count }, token); } return Promise.reject(new Error('no debug adapter')); } evaluate(expression: string, frameId: number, context?: string): Promise { if (this.raw) { return this.raw.evaluate({ expression, frameId, context }); } return Promise.reject(new Error('no debug adapter')); } restartFrame(frameId: number, threadId: number): Promise { if (this.raw) { return this.raw.restartFrame({ frameId }, threadId).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } next(threadId: number): Promise { if (this.raw) { return this.raw.next({ threadId }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } stepIn(threadId: number): Promise { if (this.raw) { return this.raw.stepIn({ threadId }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } stepOut(threadId: number): Promise { if (this.raw) { return this.raw.stepOut({ threadId }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } stepBack(threadId: number): Promise { if (this.raw) { return this.raw.stepBack({ threadId }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } continue(threadId: number): Promise { if (this.raw) { return this.raw.continue({ threadId }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } reverseContinue(threadId: number): Promise { if (this.raw) { return this.raw.reverseContinue({ threadId }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } pause(threadId: number): Promise { if (this.raw) { return this.raw.pause({ threadId }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } terminateThreads(threadIds?: number[]): Promise { if (this.raw) { return this.raw.terminateThreads({ threadIds }).then(() => undefined); } return Promise.reject(new Error('no debug adapter')); } setVariable(variablesReference: number, name: string, value: string): Promise { if (this.raw) { return this.raw.setVariable({ variablesReference, name, value }); } return Promise.reject(new Error('no debug adapter')); } gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise { if (this.raw) { return this.raw.gotoTargets({ source, line, column }); } return Promise.reject(new Error('no debug adapter')); } goto(threadId: number, targetId: number): Promise { if (this.raw) { return this.raw.goto({ threadId, targetId }); } return Promise.reject(new Error('no debug adapter')); } loadSource(resource: URI): Promise { if (!this.raw) { return Promise.reject(new Error('no debug adapter')); } const source = this.getSourceForUri(resource); let rawSource: DebugProtocol.Source; if (source) { rawSource = source.raw; } else { // create a Source const data = Source.getEncodedDebugData(resource); rawSource = { path: data.path, sourceReference: data.sourceReference }; } return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource }); } getLoadedSources(): Promise { if (this.raw) { return this.raw.loadedSources({}).then(response => { if (response.body && response.body.sources) { return response.body.sources.map(src => this.getSource(src)); } else { return []; } }, () => { return []; }); } return Promise.reject(new Error('no debug adapter')); } completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { if (this.raw) { return this.raw.completions({ frameId, text, column: position.column, line: position.lineNumber, }, token).then(response => { const result: CompletionItem[] = []; if (response && response.body && response.body.targets) { response.body.targets.forEach(item => { if (item && item.label) { result.push({ label: item.label, insertText: item.text || item.label, kind: completionKindFromString(item.type || 'property'), filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position), sortText: item.sortText }); } }); } return result; }); } return Promise.reject(new Error('no debug adapter')); } //---- threads getThread(threadId: number): Thread | undefined { return this.threads.get(threadId); } getAllThreads(): IThread[] { const result: IThread[] = []; this.threads.forEach(t => result.push(t)); return result; } clearThreads(removeThreads: boolean, reference: number | undefined = undefined): void { if (reference !== undefined && reference !== null) { const thread = this.threads.get(reference); if (thread) { thread.clearCallStack(); thread.stoppedDetails = undefined; thread.stopped = false; if (removeThreads) { this.threads.delete(reference); } } } else { this.threads.forEach(thread => { thread.clearCallStack(); thread.stoppedDetails = undefined; thread.stopped = false; }); if (removeThreads) { this.threads.clear(); ExpressionContainer.allValues.clear(); } } } rawUpdate(data: IRawModelUpdate): void { const threadIds: number[] = []; data.threads.forEach(thread => { threadIds.push(thread.id); if (!this.threads.has(thread.id)) { // A new thread came in, initialize it. this.threads.set(thread.id, new Thread(this, thread.name, thread.id)); } else if (thread.name) { // Just the thread name got updated #18244 const oldThread = this.threads.get(thread.id); if (oldThread) { oldThread.name = thread.name; } } }); this.threads.forEach(t => { // Remove all old threads which are no longer part of the update #75980 if (threadIds.indexOf(t.threadId) === -1) { this.threads.delete(t.threadId); } }); const stoppedDetails = data.stoppedDetails; if (stoppedDetails) { // Set the availability of the threads' callstacks depending on // whether the thread is stopped or not if (stoppedDetails.allThreadsStopped) { this.threads.forEach(thread => { thread.stoppedDetails = thread.threadId === stoppedDetails.threadId ? stoppedDetails : { reason: undefined }; thread.stopped = true; thread.clearCallStack(); }); } else { const thread = typeof stoppedDetails.threadId === 'number' ? this.threads.get(stoppedDetails.threadId) : undefined; if (thread) { // One thread is stopped, only update that thread. thread.stoppedDetails = stoppedDetails; thread.clearCallStack(); thread.stopped = true; } } } } private fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise { return this.raw ? this.raw.threads().then(response => { if (response && response.body && response.body.threads) { this.model.rawUpdate({ sessionId: this.getId(), threads: response.body.threads, stoppedDetails }); } }) : Promise.resolve(undefined); } //---- private private registerListeners(): void { if (!this.raw) { return; } this.rawListeners.push(this.raw.onDidInitialize(() => { aria.status(nls.localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = () => { if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) { return this.raw.configurationDone().then(undefined, e => { // Disconnect the debug session on configuration done error #10596 if (this.raw) { this.raw.disconnect(); } if (e.command !== 'canceled' && e.message !== 'canceled') { this.notificationService.error(e); } }); } return undefined; }; // Send all breakpoints this.debugService.sendAllBreakpoints(this).then(sendConfigurationDone, sendConfigurationDone) .then(() => this.fetchThreads()); })); this.rawListeners.push(this.raw.onDidStop(event => { this.fetchThreads(event.body).then(() => { const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined; if (thread) { // Call fetch call stack twice, the first only return the top stack frame. // Second retrieves the rest of the call stack. For performance reasons #25605 const promises = this.model.fetchCallStack(thread); const focus = () => { if (!event.body.preserveFocusHint && thread.getCallStack().length) { this.debugService.focusStackFrame(undefined, thread); if (thread.stoppedDetails) { if (this.configurationService.getValue('debug').openDebug === 'openOnDebugBreak') { this.viewletService.openViewlet(VIEWLET_ID); } if (this.configurationService.getValue('debug').focusWindowOnBreak) { this.windowService.focusWindow(); } } } }; promises.topCallStack.then(focus); promises.wholeCallStack.then(() => { if (!this.debugService.getViewModel().focusedStackFrame) { // The top stack frame can be deemphesized so try to focus again #68616 focus(); } }); } }).then(() => this._onDidChangeState.fire()); })); this.rawListeners.push(this.raw.onDidThread(event => { if (event.body.reason === 'started') { // debounce to reduce threadsRequest frequency and improve performance if (!this.fetchThreadsScheduler) { this.fetchThreadsScheduler = new RunOnceScheduler(() => { this.fetchThreads(); }, 100); this.rawListeners.push(this.fetchThreadsScheduler); } if (!this.fetchThreadsScheduler.isScheduled()) { this.fetchThreadsScheduler.schedule(); } } else if (event.body.reason === 'exited') { this.model.clearThreads(this.getId(), true, event.body.threadId); } })); this.rawListeners.push(this.raw.onDidTerminateDebugee(event => { aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); if (event.body && event.body.restart) { this.debugService.restartSession(this, event.body.restart).then(undefined, onUnexpectedError); } else if (this.raw) { this.raw.disconnect(); } })); this.rawListeners.push(this.raw.onDidContinued(event => { const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId; if (threadId) { const tokens = this.cancellationMap.get(threadId); this.cancellationMap.delete(threadId); if (tokens) { tokens.forEach(t => t.cancel()); } } else { this.cancelAllRequests(); } this.model.clearThreads(this.getId(), false, threadId); this._onDidChangeState.fire(); })); let outpuPromises: Promise[] = []; this.rawListeners.push(this.raw.onDidOutput(event => { if (!event.body || !this.raw) { return; } const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info; if (event.body.category === 'telemetry') { // only log telemetry events from debug adapter if the debug extension provided the telemetry key // and the user opted in telemetry if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) { // __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly. this.raw.customTelemetryService.publicLog(event.body.output, event.body.data); } return; } // Make sure to append output in the correct order by properly waiting on preivous promises #33822 const waitFor = outpuPromises.slice(); const source = event.body.source && event.body.line ? { lineNumber: event.body.line, column: event.body.column ? event.body.column : 1, source: this.getSource(event.body.source) } : undefined; if (event.body.variablesReference) { const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid()); outpuPromises.push(container.getChildren().then(children => { return Promise.all(waitFor).then(() => children.forEach(child => { // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) (child).name = null; this.appendToRepl(child, outputSeverity, source); })); })); } else if (typeof event.body.output === 'string') { Promise.all(waitFor).then(() => this.appendToRepl(event.body.output, outputSeverity, source)); } Promise.all(outpuPromises).then(() => outpuPromises = []); })); this.rawListeners.push(this.raw.onDidBreakpoint(event => { const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined; const breakpoint = this.model.getBreakpoints().filter(bp => bp.getIdFromAdapter(this.getId()) === id).pop(); const functionBreakpoint = this.model.getFunctionBreakpoints().filter(bp => bp.getIdFromAdapter(this.getId()) === id).pop(); if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) { const source = this.getSource(event.body.breakpoint.source); const bps = this.model.addBreakpoints(source.uri, [{ column: event.body.breakpoint.column, enabled: true, lineNumber: event.body.breakpoint.line, }], false); if (bps.length === 1) { const data = new Map([[bps[0].getId(), event.body.breakpoint]]); this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } } if (event.body.reason === 'removed') { if (breakpoint) { this.model.removeBreakpoints([breakpoint]); } if (functionBreakpoint) { this.model.removeFunctionBreakpoints(functionBreakpoint.getId()); } } if (event.body.reason === 'changed') { if (breakpoint) { if (!breakpoint.column) { event.body.breakpoint.column = undefined; } const data = new Map([[breakpoint.getId(), event.body.breakpoint]]); this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } if (functionBreakpoint) { const data = new Map([[functionBreakpoint.getId(), event.body.breakpoint]]); this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } } })); this.rawListeners.push(this.raw.onDidLoadedSource(event => { this._onDidLoadedSource.fire({ reason: event.body.reason, source: this.getSource(event.body.source) }); })); this.rawListeners.push(this.raw.onDidCustomEvent(event => { this._onDidCustomEvent.fire(event); })); this.rawListeners.push(this.raw.onDidExitAdapter(event => { this.initialized = true; this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined); this._onDidEndAdapter.fire(event); })); } shutdown(): void { dispose(this.rawListeners); if (this.raw) { this.raw.disconnect(); this.raw.dispose(); } this.raw = undefined; this.model.clearThreads(this.getId(), true); this._onDidChangeState.fire(); } //---- sources getSourceForUri(uri: URI): Source | undefined { return this.sources.get(this.getUriKey(uri)); } getSource(raw?: DebugProtocol.Source): Source { let source = new Source(raw, this.getId()); const uriKey = this.getUriKey(source.uri); const found = this.sources.get(uriKey); if (found) { source = found; // merge attributes of new into existing source.raw = mixin(source.raw, raw); if (source.raw && raw) { // Always take the latest presentation hint from adapter #42139 source.raw.presentationHint = raw.presentationHint; } } else { this.sources.set(uriKey, source); } return source; } private getRawSource(uri: URI): DebugProtocol.Source { const source = this.getSourceForUri(uri); if (source) { return source.raw; } else { const data = Source.getEncodedDebugData(uri); return { name: data.name, path: data.path, sourceReference: data.sourceReference }; } } private getNewCancellationToken(threadId: number): CancellationToken { const tokenSource = new CancellationTokenSource(); const tokens = this.cancellationMap.get(threadId) || []; tokens.push(tokenSource); this.cancellationMap.set(threadId, tokens); return tokenSource.token; } private cancelAllRequests(): void { this.cancellationMap.forEach(tokens => tokens.forEach(t => t.cancel())); this.cancellationMap.clear(); } private getUriKey(uri: URI): string { // TODO: the following code does not make sense if uri originates from a different platform return platform.isLinux ? uri.toString() : uri.toString().toLowerCase(); } // REPL getReplElements(): IReplElement[] { return this.repl.getReplElements(); } hasSeparateRepl(): boolean { return !this.parentSession || this._options.repl !== 'mergeWithParent'; } removeReplExpressions(): void { this.repl.removeReplExpressions(); this._onDidChangeREPLElements.fire(); } async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { const expressionEvaluated = this.repl.addReplExpression(this, stackFrame, name); this._onDidChangeREPLElements.fire(); await expressionEvaluated; this._onDidChangeREPLElements.fire(); // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. variableSetEmitter.fire(); } appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void { this.repl.appendToRepl(this, data, severity, source); this._onDidChangeREPLElements.fire(); } logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { this.repl.logToRepl(this, sev, args, frame); this._onDidChangeREPLElements.fire(); } }