diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 802c06809ffb329fb3fc2e9fc5989a24d3453520..51e1c1d419f820017d593db9466012b418704c3b 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -359,6 +359,39 @@ declare module 'vscode' { constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string); } + /** + * Represents a debug adapter executable and optional arguments passed to it. + */ + export class DebugAdapterExecutable { + /** + * The command path of the debug adapter executable. + * A command must be a either an absolute path or a name of an executable looked up via the PATH environment variable. + * The special value 'node' will be mapped to VS Code's built-in node runtime. + */ + readonly command: string; + + /** + * Optional arguments passed to the debug adapter. + */ + readonly args: string[]; + + /** + * Create a new debug adapter specification. + */ + constructor(command: string, args?: string[]); + } + + export interface DebugConfigurationProvider { + /** + * This optional method is called just before a debug adapter is started to determine its excutable path and arguments. + * Registering more than one debugAdapterExecutable for a type results in an error. + * @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup. + * @param token A cancellation token. + * @return a [debug adapter's executable and optional arguments](#DebugAdapterExecutable) or undefined. + */ + debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; + } + /** * The severity level of a log message */ diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index 46140b8f54b123071e68ecaaf4916be2c8fdf7a1..5ddc7b3162fd707c799537086fcbd8c83cadd76f 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -148,7 +148,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { }); } - public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, handle: number): TPromise { + public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasDebugAdapterExecutable: boolean, handle: number): TPromise { const provider = { type: debugType @@ -163,6 +163,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { return this._proxy.$resolveDebugConfiguration(handle, folder, debugConfiguration); }; } + if (hasDebugAdapterExecutable) { + provider.debugAdapterExecutable = (folder) => { + return this._proxy.$debugAdapterExecutable(handle, folder); + }; + } this.debugService.getConfigurationManager().registerDebugConfigurationProvider(handle, provider); return TPromise.wrap(undefined); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 7f82fdd7579641640af306db1ef5ec9dbd2c0de6..ce084b3fb4b6d2fe7fa2aa04f6f4a1ef8741e64e 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -576,6 +576,7 @@ export function createApiFactory( CompletionItemKind: extHostTypes.CompletionItemKind, CompletionList: extHostTypes.CompletionList, CompletionTriggerKind: extHostTypes.CompletionTriggerKind, + DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable, Diagnostic: extHostTypes.Diagnostic, DiagnosticSeverity: extHostTypes.DiagnosticSeverity, Disposable: extHostTypes.Disposable, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index cd5e404db146593dc64b5047aad6214e9a50d84e..71ac999ca305f53119f889d1fb845845d6d7cb0b 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -24,7 +24,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import * as modes from 'vs/editor/common/modes'; import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; -import { IConfig } from 'vs/workbench/parts/debug/common/debug'; +import { IConfig, IAdapterExecutable } from 'vs/workbench/parts/debug/common/debug'; import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -437,7 +437,7 @@ export interface MainThreadSCMShape extends IDisposable { export type DebugSessionUUID = string; export interface MainThreadDebugServiceShape extends IDisposable { - $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, handle: number): TPromise; + $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasDebugAdapterExecutable: boolean, handle: number): TPromise; $unregisterDebugConfigurationProvider(handle: number): TPromise; $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): TPromise; @@ -742,6 +742,7 @@ export interface ISourceMultiBreakpointDto { export interface ExtHostDebugServiceShape { $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): TPromise; $provideDebugConfigurations(handle: number, folder: UriComponents | undefined): TPromise; + $debugAdapterExecutable(handle: number, folder: UriComponents | undefined): TPromise; $acceptDebugSessionStarted(id: DebugSessionUUID, type: string, name: string): void; $acceptDebugSessionTerminated(id: DebugSessionUUID, type: string, name: string): void; $acceptDebugSessionActiveChanged(id: DebugSessionUUID | undefined, type?: string, name?: string): void; diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 7b1cba6a42ce66e8be2a6f54814edbff5ebffbea..8e837928e5fcf266da18d346009a9105bf7f459f 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -263,7 +263,10 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { let handle = this.nextHandle(); this._handlers.set(handle, provider); - this._debugServiceProxy.$registerDebugConfigurationProvider(type, !!provider.provideDebugConfigurations, !!provider.resolveDebugConfiguration, handle); + this._debugServiceProxy.$registerDebugConfigurationProvider(type, + !!provider.provideDebugConfigurations, + !!provider.resolveDebugConfiguration, + !!provider.debugAdapterExecutable, handle); return new Disposable(() => { this._handlers.delete(handle); @@ -293,6 +296,17 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return asWinJsPromise(token => handler.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, token)); } + public $debugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): TPromise { + let handler = this._handlers.get(handle); + if (!handler) { + return TPromise.wrapError(new Error('no handler found')); + } + if (!handler.debugAdapterExecutable) { + return TPromise.wrapError(new Error('handler has no method debugAdapterExecutable')); + } + return asWinJsPromise(token => handler.debugAdapterExecutable(this.getFolder(folderUri), token)); + } + public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise { return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig); } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index ce9749e81fc83ab30fb4ac110bce6edadb02ba90..9a0865262428da000fa4f8b8b7f64912665f2acc 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1610,6 +1610,16 @@ export class FunctionBreakpoint extends Breakpoint { } } +export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { + readonly command: string; + readonly args: string[]; + + constructor(command: string, args?: string[]) { + this.command = command; + this.args = args; + } +} + export enum LogLevel { Trace = 1, Debug = 2, diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 7c951a07014b18894bc82ba001ccda634cee3136..ff275a4d03a5450816c260a36eaa3cba5e80e9a4 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -402,6 +402,7 @@ export interface IDebugConfigurationProvider { handle: number; resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): TPromise; provideDebugConfigurations?(folderUri: uri | undefined): TPromise; + debugAdapterExecutable(folderUri: uri | undefined): TPromise; } export interface IConfigurationManager { @@ -428,7 +429,9 @@ export interface IConfigurationManager { registerDebugConfigurationProvider(handle: number, debugConfigurationProvider: IDebugConfigurationProvider): void; unregisterDebugConfigurationProvider(handle: number): void; + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): TPromise; + debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise | undefined; } export interface ILaunch { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 6fe509caccb9b8516e0ea922663a643380c8e46d..72e840a7a4973d22aac2e5c89a8d4f3dc520f378 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -27,7 +27,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, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch, IAdapterExecutable } from 'vs/workbench/parts/debug/common/debug'; import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -291,6 +291,14 @@ export class ConfigurationManager implements IConfigurationManager { .then(results => results.reduce((first, second) => first.concat(second), [])); } + public debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise | undefined { + const providers = this.providers.filter(p => p.type === type && p.debugAdapterExecutable); + if (providers.length === 1) { + return providers[0].debugAdapterExecutable(folderUri); + } + return undefined; + } + private registerListeners(lifecycleService: ILifecycleService): void { debuggersExtPoint.setHandler((extensions) => { extensions.forEach(extension => { @@ -308,7 +316,7 @@ export class ConfigurationManager implements IConfigurationManager { if (duplicate) { duplicate.merge(rawAdapter, extension.description); } else { - this.adapters.push(new Adapter(rawAdapter, extension.description, this.configurationService, this.commandService)); + this.adapters.push(new Adapter(this, rawAdapter, extension.description, this.configurationService, this.commandService)); } }); }); diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts index 39e277acd4c053aa240699ae074ef2d5bd5b8c3f..4544eaa62abd7d5060379470cf2f5955aaf701ea 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts @@ -13,14 +13,14 @@ import * as paths from 'vs/base/common/paths'; import * as platform from 'vs/base/common/platform'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfig, IRawAdapter, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/parts/debug/common/debug'; +import { IConfig, IRawAdapter, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; export class Adapter { - constructor(private rawAdapter: IRawAdapter, public extensionDescription: IExtensionDescription, + constructor(private configurationManager: IConfigurationManager, private rawAdapter: IRawAdapter, public extensionDescription: IExtensionDescription, @IConfigurationService private configurationService: IConfigurationService, @ICommandService private commandService: ICommandService ) { @@ -33,12 +33,24 @@ export class Adapter { public getAdapterExecutable(root: IWorkspaceFolder, verifyAgainstFS = true): TPromise { + // start with extension API + if (this.configurationManager) { + const adapterExecutablePromise = this.configurationManager.debugAdapterExecutable(root.uri, this.rawAdapter.type); + if (adapterExecutablePromise) { + return adapterExecutablePromise.then(adapterExecutable => { + return this.verifyAdapterDetails(adapterExecutable, verifyAgainstFS); + }); + } + } + + // try deprecated command based extension API if (this.rawAdapter.adapterExecutableCommand && root) { return this.commandService.executeCommand(this.rawAdapter.adapterExecutableCommand, root.uri.toString()).then(ad => { return this.verifyAdapterDetails(ad, verifyAgainstFS); }); } + // old style: executable contribution specified in package.json const adapterExecutable = { command: this.getProgram(), args: this.getAttributeBasedOnPlatform('args') diff --git a/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts b/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts index 9e626d4be6940c4f2fb4341bd19d5f6cafb058e7..833728de92c24a2577b8d613c4c7de3b7fa1b067 100644 --- a/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts +++ b/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts @@ -43,7 +43,7 @@ suite('Debug - Adapter', () => { }; setup(() => { - adapter = new Adapter(rawAdapter, { extensionFolderPath, id: 'adapter', name: 'myAdapter', version: '1.0.0', publisher: 'vscode', isBuiltin: false, engines: null }, + adapter = new Adapter(null, rawAdapter, { extensionFolderPath, id: 'adapter', name: 'myAdapter', version: '1.0.0', publisher: 'vscode', isBuiltin: false, engines: null }, new TestConfigurationService(), null); });