debugConfigurationManager.ts 12.9 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 objects = require('vs/base/common/objects');
11
import uri from 'vs/base/common/uri';
A
Alex Dima 已提交
12
import { Schemas } from 'vs/base/common/network';
13 14 15
import paths = require('vs/base/common/paths');
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import editor = require('vs/editor/common/editorCommon');
16
import extensionsRegistry = require('vs/platform/extensions/common/extensionsRegistry');
17
import platform = require('vs/platform/platform');
M
Martin Aeschlimann 已提交
18
import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry');
19 20 21
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
22 23 24 25 26
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';
27
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
28

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

31
export var debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<debug.IRawAdapter[]>('debuggers', {
32 33
	description: nls.localize('vscode.extension.contributes.debuggers', 'Contributes debug adapters.'),
	type: 'array',
34
	defaultSnippets: [{ body: [{ type: '', extensions: [] }] }],
35 36
	items: {
		type: 'object',
37
		defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [ '' ] } } }],
38 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
		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'
			},
64 65 66 67
			args: {
				description: nls.localize('vscode.extension.contributes.debuggers.args', "Optional arguments to pass to the adapter."),
				type: 'array'
			},
68 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
			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 已提交
118
// debug general schema
119

120
export var schemaId = 'vscode://schemas/launch';
I
isidor 已提交
121
const schema: IJSONSchema = {
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
	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 已提交
140
};
141

I
isidor 已提交
142
const jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>platform.Registry.as(jsonContributionRegistry.Extensions.JSONContribution);
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
jsonRegistry.registerSchema(schemaId, schema);
jsonRegistry.addSchemaFileAssociation('/.vscode/launch.json', schemaId);

export class ConfigurationManager {

	private configuration: debug.IConfig;
	private systemVariables: SystemVariables;
	private adapters: Adapter[];
	private allModeIdsForBreakpoints: { [key: string]: boolean };

	constructor(
		configName: string,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IFileService private fileService: IFileService,
		@ITelemetryService private telemetryService: ITelemetryService,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IConfigurationService private configurationService: IConfigurationService,
160
		@IQuickOpenService private quickOpenService: IQuickOpenService
161
	) {
162
		this.systemVariables = this.contextService.getWorkspace() ? new SystemVariables(this.editorService, this.contextService) : null;
163
		this.setConfiguration(configName);
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
		this.adapters = [];
		this.registerListeners();
		this.allModeIdsForBreakpoints = {};
	}

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

			extensions.forEach(extension => {
				extension.value.forEach(rawAdapter => {
					const adapter = new Adapter(rawAdapter, this.systemVariables, extension.description.extensionFolderPath);
					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);
								} else if (duplicate[attribute] && attribute !== 'type') {
I
isidor 已提交
186
									// give priority to the later registered extension.
187
									duplicate[attribute] = adapter[attribute];
188
									extension.collector.error(nls.localize('duplicateDebuggerType', "Debug type '{0}' is already registered and has attribute '{1}', ignoring attribute '{1}'.", adapter.type, attribute));
189 190 191 192 193 194 195 196 197
								} else {
									duplicate[attribute] = adapter[attribute];
								}
							}
						});
					} else {
						this.adapters.push(adapter);
					}

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

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

	public getConfiguration(): debug.IConfig {
		return this.configuration;
	}

	public getConfigurationName(): string {
		return this.configuration ? this.configuration.name : null;
	}

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

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

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

I
isidor 已提交
239
			// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
240
			this.configuration = filtered.length === 1 ? objects.deepClone(filtered[0]) : null;
241 242 243
			if (this.configuration) {
				if (this.systemVariables) {
					Object.keys(this.configuration).forEach(key => {
I
isidor 已提交
244
						this.configuration[key] = this.systemVariables.resolveAny(this.configuration[key]);
245 246
					});
				}
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
				this.configuration.debugServer = config.debugServer;
			}
		});
	}

	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 已提交
281
		return this.quickOpenService.pick(this.adapters, { placeHolder: nls.localize('selectDebug', "Select Environment") })
282 283 284 285 286 287 288 289 290 291
		.then(adapter => {
			if (!adapter) {
				return null;
			}

			return this.massageInitialConfigurations(adapter).then(() =>
				JSON.stringify({
					version: '0.2.0',
					configurations: adapter.initialConfigurations ? adapter.initialConfigurations : []
				}, null, '\t')
I
isidor 已提交
292
			);
293 294 295
		});
	}

I
isidor 已提交
296
	private massageInitialConfigurations(adapter: Adapter): TPromise<void> {
297
		if (!adapter || !adapter.initialConfigurations || adapter.type !== 'node') {
I
isidor 已提交
298
			return TPromise.as(undefined);
299 300
		}

I
isidor 已提交
301
		// check package.json for 'main' or 'scripts' so we generate a more pecise 'program' attribute in launch.json.
302 303 304 305 306 307 308 309 310 311 312 313 314
		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;
315
		}, err => null).then((program: string) => {
316
			adapter.initialConfigurations.forEach(config => {
I
isidor 已提交
317
				if (program && config.program) {
318
					if (!path.isAbsolute(program)) {
I
isidor 已提交
319
						program = paths.join('${workspaceRoot}', program);
320 321
					}

I
isidor 已提交
322
					config.program = program;
323 324 325 326 327
				}
			});
		});
	}

I
isidor 已提交
328
	public canSetBreakpointsIn(model: editor.IModel): boolean {
A
Alex Dima 已提交
329
		if (model.getAssociatedResource().scheme === Schemas.inMemory) {
330 331 332
			return false;
		}

I
isidor 已提交
333 334
		const mode = model ? model.getMode() : null;
		const modeId = mode ? mode.getId() : null;
335 336 337 338 339 340 341 342

		return !!this.allModeIdsForBreakpoints[modeId];
	}

	public loadLaunchConfig(): TPromise<debug.IGlobalConfig> {
		return this.configurationService.loadConfiguration('launch');
	}
}