debugConfigurationManager.ts 13.5 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import path = require('path');
import nls = require('vs/nls');
I
isidor 已提交
8
import { TPromise } from 'vs/base/common/winjs.base';
I
isidor 已提交
9
import strings = require('vs/base/common/strings');
10
import Event, { Emitter } from 'vs/base/common/event';
11
import objects = require('vs/base/common/objects');
12
import uri from 'vs/base/common/uri';
A
Alex Dima 已提交
13
import { Schemas } from 'vs/base/common/network';
14 15 16
import paths = require('vs/base/common/paths');
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import editor = require('vs/editor/common/editorCommon');
17
import extensionsRegistry = require('vs/platform/extensions/common/extensionsRegistry');
18
import platform = require('vs/platform/platform');
M
Martin Aeschlimann 已提交
19
import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry');
20 21 22
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
23 24 25 26 27
import debug = require('vs/workbench/parts/debug/common/debug');
import { SystemVariables } from 'vs/workbench/parts/lib/node/systemVariables';
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
28
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
29

I
isidor 已提交
30
// debuggers extension point
31

32
export var debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<debug.IRawAdapter[]>('debuggers', {
33 34
	description: nls.localize('vscode.extension.contributes.debuggers', 'Contributes debug adapters.'),
	type: 'array',
35
	defaultSnippets: [{ body: [{ type: '', extensions: [] }] }],
36 37
	items: {
		type: 'object',
38
		defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [ '' ] } } }],
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
		properties: {
			type: {
				description: nls.localize('vscode.extension.contributes.debuggers.type', "Unique identifier for this debug adapter."),
				type: 'string'
			},
			label: {
				description: nls.localize('vscode.extension.contributes.debuggers.label', "Display name for this debug adapter."),
				type: 'string'
			},
			enableBreakpointsFor: {
				description: nls.localize('vscode.extension.contributes.debuggers.enableBreakpointsFor', "Allow breakpoints for these languages."),
				type: 'object',
				properties: {
					languageIds : {
						description: nls.localize('vscode.extension.contributes.debuggers.enableBreakpointsFor.languageIds', "List of languages."),
						type: 'array',
						items: {
							type: 'string'
						}
					}
				}
			},
			program: {
				description: nls.localize('vscode.extension.contributes.debuggers.program', "Path to the debug adapter program. Path is either absolute or relative to the extension folder."),
				type: 'string'
			},
65 66 67 68
			args: {
				description: nls.localize('vscode.extension.contributes.debuggers.args', "Optional arguments to pass to the adapter."),
				type: 'array'
			},
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
			runtime : {
				description: nls.localize('vscode.extension.contributes.debuggers.runtime', "Optional runtime in case the program attribute is not an executable but requires a runtime."),
				type: 'string'
			},
			runtimeArgs : {
				description: nls.localize('vscode.extension.contributes.debuggers.runtimeArgs', "Optional runtime arguments."),
				type: 'array'
			},
			initialConfigurations: {
				description: nls.localize('vscode.extension.contributes.debuggers.initialConfigurations', "Configurations for generating the initial \'launch.json\'."),
				type: 'array',
			},
			configurationAttributes: {
				description: nls.localize('vscode.extension.contributes.debuggers.configurationAttributes', "JSON schema configurations for validating \'launch.json\'."),
				type: 'object'
			},
			windows: {
				description: nls.localize('vscode.extension.contributes.debuggers.windows', "Windows specific settings."),
				type: 'object',
				properties: {
					runtime : {
						description: nls.localize('vscode.extension.contributes.debuggers.windows.runtime', "Runtime used for Windows."),
						type: 'string'
					}
				}
			},
			osx: {
				description: nls.localize('vscode.extension.contributes.debuggers.osx', "OS X specific settings."),
				type: 'object',
				properties: {
					runtime : {
						description: nls.localize('vscode.extension.contributes.debuggers.osx.runtime', "Runtime used for OSX."),
						type: 'string'
					}
				}
			},
			linux: {
				description: nls.localize('vscode.extension.contributes.debuggers.linux', "Linux specific settings."),
				type: 'object',
				properties: {
					runtime : {
						description: nls.localize('vscode.extension.contributes.debuggers.linux.runtime', "Runtime used for Linux."),
						type: 'string'
					}
				}
			}
		}
	}
});

I
isidor 已提交
119
// debug general schema
120

