diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 85d90e939d6794964c9cebf05647e53cfce843dc..63cacd855710672d8972efca329b4bb9944a03c0 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -491,11 +491,14 @@ declare module 'vscode' { readonly cwd?: string; /** - * Create a new debug adapter specification. + * Create a description for a debug adapter based on an executable program. */ constructor(command: string, args?: string[], env?: { [key: string]: string }, cwd?: string); } + /** + * Represents a debug adapter running as a socket based server. + */ export class DebugAdapterServer { readonly type: 'server'; @@ -511,12 +514,28 @@ declare module 'vscode' { readonly host?: string; /** - * Create a new debug adapter specification. + * Create a description for a debug adapter running as a socket based server. */ constructor(port: number, host?: string); } - export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer; + /** + * Represents a debug adapter that is implemented in the extension. + */ + export class DebugAdapterImplementation { + + readonly type: 'implementation'; + + readonly implementation: any; + + /** + * Create a description for a debug adapter directly implemented in the extension. + * The implementation's "type": TBD + */ + constructor(implementation: any); + } + + export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterImplementation; export interface DebugConfigurationProvider { /** diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index e8ba986c4bf537de777838d3f52a676f3264a1de..7cf3cec2ab5d27f217a3798b3a9cc46feef8187c 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -726,6 +726,7 @@ export function createApiFactory( ConfigurationTarget: extHostTypes.ConfigurationTarget, DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable, DebugAdapterServer: extHostTypes.DebugAdapterServer, + DebugAdapterImplementation: extHostTypes.DebugAdapterImplementation, DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior, Diagnostic: extHostTypes.Diagnostic, DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation, diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 5f82ffd36cdaec919f67f2a6f4bacd7eb30df001..276220ca1b81695fb0cbca151e85c864e1893664 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -18,7 +18,7 @@ import { import * as vscode from 'vscode'; import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable } from 'vs/workbench/api/node/extHostTypes'; import { generateUuid } from 'vs/base/common/uuid'; -import { DebugAdapter, SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; +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'; @@ -107,7 +107,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._breakpoints = new Map(); this._breakpointEventsActive = false; - this._debugAdapters = new Map(); + this._debugAdapters = new Map(); // register all debug extensions const debugTypes: string[] = []; @@ -336,40 +336,20 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return this.getAdapterDescriptor(this._providerByType.get(config.type), sessionDto, folderUri, config).then(adapter => { - let da: IDebugAdapter = undefined; + let da: AbstractDebugAdapter = undefined; switch (adapter.type) { case 'server': - da = new class extends SocketDebugAdapter { - - // DA -> VS Code - public acceptMessage(message: DebugProtocol.ProtocolMessage) { - convertToVSCPaths(message, source => { - if (paths.isAbsolute(source.path)) { - (source).path = URI.file(source.path); - } - }); - mythis._debugServiceProxy.$acceptDAMessage(handle, message); - } - - }(adapter); + da = new SocketDebugAdapter(adapter); break; case 'executable': - da = new class extends DebugAdapter { - - // DA -> VS Code - public acceptMessage(message: DebugProtocol.ProtocolMessage) { - convertToVSCPaths(message, source => { - if (paths.isAbsolute(source.path)) { - (source).path = URI.file(source.path); - } - }); - mythis._debugServiceProxy.$acceptDAMessage(handle, message); - } + da = new ExecutableDebugAdapter(adapter, config.type); + break; - }(adapter, config.type); + case 'implementation': + da = new DirectDebugAdapter(adapter.implementation); break; default: @@ -378,6 +358,15 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (da) { this._debugAdapters.set(handle, da); + da.onMessage(message => { + // DA -> VS Code + convertToVSCPaths(message, source => { + if (paths.isAbsolute(source.path)) { + (source).path = URI.file(source.path); + } + }); + mythis._debugServiceProxy.$acceptDAMessage(handle, message); + }); da.onError(err => this._debugServiceProxy.$acceptDAError(handle, err.name, err.message, err.stack)); da.onExit(code => this._debugServiceProxy.$acceptDAExit(handle, code, null)); return da.startSession(); @@ -541,7 +530,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (debugConfigProvider) { // try the proposed "provideDebugAdapter" API if (debugConfigProvider.provideDebugAdapter) { - const adapterExecutable = DebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type); + 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 @@ -560,7 +549,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } // fallback: use executable information from package.json - return TPromise.wrap(DebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type)); + return TPromise.wrap(ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type)); } private startBreakpoints() { @@ -698,3 +687,64 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ }); } } + +interface IDapTransport { + start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void); + send(message: DebugProtocol.ProtocolMessage); + stop(): void; +} + +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.'); + } +} + +class DirectDebugAdapter extends AbstractDebugAdapter { + + readonly onError: Event; + readonly onExit: Event; + + private transport: DirectTransport; + + constructor(implementation: any) { + super(); + if (implementation.__setTransport) { + this.transport = new DirectTransport(this); + implementation.__setTransport(this.transport); + } + } + + startSession(): TPromise { + return TPromise.wrap(void 0); + } + + // VSCode -> DA + sendMessage(message: DebugProtocol.ProtocolMessage): void { + this.transport.sendUp(message); + } + + stopSession(): TPromise { + this.transport.stop(); + return TPromise.wrap(void 0); + } +} diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 7eee39b2e322085bc9f4086a2fb9ae6b352de454..4cd8e8051d131f04b00e7c24aa5be2294cca9fbe 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1914,6 +1914,15 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer { } } +export class DebugAdapterImplementation implements vscode.DebugAdapterImplementation { + readonly type = 'implementation'; + readonly implementation: any; + + constructor(transport: any) { + this.implementation = transport; + } +} + 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 e4d9cd718251a720e8c2e4f58466695d3a632f7c..293f9e5f73068de564b54611eb94ba416967e798 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -459,7 +459,7 @@ export interface IDebugAdapterProvider extends ITerminalLauncher { substituteVariables(folder: IWorkspaceFolder, config: IConfig): TPromise; } -export interface IAdapterExecutable { +export interface IDebugAdapterExecutable { readonly type: 'executable'; readonly command: string; readonly args: string[]; @@ -467,13 +467,18 @@ export interface IAdapterExecutable { readonly env?: { [key: string]: string }; } -export interface IAdapterServer { +export interface IDebugAdapterServer { readonly type: 'server'; readonly port: number; readonly host?: string; } -export type IAdapterDescriptor = IAdapterExecutable | IAdapterServer; +export interface IDebugAdapterImplementation { + readonly type: 'implementation'; + readonly implementation: any; +} + +export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImplementation; export interface IPlatformSpecificAdapterContribution { program?: string; diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts index cb6808251cfe595f12a4e8f66e1d38e00ba5e302..6f20b0101daf8cec0344720af9e908722627a0d2 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts @@ -17,7 +17,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IOutputService } from 'vs/workbench/parts/output/common/output'; -import { IDebugAdapter, IAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IAdapterServer } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugAdapter, IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer } from 'vs/workbench/parts/debug/common/debug'; /** * Abstract implementation of the low level API for a debug adapter. @@ -29,6 +29,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { private pendingRequests: Map void>; private requestCallback: (request: DebugProtocol.Request) => void; private eventCallback: (request: DebugProtocol.Event) => void; + private messageCallback: (message: DebugProtocol.ProtocolMessage) => void; protected readonly _onError: Emitter; protected readonly _onExit: Emitter; @@ -57,6 +58,13 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { return this._onExit.event; } + public onMessage(callback: (message: DebugProtocol.ProtocolMessage) => void): void { + if (this.eventCallback) { + this._onError.fire(new Error(`attempt to set more than one 'Message' callback`)); + } + this.messageCallback = callback; + } + public onEvent(callback: (event: DebugProtocol.Event) => void): void { if (this.eventCallback) { this._onError.fire(new Error(`attempt to set more than one 'Event' callback`)); @@ -116,25 +124,29 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { } public acceptMessage(message: DebugProtocol.ProtocolMessage): void { - switch (message.type) { - case 'event': - if (this.eventCallback) { - this.eventCallback(message); - } - break; - case 'request': - if (this.requestCallback) { - this.requestCallback(message); - } - break; - case 'response': - const response = message; - const clb = this.pendingRequests.get(response.request_seq); - if (clb) { - this.pendingRequests.delete(response.request_seq); - clb(response); - } - break; + if (this.messageCallback) { + this.messageCallback(message); + } else { + switch (message.type) { + case 'event': + if (this.eventCallback) { + this.eventCallback(message); + } + break; + case 'request': + if (this.requestCallback) { + this.requestCallback(message); + } + break; + case 'response': + const response = message; + const clb = this.pendingRequests.get(response.request_seq); + if (clb) { + this.pendingRequests.delete(response.request_seq); + clb(response); + } + break; + } } } @@ -245,7 +257,7 @@ export class SocketDebugAdapter extends StreamDebugAdapter { private socket: net.Socket; - constructor(private adapterServer: IAdapterServer) { + constructor(private adapterServer: IDebugAdapterServer) { super(); } @@ -290,11 +302,11 @@ export class SocketDebugAdapter extends StreamDebugAdapter { /** * An implementation that launches the debug adapter as a separate process and communicates via stdin/stdout. */ -export class DebugAdapter extends StreamDebugAdapter { +export class ExecutableDebugAdapter extends StreamDebugAdapter { private serverProcess: cp.ChildProcess; - constructor(private adapterExecutable: IAdapterExecutable, private debugType: string, private outputService?: IOutputService) { + constructor(private adapterExecutable: IDebugAdapterExecutable, private debugType: string, private outputService?: IOutputService) { super(); } @@ -445,24 +457,24 @@ export class DebugAdapter extends StreamDebugAdapter { } if (contribution.win) { - result.win = DebugAdapter.extract(contribution.win, extensionFolderPath); + result.win = ExecutableDebugAdapter.extract(contribution.win, extensionFolderPath); } if (contribution.winx86) { - result.winx86 = DebugAdapter.extract(contribution.winx86, extensionFolderPath); + result.winx86 = ExecutableDebugAdapter.extract(contribution.winx86, extensionFolderPath); } if (contribution.windows) { - result.windows = DebugAdapter.extract(contribution.windows, extensionFolderPath); + result.windows = ExecutableDebugAdapter.extract(contribution.windows, extensionFolderPath); } if (contribution.osx) { - result.osx = DebugAdapter.extract(contribution.osx, extensionFolderPath); + result.osx = ExecutableDebugAdapter.extract(contribution.osx, extensionFolderPath); } if (contribution.linux) { - result.linux = DebugAdapter.extract(contribution.linux, extensionFolderPath); + result.linux = ExecutableDebugAdapter.extract(contribution.linux, extensionFolderPath); } return result; } - public static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IAdapterExecutable { + public static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IDebugAdapterExecutable { const result: IDebuggerContribution = Object.create(null); debugType = debugType.toLowerCase(); @@ -473,7 +485,7 @@ export class DebugAdapter extends StreamDebugAdapter { if (debuggers && debuggers.length > 0) { debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, debugType)).forEach(dbg => { // extract relevant attributes and make then absolute where needed - const extractedDbg = DebugAdapter.extract(dbg, ed.extensionLocation.fsPath); + const extractedDbg = ExecutableDebugAdapter.extract(dbg, ed.extensionLocation.fsPath); // merge objects.mixin(result, extractedDbg, ed.isBuiltin); diff --git a/src/vs/workbench/parts/debug/node/debugger.ts b/src/vs/workbench/parts/debug/node/debugger.ts index 608109ead79aee6e479c2ab5721079bbfd6a0592..cb7f4a89010af561eba06487184180788339e5ae 100644 --- a/src/vs/workbench/parts/debug/node/debugger.ts +++ b/src/vs/workbench/parts/debug/node/debugger.ts @@ -11,12 +11,12 @@ import * as objects from 'vs/base/common/objects'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfig, IDebuggerContribution, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, IDebugConfiguration, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IAdapterServer } from 'vs/workbench/parts/debug/common/debug'; +import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, IDebugConfiguration, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/parts/debug/common/debug'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IOutputService } from 'vs/workbench/parts/output/common/output'; -import { DebugAdapter, SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; +import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -48,7 +48,9 @@ export class Debugger implements IDebugger { case 'server': return new SocketDebugAdapter(adapterDescriptor); case 'executable': - return new DebugAdapter(adapterDescriptor, this.type, outputService); + return new ExecutableDebugAdapter(adapterDescriptor, this.type, outputService); + case 'implementation': + return undefined; // seeing 'direct' here is an error default: return undefined; } @@ -60,7 +62,7 @@ export class Debugger implements IDebugger { // a "debugServer" attribute in the launch config takes precedence if (typeof config.debugServer === 'number') { - return TPromise.wrap({ + return TPromise.wrap({ type: 'server', port: config.debugServer }); @@ -76,7 +78,7 @@ export class Debugger implements IDebugger { // try deprecated command based extension API "adapterExecutableCommand" to determine the executable if (this.debuggerContribution.adapterExecutableCommand) { const rootFolder = root ? root.uri.toString() : undefined; - return this.commandService.executeCommand(this.debuggerContribution.adapterExecutableCommand, rootFolder).then((ae: { command: string, args: string[] }) => { + return this.commandService.executeCommand(this.debuggerContribution.adapterExecutableCommand, rootFolder).then((ae: { command: string, args: string[] }) => { return { type: 'executable', command: ae.command, @@ -86,7 +88,7 @@ export class Debugger implements IDebugger { } // fallback: use executable information from package.json - return DebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type); + return ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type); }); } diff --git a/src/vs/workbench/parts/debug/test/node/debugger.test.ts b/src/vs/workbench/parts/debug/test/node/debugger.test.ts index f62841b611d9dae45d0e68f829a6bc2d04eea712..fc68819ea7580e97e52c5b2d5a6bfc3393e9d018 100644 --- a/src/vs/workbench/parts/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/parts/debug/test/node/debugger.test.ts @@ -6,12 +6,12 @@ import * as assert from 'assert'; import * as paths from 'vs/base/common/paths'; import * as platform from 'vs/base/common/platform'; -import { IAdapterExecutable, IConfigurationManager, IConfig, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugAdapterExecutable, IConfigurationManager, IConfig, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; import { Debugger } from 'vs/workbench/parts/debug/node/debugger'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { DebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; +import { ExecutableDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; suite('Debug - Debugger', () => { @@ -117,7 +117,7 @@ suite('Debug - Debugger', () => { const configurationManager = { - provideDebugAdapter(session: IDebugSession, folderUri: URI | undefined, config: IConfig): TPromise { + provideDebugAdapter(session: IDebugSession, folderUri: URI | undefined, config: IConfig): TPromise { return TPromise.as(undefined); } }; @@ -134,7 +134,7 @@ suite('Debug - Debugger', () => { assert.equal(_debugger.type, debuggerContribution.type); assert.equal(_debugger.label, debuggerContribution.label); - const ae = DebugAdapter.platformAdapterExecutable([extensionDescriptor0], 'mock'); + const ae = ExecutableDebugAdapter.platformAdapterExecutable([extensionDescriptor0], 'mock'); assert.equal(ae.command, paths.join(extensionFolderPath, debuggerContribution.program)); assert.deepEqual(ae.args, debuggerContribution.args); @@ -155,7 +155,7 @@ suite('Debug - Debugger', () => { }); test('merge platform specific attributes', () => { - const ae = DebugAdapter.platformAdapterExecutable([extensionDescriptor1, extensionDescriptor2], 'mock'); + const ae = ExecutableDebugAdapter.platformAdapterExecutable([extensionDescriptor1, extensionDescriptor2], 'mock'); assert.equal(ae.command, platform.isLinux ? 'linuxRuntime' : (platform.isMacintosh ? 'osxRuntime' : 'winRuntime')); const xprogram = platform.isLinux ? 'linuxProgram' : (platform.isMacintosh ? 'osxProgram' : 'winProgram'); assert.deepEqual(ae.args, ['rarg', '/e2/b/c/' + xprogram, 'parg']);