/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as paths from 'vs/base/common/paths'; import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { asThenable } from 'vs/base/common/async'; import * as nls from 'vs/nls'; import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto } from 'vs/workbench/api/node/extHost.protocol'; import * as vscode from 'vscode'; import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DebugAdapterImplementation } from 'vs/workbench/api/node/extHostTypes'; import { generateUuid } from 'vs/base/common/uuid'; import { ExecutableDebugAdapter, SocketDebugAdapter, AbstractDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; import { ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IDebugAdapterImplementation, IAdapterDescriptor } from 'vs/workbench/parts/debug/common/debug'; import { getTerminalLauncher, hasChildprocesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/node/variableResolver'; import { ExtHostConfiguration } from './extHostConfiguration'; import { convertToVSCPaths, convertToDAPaths, stringToUri, uriToString } from 'vs/workbench/parts/debug/common/debugUtils'; import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; export class ExtHostDebugService implements ExtHostDebugServiceShape { private _configProviderHandleCounter: number; private _configProviders: TypeProviderPair[]; private _adapterProviderHandleCounter: number; private _adapterProviders: TypeDaProviderPair[]; private _debugServiceProxy: MainThreadDebugServiceShape; private _debugSessions: Map = new Map(); private readonly _onDidStartDebugSession: Emitter; get onDidStartDebugSession(): Event { return this._onDidStartDebugSession.event; } private readonly _onDidTerminateDebugSession: Emitter; get onDidTerminateDebugSession(): Event { return this._onDidTerminateDebugSession.event; } private readonly _onDidChangeActiveDebugSession: Emitter; get onDidChangeActiveDebugSession(): Event { return this._onDidChangeActiveDebugSession.event; } private _activeDebugSession: ExtHostDebugSession | undefined; get activeDebugSession(): ExtHostDebugSession | undefined { return this._activeDebugSession; } private readonly _onDidReceiveDebugSessionCustomEvent: Emitter; get onDidReceiveDebugSessionCustomEvent(): Event { return this._onDidReceiveDebugSessionCustomEvent.event; } private _activeDebugConsole: ExtHostDebugConsole; get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; } private _breakpoints: Map; private _breakpointEventsActive: boolean; private readonly _onDidChangeBreakpoints: Emitter; private _aexCommands: Map; private _debugAdapters: Map; private _debugAdaptersTrackers: Map; private _variableResolver: IConfigurationResolverService; private _integratedTerminalInstance: vscode.Terminal; private _terminalDisposedListener: IDisposable; constructor(mainContext: IMainContext, private _workspaceService: ExtHostWorkspace, private _extensionService: ExtHostExtensionService, private _editorsService: ExtHostDocumentsAndEditors, private _configurationService: ExtHostConfiguration, private _terminalService: ExtHostTerminalService, private _commandService: ExtHostCommands ) { this._configProviderHandleCounter = 0; this._configProviders = []; this._adapterProviderHandleCounter = 0; this._adapterProviders = []; this._aexCommands = new Map(); this._debugAdapters = new Map(); this._debugAdaptersTrackers = new Map(); this._onDidStartDebugSession = new Emitter(); this._onDidTerminateDebugSession = new Emitter(); this._onDidChangeActiveDebugSession = new Emitter(); this._onDidReceiveDebugSessionCustomEvent = new Emitter(); this._debugServiceProxy = mainContext.getProxy(MainContext.MainThreadDebugService); this._onDidChangeBreakpoints = new Emitter({ onFirstListenerAdd: () => { this.startBreakpoints(); } }); this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); this._breakpoints = new Map(); this._breakpointEventsActive = false; // register all debug extensions const debugTypes: string[] = []; for (const ed of this._extensionService.getAllExtensionDescriptions()) { if (ed.contributes) { const debuggers = ed.contributes['debuggers']; if (debuggers && debuggers.length > 0) { for (const dbg of debuggers) { // only debugger contributions with a "label" are considered a "defining" debugger contribution if (dbg.type && dbg.label) { debugTypes.push(dbg.type); if (dbg.adapterExecutableCommand) { this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); } } } } } } if (debugTypes.length > 0) { this._debugServiceProxy.$registerDebugTypes(debugTypes); } } // extension debug API get onDidChangeBreakpoints(): Event { return this._onDidChangeBreakpoints.event; } get breakpoints(): vscode.Breakpoint[] { this.startBreakpoints(); const result: vscode.Breakpoint[] = []; this._breakpoints.forEach(bp => result.push(bp)); return result; } public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Thenable { this.startBreakpoints(); // assign uuids for brand new breakpoints const breakpoints: vscode.Breakpoint[] = []; for (const bp of breakpoints0) { let id = bp['_id']; if (id) { // has already id if (this._breakpoints.has(id)) { // already there } else { breakpoints.push(bp); } } else { id = generateUuid(); bp['_id'] = id; this._breakpoints.set(id, bp); breakpoints.push(bp); } } // send notification for added breakpoints this.fireBreakpointChanges(breakpoints, [], []); // convert added breakpoints to DTOs const dtos: (ISourceMultiBreakpointDto | IFunctionBreakpointDto)[] = []; const map = new Map(); for (const bp of breakpoints) { if (bp instanceof SourceBreakpoint) { let dto = map.get(bp.location.uri.toString()); if (!dto) { dto = { type: 'sourceMulti', uri: bp.location.uri, lines: [] }; map.set(bp.location.uri.toString(), dto); dtos.push(dto); } dto.lines.push({ id: bp['_id'], enabled: bp.enabled, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage, line: bp.location.range.start.line, character: bp.location.range.start.character }); } else if (bp instanceof FunctionBreakpoint) { dtos.push({ type: 'function', id: bp['_id'], enabled: bp.enabled, hitCondition: bp.hitCondition, logMessage: bp.logMessage, condition: bp.condition, functionName: bp.functionName }); } } // send DTOs to VS Code return this._debugServiceProxy.$registerBreakpoints(dtos); } public removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Thenable { this.startBreakpoints(); // remove from array const breakpoints: vscode.Breakpoint[] = []; for (const b of breakpoints0) { let id = b['_id']; if (id && this._breakpoints.delete(id)) { breakpoints.push(b); } } // send notification this.fireBreakpointChanges([], breakpoints, []); // unregister with VS Code const ids = breakpoints.filter(bp => bp instanceof SourceBreakpoint).map(bp => bp['_id']); const fids = breakpoints.filter(bp => bp instanceof FunctionBreakpoint).map(bp => bp['_id']); return this._debugServiceProxy.$unregisterBreakpoints(ids, fids); } public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration): Thenable { return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig); } public registerDebugConfigurationProvider(extension: IExtensionDescription, type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { if (!provider) { return new Disposable(() => { }); } let handle = this._configProviderHandleCounter++; this._configProviders.push({ type, handle, provider }); this._debugServiceProxy.$registerDebugConfigurationProvider(type, !!provider.provideDebugConfigurations, !!provider.resolveDebugConfiguration, !!provider.debugAdapterExecutable, // TODO@AW: legacy !!provider.provideDebugAdapterTracker, handle); return new Disposable(() => { this._configProviders = this._configProviders.filter(p => p.provider !== provider); // remove this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle); }); } public registerDebugAdapterProvider(extension: IExtensionDescription, type: string, provider: vscode.DebugAdapterProvider): vscode.Disposable { if (!provider) { return new Disposable(() => { }); } // a DebugAdapterProvider can only be registered in the extension that contributes the debugger if (!this.definesDebugType(extension, type)) { throw new Error(`method 'provideDebugAdapter' must only be called from the extension that defines the '${type}' debugger.`); } // make sure that only one provider for this type is registered if (this.getAdapterProviderByType(type)) { throw new Error(`a provider with method 'provideDebugAdapter' can only be registered once per a type.`); } let handle = this._adapterProviderHandleCounter++; this._adapterProviders.push({ type, handle, provider }); this._debugServiceProxy.$registerDebugAdapterProvider(type, handle); return new Disposable(() => { this._adapterProviders = this._adapterProviders.filter(p => p.provider !== provider); // remove this._debugServiceProxy.$unregisterDebugAdapterProvider(handle); }); } // RPC methods (ExtHostDebugServiceShape) public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Thenable { if (args.kind === 'integrated') { if (!this._terminalDisposedListener) { // React on terminal disposed and check if that is the debug terminal #12956 this._terminalDisposedListener = this._terminalService.onDidCloseTerminal(terminal => { if (this._integratedTerminalInstance && this._integratedTerminalInstance === terminal) { this._integratedTerminalInstance = null; } }); } return new Promise(resolve => { if (this._integratedTerminalInstance) { this._integratedTerminalInstance.processId.then(pid => { resolve(hasChildprocesses(pid)); }, err => { resolve(true); }); } else { resolve(true); } }).then(needNewTerminal => { if (needNewTerminal) { this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee")); } this._integratedTerminalInstance.show(); return new Promise((resolve) => { setTimeout(_ => { const command = prepareCommand(args, config); this._integratedTerminalInstance.sendText(command, true); resolve(void 0); }, 500); }); }); } else if (args.kind === 'external') { const terminalLauncher = getTerminalLauncher(); if (terminalLauncher) { return terminalLauncher.runInTerminal(args, config); } } return void 0; } public $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise { if (!this._variableResolver) { this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, this._configurationService); } let ws: IWorkspaceFolder; const folder = this.getFolder(folderUri); if (folder) { ws = { uri: folder.uri, name: folder.name, index: folder.index, toResource: () => { throw new Error('Not implemented'); } }; } return Promise.resolve(this._variableResolver.resolveAny(ws, config)); } public $startDASession(handle: number, sessionDto: IDebugSessionDto, config: vscode.DebugConfiguration): Thenable { const mythis = this; return this.getAdapterDescriptor(this.getAdapterProviderByType(config.type), sessionDto, config).then(x => { const adapter = this.convertToDto(x); let da: AbstractDebugAdapter | undefined = undefined; switch (adapter.type) { case 'server': da = new SocketDebugAdapter(adapter); break; case 'executable': da = new ExecutableDebugAdapter(adapter, config.type); break; case 'implementation': da = new DirectDebugAdapter(adapter.implementation); break; default: break; } if (da) { this._debugAdapters.set(handle, da); return this.getDebugAdapterTrackers(sessionDto, config).then(tracker => { if (tracker) { this._debugAdaptersTrackers.set(handle, tracker); } da.onMessage(message => { if (tracker) { tracker.fromDebugAdapter(message); } // DA -> VS Code message = convertToVSCPaths(message, source => stringToUri(source)); mythis._debugServiceProxy.$acceptDAMessage(handle, message); }); da.onError(err => { if (tracker) { tracker.debugAdapterError(err); } this._debugServiceProxy.$acceptDAError(handle, err.name, err.message, err.stack); }); da.onExit(code => { if (tracker) { tracker.debugAdapterExit(code, null); } this._debugServiceProxy.$acceptDAExit(handle, code, null); }); if (tracker) { tracker.startDebugAdapter(); } return da.startSession(); }); } return undefined; }); } public $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): Promise { // VS Code -> DA message = convertToDAPaths(message, source => uriToString(source)); const tracker = this._debugAdaptersTrackers.get(handle); if (tracker) { tracker.toDebugAdapter(message); } const da = this._debugAdapters.get(handle); if (da) { da.sendMessage(message); } return void 0; } public $stopDASession(handle: number): Thenable { const tracker = this._debugAdaptersTrackers.get(handle); this._debugAdaptersTrackers.delete(handle); if (tracker) { tracker.stopDebugAdapter(); } const da = this._debugAdapters.get(handle); this._debugAdapters.delete(handle); if (da) { return da.stopSession(); } else { return void 0; } } public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { let a: vscode.Breakpoint[] = []; let r: vscode.Breakpoint[] = []; let c: vscode.Breakpoint[] = []; if (delta.added) { for (const bpd of delta.added) { if (!this._breakpoints.has(bpd.id)) { let bp: vscode.Breakpoint; if (bpd.type === 'function') { bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); } else { const uri = URI.revive(bpd.uri); bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); } bp['_id'] = bpd.id; this._breakpoints.set(bpd.id, bp); a.push(bp); } } } if (delta.removed) { for (const id of delta.removed) { const bp = this._breakpoints.get(id); if (bp) { this._breakpoints.delete(id); r.push(bp); } } } if (delta.changed) { for (const bpd of delta.changed) { let bp = this._breakpoints.get(bpd.id); if (bp) { if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { const fbp = bp; fbp.enabled = bpd.enabled; fbp.condition = bpd.condition; fbp.hitCondition = bpd.hitCondition; fbp.logMessage = bpd.logMessage; fbp.functionName = bpd.functionName; } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { const sbp = bp; sbp.enabled = bpd.enabled; sbp.condition = bpd.condition; sbp.hitCondition = bpd.hitCondition; sbp.logMessage = bpd.logMessage; sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); } c.push(bp); } } } this.fireBreakpointChanges(a, r, c); } public $provideDebugConfigurations(handle: number, folderUri: UriComponents | undefined): Thenable { let provider = this.getConfigProviderByHandle(handle); if (!provider) { return Promise.reject(new Error('no handler found')); } if (!provider.provideDebugConfigurations) { return Promise.reject(new Error('handler has no method provideDebugConfigurations')); } return asThenable(() => provider.provideDebugConfigurations(this.getFolder(folderUri), CancellationToken.None)); } public $resolveDebugConfiguration(handle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Thenable { let provider = this.getConfigProviderByHandle(handle); if (!provider) { return Promise.reject(new Error('no handler found')); } if (!provider.resolveDebugConfiguration) { return Promise.reject(new Error('handler has no method resolveDebugConfiguration')); } return asThenable(() => provider.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, CancellationToken.None)); } // TODO@AW legacy public $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Thenable { let provider = this.getConfigProviderByHandle(handle); if (!provider) { return Promise.reject(new Error('no handler found')); } if (!provider.debugAdapterExecutable) { return Promise.reject(new Error('handler has no method debugAdapterExecutable')); } return asThenable(() => provider.debugAdapterExecutable(this.getFolder(folderUri), CancellationToken.None)).then(x => this.convertToDto(x)); } public $provideDebugAdapter(handle: number, sessionDto: IDebugSessionDto, config: vscode.DebugConfiguration): Thenable { let adapterProvider = this.getAdapterProviderByHandle(handle); if (!adapterProvider) { return Promise.reject(new Error('no handler found')); } return this.getAdapterDescriptor(adapterProvider, sessionDto, config).then(x => this.convertToDto(x)); } public $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): void { this._onDidStartDebugSession.fire(this.getSession(sessionDto)); } public $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): void { this._onDidTerminateDebugSession.fire(this.getSession(sessionDto)); this._debugSessions.delete(sessionDto.id); } public $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto): void { this._activeDebugSession = sessionDto ? this.getSession(sessionDto) : undefined; this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); } public $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): void { const ee: vscode.DebugSessionCustomEvent = { session: this.getSession(sessionDto), event: event.event, body: event.body }; this._onDidReceiveDebugSessionCustomEvent.fire(ee); } // private & dto helpers private convertToDto(x: vscode.DebugAdapterDescriptor) { if (x instanceof DebugAdapterExecutable) { return { type: 'executable', command: x.command, args: x.args, cwd: x.cwd, env: x.env }; } else if (x instanceof DebugAdapterServer) { return { type: 'server', port: x.port, host: x.host }; } else if (x instanceof DebugAdapterImplementation) { return { type: 'implementation', implementation: x.implementation }; } else { throw new Error('unexpected type'); } } private getAdapterProviderByType(type: string): vscode.DebugAdapterProvider { const results = this._adapterProviders.filter(p => p.type === type); if (results.length > 0) { return results[0].provider; } return undefined; } private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterProvider { const results = this._adapterProviders.filter(p => p.handle === handle); if (results.length > 0) { return results[0].provider; } return undefined; } private getConfigProviderByHandle(handle: number): vscode.DebugConfigurationProvider { const results = this._configProviders.filter(p => p.handle === handle); if (results.length > 0) { return results[0].provider; } return undefined; } private definesDebugType(ed: IExtensionDescription, type: string) { if (ed.contributes) { const debuggers = ed.contributes['debuggers']; if (debuggers && debuggers.length > 0) { for (const dbg of debuggers) { // only debugger contributions with a "label" are considered a "defining" debugger contribution if (dbg.label && dbg.type) { if (dbg.type === type) { return true; } } } } } return false; } private getDebugAdapterTrackers(sessionDto: IDebugSessionDto, config: vscode.DebugConfiguration): Promise { const session = this.getSession(sessionDto); const type = config.type; const promises = this._configProviders .filter(pair => pair.provider.provideDebugAdapterTracker && (pair.type === type || pair.type === '*')) .map(pair => asThenable(() => pair.provider.provideDebugAdapterTracker(session, config, CancellationToken.None)).then(p => p).catch(err => null)); return Promise.race([ Promise.all(promises).then(trackers => { trackers = trackers.filter(t => t); // filter null if (trackers.length > 0) { return new MultiTracker(trackers); } return undefined; }), new Promise((resolve, reject) => { const timeout = setTimeout(() => { clearTimeout(timeout); reject(new Error('timeout')); }, 1000); }) ]).catch(err => { // ignore errors return undefined; }); } private getAdapterDescriptor(adapterProvider: vscode.DebugAdapterProvider, sessionDto: IDebugSessionDto, config: vscode.DebugConfiguration): Thenable { // a "debugServer" attribute in the launch config takes precedence if (typeof config.debugServer === 'number') { return Promise.resolve(new DebugAdapterServer(config.debugServer)); } const session = this.getSession(sessionDto); // TODO@AW legacy const pairs = this._configProviders.filter(p => p.type === config.type); if (pairs.length > 0) { if (pairs[0].provider.debugAdapterExecutable) { return asThenable(() => pairs[0].provider.debugAdapterExecutable(session.workspaceFolder, CancellationToken.None)); } } if (adapterProvider) { const adapterExecutable = ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type); return asThenable(() => adapterProvider.provideDebugAdapter(session, adapterExecutable, config, CancellationToken.None)); } // try deprecated command based extension API "adapterExecutableCommand" to determine the executable // TODO@AW legacy const aex = this._aexCommands.get(config.type); if (aex) { const folder = session.workspaceFolder; const rootFolder = folder ? folder.uri.toString() : undefined; return this._commandService.executeCommand(aex, rootFolder).then((ae: { command: string, args: string[] }) => { return new DebugAdapterExecutable(ae.command, ae.args || []); }); } // fallback: use executable information from package.json return Promise.resolve(ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type)); } private startBreakpoints() { if (!this._breakpointEventsActive) { this._breakpointEventsActive = true; this._debugServiceProxy.$startBreakpointEvents(); } } private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { if (added.length > 0 || removed.length > 0 || changed.length > 0) { this._onDidChangeBreakpoints.fire(Object.freeze({ added, removed, changed, })); } } private getSession(dto: IDebugSessionDto): ExtHostDebugSession { if (dto) { let debugSession = this._debugSessions.get(dto.id); if (!debugSession) { debugSession = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, this.getFolder(dto.folderUri)); this._debugSessions.set(dto.id, debugSession); } return debugSession; } return undefined; } private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined { if (_folderUri) { const folderURI = URI.revive(_folderUri); return this._workspaceService.resolveWorkspaceFolder(folderURI); } return undefined; } } export class ExtHostDebugSession implements vscode.DebugSession { constructor( private _debugServiceProxy: MainThreadDebugServiceShape, private _id: DebugSessionUUID, private _type: string, private _name: string, private _workspaceFolder: vscode.WorkspaceFolder | undefined) { } public get id(): string { return this._id; } public get type(): string { return this._type; } public get name(): string { return this._name; } public get workspaceFolder(): vscode.WorkspaceFolder | undefined { return this._workspaceFolder; } public customRequest(command: string, args: any): Thenable { return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args); } } export class ExtHostDebugConsole implements vscode.DebugConsole { private _debugServiceProxy: MainThreadDebugServiceShape; constructor(proxy: MainThreadDebugServiceShape) { this._debugServiceProxy = proxy; } append(value: string): void { this._debugServiceProxy.$appendDebugConsole(value); } appendLine(value: string): void { this.append(value + '\n'); } } export class ExtHostVariableResolverService extends AbstractVariableResolverService { constructor(workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfiguration) { super({ getFolderUri: (folderName: string): URI => { const folders = workspaceService.getWorkspaceFolders(); const found = folders.filter(f => f.name === folderName); if (found && found.length > 0) { return found[0].uri; } return undefined; }, getWorkspaceFolderCount: (): number => { return workspaceService.getWorkspaceFolders().length; }, getConfigurationValue: (folderUri: URI, section: string) => { return configurationService.getConfiguration(undefined, folderUri).get(section); }, getExecPath: (): string | undefined => { return process.env['VSCODE_EXEC_PATH']; }, getFilePath: (): string | undefined => { const activeEditor = editorService.activeEditor(); if (activeEditor) { const resource = activeEditor.document.uri; if (resource.scheme === Schemas.file) { return paths.normalize(resource.fsPath, true); } } return undefined; }, getSelectedText: (): string | undefined => { const activeEditor = editorService.activeEditor(); if (activeEditor && !activeEditor.selection.isEmpty) { return activeEditor.document.getText(activeEditor.selection); } return undefined; }, getLineNumber: (): string => { const activeEditor = editorService.activeEditor(); if (activeEditor) { return String(activeEditor.selection.end.line + 1); } return undefined; } }); } } interface TypeProviderPair { type: string; handle: number; provider: vscode.DebugConfigurationProvider; } interface TypeDaProviderPair { type: string; handle: number; provider: vscode.DebugAdapterProvider; } class MultiTracker implements vscode.DebugAdapterTracker { constructor(private trackers: vscode.DebugAdapterTracker[]) { } startDebugAdapter(): void { this.trackers.forEach(t => t.startDebugAdapter ? t.startDebugAdapter() : void 0); } toDebugAdapter(message: any): void { this.trackers.forEach(t => t.toDebugAdapter ? t.toDebugAdapter(message) : void 0); } fromDebugAdapter(message: any): void { this.trackers.forEach(t => t.fromDebugAdapter ? t.fromDebugAdapter(message) : void 0); } debugAdapterError(error: Error): void { this.trackers.forEach(t => t.debugAdapterError ? t.debugAdapterError(error) : void 0); } debugAdapterExit(code: number, signal: string): void { this.trackers.forEach(t => t.debugAdapterExit ? t.debugAdapterExit(code, signal) : void 0); } stopDebugAdapter(): void { this.trackers.forEach(t => t.stopDebugAdapter ? t.stopDebugAdapter() : void 0); } } interface IDapTransport { start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void); send(message: DebugProtocol.ProtocolMessage); stop(): void; } class DirectDebugAdapter extends AbstractDebugAdapter implements IDapTransport { readonly onError: Event; readonly onExit: Event; private _sendUp: (msg: DebugProtocol.ProtocolMessage) => void; constructor(implementation: any) { super(); if (implementation.__setTransport) { implementation.__setTransport(this); } } // IDapTransport start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void) { this._sendUp = cb; } // AbstractDebugAdapter startSession(): Promise { return Promise.resolve(void 0); } // AbstractDebugAdapter // VSCode -> DA sendMessage(message: DebugProtocol.ProtocolMessage): void { this._sendUp(message); } // AbstractDebugAdapter stopSession(): Promise { this.stop(); return Promise.resolve(void 0); } // IDapTransport // DA -> VSCode send(message: DebugProtocol.ProtocolMessage) { this.acceptMessage(message); } // IDapTransport stop(): void { throw new Error('Method not implemented.'); } }