提交 8ec8c977 编写于 作者: A Andre Weinand

debug API: introduce DebugAdapterProvider

上级 fd824ee5
......@@ -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<DebugAdapterExecutable>;
/**
* 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<DebugAdapterTracker>;
}
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<DebugAdapterDescriptor>;
/**
* 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<DebugAdapterTracker>;
provideDebugAdapter(session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugAdapterDescriptor>;
}
export namespace debug {
/**
* Deprecated, use DebugConfigurationProvider.provideDebugAdapter instead.
* @deprecated Use DebugConfigurationProvider.provideDebugAdapter instead
* 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.
*/
debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugAdapterExecutable>;
export function registerDebugAdapterProvider(debugType: string, provider: DebugAdapterProvider): Disposable;
}
//#endregion
......
......@@ -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<number, ExtensionHostDebugAdapter>;
private _debugAdaptersHandleCounter = 1;
private _debugConfigurationProviders: Map<number, IDebugConfigurationProvider>;
private _debugAdapterProviders: Map<number, IDebugAdapterProvider>;
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<void> {
const provider = <IDebugAdapterProvider>{
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<boolean> {
const folderUri = _folderUri ? uri.revive(_folderUri) : undefined;
const launch = this.debugService.getConfigurationManager().getLaunch(folderUri);
......
......@@ -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);
},
......
......@@ -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<void>;
$registerDebugAdapterProvider(type: string, handle: number): Thenable<void>;
$unregisterDebugConfigurationProvider(handle: number): void;
$unregisterDebugAdapterProvider(handle: number): void;
$startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration): Thenable<boolean>;
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Thenable<any>;
$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<IConfig>;
$provideDebugConfigurations(handle: number, folder: UriComponents | undefined): Thenable<IConfig[]>;
$legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Thenable<IAdapterDescriptor>; // TODO@AW legacy
$provideDebugAdapter(handle: number, session: IDebugSessionDto, folderUri: UriComponents | undefined, debugConfiguration: IConfig): Thenable<IAdapterDescriptor>;
$acceptDebugSessionStarted(session: IDebugSessionDto): void;
$acceptDebugSessionTerminated(session: IDebugSessionDto): void;
......
......@@ -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<number, vscode.DebugConfigurationProvider>;
private _providerByType: Map<string, vscode.DebugConfigurationProvider>;
private _providers: TypeProviderPair[];
private _configProviderHandleCounter: number;
private _configProviders: TypeProviderPair[];
private _adapterProviderHandleCounter: number;
private _adapterProviders: TypeDaProviderPair[];
private _debugServiceProxy: MainThreadDebugServiceShape;
private _debugSessions: Map<DebugSessionUUID, ExtHostDebugSession> = new Map<DebugSessionUUID, ExtHostDebugSession>();
......@@ -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<void> {
......@@ -357,8 +367,9 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
public $startDASession(handle: number, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable<void> {
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<vscode.DebugConfiguration[]> {
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<vscode.DebugConfiguration> {
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<vscode.DebugAdapterDescriptor> {
let provider = this._providerByHandle.get(handle);
// TODO@AW legacy
public $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Thenable<IAdapterDescriptor> {
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 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<IAdapterDescriptor> {
let adapterProvider = this.getAdapterProviderByHandle(handle);
if (!adapterProvider) {
return Promise.reject(new Error('no handler found'));
}
return this.getAdapterDescriptor(provider, this.getSession(sessionDto), folderUri, config);
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 <IDebugAdapterExecutable>{
type: 'executable',
command: x.command,
args: x.args,
cwd: x.cwd,
env: x.env
};
} else if (x instanceof DebugAdapterServer) {
return <IDebugAdapterServer>{
type: 'server',
port: x.port,
host: x.host
};
} else if (x instanceof DebugAdapterImplementation) {
return <IDebugAdapterImplementation>{
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 = <IDebuggerContribution[]>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<vscode.DebugAdapterDescriptor> {
private getAdapterDescriptor(adapterProvider: vscode.DebugAdapterProvider, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable<vscode.DebugAdapterDescriptor> {
// 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<Error>;
readonly onExit: Event<number>;
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<void> {
return Promise.resolve(void 0);
}
// AbstractDebugAdapter
// VSCode -> DA
sendMessage(message: DebugProtocol.ProtocolMessage): void {
this.transport.sendUp(message);
this._sendUp(message);
}
// AbstractDebugAdapter
stopSession(): Promise<void> {
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.');
}
}
......@@ -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) {
......
......@@ -467,7 +467,7 @@ export interface IDebugAdapter extends IDisposable {
stopSession(): Promise<void>;
}
export interface IDebugAdapterProvider extends ITerminalLauncher {
export interface IDebugAdapterFactory extends ITerminalLauncher {
createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter;
substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise<IConfig>;
}
......@@ -529,10 +529,15 @@ export interface IDebugConfigurationProvider {
readonly type: string;
resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): Promise<IConfig>;
provideDebugConfigurations?(folderUri: uri | undefined): Promise<IConfig[]>;
provideDebugAdapter?(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise<IAdapterDescriptor>;
debugAdapterExecutable?(folderUri: uri | undefined): Promise<IAdapterDescriptor>; // TODO@AW legacy
hasTracker: boolean;
}
export interface IDebugAdapterProvider {
readonly type: string;
provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise<IAdapterDescriptor>;
}
export interface ITerminalLauncher {
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise<void>;
}
......@@ -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<any>;
provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): Promise<IAdapterDescriptor | undefined>;
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<IConfig>;
......
......@@ -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<void>();
private providers: IDebugConfigurationProvider[];
private debugAdapterProviders: Map<string, IDebugAdapterProvider>;
private configProviders: IDebugConfigurationProvider[];
private adapterProviders: IDebugAdapterProvider[];
private debugAdapterFactories: Map<string, IDebugAdapterFactory>;
private terminalLauncher: ITerminalLauncher;
private debugConfigurationTypeContext: IContextKey<string>;
......@@ -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<string, IDebugAdapterProvider>();
this.debugAdapterFactories = new Map();
if (previousSelectedLaunch) {
this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
}
}
public registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
// 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<IConfig> {
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<void> {
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<IAdapterDescriptor | undefined> {
// 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);
}
this.providers.push(debugConfigurationProvider);
// debug configurations
public registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
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<IConfig> {
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<any[]> {
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<IAdapterDescriptor | undefined> {
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<IConfig> {
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<void> {
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) => {
......
......@@ -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.');
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册