121
export var schemaId = 'vscode://schemas/launch';
I
isidor 已提交
122
const schema: IJSONSchema = {
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	id: schemaId,
	type: 'object',
	title: nls.localize('app.launch.json.title', "Launch configuration"),
	required: ['version', 'configurations'],
	properties: {
		version: {
			type: 'string',
			description: nls.localize('app.launch.json.version', "Version of this file format."),
			default: '0.2.0'
		},
		configurations: {
			type: 'array',
			description: nls.localize('app.launch.json.configurations', "List of configurations. Add new configurations or edit existing ones."),
			items: {
				oneOf: []
			}
		}
	}
I
isidor 已提交
141
};
142

I
isidor 已提交
143
const jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>platform.Registry.as(jsonContributionRegistry.Extensions.JSONContribution);
144 145
jsonRegistry.registerSchema(schemaId, schema);

146
export class ConfigurationManager implements debug.IConfigurationManager {
147

148
	public configuration: debug.IConfig;
149 150 151
	private systemVariables: SystemVariables;
	private adapters: Adapter[];
	private allModeIdsForBreakpoints: { [key: string]: boolean };
152
	private _onDidConfigurationChange: Emitter<string>;
153 154 155 156 157 158 159 160

	constructor(
		configName: string,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IFileService private fileService: IFileService,
		@ITelemetryService private telemetryService: ITelemetryService,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IConfigurationService private configurationService: IConfigurationService,
161
		@IQuickOpenService private quickOpenService: IQuickOpenService
162
	) {
163
		this._onDidConfigurationChange = new Emitter<string>();
164
		this.systemVariables = this.contextService.getWorkspace() ? new SystemVariables(this.editorService, this.contextService) : null;
165
		this.setConfiguration(configName);
166 167 168 169 170 171 172 173 174 175
		this.adapters = [];
		this.registerListeners();
		this.allModeIdsForBreakpoints = {};
	}

	private registerListeners(): void {
		debuggersExtPoint.setHandler((extensions) => {

			extensions.forEach(extension => {
				extension.value.forEach(rawAdapter => {
176
					const adapter = new Adapter(rawAdapter, this.systemVariables, extension.description);
177 178 179 180 181 182 183 184 185 186
					const duplicate = this.adapters.filter(a => a.type === adapter.type)[0];
					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'."));
					}

					if (duplicate) {
						Object.keys(adapter).forEach(attribute => {
							if (adapter[attribute]) {
								if (attribute === 'enableBreakpointsFor') {
									Object.keys(adapter.enableBreakpointsFor).forEach(languageId => duplicate.enableBreakpointsFor[languageId] = true);
187
								} else if (duplicate[attribute] && attribute !== 'type' && attribute !== 'extensionDescription') {
I
isidor 已提交
188
									// give priority to the later registered extension.
189
									duplicate[attribute] = adapter[attribute];
190
									extension.collector.error(nls.localize('duplicateDebuggerType', "Debug type '{0}' is already registered and has attribute '{1}', ignoring attribute '{1}'.", adapter.type, attribute));
191 192 193 194 195 196 197 198 199
								} else {
									duplicate[attribute] = adapter[attribute];
								}
							}
						});
					} else {
						this.adapters.push(adapter);
					}

I
isidor 已提交
200 201 202 203 204
					if (adapter.enableBreakpointsFor) {
						adapter.enableBreakpointsFor.languageIds.forEach(modeId => {
							this.allModeIdsForBreakpoints[modeId] = true;
						});
					}
205 206 207
				});
			});

I
isidor 已提交
208
			// update the schema to include all attributes and types from extensions.
209 210 211 212
			// debug.schema.properties['configurations'].items.properties.type.enum = this.adapters.map(adapter => adapter.type);
			this.adapters.forEach(adapter => {
				const schemaAttributes = adapter.getSchemaAttributes();
				if (schemaAttributes) {
213
					(<IJSONSchema> schema.properties['configurations'].items).oneOf.push(...schemaAttributes);
214 215 216 217 218
				}
			});
		});
	}

219 220
	public get onDidConfigurationChange(): Event<string> {
		return this._onDidConfigurationChange.event;
221 222
	}

223
	public get configurationName(): string {
224 225 226
		return this.configuration ? this.configuration.name : null;
	}

227
	public get adapter(): Adapter {
I
isidor 已提交
228
		return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, this.configuration.type)).pop();
229 230
	}

