From 81142e2cb5899e507ffe99757f1193944a161b63 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 29 Jan 2018 14:27:48 +0100 Subject: [PATCH] support for debugAdapterExecutable; fixes #33801 --- src/vs/vscode.proposed.d.ts | 33 +++++++++++++++++++ .../mainThreadDebugService.ts | 7 +++- src/vs/workbench/api/node/extHost.api.impl.ts | 1 + src/vs/workbench/api/node/extHost.protocol.ts | 5 +-- .../workbench/api/node/extHostDebugService.ts | 16 ++++++++- src/vs/workbench/api/node/extHostTypes.ts | 10 ++++++ src/vs/workbench/parts/debug/common/debug.ts | 3 ++ .../debugConfigurationManager.ts | 12 +++++-- .../parts/debug/node/debugAdapter.ts | 16 +++++++-- .../debug/test/node/debugAdapter.test.ts | 2 +- 10 files changed, 96 insertions(+), 9 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 802c06809ff..51e1c1d419f 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 46140b8f54b..5ddc7b3162f 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 7f82fdd7579..ce084b3fb4b 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 cd5e404db14..71ac999ca30 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 7b1cba6a42c..8e837928e5f 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 ce9749e81fc..9a086526242 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 7c951a07014..ff275a4d03a 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 6fe509caccb..72e840a7a49 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 39e277acd4c..4544eaa62ab 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 9e626d4be69..833728de92c 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); }); -- GitLab