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

I
isidor 已提交
6
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
I
isidor 已提交
8 9
import * as strings from 'vs/base/common/strings';
import * as types from 'vs/base/common/types';
J
Johannes Rieken 已提交
10
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
I
isidor 已提交
11
import * as objects from 'vs/base/common/objects';
12
import uri from 'vs/base/common/uri';
J
Johannes Rieken 已提交
13
import { Schemas } from 'vs/base/common/network';
I
isidor 已提交
14
import * as paths from 'vs/base/common/paths';
J
Johannes Rieken 已提交
15
import { IJSONSchema } from 'vs/base/common/jsonSchema';
I
isidor 已提交
16 17 18 19
import { IModel } from 'vs/editor/common/editorCommon';
import * as extensionsRegistry from 'vs/platform/extensions/common/extensionsRegistry';
import { Registry } from 'vs/platform/platform';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
J
Johannes Rieken 已提交
20 21 22 23 24
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
I
isidor 已提交
25
import * as debug from 'vs/workbench/parts/debug/common/debug';
J
Johannes Rieken 已提交
26 27 28 29
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
30

31
// debuggers extension point
A
Alex Dima 已提交
32
export const 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',
J
Johannes Rieken 已提交
38
		defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [''] } } }],
39 40 41 42 43 44 45 46 47 48 49 50 51
		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: {
J
Johannes Rieken 已提交
52
					languageIds: {
53 54 55 56 57 58 59 60 61 62 63 64
						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'
			},
J
Johannes Rieken 已提交
69
			runtime: {
70 71 72
				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'
			},
J
Johannes Rieken 已提交
73
			runtimeArgs: {
74 75 76
				description: nls.localize('vscode.extension.contributes.debuggers.runtimeArgs', "Optional runtime arguments."),
				type: 'array'
			},
J
Johannes Rieken 已提交
77
			variables: {
78 79 80
				description: nls.localize('vscode.extension.contributes.debuggers.variables', "Mapping from interactive variables (e.g ${action.pickProcess}) in `launch.json` to a command."),
				type: 'object'
			},
81 82
			initialConfigurations: {
				description: nls.localize('vscode.extension.contributes.debuggers.initialConfigurations', "Configurations for generating the initial \'launch.json\'."),
83
				type: ['array', 'string'],
84 85 86 87 88 89 90 91 92
			},
			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: {
J
Johannes Rieken 已提交
93
					runtime: {
94 95 96 97 98 99 100 101 102
						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: {
J
Johannes Rieken 已提交
103
					runtime: {
104 105 106 107 108 109 110 111 112
						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: {
J
Johannes Rieken 已提交
113
					runtime: {
114 115 116 117 118 119 120 121 122
						description: nls.localize('vscode.extension.contributes.debuggers.linux.runtime', "Runtime used for Linux."),
						type: 'string'
					}
				}
			}
		}
	}
});

123
// breakpoints extension point #9037
A
Alex Dima 已提交
124
export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<debug.IRawBreakpointContribution[]>('breakpoints', [], {
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
	description: nls.localize('vscode.extension.contributes.breakpoints', 'Contributes breakpoints.'),
	type: 'array',
	defaultSnippets: [{ body: [{ language: '' }] }],
	items: {
		type: 'object',
		defaultSnippets: [{ body: { language: '' } }],
		properties: {
			language: {
				description: nls.localize('vscode.extension.contributes.breakpoints.language', "Allow breakpoints for this language."),
				type: 'string'
			},
		}
	}
});

I
isidor 已提交
140
// debug general schema
141

B
Benjamin Pasero 已提交
142
export const schemaId = 'vscode://schemas/launch';
I
isidor 已提交
143
const schema: IJSONSchema = {
144 145
	id: schemaId,
	type: 'object',
146
	title: nls.localize('app.launch.json.title', "Launch"),
147 148 149 150 151 152 153 154 155
	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',
156
			description: nls.localize('app.launch.json.configurations', "List of configurations. Add new configurations or edit existing ones by using IntelliSense."),
157
			items: {
158
				'type': 'object',
159 160
				oneOf: []
			}
161 162 163 164 165 166
		},
		// TODO@Isidor remove support for this in December
		debugServer: {
			type: 'number',
			description: nls.localize('app.launch.json.debugServer', "DEPRECATED: please move debugServer inside a configuration.")
		},
167
	}
I
isidor 已提交
168
};
169

I
isidor 已提交
170
const jsonRegistry = <IJSONContributionRegistry>Registry.as(JSONExtensions.JSONContribution);
171 172
jsonRegistry.registerSchema(schemaId, schema);

173
export class ConfigurationManager implements debug.IConfigurationManager {
174 175 176 177 178 179 180 181 182
	private adapters: Adapter[];
	private allModeIdsForBreakpoints: { [key: string]: boolean };

	constructor(
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IFileService private fileService: IFileService,
		@ITelemetryService private telemetryService: ITelemetryService,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IConfigurationService private configurationService: IConfigurationService,
183
		@IQuickOpenService private quickOpenService: IQuickOpenService,
184 185
		@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
		@IInstantiationService private instantiationService: IInstantiationService
186 187 188 189 190 191 192 193 194 195 196
	) {
		this.adapters = [];
		this.registerListeners();
		this.allModeIdsForBreakpoints = {};
	}

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