I
isidor 已提交
231
	public setConfiguration(name: string): TPromise<void> {
232 233 234 235 236 237
		return this.loadLaunchConfig().then(config => {
			if (!config || !config.configurations) {
				this.configuration = null;
				return;
			}

I
isidor 已提交
238
			// if the configuration name is not set yet, take the first launch config (can happen if debug viewlet has not been opened yet).
239 240
			const filtered = name ? config.configurations.filter(cfg => cfg.name === name) : [config.configurations[0]];

I
isidor 已提交
241
			// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
242
			this.configuration = filtered.length === 1 ? objects.deepClone(filtered[0]) : null;
243
			if (this.configuration) {
244
				this.resloveConfiguration(this.configuration);
245 246
				this.configuration.debugServer = config.debugServer;
			}
247
		}).then(() => this._onDidConfigurationChange.fire(this.configurationName));
248 249
	}

250 251 252 253 254 255 256 257
	public resloveConfiguration(configuration: debug.IConfig) {
		if (this.systemVariables && configuration) {
			Object.keys(configuration).forEach(key => {
				configuration[key] = this.systemVariables.resolveAny(configuration[key]);
			});
		}
	}

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
	public openConfigFile(sideBySide: boolean): TPromise<boolean> {
		const resource = uri.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '/.vscode/launch.json'));

		return this.fileService.resolveContent(resource).then(content => true, err =>
			this.getInitialConfigFileContent().then(content => {
				if (!content) {
					return false;
				}

				return this.fileService.updateContent(resource, content).then(() => true);
			}
		)).then(configFileCreated => {
			if (!configFileCreated) {
				return false;
			}
			this.telemetryService.publicLog('debugConfigure');

			return this.editorService.openEditor({
				resource: resource,
				options: {
					forceOpen: true
				}
			}, sideBySide).then(() => true);
		}, (error) => {
			throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
		});
	}

	private getInitialConfigFileContent(): TPromise<string> {
I
isidor 已提交
287
		return this.quickOpenService.pick(this.adapters, { placeHolder: nls.localize('selectDebug', "Select Environment") })
288 289 290 291 292
		.then(adapter => {
			if (!adapter) {
				return null;
			}

293 294 295 296 297 298 299 300
			return this.massageInitialConfigurations(adapter).then(() => {
				let editorConfig = this.configurationService.getConfiguration<any>();
				return JSON.stringify(
					{
						version: '0.2.0',
						configurations: adapter.initialConfigurations ? adapter.initialConfigurations : []
					},
					null,
301
					editorConfig.editor.insertSpaces ? strings.repeat(' ', editorConfig.editor.tabSize) : '\t');
302
			});
303 304 305
		});
	}

I
isidor 已提交
306
	private massageInitialConfigurations(adapter: Adapter): TPromise<void> {
307
		if (!adapter || !adapter.initialConfigurations || adapter.type !== 'node') {
I
isidor 已提交
308
			return TPromise.as(undefined);
309 310
		}

I
isidor 已提交
311
		// check package.json for 'main' or 'scripts' so we generate a more pecise 'program' attribute in launch.json.
312 313 314 315 316 317 318 319 320 321 322 323 324
		const packageJsonUri = uri.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '/package.json'));
		return this.fileService.resolveContent(packageJsonUri).then(jsonContent => {
			try {
				const jsonObject = JSON.parse(jsonContent.value);
				if (jsonObject.main) {
					return jsonObject.main;
				} else if (jsonObject.scripts && typeof jsonObject.scripts.start === 'string') {
					return (<string>jsonObject.scripts.start).split(' ').pop();
				}

			} catch (error) { }

			return null;
325
		}, err => null).then((program: string) => {
326
			adapter.initialConfigurations.forEach(config => {
I
isidor 已提交
327
				if (program && config.program) {
328
					if (!path.isAbsolute(program)) {
I
isidor 已提交
329
						program = paths.join('${workspaceRoot}', program);
330 331
					}

I
isidor 已提交
332
					config.program = program;
333 334 335 336 337
				}
			});
		});
	}

I
isidor 已提交
338
	public canSetBreakpointsIn(model: editor.IModel): boolean {
339
		if (model.uri.scheme === Schemas.inMemory) {
340 341 342
			return false;
		}

I
isidor 已提交
343 344
		const mode = model ? model.getMode() : null;
		const modeId = mode ? mode.getId() : null;
345 346 347 348 349

		return !!this.allModeIdsForBreakpoints[modeId];
	}

	public loadLaunchConfig(): TPromise<debug.IGlobalConfig> {
350
		return TPromise.as(this.configurationService.getConfiguration<debug.IGlobalConfig>('launch'));
351 352
	}
}