diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e7d1ee961b5..552f9f8686d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -437,6 +437,9 @@ declare module 'vscode' { * Represents a debug adapter executable and optional arguments passed to it. */ 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. @@ -455,6 +458,23 @@ declare module 'vscode' { constructor(command: string, args?: string[]); } + export class DebugAdapterServer { + + readonly type: 'server'; + + /** + * The port. + */ + readonly port: number; + + /** + * Create a new debug adapter specification. + */ + constructor(port: number); + } + + export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer; + export interface DebugConfigurationProvider { /** * This optional method is called just before a debug adapter is started to determine its executable path and arguments. @@ -462,8 +482,18 @@ declare module 'vscode' { * @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. + * @deprecated Use DebugConfigurationProvider.provideDebugAdapter instead */ debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; + + /** + * This optional method is called just before a debug adapter is started to determine its executable path and arguments. + * Registering more than one provideDebugAdapter 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 descriptor](#DebugAdapterDescriptor) or undefined. + */ + provideDebugAdapter?(session: DebugSession, folder: WorkspaceFolder | undefined, executable: DebugAdapterExecutable, config: DebugConfiguration, token?: CancellationToken): ProviderResult; } //#endregion diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index 2d741ad475b..4f8d4b42dce 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -6,11 +6,11 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IAdapterExecutable, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, - IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto + IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto } from 'vs/workbench/api/node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import severity from 'vs/base/common/severity'; @@ -36,33 +36,31 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService); this._toDispose = []; this._toDispose.push(debugService.onDidNewSession(session => { - this._proxy.$acceptDebugSessionStarted(session.getId(), session.configuration.type, session.getName(false)); + this._proxy.$acceptDebugSessionStarted(this.getSessionDto(session)); })); // Need to start listening early to new session events because a custom event can come while a session is initialising this._toDispose.push(debugService.onWillNewSession(session => { - this._toDispose.push(session.onDidCustomEvent(event => this._proxy.$acceptDebugSessionCustomEvent(session.getId(), session.configuration.type, session.configuration.name, event))); + this._toDispose.push(session.onDidCustomEvent(event => this._proxy.$acceptDebugSessionCustomEvent(this.getSessionDto(session), event))); })); this._toDispose.push(debugService.onDidEndSession(session => { - this._proxy.$acceptDebugSessionTerminated(session.getId(), session.configuration.type, session.getName(false)); + this._proxy.$acceptDebugSessionTerminated(this.getSessionDto(session)); })); - this._toDispose.push(debugService.getViewModel().onDidFocusSession(proc => { - if (proc) { - this._proxy.$acceptDebugSessionActiveChanged(proc.getId(), proc.configuration.type, proc.getName(false)); - } else { - this._proxy.$acceptDebugSessionActiveChanged(undefined); - } + this._toDispose.push(debugService.getViewModel().onDidFocusSession(session => { + this._proxy.$acceptDebugSessionActiveChanged(this.getSessionDto(session)); })); this._debugAdapters = new Map(); } - public $registerDebugTypes(debugTypes: string[]) { - this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterProvider(debugTypes, this)); + public dispose(): void { + this._toDispose = dispose(this._toDispose); } - createDebugAdapter(debugType: string, adapterInfo, debugPort: number): IDebugAdapter { + // interface IDebugAdapterProvider + + createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter { const handle = this._debugAdaptersHandleCounter++; - const da = new ExtensionHostDebugAdapter(handle, this._proxy, debugType, adapterInfo, debugPort); + const da = new ExtensionHostDebugAdapter(handle, this._proxy, this.getSessionDto(session), folder, config); this._debugAdapters.set(handle, da); return da; } @@ -75,8 +73,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return TPromise.wrap(this._proxy.$runInTerminal(args, config)); } - public dispose(): void { - this._toDispose = dispose(this._toDispose); + + // RPC methods (MainThreadDebugServiceShape) + + public $registerDebugTypes(debugTypes: string[]) { + this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterProvider(debugTypes, this)); } public $startBreakpointEvents(): Thenable { @@ -147,54 +148,25 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return void 0; } - private convertToDto(bps: (ReadonlyArray)): (ISourceBreakpointDto | IFunctionBreakpointDto)[] { - return bps.map(bp => { - if ('name' in bp) { - const fbp = bp; - return { - type: 'function', - id: fbp.getId(), - enabled: fbp.enabled, - condition: fbp.condition, - hitCondition: fbp.hitCondition, - logMessage: fbp.logMessage, - functionName: fbp.name - }; - } else { - const sbp = bp; - return { - type: 'source', - id: sbp.getId(), - enabled: sbp.enabled, - condition: sbp.condition, - hitCondition: sbp.hitCondition, - logMessage: sbp.logMessage, - uri: sbp.uri, - line: sbp.lineNumber > 0 ? sbp.lineNumber - 1 : 0, - character: (typeof sbp.column === 'number' && sbp.column > 0) ? sbp.column - 1 : 0, - }; - } - }); - } - public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasDebugAdapterExecutable: boolean, handle: number): Thenable { + public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasProvideDebugAdapter: boolean, handle: number): Thenable { const provider = { type: debugType }; if (hasProvide) { - provider.provideDebugConfigurations = folder => { + provider.provideDebugConfigurations = (folder) => { return TPromise.wrap(this._proxy.$provideDebugConfigurations(handle, folder)); }; } if (hasResolve) { - provider.resolveDebugConfiguration = (folder, debugConfiguration) => { - return TPromise.wrap(this._proxy.$resolveDebugConfiguration(handle, folder, debugConfiguration)); + provider.resolveDebugConfiguration = (folder, config) => { + return TPromise.wrap(this._proxy.$resolveDebugConfiguration(handle, folder, config)); }; } - if (hasDebugAdapterExecutable) { - provider.debugAdapterExecutable = (folder) => { - return TPromise.wrap(this._proxy.$debugAdapterExecutable(handle, folder)); + if (hasProvideDebugAdapter) { + provider.provideDebugAdapter = (session, folder, config) => { + return TPromise.wrap(this._proxy.$provideDebugAdapter(handle, this.getSessionDto(session), folder, config)); }; } this.debugService.getConfigurationManager().registerDebugConfigurationProvider(handle, provider); @@ -255,6 +227,49 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb public $acceptDAExit(handle: number, code: number, signal: string) { this._debugAdapters.get(handle).fireExit(handle, code, signal); } + + // dto helpers + + private getSessionDto(session: IDebugSession): IDebugSessionDto { + if (session) { + return { + id: session.getId(), + type: session.configuration.type, + name: session.getName(false) + }; + } + return undefined; + } + + private convertToDto(bps: (ReadonlyArray)): (ISourceBreakpointDto | IFunctionBreakpointDto)[] { + return bps.map(bp => { + if ('name' in bp) { + const fbp = bp; + return { + type: 'function', + id: fbp.getId(), + enabled: fbp.enabled, + condition: fbp.condition, + hitCondition: fbp.hitCondition, + logMessage: fbp.logMessage, + functionName: fbp.name + }; + } else { + const sbp = bp; + return { + type: 'source', + id: sbp.getId(), + enabled: sbp.enabled, + condition: sbp.condition, + hitCondition: sbp.hitCondition, + logMessage: sbp.logMessage, + uri: sbp.uri, + line: sbp.lineNumber > 0 ? sbp.lineNumber - 1 : 0, + character: (typeof sbp.column === 'number' && sbp.column > 0) ? sbp.column - 1 : 0, + }; + } + }); + } } /** @@ -262,7 +277,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb */ class ExtensionHostDebugAdapter extends AbstractDebugAdapter { - constructor(private _handle: number, private _proxy: ExtHostDebugServiceShape, private _debugType: string, private _adapterExecutable: IAdapterExecutable | null, private _debugPort: number) { + constructor(private _handle: number, private _proxy: ExtHostDebugServiceShape, private _sessionDto: IDebugSessionDto, private folder: IWorkspaceFolder, private config: IConfig) { super(); } @@ -275,7 +290,7 @@ class ExtensionHostDebugAdapter extends AbstractDebugAdapter { } public startSession(): TPromise { - return TPromise.wrap(this._proxy.$startDASession(this._handle, this._debugType, this._adapterExecutable, this._debugPort)); + return TPromise.wrap(this._proxy.$startDASession(this._handle, this._sessionDto, this.folder ? this.folder.uri : undefined, this.config)); } public sendMessage(message: DebugProtocol.ProtocolMessage): void { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index d445d204876..d74d4a4f9ff 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -725,6 +725,7 @@ export function createApiFactory( CompletionTriggerKind: extHostTypes.CompletionTriggerKind, ConfigurationTarget: extHostTypes.ConfigurationTarget, DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable, + DebugAdapterServer: extHostTypes.DebugAdapterServer, DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior, Diagnostic: extHostTypes.Diagnostic, DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 002f815ac7b..6b9961f7e78 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -33,7 +33,7 @@ import { EndOfLine, IFileOperationOptions, TextEditorLineNumbersStyle } from 'vs import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks'; import { ITreeItem } from 'vs/workbench/common/views'; -import { IAdapterExecutable, IConfig, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; +import { IConfig, ITerminalSettings, IAdapterDescriptor } from 'vs/workbench/parts/debug/common/debug'; import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks'; import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; @@ -943,20 +943,26 @@ export interface ISourceMultiBreakpointDto { }[]; } +export interface IDebugSessionDto { + id: DebugSessionUUID; + type: string; + name: string; +} + export interface ExtHostDebugServiceShape { $substituteVariables(folder: UriComponents | undefined, config: IConfig): Thenable; $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Thenable; - $startDASession(handle: number, debugType: string, adapterExecutableInfo: IAdapterExecutable | null, debugPort: number): Thenable; + $startDASession(handle: number, session: IDebugSessionDto, folder: UriComponents | undefined, debugConfiguration: IConfig): Thenable; $stopDASession(handle: number): Thenable; $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): Thenable; $provideDebugConfigurations(handle: number, folder: UriComponents | undefined): Thenable; - $debugAdapterExecutable(handle: number, folder: UriComponents | undefined): Thenable; - $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; - $acceptDebugSessionCustomEvent(id: DebugSessionUUID, type: string, name: string, event: any): void; - $acceptBreakpointsDelta(delat: IBreakpointsDeltaDto): void; + $provideDebugAdapter(handle: number, session: IDebugSessionDto, folderUri: UriComponents | undefined, debugConfiguration: IConfig): Thenable; + $acceptDebugSessionStarted(session: IDebugSessionDto): void; + $acceptDebugSessionTerminated(session: IDebugSessionDto): void; + $acceptDebugSessionActiveChanged(session: IDebugSessionDto): void; + $acceptDebugSessionCustomEvent(session: IDebugSessionDto, event: any): void; + $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void; } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 6bd5c081c1c..68ffe2d93a6 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -13,16 +13,16 @@ import { asThenable } from 'vs/base/common/async'; import * as nls from 'vs/nls'; import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, - IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto + IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto } from 'vs/workbench/api/node/extHost.protocol'; import * as vscode from 'vscode'; -import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint } from 'vs/workbench/api/node/extHostTypes'; +import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer } from 'vs/workbench/api/node/extHostTypes'; import { generateUuid } from 'vs/base/common/uuid'; -import { DebugAdapter, StreamDebugAdapter, SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; +import { DebugAdapter, SocketDebugAdapter } 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 { IAdapterExecutable, ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter } from 'vs/workbench/parts/debug/common/debug'; +import { ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter } 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'; @@ -37,7 +37,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; export class ExtHostDebugService implements ExtHostDebugServiceShape { private _handleCounter: number; - private _handlers: Map; + private _providerByHandle: Map; + private _providerByType: Map; private _debugServiceProxy: MainThreadDebugServiceShape; private _debugSessions: Map = new Map(); @@ -82,7 +83,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { ) { this._handleCounter = 0; - this._handlers = new Map(); + this._providerByHandle = new Map(); + this._providerByType = new Map(); this._onDidStartDebugSession = new Emitter(); this._onDidTerminateDebugSession = new Emitter(); @@ -124,143 +126,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } } - public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { - - if (args.kind === 'integrated') { - - if (!this._terminalDisposedListener) { - // React on terminal disposed and check if that is the debug terminal #12956 - this._terminalDisposedListener = this._terminalService.onDidCloseTerminal(terminal => { - if (this._integratedTerminalInstance && this._integratedTerminalInstance === terminal) { - this._integratedTerminalInstance = null; - } - }); - } - - return new TPromise(resolve => { - if (this._integratedTerminalInstance) { - this._integratedTerminalInstance.processId.then(pid => { - resolve(hasChildprocesses(pid)); - }, err => { - resolve(true); - }); - } else { - resolve(true); - } - }).then(needNewTerminal => { - - if (needNewTerminal) { - this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee")); - } - - this._integratedTerminalInstance.show(); - - return new TPromise((resolve, error) => { - setTimeout(_ => { - const command = prepareCommand(args, config); - this._integratedTerminalInstance.sendText(command, true); - resolve(void 0); - }, 500); - }); - }); - - } else if (args.kind === 'external') { - - const terminalLauncher = getTerminalLauncher(); - if (terminalLauncher) { - return terminalLauncher.runInTerminal(args, config); - } - } - return void 0; - } - - public $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): TPromise { - if (!this._variableResolver) { - this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, this._configurationService); - } - let ws: IWorkspaceFolder; - const folder = this.getFolder(folderUri); - if (folder) { - ws = { - uri: folder.uri, - name: folder.name, - index: folder.index, - toResource: () => { - throw new Error('Not implemented'); - } - }; - } - return TPromise.wrap(this._variableResolver.resolveAny(ws, config)); - } - - public $startDASession(handle: number, debugType: string, adpaterExecutable: IAdapterExecutable | null, debugPort: number): TPromise { - const mythis = this; - - let da: StreamDebugAdapter = null; - - if (debugPort > 0) { - 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); - } - - }(debugPort); - - } else { - 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); - } - - }(debugType, adpaterExecutable, this._extensionService.getAllExtensionDescriptions()); - } - - this._debugAdapters.set(handle, da); - 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(); - } - - public $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): TPromise { - // VS Code -> DA - convertToDAPaths(message, source => { - if (typeof source.path === 'object') { - source.path = URI.revive(source.path).fsPath; - } - }); - const da = this._debugAdapters.get(handle); - if (da) { - da.sendMessage(message); - } - return void 0; - } - - public $stopDASession(handle: number): TPromise { - const da = this._debugAdapters.get(handle); - this._debugAdapters.delete(handle); - return da ? da.stopSession() : void 0; - } - - private startBreakpoints() { - if (!this._breakpointEventsActive) { - this._breakpointEventsActive = true; - this._debugServiceProxy.$startBreakpointEvents(); - } - } + // extension debug API get onDidChangeBreakpoints(): Event { return this._onDidChangeBreakpoints.event; @@ -275,67 +141,6 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return result; } - public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { - - let a: vscode.Breakpoint[] = []; - let r: vscode.Breakpoint[] = []; - let c: vscode.Breakpoint[] = []; - - if (delta.added) { - for (const bpd of delta.added) { - - if (!this._breakpoints.has(bpd.id)) { - let bp: vscode.Breakpoint; - if (bpd.type === 'function') { - bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); - } else { - const uri = URI.revive(bpd.uri); - bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); - } - bp['_id'] = bpd.id; - this._breakpoints.set(bpd.id, bp); - a.push(bp); - } - } - } - - if (delta.removed) { - for (const id of delta.removed) { - const bp = this._breakpoints.get(id); - if (bp) { - this._breakpoints.delete(id); - r.push(bp); - } - } - } - - if (delta.changed) { - for (const bpd of delta.changed) { - let bp = this._breakpoints.get(bpd.id); - if (bp) { - if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { - const fbp = bp; - fbp.enabled = bpd.enabled; - fbp.condition = bpd.condition; - fbp.hitCondition = bpd.hitCondition; - fbp.logMessage = bpd.logMessage; - fbp.functionName = bpd.functionName; - } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { - const sbp = bp; - sbp.enabled = bpd.enabled; - sbp.condition = bpd.condition; - sbp.hitCondition = bpd.hitCondition; - sbp.logMessage = bpd.logMessage; - sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); - } - c.push(bp); - } - } - } - - this.fireBreakpointChanges(a, r, c); - } - public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Thenable { this.startBreakpoints(); @@ -424,6 +229,328 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return this._debugServiceProxy.$unregisterBreakpoints(ids, fids); } + public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration): Thenable { + return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig); + } + + public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { + if (!provider) { + return new Disposable(() => { }); + } + + let handle = this._handleCounter++; + this._providerByHandle.set(handle, provider); + this._providerByType.set(type, provider); + + this._debugServiceProxy.$registerDebugConfigurationProvider(type, + !!provider.provideDebugConfigurations, + !!provider.resolveDebugConfiguration, + !!provider.debugAdapterExecutable || !!provider.provideDebugAdapter, handle); + + return new Disposable(() => { + this._providerByHandle.delete(handle); + this._providerByType.delete(type); // TODO@AW support more than one + this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle); + }); + } + + // RPC methods (ExtHostDebugServiceShape) + + public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise { + + if (args.kind === 'integrated') { + + if (!this._terminalDisposedListener) { + // React on terminal disposed and check if that is the debug terminal #12956 + this._terminalDisposedListener = this._terminalService.onDidCloseTerminal(terminal => { + if (this._integratedTerminalInstance && this._integratedTerminalInstance === terminal) { + this._integratedTerminalInstance = null; + } + }); + } + + return new TPromise(resolve => { + if (this._integratedTerminalInstance) { + this._integratedTerminalInstance.processId.then(pid => { + resolve(hasChildprocesses(pid)); + }, err => { + resolve(true); + }); + } else { + resolve(true); + } + }).then(needNewTerminal => { + + if (needNewTerminal) { + this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee")); + } + + this._integratedTerminalInstance.show(); + + return new TPromise((resolve, error) => { + setTimeout(_ => { + const command = prepareCommand(args, config); + this._integratedTerminalInstance.sendText(command, true); + resolve(void 0); + }, 500); + }); + }); + + } else if (args.kind === 'external') { + + const terminalLauncher = getTerminalLauncher(); + if (terminalLauncher) { + return terminalLauncher.runInTerminal(args, config); + } + } + return void 0; + } + + public $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): TPromise { + if (!this._variableResolver) { + this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, this._configurationService); + } + let ws: IWorkspaceFolder; + const folder = this.getFolder(folderUri); + if (folder) { + ws = { + uri: folder.uri, + name: folder.name, + index: folder.index, + toResource: () => { + throw new Error('Not implemented'); + } + }; + } + return TPromise.wrap(this._variableResolver.resolveAny(ws, config)); + } + + public $startDASession(handle: number, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): TPromise { + const mythis = this; + + return this.getAdapterDescriptor(this._providerByType.get(config.type), sessionDto, folderUri, config).then(adapter => { + + let da: IDebugAdapter = 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.port); + 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); + } + + }(config.type, adapter); + break; + + default: + break; + } + + if (da) { + this._debugAdapters.set(handle, da); + 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(); + } + return undefined; + }); + } + + public $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): TPromise { + // VS Code -> DA + convertToDAPaths(message, source => { + if (typeof source.path === 'object') { + source.path = URI.revive(source.path).fsPath; + } + }); + const da = this._debugAdapters.get(handle); + if (da) { + da.sendMessage(message); + } + return void 0; + } + + public $stopDASession(handle: number): TPromise { + const da = this._debugAdapters.get(handle); + this._debugAdapters.delete(handle); + return da ? da.stopSession() : void 0; + } + + + public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { + + let a: vscode.Breakpoint[] = []; + let r: vscode.Breakpoint[] = []; + let c: vscode.Breakpoint[] = []; + + if (delta.added) { + for (const bpd of delta.added) { + + if (!this._breakpoints.has(bpd.id)) { + let bp: vscode.Breakpoint; + if (bpd.type === 'function') { + bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + } else { + const uri = URI.revive(bpd.uri); + bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + } + bp['_id'] = bpd.id; + this._breakpoints.set(bpd.id, bp); + a.push(bp); + } + } + } + + if (delta.removed) { + for (const id of delta.removed) { + const bp = this._breakpoints.get(id); + if (bp) { + this._breakpoints.delete(id); + r.push(bp); + } + } + } + + if (delta.changed) { + for (const bpd of delta.changed) { + let bp = this._breakpoints.get(bpd.id); + if (bp) { + if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { + const fbp = bp; + fbp.enabled = bpd.enabled; + fbp.condition = bpd.condition; + fbp.hitCondition = bpd.hitCondition; + fbp.logMessage = bpd.logMessage; + fbp.functionName = bpd.functionName; + } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { + const sbp = bp; + sbp.enabled = bpd.enabled; + sbp.condition = bpd.condition; + sbp.hitCondition = bpd.hitCondition; + sbp.logMessage = bpd.logMessage; + sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); + } + c.push(bp); + } + } + } + + this.fireBreakpointChanges(a, r, c); + } + + + public $provideDebugConfigurations(handle: number, folderUri: UriComponents | undefined): Thenable { + let provider = this._providerByHandle.get(handle); + if (!provider) { + return TPromise.wrapError(new Error('no handler found')); + } + if (!provider.provideDebugConfigurations) { + return TPromise.wrapError(new Error('handler has no method provideDebugConfigurations')); + } + return asThenable(() => provider.provideDebugConfigurations(this.getFolder(folderUri), CancellationToken.None)); + } + + public $resolveDebugConfiguration(handle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Thenable { + let provider = this._providerByHandle.get(handle); + if (!provider) { + return TPromise.wrapError(new Error('no handler found')); + } + if (!provider.resolveDebugConfiguration) { + return TPromise.wrapError(new Error('handler has no method resolveDebugConfiguration')); + } + return asThenable(() => provider.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, CancellationToken.None)); + } + + public $provideDebugAdapter(handle: number, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable { + let provider = this._providerByHandle.get(handle); + if (!provider) { + return TPromise.wrapError(new Error('no handler found')); + } + if (!provider.debugAdapterExecutable && !provider.provideDebugAdapter) { + return TPromise.wrapError(new Error('handler has no methods provideDebugAdapter or debugAdapterExecutable')); + } + return this.getAdapterDescriptor(provider, this.getSession(sessionDto), folderUri, config); + } + + public $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): void { + + this._onDidStartDebugSession.fire(this.getSession(sessionDto)); + } + + public $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): void { + + this._onDidTerminateDebugSession.fire(this.getSession(sessionDto)); + this._debugSessions.delete(sessionDto.id); + } + + public $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto): void { + + this._activeDebugSession = sessionDto ? this.getSession(sessionDto) : undefined; + this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); + } + + public $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): void { + + const ee: vscode.DebugSessionCustomEvent = { + session: this.getSession(sessionDto), + event: event.event, + body: event.body + }; + this._onDidReceiveDebugSessionCustomEvent.fire(ee); + } + + // private & dto helpers + + private getAdapterDescriptor(debugConfigProvider, sessionDto: IDebugSessionDto, folderUri: UriComponents | undefined, config: vscode.DebugConfiguration): Thenable { + if (debugConfigProvider) { + if (debugConfigProvider.provideDebugAdapter) { + const adapterExecutable = DebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type); + return asThenable(() => debugConfigProvider.provideDebugAdapter(this.getSession(sessionDto), this.getFolder(folderUri), adapterExecutable, config, CancellationToken.None)); + } + // deprecated + if (debugConfigProvider.debugAdapterExecutable) { + return asThenable(() => debugConfigProvider.debugAdapterExecutable(this.getFolder(folderUri), CancellationToken.None)); + } + } + // fallback: use serverport or executable information from package.json + // TODO@AW support legacy command based mechanism + if (typeof config.debugServer === 'number') { + return TPromise.wrap(new DebugAdapterServer(config.debugServer)); + } + return TPromise.wrap(DebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), config.type)); + } + + private startBreakpoints() { + if (!this._breakpointEventsActive) { + this._breakpointEventsActive = true; + this._debugServiceProxy.$startBreakpointEvents(); + } + } + private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { if (added.length > 0 || removed.length > 0 || changed.length > 0) { this._onDidChangeBreakpoints.fire(Object.freeze({ @@ -434,109 +561,16 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } } - public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { - if (!provider) { - return new Disposable(() => { }); - } - - let handle = this.nextHandle(); - this._handlers.set(handle, provider); - this._debugServiceProxy.$registerDebugConfigurationProvider(type, - !!provider.provideDebugConfigurations, - !!provider.resolveDebugConfiguration, - !!provider.debugAdapterExecutable, handle); - - return new Disposable(() => { - this._handlers.delete(handle); - this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle); - }); - } - - public $provideDebugConfigurations(handle: number, folderUri: UriComponents | undefined): Thenable { - let handler = this._handlers.get(handle); - if (!handler) { - return TPromise.wrapError(new Error('no handler found')); - } - if (!handler.provideDebugConfigurations) { - return TPromise.wrapError(new Error('handler has no method provideDebugConfigurations')); - } - return asThenable(() => handler.provideDebugConfigurations(this.getFolder(folderUri), CancellationToken.None)); - } - - public $resolveDebugConfiguration(handle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Thenable { - let handler = this._handlers.get(handle); - if (!handler) { - return TPromise.wrapError(new Error('no handler found')); - } - if (!handler.resolveDebugConfiguration) { - return TPromise.wrapError(new Error('handler has no method resolveDebugConfiguration')); - } - return asThenable(() => handler.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, CancellationToken.None)); - } - - public $debugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Thenable { - 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 asThenable(() => handler.debugAdapterExecutable(this.getFolder(folderUri), CancellationToken.None)); - } - - public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration): Thenable { - return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig); - } - - public $acceptDebugSessionStarted(id: DebugSessionUUID, type: string, name: string): void { - - let debugSession = this._debugSessions.get(id); - if (!debugSession) { - debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name); - this._debugSessions.set(id, debugSession); - } - this._onDidStartDebugSession.fire(debugSession); - } - - public $acceptDebugSessionTerminated(id: DebugSessionUUID, type: string, name: string): void { - - let debugSession = this._debugSessions.get(id); - if (!debugSession) { - debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name); - this._debugSessions.set(id, debugSession); - } - this._onDidTerminateDebugSession.fire(debugSession); - this._debugSessions.delete(id); - } - - public $acceptDebugSessionActiveChanged(id: DebugSessionUUID | undefined, type?: string, name?: string): void { - - if (id) { - this._activeDebugSession = this._debugSessions.get(id); - if (!this._activeDebugSession) { - this._activeDebugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name); - this._debugSessions.set(id, this._activeDebugSession); + private getSession(dto: IDebugSessionDto): ExtHostDebugSession { + if (dto) { + let debugSession = this._debugSessions.get(dto.id); + if (!debugSession) { + debugSession = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name); + this._debugSessions.set(dto.id, debugSession); } - } else { - this._activeDebugSession = undefined; + return debugSession; } - this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); - } - - public $acceptDebugSessionCustomEvent(id: DebugSessionUUID, type: string, name: string, event: any): void { - - let debugSession = this._debugSessions.get(id); - if (!debugSession) { - debugSession = new ExtHostDebugSession(this._debugServiceProxy, id, type, name); - this._debugSessions.set(id, debugSession); - } - const ee: vscode.DebugSessionCustomEvent = { - session: debugSession, - event: event.event, - body: event.body - }; - this._onDidReceiveDebugSessionCustomEvent.fire(ee); + return undefined; } private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined { @@ -546,10 +580,6 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } return undefined; } - - private nextHandle(): number { - return this._handleCounter++; - } } export class ExtHostDebugSession implements vscode.DebugSession { @@ -557,7 +587,6 @@ export class ExtHostDebugSession implements vscode.DebugSession { private _debugServiceProxy: MainThreadDebugServiceShape; private _id: DebugSessionUUID; - private _type: string; private _name: string; diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index a0ef74e64bf..ee51dbde3af 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1889,6 +1889,7 @@ export class FunctionBreakpoint extends Breakpoint { } export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { + readonly type = 'executable'; readonly command: string; readonly args: string[]; @@ -1898,6 +1899,15 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { } } +export class DebugAdapterServer implements vscode.DebugAdapterServer { + readonly type = 'server'; + readonly port: number; + + constructor(port: number) { + this.port = port; + } +} + 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 c7b673ce491..c06771cb047 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -111,7 +111,7 @@ export interface IExpression extends IReplElement, IExpressionContainer { } export interface IDebugger { - createDebugAdapter(root: IWorkspaceFolder, outputService: IOutputService, debugPort?: number): TPromise; + createDebugAdapter(session: IDebugSession, root: IWorkspaceFolder, config: IConfig, outputService: IOutputService): TPromise; runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): TPromise; getCustomTelemetryService(): TPromise; } @@ -452,15 +452,23 @@ export interface IDebugAdapter extends IDisposable { } export interface IDebugAdapterProvider extends ITerminalLauncher { - createDebugAdapter(debugType: string, adapterInfo: IAdapterExecutable | null, debugPort: number): IDebugAdapter; + createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter; substituteVariables(folder: IWorkspaceFolder, config: IConfig): TPromise; } export interface IAdapterExecutable { - readonly command?: string; - readonly args?: string[]; + readonly type: 'executable'; + readonly command: string; + readonly args: string[]; } +export interface IAdapterServer { + readonly type: 'server'; + readonly port: number; +} + +export type IAdapterDescriptor = IAdapterExecutable | IAdapterServer; + export interface IPlatformSpecificAdapterContribution { program?: string; args?: string[]; @@ -498,7 +506,7 @@ export interface IDebugConfigurationProvider { handle: number; resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): TPromise; provideDebugConfigurations?(folderUri: uri | undefined): TPromise; - debugAdapterExecutable(folderUri: uri | undefined): TPromise; + provideDebugAdapter?(session: IDebugSession, folderUri: uri | undefined, config: IConfig): TPromise; } export interface ITerminalLauncher { @@ -549,10 +557,11 @@ export interface IConfigurationManager { unregisterDebugConfigurationProvider(handle: number): void; resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): TPromise; - debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise; + provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): TPromise; registerDebugAdapterProvider(debugTypes: string[], debugAdapterLauncher: IDebugAdapterProvider): IDisposable; - createDebugAdapter(debugType: string, adapterExecutable: IAdapterExecutable | null, debugPort?: number): IDebugAdapter | undefined; + createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter; + substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): TPromise; runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise; } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 0062a0fdc73..73b8b7f5caf 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -22,7 +22,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, IAdapterExecutable, IDebugAdapterProvider, IDebugAdapter, ITerminalSettings, ITerminalLauncher } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterProvider, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor } 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'; @@ -119,10 +119,12 @@ export class ConfigurationManager implements IConfigurationManager { .then(results => results.reduce((first, second) => first.concat(second), [])); } - public debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise { - const providers = this.providers.filter(p => p.type === type && p.debugAdapterExecutable); + public provideDebugAdapter(session: IDebugSession, folderUri: uri | undefined, config: IConfig): TPromise { + const providers = this.providers.filter(p => p.type === config.type && p.provideDebugAdapter); if (providers.length === 1) { - return providers[0].debugAdapterExecutable(folderUri); + return providers[0].provideDebugAdapter(session, folderUri, config); + } else { + // TODO@AW handle n > 1 case } return TPromise.as(undefined); } @@ -140,10 +142,10 @@ export class ConfigurationManager implements IConfigurationManager { return this.debugAdapterProviders.get(type); } - public createDebugAdapter(debugType: string, adapterExecutable: IAdapterExecutable, debugPort: number): IDebugAdapter | undefined { - let dap = this.getDebugAdapterProvider(debugType); + public createDebugAdapter(session: IDebugSession, folder: IWorkspaceFolder, config: IConfig): IDebugAdapter { + let dap = this.getDebugAdapterProvider(config.type); if (dap) { - return dap.createDebugAdapter(debugType, adapterExecutable, debugPort); + return dap.createDebugAdapter(session, folder, config); } return undefined; } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts b/src/vs/workbench/parts/debug/electron-browser/debugSession.ts index 31ccf646810..43da894f398 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugSession.ts @@ -127,7 +127,7 @@ export class DebugSession implements IDebugSession { return dbgr.getCustomTelemetryService().then(customTelemetryService => { - return dbgr.createDebugAdapter(this.root, this.outputService, this._configuration.resolved.debugServer).then(debugAdapter => { + return dbgr.createDebugAdapter(this, this.root, this._configuration.resolved, this.outputService).then(debugAdapter => { this._raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService); diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts index 58924648783..54b758b9b92 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts @@ -294,12 +294,8 @@ export class DebugAdapter extends StreamDebugAdapter { private serverProcess: cp.ChildProcess; - constructor(private debugType: string, private adapterExecutable: IAdapterExecutable | null, extensionDescriptions: IExtensionDescription[], private outputService?: IOutputService) { + constructor(private debugType: string, private adapterExecutable: IAdapterExecutable, private outputService?: IOutputService) { super(); - - if (!this.adapterExecutable) { - this.adapterExecutable = DebugAdapter.platformAdapterExecutable(extensionDescriptions, this.debugType); - } } startSession(): TPromise { @@ -492,11 +488,13 @@ export class DebugAdapter extends StreamDebugAdapter { if (runtime) { return { + type: 'executable', command: runtime, args: (runtimeArgs || []).concat([program]).concat(args || []) }; } else { return { + type: 'executable', command: program, args: args || [] }; diff --git a/src/vs/workbench/parts/debug/node/debugger.ts b/src/vs/workbench/parts/debug/node/debugger.ts index 169c8e1fab5..5b1fb78dce2 100644 --- a/src/vs/workbench/parts/debug/node/debugger.ts +++ b/src/vs/workbench/parts/debug/node/debugger.ts @@ -11,7 +11,7 @@ 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 } from 'vs/workbench/parts/debug/common/debug'; +import { IConfig, IDebuggerContribution, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, IDebugConfiguration, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IAdapterServer } 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'; @@ -39,36 +39,43 @@ export class Debugger implements IDebugger { public hasConfigurationProvider = false; - public createDebugAdapter(root: IWorkspaceFolder, outputService: IOutputService, debugPort?: number): TPromise { - return this.getAdapterExecutable(root).then(adapterExecutable => { - if (this.inEH()) { - return this.configurationManager.createDebugAdapter(this.type, adapterExecutable, debugPort); - } else { - if (debugPort) { - return new SocketDebugAdapter(debugPort); - } else { - return new DebugAdapter(this.type, adapterExecutable, this.mergedExtensionDescriptions, outputService); + public createDebugAdapter(session: IDebugSession, root: IWorkspaceFolder, config: IConfig, outputService: IOutputService): TPromise { + if (this.inEH()) { + return TPromise.as(this.configurationManager.createDebugAdapter(session, root, config)); + } else { + return this.getAdapterDescriptor(session, root, config).then(adapterDescriptor => { + switch (adapterDescriptor.type) { + case 'server': + return new SocketDebugAdapter(adapterDescriptor.port); + case 'executable': + return new DebugAdapter(this.type, adapterDescriptor, outputService); + default: + return undefined; } - } - }); + }); + } } - public getAdapterExecutable(root: IWorkspaceFolder): TPromise { + private getAdapterDescriptor(session: IDebugSession, root: IWorkspaceFolder, config: IConfig): TPromise { - // first try to get an executable from DebugConfigurationProvider - return this.configurationManager.debugAdapterExecutable(root ? root.uri : undefined, this.type).then(adapterExecutable => { + // try deprecated command based extension API to receive an executable + if (this.debuggerContribution.adapterExecutableCommand) { + const adapterExecutable = this.commandService.executeCommand(this.debuggerContribution.adapterExecutableCommand, root ? root.uri.toString() : undefined); + return TPromise.wrap(adapterExecutable); + } - if (adapterExecutable) { - return adapterExecutable; + return this.configurationManager.provideDebugAdapter(session, root ? root.uri : undefined, config).then(adapter => { + if (adapter) { + return adapter; } - - // try deprecated command based extension API to receive an executable - if (this.debuggerContribution.adapterExecutableCommand) { - return this.commandService.executeCommand(this.debuggerContribution.adapterExecutableCommand, root ? root.uri.toString() : undefined); + if (typeof config.debugServer === 'number') { + return { + type: 'server', + port: config.debugServer + }; } - - // give up and let DebugAdapter determine executable based on package.json contribution - return TPromise.as(null); + // fallback: use information from package.json + return DebugAdapter.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 ba40f30db8e..f62841b611d 100644 --- a/src/vs/workbench/parts/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/parts/debug/test/node/debugger.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as paths from 'vs/base/common/paths'; import * as platform from 'vs/base/common/platform'; -import { IAdapterExecutable, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug'; +import { IAdapterExecutable, 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'; @@ -117,7 +117,7 @@ suite('Debug - Debugger', () => { const configurationManager = { - debugAdapterExecutable(folderUri: URI | undefined, type: string): TPromise { + provideDebugAdapter(session: IDebugSession, folderUri: URI | undefined, config: IConfig): TPromise { return TPromise.as(undefined); } };