diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d2853a3b47e..a2ac862a1ff 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -566,8 +566,6 @@ declare module 'vscode' { */ export class DebugAdapterExecutable { - readonly type: 'executable'; - /** * The command path of the debug adapter executable. * A command must be either an absolute path or the name of an executable looked up via the PATH environment variable. @@ -603,8 +601,6 @@ declare module 'vscode' { */ export class DebugAdapterServer { - readonly type: 'server'; - /** * The port. */ @@ -626,8 +622,6 @@ declare module 'vscode' { */ export class DebugAdapterImplementation { - readonly type: 'implementation'; - readonly implementation: any; /** @@ -656,7 +650,24 @@ declare module 'vscode' { export interface DebugConfigurationProvider { /** - * The optional method 'provideDebugAdapter' is called at the start of a debug session to provide details about the debug adapter to use. + * Deprecated, use DebugConfigurationProvider.provideDebugAdapter instead. + * @deprecated Use DebugConfigurationProvider.provideDebugAdapter instead + */ + debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; + + /** + * The optional method 'provideDebugAdapterTracker' is called at the start of a debug session to provide a tracker that gives access to the communication between VS Code and a Debug Adapter. + * @param session The [debug session](#DebugSession) for which the tracker will be used. + * @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup. + * @param config The resolved debug configuration. + * @param token A cancellation token. + */ + provideDebugAdapterTracker?(session: DebugSession, folder: WorkspaceFolder | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult; + } + + export interface DebugAdapterProvider { + /** + * Method 'provideDebugAdapter' is called at the start of a debug session to provide details about the debug adapter to use. * These details must be returned as objects of type DebugAdapterDescriptor. * Currently two types of debug adapters are supported: * - a debug adapter executable specified as a command path and arguments (see DebugAdapterExecutable), @@ -668,8 +679,6 @@ declare module 'vscode' { * } * return executable; * } - * An extension is only allowed to register a DebugConfigurationProvider with a provideDebugAdapter method if the extension defines the debug type. Otherwise an error is thrown. - * Registering more than one DebugConfigurationProvider with a provideDebugAdapter method for a type results in an error. * @param session The [debug session](#DebugSession) for which the debug adapter will be used. * @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup. * @param executable The debug adapter's executable information as specified in the package.json (or undefined if no such information exists). @@ -677,22 +686,21 @@ declare module 'vscode' { * @param token A cancellation token. * @return a [debug adapter's descriptor](#DebugAdapterDescriptor) or undefined. */ - provideDebugAdapter?(session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult; + provideDebugAdapter(session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult; + } + export namespace debug { /** - * The optional method 'provideDebugAdapterTracker' is called at the start of a debug session to provide a tracker that gives access to the communication between VS Code and a Debug Adapter. - * @param session The [debug session](#DebugSession) for which the tracker will be used. - * @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup. - * @param config The resolved debug configuration. - * @param token A cancellation token. + * Register a [debug adapter provider](#DebugConfigurationProvider) for a specific debug type. + * Only one provider can be registered for the same type. + * An extension is only allowed to register a DebugAdapterProvider with if the extension defines the debug type. Otherwise an error is thrown. + * Registering more than one DebugAdapterProvider for a type results in an error. + * + * @param type The debug type for which the provider is registered. + * @param provider The [debug adapter provider](#DebugAdapterProvider) to register. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - provideDebugAdapterTracker?(session: DebugSession, folder: WorkspaceFolder | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult; - - /** - * Deprecated, use DebugConfigurationProvider.provideDebugAdapter instead. - * @deprecated Use DebugConfigurationProvider.provideDebugAdapter instead - */ - debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; + export function registerDebugAdapterProvider(debugType: string, provider: DebugAdapterProvider): Disposable; } //#endregion diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index 992a0a037a5..b867082b198 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -5,7 +5,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/parts/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto @@ -17,7 +17,7 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { convertToVSCPaths, convertToDAPaths, stringToUri, uriToString } from 'vs/workbench/parts/debug/common/debugUtils'; @extHostNamedCustomer(MainContext.MainThreadDebugService) -export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterProvider { +export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory { private _proxy: ExtHostDebugServiceShape; private _toDispose: IDisposable[]; @@ -25,6 +25,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb private _debugAdapters: Map; private _debugAdaptersHandleCounter = 1; private _debugConfigurationProviders: Map; + private _debugAdapterProviders: Map; constructor( extHostContext: IExtHostContext, @@ -48,6 +49,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this._debugAdapters = new Map(); this._debugConfigurationProviders = new Map(); + this._debugAdapterProviders = new Map(); } public dispose(): void { @@ -74,7 +76,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb // RPC methods (MainThreadDebugServiceShape) public $registerDebugTypes(debugTypes: string[]) { - this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterProvider(debugTypes, this)); + this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterFactory(debugTypes, this)); } public $startBreakpointEvents(): void { @@ -161,8 +163,8 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb }; } if (hasProvideDebugAdapter) { - provider.provideDebugAdapter = (session, folder, config) => { - return Promise.resolve(this._proxy.$provideDebugAdapter(handle, this.getSessionDto(session), folder, config)); + provider.debugAdapterExecutable = (folder) => { + return Promise.resolve(this._proxy.$legacyDebugAdapterExecutable(handle, folder)); }; } this._debugConfigurationProviders.set(handle, provider); @@ -179,6 +181,29 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } } + public $registerDebugAdapterProvider(debugType: string, handle: number): Thenable { + + const provider = { + type: debugType, + provideDebugAdapter: (session, folder, config) => { + return Promise.resolve(this._proxy.$provideDebugAdapter(handle, this.getSessionDto(session), folder, config)); + } + }; + this._debugAdapterProviders.set(handle, provider); + this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterProvider(provider)); + + return Promise.resolve(undefined); + } + + public $unregisterDebugAdapterProvider(handle: number): void { + const provider = this._debugAdapterProviders.get(handle); + if (provider) { + this._debugAdapterProviders.delete(handle); + this.debugService.getConfigurationManager().unregisterDebugAdapterProvider(provider); + } + } + + public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig): Thenable { const folderUri = _folderUri ? uri.revive(_folderUri) : undefined; const launch = this.debugService.getConfigurationManager().getLaunch(folderUri); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 5105bd70299..834d2df052a 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -678,6 +678,9 @@ export function createApiFactory( registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider) { return extHostDebugService.registerDebugConfigurationProvider(extension, debugType, provider); }, + registerDebugAdapterProvider(debugType: string, provider: vscode.DebugAdapterProvider) { + return extHostDebugService.registerDebugAdapterProvider(extension, debugType, provider); + }, startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration) { return extHostDebugService.startDebugging(folder, nameOrConfig); }, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 74b922cf9bf..576e70a1200 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -594,7 +594,9 @@ export interface MainThreadDebugServiceShape extends IDisposable { $acceptDAError(handle: number, name: string, message: string, stack: string): void; $acceptDAExit(handle: number, code: number, signal: string): void; $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasProvideDaMethod: boolean, hasProvideTrackerMethod: boolean, handle: number): Thenable; + $registerDebugAdapterProvider(type: string, handle: number): Thenable; $unregisterDebugConfigurationProvider(handle: number): void; + $unregisterDebugAdapterProvider(handle: number): void; $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration): Thenable; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Thenable; $appendDebugConsole(value: string): void; @@ -976,6 +978,7 @@ export interface ExtHostDebugServiceShape { $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): Thenable; $provideDebugConfigurations(handle: number, folder: UriComponents | undefined): Thenable; + $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Thenable; // TODO@AW legacy $provideDebugAdapter(handle: number, session: IDebugSessionDto, folderUri: UriComponents | undefined, debugConfiguration: IConfig): Thenable; $acceptDebugSessionStarted(session: IDebugSessionDto): void; $acceptDebugSessionTerminated(session: IDebugSessionDto): void; diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 98730938a27..d6b43732472 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -14,13 +14,13 @@ import { 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 } from 'vs/workbench/api/node/extHostTypes'; +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 } from 'vs/workbench/parts/debug/common/debug'; +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'; @@ -36,10 +36,11 @@ import { IExtensionDescription } from 'vs/workbench/services/extensions/common/e export class ExtHostDebugService implements ExtHostDebugServiceShape { - private _providerHandleCounter: number; - private _providerByHandle: Map; - private _providerByType: Map; - private _providers: TypeProviderPair[]; + private _configProviderHandleCounter: number; + private _configProviders: TypeProviderPair[]; + + private _adapterProviderHandleCounter: number; + private _adapterProviders: TypeDaProviderPair[]; private _debugServiceProxy: MainThreadDebugServiceShape; private _debugSessions: Map = new Map(); @@ -85,10 +86,11 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { private _terminalService: ExtHostTerminalService, private _commandService: ExtHostCommands ) { - this._providerHandleCounter = 0; - this._providerByHandle = new Map(); - this._providerByType = new Map(); - this._providers = []; + this._configProviderHandleCounter = 0; + this._configProviders = []; + + this._adapterProviderHandleCounter = 0; + this._adapterProviders = []; this._aexCommands = new Map(); this._debugAdapters = new Map(); @@ -249,40 +251,48 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return new Disposable(() => { }); } - // if a provider has a provideDebugAdapter method, we check the constraints specified in the API doc - if (provider.provideDebugAdapter) { - - // a provider with this method 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._providerByType.has(type)) { - throw new Error(`a provider with method 'provideDebugAdapter' can only be registered once per a type.`); - } else { - this._providerByType.set(type, provider); - } - } - - let handle = this._providerHandleCounter++; - this._providerByHandle.set(handle, provider); - this._providers.push({ type, provider }); + let handle = this._configProviderHandleCounter++; + this._configProviders.push({ type, handle, provider }); this._debugServiceProxy.$registerDebugConfigurationProvider(type, !!provider.provideDebugConfigurations, !!provider.resolveDebugConfiguration, - !!provider.debugAdapterExecutable || !!provider.provideDebugAdapter, + !!provider.debugAdapterExecutable, // TODO@AW: legacy !!provider.provideDebugAdapterTracker, handle); return new Disposable(() => { - this._providerByHandle.delete(handle); - this._providerByType.delete(type); - this._providers = this._providers.filter(p => p.provider !== provider); // remove + 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 { @@ -357,8 +367,9 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { public $startDASession(handle: number, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable { const mythis = this; - return this.getAdapterDescriptor(this._providerByType.get(config.type), sessionDto, folderUri, config).then(adapter => { + return this.getAdapterDescriptor(this.getAdapterProviderByType(config.type), sessionDto, folderUri, config).then(x => { + const adapter = this.convertToDto(x); let da: AbstractDebugAdapter | undefined = undefined; switch (adapter.type) { @@ -520,7 +531,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } public $provideDebugConfigurations(handle: number, folderUri: UriComponents | undefined): Thenable { - let provider = this._providerByHandle.get(handle); + let provider = this.getConfigProviderByHandle(handle); if (!provider) { return Promise.reject(new Error('no handler found')); } @@ -531,7 +542,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } public $resolveDebugConfiguration(handle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Thenable { - let provider = this._providerByHandle.get(handle); + let provider = this.getConfigProviderByHandle(handle); if (!provider) { return Promise.reject(new Error('no handler found')); } @@ -541,15 +552,25 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return asThenable(() => provider.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, CancellationToken.None)); } - public $provideDebugAdapter(handle: number, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable { - let provider = this._providerByHandle.get(handle); + // 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 && !provider.provideDebugAdapter) { - return Promise.reject(new Error('handler has no methods provideDebugAdapter or debugAdapterExecutable')); + if (!provider.debugAdapterExecutable) { + return Promise.reject(new Error('handler has no method debugAdapterExecutable')); } - return this.getAdapterDescriptor(provider, this.getSession(sessionDto), folderUri, config); + return asThenable(() => provider.debugAdapterExecutable(this.getFolder(folderUri), CancellationToken.None)).then(x => this.convertToDto(x)); + } + + public $provideDebugAdapter(handle: number, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable { + let adapterProvider = this.getAdapterProviderByHandle(handle); + if (!adapterProvider) { + return Promise.reject(new Error('no handler found')); + } + return this.getAdapterDescriptor(adapterProvider, sessionDto, folderUri, config).then(x => this.convertToDto(x)); } public $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): void { @@ -581,6 +602,55 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { // 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']; @@ -604,7 +674,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { const folder = this.getFolder(folderUri); const type = config.type; - const promises = this._providers + const promises = this._configProviders .filter(pair => pair.provider.provideDebugAdapterTracker && (pair.type === type || pair.type === '*')) .map(pair => asThenable(() => pair.provider.provideDebugAdapterTracker(session, folder, config, CancellationToken.None)).then(p => p).catch(err => null)); @@ -628,25 +698,26 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - private getAdapterDescriptor(debugConfigProvider, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable { + private getAdapterDescriptor(adapterProvider: vscode.DebugAdapterProvider, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, 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)); } - if (debugConfigProvider) { - // try the proposed "provideDebugAdapter" API - if (debugConfigProvider.provideDebugAdapter) { - const adapterExecutable = ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type); - return asThenable(() => debugConfigProvider.provideDebugAdapter(this.getSession(sessionDto), this.getFolder(folderUri), adapterExecutable, config, CancellationToken.None)); - } - // try the deprecated "debugAdapterExecutable" API - if (debugConfigProvider.debugAdapterExecutable) { - return asThenable(() => debugConfigProvider.debugAdapterExecutable(this.getFolder(folderUri), CancellationToken.None)); + // 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(this.getFolder(folderUri), CancellationToken.None)); } } + if (adapterProvider) { + const adapterExecutable = ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type); + return asThenable(() => adapterProvider.provideDebugAdapter(this.getSession(sessionDto), this.getFolder(folderUri), adapterExecutable, config, CancellationToken.None)); + } + // try deprecated command based extension API "adapterExecutableCommand" to determine the executable const aex = this._aexCommands.get(config.type); if (aex) { @@ -798,13 +869,14 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ interface TypeProviderPair { type: string; + handle: number; provider: vscode.DebugConfigurationProvider; } -interface IDapTransport { - start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void); - send(message: DebugProtocol.ProtocolMessage); - stop(): void; +interface TypeDaProviderPair { + type: string; + handle: number; + provider: vscode.DebugAdapterProvider; } class MultiTracker implements vscode.DebugAdapterTracker { @@ -837,57 +909,56 @@ class MultiTracker implements vscode.DebugAdapterTracker { } } -class DirectTransport implements IDapTransport { - - private _sendUp: (msg: DebugProtocol.ProtocolMessage) => void; - - constructor(private da: DirectDebugAdapter) { - } - - start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void) { - this._sendUp = cb; - } - - sendUp(message: DebugProtocol.ProtocolMessage) { - this._sendUp(message); - } - - // DA -> VSCode - send(message: DebugProtocol.ProtocolMessage) { - this.da.acceptMessage(message); - } - - stop(): void { - throw new Error('Method not implemented.'); - } +interface IDapTransport { + start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void); + send(message: DebugProtocol.ProtocolMessage); + stop(): void; } -class DirectDebugAdapter extends AbstractDebugAdapter { +class DirectDebugAdapter extends AbstractDebugAdapter implements IDapTransport { readonly onError: Event; readonly onExit: Event; - private transport: DirectTransport; + private _sendUp: (msg: DebugProtocol.ProtocolMessage) => void; constructor(implementation: any) { super(); if (implementation.__setTransport) { - this.transport = new DirectTransport(this); - implementation.__setTransport(this.transport); + 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.transport.sendUp(message); + this._sendUp(message); } + // AbstractDebugAdapter stopSession(): Promise { - this.transport.stop(); + 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.'); + } } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 48b9f1afdba..859bbfe80aa 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1926,7 +1926,6 @@ export class FunctionBreakpoint extends Breakpoint { } export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { - readonly type = 'executable'; readonly command: string; readonly args: string[]; readonly env?: { [key: string]: string }; @@ -1941,7 +1940,6 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { } export class DebugAdapterServer implements vscode.DebugAdapterServer { - readonly type = 'server'; readonly port: number; readonly host: string; @@ -1952,7 +1950,6 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer { } export class DebugAdapterImplementation implements vscode.DebugAdapterImplementation { - readonly type = 'implementation'; readonly implementation: any; constructor(transport: any) { diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index c889d675f65..bfe266f4b68 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -467,7 +467,7 @@ export interface IDebugAdapter extends IDisposable { stopSession(): Promise; } -export interface IDebugAdapterProvider extends ITerminalLauncher { +export interface IDebugAdapterFactory extends ITerminalLauncher { createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter; substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise; } @@ -529,10 +529,15 @@ export interface IDebugConfigurationProvider { readonly type: string; resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): Promise; provideDebugConfigurations?(folderUri: uri | undefined): Promise; - provideDebugAdapter?(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise; + debugAdapterExecutable?(folderUri: uri | undefined): Promise; // TODO@AW legacy hasTracker: boolean; } +export interface IDebugAdapterProvider { + readonly type: string; + provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise; +} + export interface ITerminalLauncher { runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise; } @@ -583,10 +588,13 @@ export interface IConfigurationManager { registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void; + registerDebugAdapterProvider(debugConfigurationProvider: IDebugAdapterProvider): IDisposable; + unregisterDebugAdapterProvider(debugConfigurationProvider: IDebugAdapterProvider): void; + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Thenable; provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise; - registerDebugAdapterProvider(debugTypes: string[], debugAdapterLauncher: IDebugAdapterProvider): IDisposable; + registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable; createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter; substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): Promise; diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 248226309fc..49466febbe4 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterProvider, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterProvider, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory } from 'vs/workbench/parts/debug/common/debug'; import { Debugger } from 'vs/workbench/parts/debug/node/debugger'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -48,8 +48,9 @@ export class ConfigurationManager implements IConfigurationManager { private selectedLaunch: ILaunch; private toDispose: IDisposable[]; private _onDidSelectConfigurationName = new Emitter(); - private providers: IDebugConfigurationProvider[]; - private debugAdapterProviders: Map; + private configProviders: IDebugConfigurationProvider[]; + private adapterProviders: IDebugAdapterProvider[]; + private debugAdapterFactories: Map; private terminalLauncher: ITerminalLauncher; private debugConfigurationTypeContext: IContextKey; @@ -65,7 +66,8 @@ export class ConfigurationManager implements IConfigurationManager { @IExtensionService private extensionService: IExtensionService, @IContextKeyService contextKeyService: IContextKeyService ) { - this.providers = []; + this.configProviders = []; + this.adapterProviders = []; this.debuggers = []; this.toDispose = []; this.registerListeners(lifecycleService); @@ -73,16 +75,92 @@ export class ConfigurationManager implements IConfigurationManager { const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop(); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); - this.debugAdapterProviders = new Map(); + this.debugAdapterFactories = new Map(); if (previousSelectedLaunch) { this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); } } + // debuggers + + public registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { + debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher)); + return { + dispose: () => { + debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType)); + } + }; + } + + public createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter { + let dap = this.debugAdapterFactories.get(config.type); + if (dap) { + return dap.createDebugAdapter(session, folder, config); + } + return undefined; + } + + public substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): Promise { + let dap = this.debugAdapterFactories.get(debugType); + if (dap) { + return dap.substituteVariables(folder, config); + } + return Promise.resolve(config); + } + + public runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { + let tl: ITerminalLauncher = this.debugAdapterFactories.get(debugType); + if (!tl) { + if (!this.terminalLauncher) { + this.terminalLauncher = this.instantiationService.createInstance(TerminalLauncher); + } + tl = this.terminalLauncher; + } + return tl.runInTerminal(args, config); + } + + // debug adapter + + public registerDebugAdapterProvider(debugAdapterProvider: IDebugAdapterProvider): IDisposable { + this.adapterProviders.push(debugAdapterProvider); + return { + dispose: () => { + this.unregisterDebugAdapterProvider(debugAdapterProvider); + } + }; + } + + public unregisterDebugAdapterProvider(debugAdapterProvider: IDebugAdapterProvider): void { + const ix = this.adapterProviders.indexOf(debugAdapterProvider); + if (ix >= 0) { + this.configProviders.splice(ix, 1); + } + } + + public provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise { + + // first try legacy proposed API: DebugConfigurationProvider.debugAdapterExecutable + const providers0 = this.configProviders.filter(p => p.type === config.type && p.debugAdapterExecutable); + if (providers0.length === 1) { + return providers0[0].debugAdapterExecutable(folderUri); + } else { + // TODO@AW handle n > 1 case + } + + // try new proposed API + const providers = this.adapterProviders.filter(p => p.type === config.type && p.provideDebugAdapter); + if (providers.length === 1) { + return providers[0].provideDebugAdapter(session, folderUri, config); + } else { + // TODO@AW handle n > 1 case + } + return Promise.resolve(undefined); + } + + // debug configurations + public registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { - - this.providers.push(debugConfigurationProvider); - + this.configProviders.push(debugConfigurationProvider); return { dispose: () => { this.unregisterDebugConfigurationProvider(debugConfigurationProvider); @@ -91,29 +169,29 @@ export class ConfigurationManager implements IConfigurationManager { } public unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void { - const ix = this.providers.indexOf(debugConfigurationProvider); + const ix = this.configProviders.indexOf(debugConfigurationProvider); if (ix >= 0) { - this.providers.splice(ix, 1); + this.configProviders.splice(ix, 1); } } public hasDebugConfigurationProvider(debugType: string): boolean { // check if there are providers for the given type that contribute a provideDebugConfigurations method - const providers = this.providers.filter(p => p.provideDebugConfigurations && (p.type === debugType)); + const providers = this.configProviders.filter(p => p.provideDebugConfigurations && (p.type === debugType)); return providers.length > 0; } public needsToRunInExtHost(debugType: string): boolean { // if the given debugType matches any registered provider that has a provideTracker method, we need to run the DA in the EH - const providers = this.providers.filter(p => p.hasTracker && (p.type === debugType || p.type === '*')); + const providers = this.configProviders.filter(p => p.hasTracker && (p.type === debugType || p.type === '*')); return providers.length > 0; } public resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Thenable { return this.activateDebuggers(`onDebugResolve:${type}`).then(() => { // pipe the config through the promises sequentially. append at the end the '*' types - const providers = this.providers.filter(p => p.type === type && p.resolveDebugConfiguration) - .concat(this.providers.filter(p => p.type === '*' && p.resolveDebugConfiguration)); + const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) + .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); return providers.reduce((promise, provider) => { return promise.then(config => { @@ -129,60 +207,11 @@ export class ConfigurationManager implements IConfigurationManager { public provideDebugConfigurations(folderUri: uri | undefined, type: string): Thenable { return this.activateDebuggers('onDebugInitialConfigurations') - .then(() => Promise.all(this.providers.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations(folderUri))) + .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations(folderUri))) .then(results => results.reduce((first, second) => first.concat(second), []))); } - public provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise { - const providers = this.providers.filter(p => p.type === config.type && p.provideDebugAdapter); - if (providers.length === 1) { - return providers[0].provideDebugAdapter(session, folderUri, config); - } else { - // TODO@AW handle n > 1 case - } - return Promise.resolve(undefined); - } - - public registerDebugAdapterProvider(debugTypes: string[], debugAdapterLauncher: IDebugAdapterProvider): IDisposable { - debugTypes.forEach(debugType => this.debugAdapterProviders.set(debugType, debugAdapterLauncher)); - return { - dispose: () => { - debugTypes.forEach(debugType => this.debugAdapterProviders.delete(debugType)); - } - }; - } - - private getDebugAdapterProvider(type: string): IDebugAdapterProvider | undefined { - return this.debugAdapterProviders.get(type); - } - - public createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter { - let dap = this.getDebugAdapterProvider(config.type); - if (dap) { - return dap.createDebugAdapter(session, folder, config); - } - return undefined; - } - - public substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): Promise { - let dap = this.getDebugAdapterProvider(debugType); - if (dap) { - return dap.substituteVariables(folder, config); - } - return Promise.resolve(config); - } - - public runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { - - let tl: ITerminalLauncher = this.getDebugAdapterProvider(debugType); - if (!tl) { - if (!this.terminalLauncher) { - this.terminalLauncher = this.instantiationService.createInstance(TerminalLauncher); - } - tl = this.terminalLauncher; - } - return tl.runInTerminal(args, config); - } + /////////////////////////////////////////////////////////// private registerListeners(lifecycleService: ILifecycleService): void { debuggersExtPoint.setHandler((extensions) => { diff --git a/src/vs/workbench/parts/debug/node/debugger.ts b/src/vs/workbench/parts/debug/node/debugger.ts index 1d527021b79..77e3f7b49f6 100644 --- a/src/vs/workbench/parts/debug/node/debugger.ts +++ b/src/vs/workbench/parts/debug/node/debugger.ts @@ -47,10 +47,13 @@ export class Debugger implements IDebugger { } else { return this.getAdapterDescriptor(session, root, config).then(adapterDescriptor => { switch (adapterDescriptor.type) { - case 'server': - return new SocketDebugAdapter(adapterDescriptor); case 'executable': return new ExecutableDebugAdapter(adapterDescriptor, this.type, outputService); + case 'server': + return new SocketDebugAdapter(adapterDescriptor); + case 'implementation': + // TODO@AW: this.inExtHost() should now return true + return Promise.resolve(this.configurationManager.createDebugAdapter(session, root, config)); default: throw new Error('Cannot create debug adapter.'); }