			extensions.forEach(extension => {
				extension.value.forEach(rawAdapter => {
197
					const adapter = this.instantiationService.createInstance(Adapter, rawAdapter, extension.description);
198 199 200 201 202 203
					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) {
204 205
						Object.keys(rawAdapter).forEach(attribute => {
							if (rawAdapter[attribute]) {
206
								if (attribute === 'enableBreakpointsFor' && duplicate[attribute]) {
207
									Object.keys(adapter.enableBreakpointsFor).forEach(languageId => duplicate.enableBreakpointsFor[languageId] = true);
208
								} else if (duplicate[attribute] && attribute !== 'type' && attribute !== 'label') {
I
isidor 已提交
209
									// give priority to the later registered extension.
210
									duplicate[attribute] = adapter[attribute];
211
									extension.collector.error(nls.localize('duplicateDebuggerType', "Debug type '{0}' is already registered and has attribute '{1}', ignoring attribute '{1}'.", adapter.type, attribute));
212 213 214 215 216 217 218 219 220
								} else {
									duplicate[attribute] = adapter[attribute];
								}
							}
						});
					} else {
						this.adapters.push(adapter);
					}

I
isidor 已提交
221 222 223 224 225
					if (adapter.enableBreakpointsFor) {
						adapter.enableBreakpointsFor.languageIds.forEach(modeId => {
							this.allModeIdsForBreakpoints[modeId] = true;
						});
					}
226 227 228
				});
			});

I
isidor 已提交
229
			// update the schema to include all attributes and types from extensions.
230 231 232 233
			// debug.schema.properties['configurations'].items.properties.type.enum = this.adapters.map(adapter => adapter.type);
			this.adapters.forEach(adapter => {
				const schemaAttributes = adapter.getSchemaAttributes();
				if (schemaAttributes) {
J
Johannes Rieken 已提交
234
					(<IJSONSchema>schema.properties['configurations'].items).oneOf.push(...schemaAttributes);
235 236 237
				}
			});
		});
238 239 240 241 242 243 244 245

		breakpointsExtPoint.setHandler(extensions => {
			extensions.forEach(ext => {
				ext.value.forEach(breakpoints => {
					this.allModeIdsForBreakpoints[breakpoints.language] = true;
				});
			});
		});
246 247
	}

248 249
	public getAdapter(type: string): Adapter {
		return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, type)).pop();
250 251
	}

252
	public getConfiguration(nameOrConfig: string | debug.IConfig): TPromise<debug.IConfig> {
253
		const config = this.configurationService.getConfiguration<debug.IGlobalConfig>('launch');
254

255 256 257 258 259 260
		let result: debug.IConfig = null;
		if (types.isObject(nameOrConfig)) {
			result = objects.deepClone(nameOrConfig) as debug.IConfig;
		} else {
			if (!config || !config.configurations) {
				return TPromise.as(null);
261
			}
262 263
			// if the configuration name is not set yet, take the first launch config (can happen if debug viewlet has not been opened yet).
			const filtered = config.configurations.filter(cfg => cfg.name === nameOrConfig);
264

265 266
			result = filtered.length === 1 ? filtered[0] : config.configurations[0];
			result = objects.deepClone(result);
267
			if (config && result && config.debugServer) {
268 269 270
				result.debugServer = config.debugServer;
			}
		}
271

272 273 274 275 276
		if (result) {
			// Set operating system specific properties #1873
			if (isWindows && result.windows) {
				Object.keys(result.windows).forEach(key => {
					result[key] = result.windows[key];
277
				});
278
			}
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
			if (isMacintosh && result.osx) {
				Object.keys(result.osx).forEach(key => {
					result[key] = result.osx[key];
				});
			}
			if (isLinux && result.linux) {
				Object.keys(result.linux).forEach(key => {
					result[key] = result.linux[key];
				});
			}

			// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
			Object.keys(result).forEach(key => {
				result[key] = this.configurationResolverService.resolveAny(result[key]);
			});

			const adapter = this.getAdapter(result.type);
			return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null);
		}
298 299 300 301
	}

	public openConfigFile(sideBySide: boolean): TPromise<boolean> {
		const resource = uri.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '/.vscode/launch.json'));
302
		let configFileCreated = false;
303 304

		return this.fileService.resolveContent(resource).then(content => true, err =>
305 306 307 308 309 310
			this.quickOpenService.pick(this.adapters, { placeHolder: nls.localize('selectDebug', "Select Environment") })
				.then(adapter => adapter ? adapter.getInitialConfigFileContent() : null)
				.then(content => {
					if (!content) {
						return false;
					}
311

312 313 314 315
					configFileCreated = true;
					return this.fileService.updateContent(resource, content).then(() => true);
				}))
			.then(errorFree => {
J
Johannes Rieken 已提交
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
				if (!errorFree) {
					return false;
				}
				this.telemetryService.publicLog('debugConfigure');

				return this.editorService.openEditor({
					resource: resource,
					options: {
						forceOpen: true,
						pinned: configFileCreated // pin only if config file is created #8727
					},
				}, sideBySide).then(() => true);
			}, (error) => {
				throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
			});
331 332
	}

I
isidor 已提交
333
	public canSetBreakpointsIn(model: IModel): boolean {
334
		if (model.uri.scheme === Schemas.inMemory) {
335 336
			return false;
		}
I
isidor 已提交
337 338 339
		if (this.configurationService.getConfiguration<debug.IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
			return true;
		}
340

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

		return !!this.allModeIdsForBreakpoints[modeId];
	}
}