argv.ts 16.6 KB
Newer Older
J
Joao Moreno 已提交
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.
 *--------------------------------------------------------------------------------------------*/

D
Daniel Imms 已提交
6
import * as minimist from 'minimist';
J
Joao Moreno 已提交
7
import { localize } from 'vs/nls';
8
import { isWindows } from 'vs/base/common/platform';
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

export interface ParsedArgs {
	_: string[];
	'folder-uri'?: string[]; // undefined or array of 1 or more
	'file-uri'?: string[]; // undefined or array of 1 or more
	_urls?: string[];
	help?: boolean;
	version?: boolean;
	telemetry?: boolean;
	status?: boolean;
	wait?: boolean;
	waitMarkerFilePath?: string;
	diff?: boolean;
	add?: boolean;
	goto?: boolean;
	'new-window'?: boolean;
	'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch.
	'reuse-window'?: boolean;
	locale?: string;
	'user-data-dir'?: string;
	'prof-startup'?: boolean;
	'prof-startup-prefix'?: string;
	'prof-append-timers'?: string;
	verbose?: boolean;
	trace?: boolean;
	'trace-category-filter'?: string;
	'trace-options'?: string;
	log?: string;
	logExtensionHostCommunication?: boolean;
	'extensions-dir'?: string;
39
	'extensions-download-dir'?: string;
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
	'builtin-extensions-dir'?: string;
	extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
	extensionTestsPath?: string; // either a local path or a URI
	'inspect-extensions'?: string;
	'inspect-brk-extensions'?: string;
	debugId?: string;
	'inspect-search'?: string;
	'inspect-brk-search'?: string;
	'disable-extensions'?: boolean;
	'disable-extension'?: string[]; // undefined or array of 1 or more
	'list-extensions'?: boolean;
	'show-versions'?: boolean;
	'category'?: string;
	'install-extension'?: string[]; // undefined or array of 1 or more
	'uninstall-extension'?: string[]; // undefined or array of 1 or more
	'locate-extension'?: string[]; // undefined or array of 1 or more
	'enable-proposed-api'?: string[]; // undefined or array of 1 or more
	'open-url'?: boolean;
58
	'skip-release-notes'?: boolean;
59 60 61 62 63 64
	'disable-restore-windows'?: boolean;
	'disable-telemetry'?: boolean;
	'export-default-configuration'?: string;
	'install-source'?: string;
	'disable-updates'?: boolean;
	'disable-crash-reporter'?: boolean;
65
	'crash-reporter-directory'?: string;
66 67 68 69 70 71 72 73 74
	'skip-add-to-recently-opened'?: boolean;
	'max-memory'?: string;
	'file-write'?: boolean;
	'file-chmod'?: boolean;
	'driver'?: string;
	'driver-verbose'?: boolean;
	remote?: string;
	'disable-user-env-probe'?: boolean;
	'force'?: boolean;
75
	'do-not-sync'?: boolean;
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
	'force-user-env'?: boolean;
	'sync'?: 'on' | 'off';

	// chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches
	'no-proxy-server'?: boolean;
	'proxy-server'?: string;
	'proxy-bypass-list'?: string;
	'proxy-pac-url'?: string;
	'inspect'?: string;
	'inspect-brk'?: string;
	'js-flags'?: string;
	'disable-gpu'?: boolean;
	'nolazy'?: boolean;
	'force-device-scale-factor'?: string;
	'force-renderer-accessibility'?: boolean;
	'ignore-certificate-errors'?: boolean;
	'allow-insecure-localhost'?: boolean;
C
Christof Marti 已提交
93
	'log-net-log'?: string;
94
}
J
Joao Moreno 已提交
95

J
Joao Moreno 已提交
96
/**
M
Martin Aeschlimann 已提交
97
 * This code is also used by standalone cli's. Avoid adding any other dependencies.
J
Joao Moreno 已提交
98
 */
99 100 101 102 103
const helpCategories = {
	o: localize('optionsUpperCase', "Options"),
	e: localize('extensionsManagement', "Extensions Management"),
	t: localize('troubleshooting', "Troubleshooting")
};
J
Joao Moreno 已提交
104

105 106
export interface Option<OptionType> {
	type: OptionType;
M
Martin Aeschlimann 已提交
107
	alias?: string;
108
	deprecates?: string; // old deprecated id
M
Martin Aeschlimann 已提交
109 110
	args?: string | string[];
	description?: string;
111
	cat?: keyof typeof helpCategories;
M
Martin Aeschlimann 已提交
112
}
113 114

