mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-12 01:28:01 +01:00
reify the DebugAdapter; fixes #45129
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import uri from 'vs/base/common/uri';
|
||||
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IAdapterExecutable } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import {
|
||||
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
} from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { AbstractDebugAdapter } from 'vs/workbench/parts/debug/node/v8Protocol';
|
||||
import { convertToDAPaths, convertToVSCPaths } from 'vs/workbench/parts/debug/node/DapPathConverter';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDebugService)
|
||||
export class MainThreadDebugService implements MainThreadDebugServiceShape {
|
||||
@@ -21,6 +25,8 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
|
||||
private _proxy: ExtHostDebugServiceShape;
|
||||
private _toDispose: IDisposable[];
|
||||
private _breakpointEventsActive: boolean;
|
||||
private _debugAdapters: Map<number, ExtensionHostDebugAdapter>;
|
||||
private _debugAdaptersHandleCounter = 1;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -46,6 +52,18 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._debugAdapters = new Map<number, ExtensionHostDebugAdapter>();
|
||||
|
||||
// register a default DA provider
|
||||
debugService.getConfigurationManager().registerDebugAdapterProvider('*', {
|
||||
createDebugAdapter: (debugType, adapterInfo) => {
|
||||
const handle = this._debugAdaptersHandleCounter++;
|
||||
const da = new ExtensionHostDebugAdapter(handle, this._proxy, debugType, adapterInfo);
|
||||
this._debugAdapters.set(handle, da);
|
||||
return da;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -208,4 +226,59 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
|
||||
this.debugService.logToRepl(value, severity.Warning);
|
||||
return TPromise.wrap<void>(undefined);
|
||||
}
|
||||
|
||||
public $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage) {
|
||||
|
||||
convertToVSCPaths(message, source => {
|
||||
if (typeof source.path === 'object') {
|
||||
source.path = uri.revive(source.path).toString();
|
||||
}
|
||||
});
|
||||
|
||||
this._debugAdapters.get(handle).acceptMessage(message);
|
||||
}
|
||||
|
||||
public $acceptDAError(handle: number, name: string, message: string, stack: string) {
|
||||
this._debugAdapters.get(handle).fireError(handle, new Error(`${name}: ${message}\n${stack}`));
|
||||
}
|
||||
|
||||
public $acceptDAExit(handle: number, code: number, signal: string) {
|
||||
this._debugAdapters.get(handle).fireExit(handle, code, signal);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionHostDebugAdapter extends AbstractDebugAdapter {
|
||||
|
||||
constructor(private _handle: number, private _proxy: ExtHostDebugServiceShape, private _debugType: string, private _adapterExecutable: IAdapterExecutable | null) {
|
||||
super();
|
||||
}
|
||||
|
||||
public fireError(handle: number, err: Error) {
|
||||
this._onError.fire(err);
|
||||
}
|
||||
|
||||
public fireExit(handle: number, code: number, signal: string) {
|
||||
this._onExit.fire(code);
|
||||
}
|
||||
|
||||
public startSession(): TPromise<void> {
|
||||
return this._proxy.$startDASession(this._handle, this._debugType, this._adapterExecutable);
|
||||
}
|
||||
|
||||
public sendMessage(message: DebugProtocol.ProtocolMessage): void {
|
||||
|
||||
convertToDAPaths(message, source => {
|
||||
if (paths.isAbsolute(source.path)) {
|
||||
(<any>source).path = uri.file(source.path);
|
||||
} else {
|
||||
(<any>source).path = uri.parse(source.path);
|
||||
}
|
||||
});
|
||||
|
||||
this._proxy.$sendDAMessage(this._handle, message);
|
||||
}
|
||||
|
||||
public stopSession(): TPromise<void> {
|
||||
return this._proxy.$stopDASession(this._handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export function createApiFactory(
|
||||
const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostHeapService, extHostLogService));
|
||||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace);
|
||||
const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace));
|
||||
const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol));
|
||||
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
|
||||
|
||||
@@ -465,6 +465,9 @@ export interface MainThreadSCMShape extends IDisposable {
|
||||
export type DebugSessionUUID = string;
|
||||
|
||||
export interface MainThreadDebugServiceShape extends IDisposable {
|
||||
$acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage);
|
||||
$acceptDAError(handle: number, name: string, message: string, stack: string);
|
||||
$acceptDAExit(handle: number, code: number, signal: string);
|
||||
$registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasDebugAdapterExecutable: boolean, handle: number): TPromise<any>;
|
||||
$unregisterDebugConfigurationProvider(handle: number): TPromise<any>;
|
||||
$startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise<boolean>;
|
||||
@@ -783,6 +786,9 @@ export interface ISourceMultiBreakpointDto {
|
||||
}
|
||||
|
||||
export interface ExtHostDebugServiceShape {
|
||||
$startDASession(handle: number, debugType: string, adapterExecutableInfo: IAdapterExecutable | null): TPromise<void>;
|
||||
$stopDASession(handle: number): TPromise<void>;
|
||||
$sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): TPromise<void>;
|
||||
$resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): TPromise<IConfig>;
|
||||
$provideDebugConfigurations(handle: number, folder: UriComponents | undefined): TPromise<IConfig[]>;
|
||||
$debugAdapterExecutable(handle: number, folder: UriComponents | undefined): TPromise<IAdapterExecutable>;
|
||||
|
||||
@@ -12,17 +12,19 @@ import {
|
||||
IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto
|
||||
} from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { DebugAdapter } from 'vs/workbench/parts/debug/node/v8Protocol';
|
||||
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/node/DapPathConverter';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { IAdapterExecutable } from 'vs/workbench/parts/debug/common/debug';
|
||||
|
||||
|
||||
export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
|
||||
private _workspace: ExtHostWorkspace;
|
||||
|
||||
private _handleCounter: number;
|
||||
private _handlers: Map<number, vscode.DebugConfigurationProvider>;
|
||||
|
||||
@@ -52,10 +54,10 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
|
||||
private readonly _onDidChangeBreakpoints: Emitter<vscode.BreakpointsChangeEvent>;
|
||||
|
||||
private _debugAdapters: Map<number, DebugAdapter>;
|
||||
|
||||
constructor(mainContext: IMainContext, workspace: ExtHostWorkspace) {
|
||||
|
||||
this._workspace = workspace;
|
||||
constructor(mainContext: IMainContext, private _workspace: ExtHostWorkspace, private _extensionService: ExtHostExtensionService) {
|
||||
|
||||
this._handleCounter = 0;
|
||||
this._handlers = new Map<number, vscode.DebugConfigurationProvider>();
|
||||
@@ -77,6 +79,51 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
|
||||
this._breakpoints = new Map<string, vscode.Breakpoint>();
|
||||
this._breakpointEventsActive = false;
|
||||
|
||||
this._debugAdapters = new Map<number, DebugAdapter>();
|
||||
}
|
||||
|
||||
public $startDASession(handle: number, debugType: string, adpaterExecutable: IAdapterExecutable | null): TPromise<void> {
|
||||
const mythis = this;
|
||||
|
||||
const da = new class extends DebugAdapter {
|
||||
|
||||
// DA -> VS Code
|
||||
public acceptMessage(message: DebugProtocol.ProtocolMessage) {
|
||||
convertToVSCPaths(message, source => {
|
||||
if (paths.isAbsolute(source.path)) {
|
||||
(<any>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<void> {
|
||||
// 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<void> {
|
||||
const da = this._debugAdapters.get(handle);
|
||||
this._debugAdapters.delete(handle);
|
||||
return da ? da.stopSession() : void 0;
|
||||
}
|
||||
|
||||
private startBreakpoints() {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.debug';
|
||||
export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView';
|
||||
@@ -347,6 +348,7 @@ export interface IDebugConfiguration {
|
||||
hideActionBar: boolean;
|
||||
showInStatusBar: 'never' | 'always' | 'onFirstSessionStart';
|
||||
internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
|
||||
extensionHostDebugAdapter: boolean;
|
||||
}
|
||||
|
||||
export interface IGlobalConfig {
|
||||
@@ -380,6 +382,22 @@ export interface ICompound {
|
||||
configurations: (string | { name: string, folder: string })[];
|
||||
}
|
||||
|
||||
export interface IDebugAdapter extends IDisposable {
|
||||
readonly onError: Event<Error>;
|
||||
readonly onExit: Event<number>;
|
||||
onRequest(callback: (request: DebugProtocol.Request) => void);
|
||||
onEvent(callback: (event: DebugProtocol.Event) => void);
|
||||
startSession(): TPromise<void>;
|
||||
sendMessage(message: DebugProtocol.ProtocolMessage): void;
|
||||
sendResponse(response: DebugProtocol.Response): void;
|
||||
sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void): void;
|
||||
stopSession(): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface IDebugAdapterProvider {
|
||||
createDebugAdapter(debugType: string, adapterInfo: IAdapterExecutable | null): IDebugAdapter;
|
||||
}
|
||||
|
||||
export interface IAdapterExecutable {
|
||||
command?: string;
|
||||
args?: string[];
|
||||
@@ -448,6 +466,9 @@ export interface IConfigurationManager {
|
||||
|
||||
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): TPromise<any>;
|
||||
debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise<IAdapterExecutable | undefined>;
|
||||
|
||||
registerDebugAdapterProvider(debugType: string, debugAdapterLauncher: IDebugAdapterProvider);
|
||||
createDebugAdapter(debugType: string, adapterExecutable: IAdapterExecutable | null): IDebugAdapter;
|
||||
}
|
||||
|
||||
export interface ILaunch {
|
||||
|
||||
@@ -209,6 +209,11 @@ configurationRegistry.registerConfiguration({
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces"),
|
||||
default: { configurations: [], compounds: [] },
|
||||
$ref: launchSchemaId
|
||||
},
|
||||
'debug.extensionHostDebugAdapter': {
|
||||
type: 'boolean',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'extensionHostDebugAdapter' }, "Run debug adapter in extension host"),
|
||||
default: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,8 +26,8 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch, IAdapterExecutable } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch, IAdapterExecutable, IDebugAdapterProvider, IDebugAdapter } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Debugger } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
@@ -221,7 +221,7 @@ const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
|
||||
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
|
||||
|
||||
export class ConfigurationManager implements IConfigurationManager {
|
||||
private adapters: Adapter[];
|
||||
private debuggers: Debugger[];
|
||||
private breakpointModeIdsSet = new Set<string>();
|
||||
private launches: ILaunch[];
|
||||
private selectedName: string;
|
||||
@@ -229,6 +229,8 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
private toDispose: IDisposable[];
|
||||
private _onDidSelectConfigurationName = new Emitter<void>();
|
||||
private providers: IDebugConfigurationProvider[];
|
||||
private debugAdapterProviders: Map<string, IDebugAdapterProvider>;
|
||||
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@@ -241,7 +243,7 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
@ILifecycleService lifecycleService: ILifecycleService
|
||||
) {
|
||||
this.providers = [];
|
||||
this.adapters = [];
|
||||
this.debuggers = [];
|
||||
this.toDispose = [];
|
||||
this.registerListeners(lifecycleService);
|
||||
this.initLaunches();
|
||||
@@ -250,6 +252,7 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
if (previousSelectedLaunch) {
|
||||
this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
|
||||
}
|
||||
this.debugAdapterProviders = new Map<string, IDebugAdapterProvider>();
|
||||
}
|
||||
|
||||
public registerDebugConfigurationProvider(handle: number, debugConfigurationProvider: IDebugConfigurationProvider): void {
|
||||
@@ -300,12 +303,24 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
public registerDebugAdapterProvider(debugType: string, debugAdapterLauncher: IDebugAdapterProvider) {
|
||||
this.debugAdapterProviders.set(debugType, debugAdapterLauncher);
|
||||
}
|
||||
|
||||
public createDebugAdapter(debugType: string, adapterExecutable: IAdapterExecutable): IDebugAdapter {
|
||||
let dap = this.debugAdapterProviders.get(debugType);
|
||||
if (!dap) {
|
||||
dap = this.debugAdapterProviders.get('*');
|
||||
}
|
||||
return dap.createDebugAdapter(debugType, adapterExecutable);
|
||||
}
|
||||
|
||||
private registerListeners(lifecycleService: ILifecycleService): void {
|
||||
debuggersExtPoint.setHandler((extensions) => {
|
||||
extensions.forEach(extension => {
|
||||
extension.value.forEach(rawAdapter => {
|
||||
if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
|
||||
extension.collector.error(nls.localize('debugNoType', "Debug adapter 'type' can not be omitted and must be of type 'string'."));
|
||||
extension.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
|
||||
}
|
||||
if (rawAdapter.enableBreakpointsFor) {
|
||||
rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
|
||||
@@ -313,17 +328,17 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
});
|
||||
}
|
||||
|
||||
const duplicate = this.adapters.filter(a => a.type === rawAdapter.type).pop();
|
||||
const duplicate = this.debuggers.filter(a => a.type === rawAdapter.type).pop();
|
||||
if (duplicate) {
|
||||
duplicate.merge(rawAdapter, extension.description);
|
||||
} else {
|
||||
this.adapters.push(new Adapter(this, rawAdapter, extension.description, this.configurationService, this.commandService));
|
||||
this.debuggers.push(new Debugger(this, rawAdapter, extension.description, this.configurationService, this.commandService));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// update the schema to include all attributes, snippets and types from extensions.
|
||||
this.adapters.forEach(adapter => {
|
||||
this.debuggers.forEach(adapter => {
|
||||
const items = (<IJSONSchema>schema.properties['configurations'].items);
|
||||
const schemaAttributes = adapter.getSchemaAttributes();
|
||||
if (schemaAttributes) {
|
||||
@@ -448,24 +463,24 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
return this.breakpointModeIdsSet.has(modeId);
|
||||
}
|
||||
|
||||
public getAdapter(type: string): Adapter {
|
||||
return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, type)).pop();
|
||||
public getAdapter(type: string): Debugger {
|
||||
return this.debuggers.filter(adapter => strings.equalsIgnoreCase(adapter.type, type)).pop();
|
||||
}
|
||||
|
||||
public guessAdapter(type?: string): TPromise<Adapter> {
|
||||
public guessAdapter(type?: string): TPromise<Debugger> {
|
||||
if (type) {
|
||||
const adapter = this.getAdapter(type);
|
||||
return TPromise.as(adapter);
|
||||
}
|
||||
|
||||
const editor = this.editorService.getActiveEditor();
|
||||
let candidates: Adapter[];
|
||||
let candidates: Debugger[];
|
||||
if (editor) {
|
||||
const codeEditor = editor.getControl();
|
||||
if (isCodeEditor(codeEditor)) {
|
||||
const model = codeEditor.getModel();
|
||||
const language = model ? model.getLanguageIdentifier().language : undefined;
|
||||
const adapters = this.adapters.filter(a => a.languages && a.languages.indexOf(language) >= 0);
|
||||
const adapters = this.debuggers.filter(a => a.languages && a.languages.indexOf(language) >= 0);
|
||||
if (adapters.length === 1) {
|
||||
return TPromise.as(adapters[0]);
|
||||
}
|
||||
@@ -476,11 +491,11 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
}
|
||||
|
||||
if (!candidates) {
|
||||
candidates = this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider);
|
||||
candidates = this.debuggers.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider);
|
||||
}
|
||||
return this.quickOpenService.pick([...candidates, { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
|
||||
.then(picked => {
|
||||
if (picked instanceof Adapter) {
|
||||
if (picked instanceof Debugger) {
|
||||
return picked;
|
||||
}
|
||||
if (picked) {
|
||||
|
||||
@@ -4,27 +4,24 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as cp from 'child_process';
|
||||
import * as net from 'net';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as stdfork from 'vs/base/node/stdFork';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution';
|
||||
import * as debug from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { V8Protocol } from 'vs/workbench/parts/debug/node/v8Protocol';
|
||||
import { Debugger } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { IOutputService } from 'vs/workbench/parts/output/common/output';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { TerminalSupport } from 'vs/workbench/parts/debug/electron-browser/terminalSupport';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { StreamDebugAdapter } from 'vs/workbench/parts/debug/node/v8Protocol';
|
||||
|
||||
|
||||
export interface SessionExitedEvent extends debug.DebugEvent {
|
||||
body: {
|
||||
@@ -40,14 +37,46 @@ export interface SessionTerminatedEvent extends debug.DebugEvent {
|
||||
};
|
||||
}
|
||||
|
||||
export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
export class SocketDebugAdapter extends StreamDebugAdapter {
|
||||
|
||||
private socket: net.Socket;
|
||||
|
||||
constructor(private host: string, private port: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
startSession(): TPromise<void> {
|
||||
return new TPromise<void>((c, e) => {
|
||||
this.socket = net.createConnection(this.port, this.host, () => {
|
||||
this.connect(this.socket, <any>this.socket);
|
||||
c(null);
|
||||
});
|
||||
this.socket.on('error', (err: any) => {
|
||||
e(err);
|
||||
});
|
||||
this.socket.on('close', () => this._onExit.fire(0));
|
||||
});
|
||||
}
|
||||
|
||||
stopSession(): TPromise<void> {
|
||||
if (this.socket !== null) {
|
||||
this.socket.end();
|
||||
this.socket = undefined;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class RawDebugSession implements debug.ISession {
|
||||
|
||||
private debugAdapter: debug.IDebugAdapter;
|
||||
|
||||
public emittedStopped: boolean;
|
||||
public readyForBreakpoints: boolean;
|
||||
|
||||
private serverProcess: cp.ChildProcess;
|
||||
private socket: net.Socket = null;
|
||||
private cachedInitServer: TPromise<void>;
|
||||
//private serverProcess: cp.ChildProcess;
|
||||
//private socket: net.Socket = null;
|
||||
private cachedInitServerP: TPromise<void>;
|
||||
private startTime: number;
|
||||
public disconnected: boolean;
|
||||
private sentPromises: TPromise<DebugProtocol.Response>[];
|
||||
@@ -66,9 +95,9 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
private readonly _onDidEvent: Emitter<DebugProtocol.Event>;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private id: string,
|
||||
private debugServerPort: number,
|
||||
private adapter: Adapter,
|
||||
private _debugger: Debugger,
|
||||
public customTelemetryService: ITelemetryService,
|
||||
public root: IWorkspaceFolder,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@@ -78,7 +107,6 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
@IExternalTerminalService private nativeTerminalService: IExternalTerminalService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
) {
|
||||
super(id);
|
||||
this.emittedStopped = false;
|
||||
this.readyForBreakpoints = false;
|
||||
this.allThreadsContinued = true;
|
||||
@@ -96,6 +124,10 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
this._onDidEvent = new Emitter<DebugProtocol.Event>();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public get onDidInitialize(): Event<DebugProtocol.InitializedEvent> {
|
||||
return this._onDidInitialize.event;
|
||||
}
|
||||
@@ -137,28 +169,49 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
}
|
||||
|
||||
private initServer(): TPromise<void> {
|
||||
if (this.cachedInitServer) {
|
||||
return this.cachedInitServer;
|
||||
|
||||
if (this.cachedInitServerP) {
|
||||
return this.cachedInitServerP;
|
||||
}
|
||||
|
||||
const serverPromise = this.debugServerPort ? this.connectServer(this.debugServerPort) : this.startServer();
|
||||
this.cachedInitServer = serverPromise.then(() => {
|
||||
const startSessionP = this.startSession();
|
||||
|
||||
this.cachedInitServerP = startSessionP.then(() => {
|
||||
this.startTime = new Date().getTime();
|
||||
}, err => {
|
||||
this.cachedInitServer = null;
|
||||
this.cachedInitServerP = null;
|
||||
return TPromise.wrapError(err);
|
||||
});
|
||||
|
||||
return this.cachedInitServer;
|
||||
return this.cachedInitServerP;
|
||||
}
|
||||
|
||||
private startSession(): TPromise<void> {
|
||||
|
||||
const debugAdapterP = this.debugServerPort
|
||||
? TPromise.as(new SocketDebugAdapter('127.0.0.1', this.debugServerPort))
|
||||
: this._debugger.createDebugAdapter(this.root, this.outputService);
|
||||
|
||||
return debugAdapterP.then(debugAdapter => {
|
||||
|
||||
this.debugAdapter = debugAdapter;
|
||||
|
||||
this.debugAdapter.onError(err => this.onDapServerError(err));
|
||||
this.debugAdapter.onEvent(event => this.onDapEvent(event));
|
||||
this.debugAdapter.onRequest(request => this.dispatchRequest(request));
|
||||
this.debugAdapter.onExit(code => this.onServerExit());
|
||||
|
||||
return this.debugAdapter.startSession();
|
||||
});
|
||||
}
|
||||
|
||||
public custom(request: string, args: any): TPromise<DebugProtocol.Response> {
|
||||
return this.send(request, args);
|
||||
}
|
||||
|
||||
protected send<R extends DebugProtocol.Response>(command: string, args: any, cancelOnDisconnect = true): TPromise<R> {
|
||||
private send<R extends DebugProtocol.Response>(command: string, args: any, cancelOnDisconnect = true): TPromise<R> {
|
||||
return this.initServer().then(() => {
|
||||
const promise = super.send<R>(command, args).then(response => response, (errorResponse: DebugProtocol.ErrorResponse) => {
|
||||
const promise = this.internalSend<R>(command, args).then(response => response, (errorResponse: DebugProtocol.ErrorResponse) => {
|
||||
const error = errorResponse && errorResponse.body ? errorResponse.body.error : null;
|
||||
const errorMessage = errorResponse ? errorResponse.message : '';
|
||||
const telemetryMessage = error ? debug.formatPII(error.format, true, error.variables) : errorMessage;
|
||||
@@ -199,8 +252,22 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
});
|
||||
}
|
||||
|
||||
protected onEvent(event: debug.DebugEvent): void {
|
||||
event.sessionId = this.getId();
|
||||
private internalSend<R extends DebugProtocol.Response>(command: string, args: any): TPromise<R> {
|
||||
let errorCallback: (error: Error) => void;
|
||||
return new TPromise<R>((completeDispatch, errorDispatch) => {
|
||||
errorCallback = errorDispatch;
|
||||
this.debugAdapter.sendRequest(command, args, (result: R) => {
|
||||
if (result.success) {
|
||||
completeDispatch(result);
|
||||
} else {
|
||||
errorDispatch(result);
|
||||
}
|
||||
});
|
||||
}, () => errorCallback(errors.canceled()));
|
||||
}
|
||||
|
||||
private onDapEvent(event: debug.DebugEvent): void {
|
||||
event.sessionId = this.id;
|
||||
|
||||
if (event.event === 'initialized') {
|
||||
this.readyForBreakpoints = true;
|
||||
@@ -317,7 +384,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
this.sentPromises = [];
|
||||
}, 1000);
|
||||
|
||||
if ((this.serverProcess || this.socket) && !this.disconnected) {
|
||||
if (this.debugAdapter && !this.disconnected) {
|
||||
// point of no return: from now on don't report any errors
|
||||
this.disconnected = true;
|
||||
return this.send('disconnect', { restart: restart }, false).then(() => this.stopServer(), () => this.stopServer());
|
||||
@@ -392,16 +459,24 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
return (new Date().getTime() - this.startTime) / 1000;
|
||||
}
|
||||
|
||||
protected dispatchRequest(request: DebugProtocol.Request, response: DebugProtocol.Response): void {
|
||||
private dispatchRequest(request: DebugProtocol.Request): void {
|
||||
|
||||
const response: DebugProtocol.Response = {
|
||||
type: 'response',
|
||||
seq: 0,
|
||||
command: request.command,
|
||||
request_seq: request.seq,
|
||||
success: true
|
||||
};
|
||||
|
||||
if (request.command === 'runInTerminal') {
|
||||
|
||||
TerminalSupport.runInTerminal(this.terminalService, this.nativeTerminalService, this.configurationService, <DebugProtocol.RunInTerminalRequestArguments>request.arguments, <DebugProtocol.RunInTerminalResponse>response).then(() => {
|
||||
this.sendResponse(response);
|
||||
this.debugAdapter.sendResponse(response);
|
||||
}, e => {
|
||||
response.success = false;
|
||||
response.message = e.message;
|
||||
this.sendResponse(response);
|
||||
this.debugAdapter.sendResponse(response);
|
||||
});
|
||||
} else if (request.command === 'handshake') {
|
||||
try {
|
||||
@@ -411,16 +486,16 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
response.body = {
|
||||
signature: sig
|
||||
};
|
||||
this.sendResponse(response);
|
||||
this.debugAdapter.sendResponse(response);
|
||||
} catch (e) {
|
||||
response.success = false;
|
||||
response.message = e.message;
|
||||
this.sendResponse(response);
|
||||
this.debugAdapter.sendResponse(response);
|
||||
}
|
||||
} else {
|
||||
response.success = false;
|
||||
response.message = `unknown request '${request.command}'`;
|
||||
this.sendResponse(response);
|
||||
this.debugAdapter.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,111 +511,36 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
|
||||
});
|
||||
}
|
||||
|
||||
private connectServer(port: number): TPromise<void> {
|
||||
return new TPromise<void>((c, e) => {
|
||||
this.socket = net.createConnection(port, '127.0.0.1', () => {
|
||||
this.connect(this.socket, <any>this.socket);
|
||||
c(null);
|
||||
});
|
||||
this.socket.on('error', (err: any) => {
|
||||
e(err);
|
||||
});
|
||||
this.socket.on('close', () => this.onServerExit());
|
||||
});
|
||||
}
|
||||
|
||||
private startServer(): TPromise<any> {
|
||||
return this.adapter.getAdapterExecutable(this.root).then(ae => this.launchServer(ae).then(() => {
|
||||
this.serverProcess.on('error', (err: Error) => this.onServerError(err));
|
||||
this.serverProcess.on('exit', (code: number, signal: string) => this.onServerExit());
|
||||
|
||||
const sanitize = (s: string) => s.toString().replace(/\r?\n$/mg, '');
|
||||
// this.serverProcess.stdout.on('data', (data: string) => {
|
||||
// console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;');
|
||||
// });
|
||||
this.serverProcess.stderr.on('data', (data: string) => {
|
||||
this.outputService.getChannel(ExtensionsChannelId).append(sanitize(data));
|
||||
});
|
||||
|
||||
this.connect(this.serverProcess.stdout, this.serverProcess.stdin);
|
||||
}));
|
||||
}
|
||||
|
||||
private launchServer(launch: debug.IAdapterExecutable): TPromise<void> {
|
||||
return new TPromise<void>((c, e) => {
|
||||
if (launch.command === 'node') {
|
||||
if (Array.isArray(launch.args) && launch.args.length > 0) {
|
||||
stdfork.fork(launch.args[0], launch.args.slice(1), {}, (err, child) => {
|
||||
if (err) {
|
||||
e(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", launch.args[0])));
|
||||
}
|
||||
this.serverProcess = child;
|
||||
c(null);
|
||||
});
|
||||
} else {
|
||||
e(new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter.")));
|
||||
}
|
||||
} else {
|
||||
this.serverProcess = cp.spawn(launch.command, launch.args, {
|
||||
stdio: [
|
||||
'pipe', // stdin
|
||||
'pipe', // stdout
|
||||
'pipe' // stderr
|
||||
],
|
||||
});
|
||||
c(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private stopServer(): TPromise<any> {
|
||||
|
||||
if (this.socket !== null) {
|
||||
this.socket.end();
|
||||
this.cachedInitServer = null;
|
||||
if (/* this.socket !== null */ this.debugAdapter instanceof SocketDebugAdapter) {
|
||||
this.debugAdapter.stopSession();
|
||||
this.cachedInitServerP = null;
|
||||
}
|
||||
|
||||
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
|
||||
if (!this.serverProcess) {
|
||||
this.onDapEvent({ event: 'exit', type: 'event', seq: 0 });
|
||||
if (/* !this.serverProcess */ this.debugAdapter instanceof SocketDebugAdapter) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.disconnected = true;
|
||||
|
||||
let ret: TPromise<void>;
|
||||
// when killing a process in windows its child
|
||||
// processes are *not* killed but become root
|
||||
// processes. Therefore we use TASKKILL.EXE
|
||||
if (platform.isWindows) {
|
||||
ret = new TPromise<void>((c, e) => {
|
||||
const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess.pid}`, function (err, stdout, stderr) {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
});
|
||||
killer.on('exit', c);
|
||||
killer.on('error', e);
|
||||
});
|
||||
} else {
|
||||
this.serverProcess.kill('SIGTERM');
|
||||
ret = TPromise.as(null);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return this.debugAdapter.stopSession();
|
||||
}
|
||||
|
||||
protected onServerError(err: Error): void {
|
||||
this.notificationService.error(nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
|
||||
private onDapServerError(err: Error): void {
|
||||
this.notificationService.error(err.message || err.toString());
|
||||
this.stopServer().done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
private onServerExit(): void {
|
||||
this.serverProcess = null;
|
||||
this.cachedInitServer = null;
|
||||
//this.serverProcess = null;
|
||||
this.debugAdapter = null;
|
||||
this.cachedInitServerP = null;
|
||||
if (!this.disconnected) {
|
||||
this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
|
||||
}
|
||||
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
|
||||
this.onDapEvent({ event: 'exit', type: 'event', seq: 0 });
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePaths: (toDA: boolean, source: DebugProtocol.Source | undefined) => void) {
|
||||
switch (msg.type) {
|
||||
case 'event':
|
||||
const event = <DebugProtocol.Event>msg;
|
||||
switch (event.event) {
|
||||
case 'output':
|
||||
fixSourcePaths(false, (<DebugProtocol.OutputEvent>event).body.source);
|
||||
break;
|
||||
case 'loadedSource':
|
||||
fixSourcePaths(false, (<DebugProtocol.LoadedSourceEvent>event).body.source);
|
||||
break;
|
||||
case 'breakpoint':
|
||||
fixSourcePaths(false, (<DebugProtocol.BreakpointEvent>event).body.breakpoint.source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'request':
|
||||
const request = <DebugProtocol.Request>msg;
|
||||
switch (request.command) {
|
||||
case 'setBreakpoints':
|
||||
fixSourcePaths(true, (<DebugProtocol.SetBreakpointsArguments>request.arguments).source);
|
||||
break;
|
||||
case 'source':
|
||||
fixSourcePaths(true, (<DebugProtocol.SourceArguments>request.arguments).source);
|
||||
break;
|
||||
case 'gotoTargets':
|
||||
fixSourcePaths(true, (<DebugProtocol.GotoTargetsArguments>request.arguments).source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'response':
|
||||
const response = <DebugProtocol.Response>msg;
|
||||
switch (response.command) {
|
||||
case 'stackTrace':
|
||||
const r1 = <DebugProtocol.StackTraceResponse>response;
|
||||
r1.body.stackFrames.forEach(frame => fixSourcePaths(false, frame.source));
|
||||
break;
|
||||
case 'loadedSources':
|
||||
const r2 = <DebugProtocol.LoadedSourcesResponse>response;
|
||||
r2.body.sources.forEach(source => fixSourcePaths(false, source));
|
||||
break;
|
||||
case 'scopes':
|
||||
const r3 = <DebugProtocol.ScopesResponse>response;
|
||||
r3.body.scopes.forEach(scope => fixSourcePaths(false, scope.source));
|
||||
break;
|
||||
case 'setFunctionBreakpoints':
|
||||
const r4 = <DebugProtocol.SetFunctionBreakpointsResponse>response;
|
||||
r4.body.breakpoints.forEach(bp => fixSourcePaths(false, bp.source));
|
||||
break;
|
||||
case 'setBreakpoints':
|
||||
const r5 = <DebugProtocol.SetBreakpointsResponse>response;
|
||||
r5.body.breakpoints.forEach(bp => fixSourcePaths(false, bp.source));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function convertToDAPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePaths: (source: DebugProtocol.Source) => void) {
|
||||
convertPaths(msg, (toDA: boolean, source: DebugProtocol.Source | undefined) => {
|
||||
if (toDA && source) {
|
||||
fixSourcePaths(source);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function convertToVSCPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePaths: (source: DebugProtocol.Source) => void) {
|
||||
convertPaths(msg, (toDA: boolean, source: DebugProtocol.Source | undefined) => {
|
||||
if (!toDA && source) {
|
||||
fixSourcePaths(source);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3,110 +3,61 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfig, IRawAdapter, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { IConfig, IRawAdapter, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, IDebugConfiguration } 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 } from 'vs/workbench/parts/debug/node/v8Protocol';
|
||||
|
||||
export class Adapter {
|
||||
|
||||
export class Debugger {
|
||||
|
||||
private _mergedExtensionDescriptions: IExtensionDescription[];
|
||||
|
||||
constructor(private configurationManager: IConfigurationManager, private rawAdapter: IRawAdapter, public extensionDescription: IExtensionDescription,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
if (rawAdapter.windows) {
|
||||
rawAdapter.win = rawAdapter.windows;
|
||||
}
|
||||
this._mergedExtensionDescriptions = [extensionDescription];
|
||||
}
|
||||
|
||||
public hasConfigurationProvider = false;
|
||||
|
||||
public getAdapterExecutable(root: IWorkspaceFolder, verifyAgainstFS = true): TPromise<IAdapterExecutable> {
|
||||
public createDebugAdapter(root: IWorkspaceFolder, outputService: IOutputService): TPromise<IDebugAdapter> {
|
||||
return this.getAdapterExecutable(root).then(adapterExecutable => {
|
||||
const debugConfigs = this.configurationService.getValue<IDebugConfiguration>('debug');
|
||||
if (debugConfigs.extensionHostDebugAdapter) {
|
||||
return this.configurationManager.createDebugAdapter(this.rawAdapter.type, adapterExecutable);
|
||||
} else {
|
||||
return new DebugAdapter(this.rawAdapter.type, adapterExecutable, this._mergedExtensionDescriptions, outputService);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getAdapterExecutable(root: IWorkspaceFolder): TPromise<IAdapterExecutable | null> {
|
||||
|
||||
return this.configurationManager.debugAdapterExecutable(root ? root.uri : undefined, this.rawAdapter.type).then(adapterExecutable => {
|
||||
|
||||
if (adapterExecutable) {
|
||||
return this.verifyAdapterDetails(adapterExecutable, verifyAgainstFS);
|
||||
return adapterExecutable;
|
||||
}
|
||||
|
||||
// try deprecated command based extension API
|
||||
if (this.rawAdapter.adapterExecutableCommand) {
|
||||
return this.commandService.executeCommand<IAdapterExecutable>(this.rawAdapter.adapterExecutableCommand, root ? root.uri.toString() : undefined).then(ad => {
|
||||
return this.verifyAdapterDetails(ad, verifyAgainstFS);
|
||||
});
|
||||
return this.commandService.executeCommand<IAdapterExecutable>(this.rawAdapter.adapterExecutableCommand, root ? root.uri.toString() : undefined);
|
||||
}
|
||||
|
||||
// fallback: executable contribution specified in package.json
|
||||
adapterExecutable = <IAdapterExecutable>{
|
||||
command: this.getProgram(),
|
||||
args: this.getAttributeBasedOnPlatform('args')
|
||||
};
|
||||
const runtime = this.getRuntime();
|
||||
if (runtime) {
|
||||
const runtimeArgs = this.getAttributeBasedOnPlatform('runtimeArgs');
|
||||
adapterExecutable.args = (runtimeArgs || []).concat([adapterExecutable.command]).concat(adapterExecutable.args || []);
|
||||
adapterExecutable.command = runtime;
|
||||
}
|
||||
return this.verifyAdapterDetails(adapterExecutable, verifyAgainstFS);
|
||||
return TPromise.as(null);
|
||||
});
|
||||
}
|
||||
|
||||
private verifyAdapterDetails(details: IAdapterExecutable, verifyAgainstFS: boolean): TPromise<IAdapterExecutable> {
|
||||
|
||||
if (details.command) {
|
||||
if (verifyAgainstFS) {
|
||||
if (path.isAbsolute(details.command)) {
|
||||
return new TPromise<IAdapterExecutable>((c, e) => {
|
||||
fs.exists(details.command, exists => {
|
||||
if (exists) {
|
||||
c(details);
|
||||
} else {
|
||||
e(new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' does not exist.", details.command)));
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// relative path
|
||||
if (details.command.indexOf('/') < 0 && details.command.indexOf('\\') < 0) {
|
||||
// no separators: command looks like a runtime name like 'node' or 'mono'
|
||||
return TPromise.as(details); // TODO: check that the runtime is available on PATH
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return TPromise.as(details);
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.wrapError(new Error(nls.localize({ key: 'debugAdapterCannotDetermineExecutable', comment: ['Adapter executable file not found'] },
|
||||
"Cannot determine executable for debug adapter '{0}'.", this.type)));
|
||||
}
|
||||
|
||||
private getRuntime(): string {
|
||||
let runtime = this.getAttributeBasedOnPlatform('runtime');
|
||||
if (runtime && runtime.indexOf('./') === 0) {
|
||||
runtime = paths.join(this.extensionDescription.extensionFolderPath, runtime);
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
|
||||
private getProgram(): string {
|
||||
let program = this.getAttributeBasedOnPlatform('program');
|
||||
if (program) {
|
||||
program = paths.join(this.extensionDescription.extensionFolderPath, program);
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
public get aiKey(): string {
|
||||
return this.rawAdapter.aiKey;
|
||||
}
|
||||
@@ -132,6 +83,10 @@ export class Adapter {
|
||||
}
|
||||
|
||||
public merge(secondRawAdapter: IRawAdapter, extensionDescription: IExtensionDescription): void {
|
||||
|
||||
// remember all ext descriptions that are the source of this debugger
|
||||
this._mergedExtensionDescriptions.push(extensionDescription);
|
||||
|
||||
// Give priority to built in debug adapters
|
||||
if (extensionDescription.isBuiltin) {
|
||||
this.extensionDescription = extensionDescription;
|
||||
@@ -248,19 +203,4 @@ export class Adapter {
|
||||
return attributes;
|
||||
});
|
||||
}
|
||||
|
||||
private getAttributeBasedOnPlatform(key: string): any {
|
||||
let result: any;
|
||||
if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && this.rawAdapter.winx86) {
|
||||
result = this.rawAdapter.winx86[key];
|
||||
} else if (platform.isWindows && this.rawAdapter.win) {
|
||||
result = this.rawAdapter.win[key];
|
||||
} else if (platform.isMacintosh && this.rawAdapter.osx) {
|
||||
result = this.rawAdapter.osx[key];
|
||||
} else if (platform.isLinux && this.rawAdapter.linux) {
|
||||
result = this.rawAdapter.linux[key];
|
||||
}
|
||||
|
||||
return result || this.rawAdapter[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,68 +3,82 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as cp from 'child_process';
|
||||
import * as stream from 'stream';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as stdfork from 'vs/base/node/stdFork';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import * as debug from 'vs/workbench/parts/debug/common/debug';
|
||||
import { IOutputService } from 'vs/workbench/parts/output/common/output';
|
||||
|
||||
export abstract class V8Protocol {
|
||||
/**
|
||||
* Abstract implementation of the low level API for a debug adapter.
|
||||
* Missing is how this API communicates with the debug adapter.
|
||||
*/
|
||||
export abstract class AbstractDebugAdapter implements debug.IDebugAdapter {
|
||||
|
||||
private static readonly TWO_CRLF = '\r\n\r\n';
|
||||
|
||||
private outputStream: stream.Writable;
|
||||
private sequence: number;
|
||||
private pendingRequests: Map<number, (e: DebugProtocol.Response) => void>;
|
||||
private rawData: Buffer;
|
||||
private contentLength: number;
|
||||
private requestCallback: (request: DebugProtocol.Request) => void;
|
||||
private eventCallback: (request: DebugProtocol.Event) => void;
|
||||
|
||||
constructor(private id: string) {
|
||||
protected readonly _onError: Emitter<Error>;
|
||||
protected readonly _onExit: Emitter<number>;
|
||||
|
||||
constructor() {
|
||||
this.sequence = 1;
|
||||
this.contentLength = -1;
|
||||
this.pendingRequests = new Map<number, (e: DebugProtocol.Response) => void>();
|
||||
this.rawData = Buffer.allocUnsafe(0);
|
||||
|
||||
this._onError = new Emitter<Error>();
|
||||
this._onExit = new Emitter<number>();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this.id;
|
||||
abstract startSession(): TPromise<void>;
|
||||
abstract stopSession(): TPromise<void>;
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
protected abstract onServerError(err: Error): void;
|
||||
protected abstract onEvent(event: DebugProtocol.Event): void;
|
||||
protected abstract dispatchRequest(request: DebugProtocol.Request, response: DebugProtocol.Response): void;
|
||||
abstract sendMessage(message: DebugProtocol.ProtocolMessage): void;
|
||||
|
||||
protected connect(readable: stream.Readable, writable: stream.Writable): void {
|
||||
|
||||
this.outputStream = writable;
|
||||
|
||||
readable.on('data', (data: Buffer) => {
|
||||
this.rawData = Buffer.concat([this.rawData, data]);
|
||||
this.handleData();
|
||||
});
|
||||
public get onError(): Event<Error> {
|
||||
return this._onError.event;
|
||||
}
|
||||
|
||||
protected send<R extends DebugProtocol.Response>(command: string, args: any): TPromise<R> {
|
||||
let errorCallback: (error: Error) => void;
|
||||
return new TPromise<R>((completeDispatch, errorDispatch) => {
|
||||
errorCallback = errorDispatch;
|
||||
this.doSend(command, args, (result: R) => {
|
||||
if (result.success) {
|
||||
completeDispatch(result);
|
||||
} else {
|
||||
errorDispatch(result);
|
||||
}
|
||||
});
|
||||
}, () => errorCallback(canceled()));
|
||||
public get onExit(): Event<number> {
|
||||
return this._onExit.event;
|
||||
}
|
||||
|
||||
public onEvent(callback: (event: DebugProtocol.Event) => void) {
|
||||
if (this.eventCallback) {
|
||||
this._onError.fire(new Error(`attempt to set more than one 'Event' callback`));
|
||||
}
|
||||
this.eventCallback = callback;
|
||||
}
|
||||
|
||||
public onRequest(callback: (request: DebugProtocol.Request) => void) {
|
||||
if (this.requestCallback) {
|
||||
this._onError.fire(new Error(`attempt to set more than one 'Request' callback`));
|
||||
}
|
||||
this.requestCallback = callback;
|
||||
}
|
||||
|
||||
public sendResponse(response: DebugProtocol.Response): void {
|
||||
if (response.seq > 0) {
|
||||
console.error(`attempt to send more than one response for command ${response.command}`);
|
||||
this._onError.fire(new Error(`attempt to send more than one response for command ${response.command}`));
|
||||
} else {
|
||||
this.sendMessage('response', response);
|
||||
this.internalSend('response', response);
|
||||
}
|
||||
}
|
||||
|
||||
private doSend(command: string, args: any, clb: (result: DebugProtocol.Response) => void): void {
|
||||
public sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void): void {
|
||||
|
||||
const request: any = {
|
||||
command: command
|
||||
@@ -73,7 +87,7 @@ export abstract class V8Protocol {
|
||||
request.arguments = args;
|
||||
}
|
||||
|
||||
this.sendMessage('request', request);
|
||||
this.internalSend('request', request);
|
||||
|
||||
if (clb) {
|
||||
// store callback for this request
|
||||
@@ -81,19 +95,85 @@ export abstract class V8Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
private sendMessage(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void {
|
||||
public acceptMessage(message: DebugProtocol.ProtocolMessage) {
|
||||
switch (message.type) {
|
||||
case 'event':
|
||||
if (this.eventCallback) {
|
||||
this.eventCallback(<DebugProtocol.Event>message);
|
||||
}
|
||||
break;
|
||||
case 'request':
|
||||
if (this.requestCallback) {
|
||||
this.requestCallback(<DebugProtocol.Request>message);
|
||||
}
|
||||
break;
|
||||
case 'response':
|
||||
const response = <DebugProtocol.Response>message;
|
||||
const clb = this.pendingRequests.get(response.request_seq);
|
||||
if (clb) {
|
||||
this.pendingRequests.delete(response.request_seq);
|
||||
clb(response);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private internalSend(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void {
|
||||
|
||||
message.type = typ;
|
||||
message.seq = this.sequence++;
|
||||
|
||||
const json = JSON.stringify(message);
|
||||
const length = Buffer.byteLength(json, 'utf8');
|
||||
this.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
this.outputStream.write('Content-Length: ' + length.toString() + V8Protocol.TWO_CRLF, 'utf8');
|
||||
this.outputStream.write(json, 'utf8');
|
||||
/**
|
||||
* An implementation that communicates via two streams with the debug adapter.
|
||||
*/
|
||||
export abstract class StreamDebugAdapter extends AbstractDebugAdapter {
|
||||
|
||||
private static readonly TWO_CRLF = '\r\n\r\n';
|
||||
|
||||
private outputStream: stream.Writable;
|
||||
private rawData: Buffer;
|
||||
private contentLength: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
private handleData(): void {
|
||||
public connect(readable: stream.Readable, writable: stream.Writable): void {
|
||||
|
||||
this.outputStream = writable;
|
||||
this.rawData = Buffer.allocUnsafe(0);
|
||||
this.contentLength = -1;
|
||||
|
||||
readable.on('data', (data: Buffer) => this.handleData(data));
|
||||
|
||||
// readable.on('close', () => {
|
||||
// this._emitEvent(new Event('close'));
|
||||
// });
|
||||
// readable.on('error', (error) => {
|
||||
// this._emitEvent(new Event('error', 'readable error: ' + (error && error.message)));
|
||||
// });
|
||||
|
||||
// writable.on('error', (error) => {
|
||||
// this._emitEvent(new Event('error', 'writable error: ' + (error && error.message)));
|
||||
// });
|
||||
}
|
||||
|
||||
public sendMessage(message: DebugProtocol.ProtocolMessage): void {
|
||||
|
||||
if (this.outputStream) {
|
||||
const json = JSON.stringify(message);
|
||||
this.outputStream.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}${StreamDebugAdapter.TWO_CRLF}${json}`, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
private handleData(data: Buffer): void {
|
||||
|
||||
this.rawData = Buffer.concat([this.rawData, data]);
|
||||
|
||||
while (true) {
|
||||
if (this.contentLength >= 0) {
|
||||
if (this.rawData.length >= this.contentLength) {
|
||||
@@ -101,55 +181,226 @@ export abstract class V8Protocol {
|
||||
this.rawData = this.rawData.slice(this.contentLength);
|
||||
this.contentLength = -1;
|
||||
if (message.length > 0) {
|
||||
this.dispatch(message);
|
||||
try {
|
||||
this.acceptMessage(<DebugProtocol.ProtocolMessage>JSON.parse(message));
|
||||
} catch (e) {
|
||||
this._onError.fire(new Error((e.message || e) + '\n' + message));
|
||||
}
|
||||
}
|
||||
continue; // there may be more complete messages to process
|
||||
}
|
||||
} else {
|
||||
const s = this.rawData.toString('utf8', 0, this.rawData.length);
|
||||
const idx = s.indexOf(V8Protocol.TWO_CRLF);
|
||||
const idx = this.rawData.indexOf(StreamDebugAdapter.TWO_CRLF);
|
||||
if (idx !== -1) {
|
||||
const match = /Content-Length: (\d+)/.exec(s);
|
||||
if (match && match[1]) {
|
||||
this.contentLength = Number(match[1]);
|
||||
this.rawData = this.rawData.slice(idx + V8Protocol.TWO_CRLF.length);
|
||||
continue; // try to handle a complete message
|
||||
const header = this.rawData.toString('utf8', 0, idx);
|
||||
const lines = header.split('\r\n');
|
||||
for (const h of lines) {
|
||||
const kvPair = h.split(/: +/);
|
||||
if (kvPair[0] === 'Content-Length') {
|
||||
this.contentLength = Number(kvPair[1]);
|
||||
}
|
||||
}
|
||||
this.rawData = this.rawData.slice(idx + StreamDebugAdapter.TWO_CRLF.length);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private dispatch(body: string): void {
|
||||
try {
|
||||
const rawData = JSON.parse(body);
|
||||
switch (rawData.type) {
|
||||
case 'event':
|
||||
this.onEvent(<DebugProtocol.Event>rawData);
|
||||
break;
|
||||
case 'response':
|
||||
const response = <DebugProtocol.Response>rawData;
|
||||
const clb = this.pendingRequests.get(response.request_seq);
|
||||
if (clb) {
|
||||
this.pendingRequests.delete(response.request_seq);
|
||||
clb(response);
|
||||
/**
|
||||
* An implementation that launches the debug adapter as a separate process and communicates via stdin/stdout.
|
||||
*/
|
||||
export class DebugAdapter extends StreamDebugAdapter {
|
||||
|
||||
private _serverProcess: cp.ChildProcess;
|
||||
|
||||
constructor(private _debugType: string, private _adapterExecutable: debug.IAdapterExecutable | null, extensionDescriptions: IExtensionDescription[], private _outputService?: IOutputService) {
|
||||
super();
|
||||
|
||||
if (!this._adapterExecutable) {
|
||||
this._adapterExecutable = DebugAdapter.platformAdapterExecutable(extensionDescriptions, this._debugType);
|
||||
}
|
||||
}
|
||||
|
||||
startSession(): TPromise<void> {
|
||||
|
||||
return new TPromise<void>((c, e) => {
|
||||
|
||||
// verify executables
|
||||
if (this._adapterExecutable.command) {
|
||||
if (paths.isAbsolute(this._adapterExecutable.command)) {
|
||||
if (!fs.existsSync(this._adapterExecutable.command)) {
|
||||
e(new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' does not exist.", this._adapterExecutable.command)));
|
||||
}
|
||||
break;
|
||||
case 'request':
|
||||
const request = <DebugProtocol.Request>rawData;
|
||||
const resp: DebugProtocol.Response = {
|
||||
type: 'response',
|
||||
seq: 0,
|
||||
command: request.command,
|
||||
request_seq: request.seq,
|
||||
success: true
|
||||
};
|
||||
this.dispatchRequest(request, resp);
|
||||
break;
|
||||
} else {
|
||||
// relative path
|
||||
if (this._adapterExecutable.command.indexOf('/') < 0 && this._adapterExecutable.command.indexOf('\\') < 0) {
|
||||
// no separators: command looks like a runtime name like 'node' or 'mono'
|
||||
// TODO: check that the runtime is available on PATH
|
||||
}
|
||||
}
|
||||
} else {
|
||||
e(new Error(nls.localize({ key: 'debugAdapterCannotDetermineExecutable', comment: ['Adapter executable file not found'] },
|
||||
"Cannot determine executable for debug adapter '{0}'.", this._debugType)));
|
||||
}
|
||||
} catch (e) {
|
||||
this.onServerError(new Error((e.message || e) + '\n' + body));
|
||||
|
||||
if (this._adapterExecutable.command === 'node' /*&& this.outputService*/) {
|
||||
if (Array.isArray(this._adapterExecutable.args) && this._adapterExecutable.args.length > 0) {
|
||||
stdfork.fork(this._adapterExecutable.args[0], this._adapterExecutable.args.slice(1), {}, (err, child) => {
|
||||
if (err) {
|
||||
e(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", this._adapterExecutable.args[0])));
|
||||
}
|
||||
this._serverProcess = child;
|
||||
c(null);
|
||||
});
|
||||
} else {
|
||||
e(new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter.")));
|
||||
}
|
||||
} else {
|
||||
this._serverProcess = cp.spawn(this._adapterExecutable.command, this._adapterExecutable.args);
|
||||
c(null);
|
||||
}
|
||||
}).then(_ => {
|
||||
this._serverProcess.on('error', (err: Error) => this._onError.fire(err));
|
||||
this._serverProcess.on('exit', (code: number, signal: string) => this._onExit.fire(code));
|
||||
|
||||
if (this._outputService) {
|
||||
const sanitize = (s: string) => s.toString().replace(/\r?\n$/mg, '');
|
||||
// this.serverProcess.stdout.on('data', (data: string) => {
|
||||
// console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;');
|
||||
// });
|
||||
this._serverProcess.stderr.on('data', (data: string) => {
|
||||
this._outputService.getChannel(ExtensionsChannelId).append(sanitize(data));
|
||||
});
|
||||
}
|
||||
|
||||
this.connect(this._serverProcess.stdout, this._serverProcess.stdin);
|
||||
}, err => {
|
||||
this._onError.fire(err);
|
||||
});
|
||||
}
|
||||
|
||||
stopSession(): TPromise<void> {
|
||||
|
||||
// when killing a process in windows its child
|
||||
// processes are *not* killed but become root
|
||||
// processes. Therefore we use TASKKILL.EXE
|
||||
if (platform.isWindows) {
|
||||
return new TPromise<void>((c, e) => {
|
||||
const killer = cp.exec(`taskkill /F /T /PID ${this._serverProcess.pid}`, function (err, stdout, stderr) {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
});
|
||||
killer.on('exit', c);
|
||||
killer.on('error', e);
|
||||
});
|
||||
} else {
|
||||
this._serverProcess.kill('SIGTERM');
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static extract(dbg: debug.IRawAdapter, extensionFolderPath: string) {
|
||||
if (!dbg) {
|
||||
return undefined;
|
||||
}
|
||||
let x: debug.IRawAdapter = {};
|
||||
|
||||
if (dbg.runtime) {
|
||||
if (dbg.runtime.indexOf('./') === 0) { // TODO
|
||||
x.runtime = paths.join(extensionFolderPath, dbg.runtime);
|
||||
} else {
|
||||
x.runtime = dbg.runtime;
|
||||
}
|
||||
}
|
||||
if (dbg.runtimeArgs) {
|
||||
x.runtimeArgs = dbg.runtimeArgs;
|
||||
}
|
||||
if (dbg.program) {
|
||||
if (!paths.isAbsolute(dbg.program)) {
|
||||
x.program = paths.join(extensionFolderPath, dbg.program);
|
||||
} else {
|
||||
x.program = dbg.program;
|
||||
}
|
||||
}
|
||||
if (dbg.args) {
|
||||
x.args = dbg.args;
|
||||
}
|
||||
|
||||
if (dbg.win) {
|
||||
x.win = DebugAdapter.extract(dbg.win, extensionFolderPath);
|
||||
}
|
||||
if (dbg.winx86) {
|
||||
x.winx86 = DebugAdapter.extract(dbg.winx86, extensionFolderPath);
|
||||
}
|
||||
if (dbg.windows) {
|
||||
x.windows = DebugAdapter.extract(dbg.windows, extensionFolderPath);
|
||||
}
|
||||
if (dbg.osx) {
|
||||
x.osx = DebugAdapter.extract(dbg.osx, extensionFolderPath);
|
||||
}
|
||||
if (dbg.linux) {
|
||||
x.linux = DebugAdapter.extract(dbg.linux, extensionFolderPath);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): debug.IAdapterExecutable {
|
||||
|
||||
let result: debug.IRawAdapter = {};
|
||||
|
||||
debugType = debugType.toLowerCase();
|
||||
|
||||
// merge all contributions into one
|
||||
for (const ed of extensionDescriptions) {
|
||||
if (ed.contributes) {
|
||||
const debuggers = <debug.IRawAdapter[]>ed.contributes['debuggers'];
|
||||
if (debuggers && debuggers.length > 0) {
|
||||
const dbgs = debuggers.filter(d => d.type.toLowerCase() === debugType);
|
||||
for (const dbg of dbgs) {
|
||||
|
||||
// extract relevant attributes and make then absolute where needed
|
||||
const dbg1 = DebugAdapter.extract(dbg, ed.extensionFolderPath);
|
||||
|
||||
// merge
|
||||
objects.mixin(result, dbg1, ed.isBuiltin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// select the right platform
|
||||
let platformInfo: debug.IRawEnvAdapter;
|
||||
if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) {
|
||||
platformInfo = result.winx86;
|
||||
} else if (platform.isWindows) {
|
||||
platformInfo = result.win || result.windows;
|
||||
} else if (platform.isMacintosh) {
|
||||
platformInfo = result.osx;
|
||||
} else if (platform.isLinux) {
|
||||
platformInfo = result.linux;
|
||||
}
|
||||
platformInfo = platformInfo || result;
|
||||
|
||||
// these are the relevant attributes
|
||||
let program = platformInfo.program || result.program;
|
||||
const args = platformInfo.args || result.args;
|
||||
let runtime = platformInfo.runtime || result.runtime;
|
||||
const runtimeArgs = platformInfo.runtimeArgs || result.runtimeArgs;
|
||||
|
||||
if (runtime) {
|
||||
return {
|
||||
command: runtime,
|
||||
args: (runtimeArgs || []).concat([program]).concat(args || [])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
command: program,
|
||||
args: args || []
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,17 @@
|
||||
import * as assert from 'assert';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IRawAdapter, IAdapterExecutable, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { IAdapterExecutable, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Debugger } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
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/v8Protocol';
|
||||
|
||||
|
||||
suite('Debug - Adapter', () => {
|
||||
let adapter: Adapter;
|
||||
suite('Debug - Debugger', () => {
|
||||
let _debugger: Debugger;
|
||||
|
||||
const extensionFolderPath = 'a/b/c/';
|
||||
const rawAdapter = {
|
||||
type: 'mock',
|
||||
@@ -44,6 +46,73 @@ suite('Debug - Adapter', () => {
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const extensionDescriptor0 = {
|
||||
id: 'adapter',
|
||||
name: 'myAdapter',
|
||||
version: '1.0.0',
|
||||
publisher: 'vscode',
|
||||
extensionFolderPath: extensionFolderPath,
|
||||
isBuiltin: false,
|
||||
engines: null,
|
||||
contributes: {
|
||||
'debuggers': [
|
||||
rawAdapter
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const extensionDescriptor1 = {
|
||||
id: 'extension1',
|
||||
name: 'extension1',
|
||||
version: '1.0.0',
|
||||
publisher: 'vscode',
|
||||
extensionFolderPath: '/e1/b/c/',
|
||||
isBuiltin: false,
|
||||
engines: null,
|
||||
contributes: {
|
||||
'debuggers': [
|
||||
{
|
||||
type: 'mock',
|
||||
runtime: 'runtime',
|
||||
runtimeArgs: ['rarg'],
|
||||
program: 'mockprogram',
|
||||
args: ['parg']
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const extensionDescriptor2 = {
|
||||
id: 'extension2',
|
||||
name: 'extension2',
|
||||
version: '1.0.0',
|
||||
publisher: 'vscode',
|
||||
extensionFolderPath: '/e2/b/c/',
|
||||
isBuiltin: false,
|
||||
engines: null,
|
||||
contributes: {
|
||||
'debuggers': [
|
||||
{
|
||||
type: 'mock',
|
||||
win: {
|
||||
runtime: 'winRuntime',
|
||||
program: 'winProgram'
|
||||
},
|
||||
linux: {
|
||||
runtime: 'linuxRuntime',
|
||||
program: 'linuxProgram'
|
||||
},
|
||||
osx: {
|
||||
runtime: 'osxRuntime',
|
||||
program: 'osxProgram'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const configurationManager = {
|
||||
debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise<IAdapterExecutable | undefined> {
|
||||
return TPromise.as(undefined);
|
||||
@@ -51,26 +120,25 @@ suite('Debug - Adapter', () => {
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
adapter = new Adapter(<IConfigurationManager>configurationManager, rawAdapter, { extensionFolderPath, id: 'adapter', name: 'myAdapter', version: '1.0.0', publisher: 'vscode', isBuiltin: false, engines: null },
|
||||
new TestConfigurationService(), null);
|
||||
_debugger = new Debugger(<IConfigurationManager>configurationManager, rawAdapter, extensionDescriptor0, new TestConfigurationService(), null);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
adapter = null;
|
||||
_debugger = null;
|
||||
});
|
||||
|
||||
test('attributes', () => {
|
||||
assert.equal(adapter.type, rawAdapter.type);
|
||||
assert.equal(adapter.label, rawAdapter.label);
|
||||
assert.equal(_debugger.type, rawAdapter.type);
|
||||
assert.equal(_debugger.label, rawAdapter.label);
|
||||
|
||||
return adapter.getAdapterExecutable(undefined, false).then(details => {
|
||||
assert.equal(details.command, paths.join(extensionFolderPath, rawAdapter.program));
|
||||
assert.deepEqual(details.args, rawAdapter.args);
|
||||
});
|
||||
const ae = DebugAdapter.platformAdapterExecutable([extensionDescriptor0], 'mock');
|
||||
|
||||
assert.equal(ae.command, paths.join(extensionFolderPath, rawAdapter.program));
|
||||
assert.deepEqual(ae.args, rawAdapter.args);
|
||||
});
|
||||
|
||||
test('schema attributes', () => {
|
||||
const schemaAttribute = adapter.getSchemaAttributes()[0];
|
||||
const schemaAttribute = _debugger.getSchemaAttributes()[0];
|
||||
assert.notDeepEqual(schemaAttribute, rawAdapter.configurationAttributes);
|
||||
Object.keys(rawAdapter.configurationAttributes.launch).forEach(key => {
|
||||
assert.deepEqual(schemaAttribute[key], rawAdapter.configurationAttributes.launch[key]);
|
||||
@@ -83,38 +151,13 @@ suite('Debug - Adapter', () => {
|
||||
assert.equal(!!schemaAttribute['properties']['preLaunchTask'], true);
|
||||
});
|
||||
|
||||
test('merge', () => {
|
||||
test('merge platform specific attributes', () => {
|
||||
|
||||
const da: IRawAdapter = {
|
||||
type: 'mock',
|
||||
win: {
|
||||
runtime: 'winRuntime'
|
||||
},
|
||||
linux: {
|
||||
runtime: 'linuxRuntime'
|
||||
},
|
||||
osx: {
|
||||
runtime: 'osxRuntime'
|
||||
},
|
||||
runtimeArgs: ['first arg'],
|
||||
program: 'mockprogram',
|
||||
args: ['arg']
|
||||
};
|
||||
|
||||
adapter.merge(da, {
|
||||
name: 'my name',
|
||||
id: 'my_id',
|
||||
version: '1.0',
|
||||
publisher: 'mockPublisher',
|
||||
isBuiltin: true,
|
||||
extensionFolderPath: 'a/b/c/d',
|
||||
engines: null
|
||||
});
|
||||
|
||||
return adapter.getAdapterExecutable(undefined, false).then(details => {
|
||||
assert.equal(details.command, platform.isLinux ? da.linux.runtime : platform.isMacintosh ? da.osx.runtime : da.win.runtime);
|
||||
assert.deepEqual(details.args, da.runtimeArgs.concat(['a/b/c/d/mockprogram'].concat(da.args)));
|
||||
});
|
||||
const ae2 = DebugAdapter.platformAdapterExecutable([extensionDescriptor1, extensionDescriptor2], 'mock');
|
||||
assert.equal(ae2.command, platform.isLinux ? 'linuxRuntime' : (platform.isMacintosh ? 'osxRuntime' : 'winRuntime'));
|
||||
const xprogram = platform.isLinux ? 'linuxProgram' : (platform.isMacintosh ? 'osxProgram' : 'winProgram');
|
||||
assert.deepEqual(ae2.args, ['rarg', '/e2/b/c/' + xprogram, 'parg']);
|
||||
});
|
||||
|
||||
test('initial config file content', () => {
|
||||
@@ -134,7 +177,7 @@ suite('Debug - Adapter', () => {
|
||||
' ]',
|
||||
'}'].join('\n');
|
||||
|
||||
return adapter.getInitialConfigurationContent().then(content => {
|
||||
return _debugger.getInitialConfigurationContent().then(content => {
|
||||
assert.equal(content, expected);
|
||||
}, err => assert.fail(err));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user