export type OptionDescriptions<T> = {
M
Martin Aeschlimann 已提交
115
	[P in keyof T]: Option<OptionTypeName<T[P]>>;
116 117
};

M
Martin Aeschlimann 已提交
118
type OptionTypeName<T> =
119 120 121 122 123 124
	T extends boolean ? 'boolean' :
	T extends string ? 'string' :
	T extends string[] ? 'string[]' :
	T extends undefined ? 'undefined' :
	'unknown';

M
Martin Aeschlimann 已提交
125
export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
126 127 128 129 130
	'diff': { type: 'boolean', cat: 'o', alias: 'd', args: ['file', 'file'], description: localize('diff', "Compare two files with each other.") },
	'add': { type: 'boolean', cat: 'o', alias: 'a', args: 'folder', description: localize('add', "Add folder(s) to the last active window.") },
	'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") },
	'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") },
	'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") },
B
Benjamin Pasero 已提交
131 132
	'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") },
	'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") },
133
	'wait': { type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") },
M
Martin Aeschlimann 已提交
134
	'waitMarkerFilePath': { type: 'string' },
135 136
	'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
	'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") },
B
Benjamin Pasero 已提交
137
	'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
138 139

	'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
140
	'extensions-download-dir': { type: 'string' },
M
Martin Aeschlimann 已提交
141
	'builtin-extensions-dir': { type: 'string' },
142 143 144
	'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
	'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
	'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
S
Sandeep Somavarapu 已提交
145
	'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts. The identifier of an extension is always `${publisher}.${name}`. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") },
146 147 148
	'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
	'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },

B
Benjamin Pasero 已提交
149
	'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") },
150 151 152 153
	'verbose': { type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") },
	'log': { type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") },
	'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") },
	'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") },
154
	'prof-append-timers': { type: 'string' },
M
Martin Aeschlimann 已提交
155
	'prof-startup-prefix': { type: 'string' },
156 157
	'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") },
	'disable-extension': { type: 'string[]', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") },
158
	'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] },
159 160 161 162 163

	'inspect-extensions': { type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") },
	'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") },
	'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") },
	'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") },
164
	'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") },
165 166 167 168 169 170 171 172 173 174 175 176

	'remote': { type: 'string' },
	'locate-extension': { type: 'string[]' },
	'extensionDevelopmentPath': { type: 'string[]' },
	'extensionTestsPath': { type: 'string' },
	'debugId': { type: 'string' },
	'inspect-search': { type: 'string', deprecates: 'debugSearch' },
	'inspect-brk-search': { type: 'string', deprecates: 'debugBrkSearch' },
	'export-default-configuration': { type: 'string' },
	'install-source': { type: 'string' },
	'driver': { type: 'string' },
	'logExtensionHostCommunication': { type: 'boolean' },
177
	'skip-release-notes': { type: 'boolean' },
178 179 180 181
	'disable-restore-windows': { type: 'boolean' },
	'disable-telemetry': { type: 'boolean' },
	'disable-updates': { type: 'boolean' },
	'disable-crash-reporter': { type: 'boolean' },
182
	'crash-reporter-directory': { type: 'string' },
M
Martin Aeschlimann 已提交
183
	'disable-user-env-probe': { type: 'boolean' },
184 185 186 187 188 189 190
	'skip-add-to-recently-opened': { type: 'boolean' },
	'unity-launch': { type: 'boolean' },
	'open-url': { type: 'boolean' },
	'file-write': { type: 'boolean' },
	'file-chmod': { type: 'boolean' },
	'driver-verbose': { type: 'boolean' },
	'force': { type: 'boolean' },
191
	'do-not-sync': { type: 'boolean' },
M
Martin Aeschlimann 已提交
192
	'trace': { type: 'boolean' },
193 194
	'trace-category-filter': { type: 'string' },
	'trace-options': { type: 'string' },
J
João Moreno 已提交
195
	'force-user-env': { type: 'boolean' },
196

197 198 199 200 201
	// chromium flags
	'no-proxy-server': { type: 'boolean' },
	'proxy-server': { type: 'string' },
	'proxy-bypass-list': { type: 'string' },
	'proxy-pac-url': { type: 'string' },
202
	'js-flags': { type: 'string' }, // chrome js flags
203 204
	'inspect': { type: 'string' },
	'inspect-brk': { type: 'string' },
205
	'nolazy': { type: 'boolean' }, // node inspect
206
	'force-device-scale-factor': { type: 'string' },
207
	'force-renderer-accessibility': { type: 'boolean' },
C
Christof Marti 已提交
208
	'ignore-certificate-errors': { type: 'boolean' },
209
	'allow-insecure-localhost': { type: 'boolean' },
C
Christof Marti 已提交
210
	'log-net-log': { type: 'string' },
M
Martin Aeschlimann 已提交
211
	'_urls': { type: 'string[]' },
212 213 214

	_: { type: 'string[]' } // main arguments
};
M
Martin Aeschlimann 已提交
215

216 217 218 219 220 221 222 223 224 225
export interface ErrorReporter {
	onUnknownOption(id: string): void;
	onMultipleValues(id: string, usedValue: string): void;
}

const ignoringReporter: ErrorReporter = {
	onUnknownOption: () => { },
	onMultipleValues: () => { }
};

226
export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, errorReporter: ErrorReporter = ignoringReporter): T {
M
Martin Aeschlimann 已提交
227 228 229
	const alias: { [key: string]: string } = {};
	const string: string[] = [];
	const boolean: string[] = [];
230
	for (let optionId in options) {
M
Martin Aeschlimann 已提交
231 232 233 234
		if (optionId[0] === '_') {
			continue;
		}

235 236 237
		const o = options[optionId];
		if (o.alias) {
			alias[optionId] = o.alias;
M
Martin Aeschlimann 已提交
238
		}
239

240
		if (o.type === 'string' || o.type === 'string[]') {
241
			string.push(optionId);
242 243 244
			if (o.deprecates) {
				string.push(o.deprecates);
			}
M
Martin Aeschlimann 已提交
245
		} else if (o.type === 'boolean') {
246
			boolean.push(optionId);
247 248 249
			if (o.deprecates) {
				boolean.push(o.deprecates);
			}
M
Martin Aeschlimann 已提交
250
		}
J
Joao Moreno 已提交
251
	}
252
	// remove aliases to avoid confusion
253
	const parsedArgs = minimist(args, { string, boolean, alias });
M
Martin Aeschlimann 已提交
254 255

	const cleanedArgs: any = {};
256
	const remainingArgs: any = parsedArgs;
M
Martin Aeschlimann 已提交
257

258
	// https://github.com/microsoft/vscode/issues/58177
D
Dan Foad 已提交
259
	cleanedArgs._ = parsedArgs._.filter(arg => String(arg).length > 0);
260 261

	delete remainingArgs._;
262 263 264

	for (let optionId in options) {
		const o = options[optionId];
M
Martin Aeschlimann 已提交
265
		if (o.alias) {
266
			delete remainingArgs[o.alias];
M
Martin Aeschlimann 已提交
267
		}
M
Martin Aeschlimann 已提交
268

269 270
		let val = remainingArgs[optionId];
		if (o.deprecates && remainingArgs.hasOwnProperty(o.deprecates)) {
M
Martin Aeschlimann 已提交
271
			if (!val) {
272
				val = remainingArgs[o.deprecates];
M
Martin Aeschlimann 已提交
273
			}
274
			delete remainingArgs[o.deprecates];
275
		}
M
Martin Aeschlimann 已提交
276

A
Andrew Wong 已提交
277
		if (typeof val !== 'undefined') {
278 279 280 281 282 283 284 285
			if (o.type === 'string[]') {
				if (val && !Array.isArray(val)) {
					val = [val];
				}
			} else if (o.type === 'string') {
				if (Array.isArray(val)) {
					val = val.pop(); // take the last
					errorReporter.onMultipleValues(optionId, val);
M
Martin Aeschlimann 已提交
286
				}
287
			}
288
			cleanedArgs[optionId] = val;
289
		}
290
		delete remainingArgs[optionId];
M
Martin Aeschlimann 已提交
291
	}
J
Joao Moreno 已提交
292

293
	for (let key in remainingArgs) {
294
		errorReporter.onUnknownOption(key);
M
Martin Aeschlimann 已提交
295
	}
296

M
Martin Aeschlimann 已提交
297
	return cleanedArgs;
J
Joao Moreno 已提交
298 299
}

300
function formatUsage(optionId: string, option: Option<any>) {
M
Martin Aeschlimann 已提交
301 302 303 304 305 306 307 308 309
	let args = '';
	if (option.args) {
		if (Array.isArray(option.args)) {
			args = ` <${option.args.join('> <')}>`;
		} else {
			args = ` <${option.args}>`;
		}
	}
	if (option.alias) {
310
		return `-${option.alias} --${optionId}${args}`;
M
Martin Aeschlimann 已提交
311
	}
312
	return `--${optionId}${args}`;
J
Joao Moreno 已提交
313 314
}

M
Martin Aeschlimann 已提交
315
// exported only for testing
316 317 318 319 320 321 322 323 324 325
export function formatOptions(options: OptionDescriptions<any>, columns: number): string[] {
	let maxLength = 0;
	let usageTexts: [string, string][] = [];
	for (const optionId in options) {
		const o = options[optionId];
		const usageText = formatUsage(optionId, o);
		maxLength = Math.max(maxLength, usageText.length);
		usageTexts.push([usageText, o.description!]);
	}
	let argLength = maxLength + 2/*left padding*/ + 1/*right padding*/;
D
Daniel Imms 已提交
326 327
	if (columns - argLength < 25) {
		// Use a condensed version on narrow terminals
328
		return usageTexts.reduce<string[]>((r, ut) => r.concat([`  ${ut[0]}`, `      ${ut[1]}`]), []);
D
Daniel Imms 已提交
329 330
	}
	let descriptionColumns = columns - argLength - 1;
M
Martin Aeschlimann 已提交
331
	let result: string[] = [];
332 333 334
	for (const ut of usageTexts) {
		let usage = ut[0];
		let wrappedDescription = wrapText(ut[1], descriptionColumns);
M
Martin Aeschlimann 已提交
335 336
		let keyPadding = indent(argLength - usage.length - 2/*left padding*/);
		result.push('  ' + usage + keyPadding + wrappedDescription[0]);
337
		for (let i = 1; i < wrappedDescription.length; i++) {
M
Martin Aeschlimann 已提交
338
			result.push(indent(argLength) + wrappedDescription[i]);
D
Daniel Imms 已提交
339
		}
340
	}
D
Daniel Imms 已提交
341
	return result;
J
Joao Moreno 已提交
342 343
}

M
Martin Aeschlimann 已提交
344 345 346 347
function indent(count: number): string {
	return (<any>' ').repeat(count);
}

J
Johannes Rieken 已提交
348
function wrapText(text: string, columns: number): string[] {
M
Martin Aeschlimann 已提交
349
	let lines: string[] = [];
D
Daniel Imms 已提交
350 351
	while (text.length) {
		let index = text.length < columns ? text.length : text.lastIndexOf(' ', columns);
D
Daniel Imms 已提交
352
		let line = text.slice(0, index).trim();
D
Daniel Imms 已提交
353 354 355 356 357 358
		text = text.slice(index);
		lines.push(line);
	}
	return lines;
}

359
export function buildHelpMessage(productName: string, executableName: string, version: string, options: OptionDescriptions<any>, isPipeSupported = true): string {
M
Martin Aeschlimann 已提交
360
	const columns = (process.stdout).isTTY && (process.stdout).columns || 80;
J
Joao Moreno 已提交
361

M
Martin Aeschlimann 已提交
362 363 364 365
	let help = [`${productName} ${version}`];
	help.push('');
	help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`);
	help.push('');
366
	if (isPipeSupported) {
367
		if (isWindows) {
368 369 370 371 372
			help.push(localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", executableName));
		} else {
			help.push(localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", executableName));
		}
		help.push('');
M
Martin Aeschlimann 已提交
373
	}
M
Martin Aeschlimann 已提交
374
	const optionsByCategory: { [P in keyof typeof helpCategories]?: OptionDescriptions<any> } = {};
375 376 377
	for (const optionId in options) {
		const o = options[optionId];
		if (o.description && o.cat) {
M
Martin Aeschlimann 已提交
378 379 380 381 382
			let optionsByCat = optionsByCategory[o.cat];
			if (!optionsByCat) {
				optionsByCategory[o.cat] = optionsByCat = {};
			}
			optionsByCat[optionId] = o;
383 384 385 386
		}
	}

	for (let helpCategoryKey in optionsByCategory) {
387 388
		const key = <keyof typeof helpCategories>helpCategoryKey;

389
		let categoryOptions = optionsByCategory[key];
M
Martin Aeschlimann 已提交
390
		if (categoryOptions) {
391
			help.push(helpCategories[key]);
M
Martin Aeschlimann 已提交
392 393 394 395 396 397
			help.push(...formatOptions(categoryOptions, columns));
			help.push('');
		}
	}
	return help.join('\n');
}
B
Benjamin Pasero 已提交
398

M
Martin Aeschlimann 已提交
399 400
export function buildVersionMessage(version: string | undefined, commit: string | undefined): string {
	return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`;
401
}
M
Martin Aeschlimann 已提交
402