argv.ts 13.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.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6
import * as minimist from 'vscode-minimist';
M
Martin Aeschlimann 已提交
7
import * as os from 'os';
J
Joao Moreno 已提交
8
import { localize } from 'vs/nls';
M
Martin Aeschlimann 已提交
9
import { ParsedArgs } from 'vs/platform/environment/common/environment';
B
Benjamin Pasero 已提交
10
import { join } from 'vs/base/common/path';
L
Logan Ramos 已提交
11
import { writeFileSync } from 'vs/base/node/pfs';
J
Joao Moreno 已提交
12

J
Joao Moreno 已提交
13
/**
M
Martin Aeschlimann 已提交
14
 * This code is also used by standalone cli's. Avoid adding any other dependencies.
J
Joao Moreno 已提交
15
 */
J
Joao Moreno 已提交
16

17 18 19 20 21
const helpCategories = {
	o: localize('optionsUpperCase', "Options"),
	e: localize('extensionsManagement', "Extensions Management"),
	t: localize('troubleshooting', "Troubleshooting")
};
J
Joao Moreno 已提交
22

23 24
export interface Option<OptionType> {
	type: OptionType;
M
Martin Aeschlimann 已提交
25
	alias?: string;
26
	deprecates?: string; // old deprecated id
M
Martin Aeschlimann 已提交
27 28
	args?: string | string[];
	description?: string;
29
	cat?: keyof typeof helpCategories;
M
Martin Aeschlimann 已提交
30
}
31 32 33 34 35 36 37 38 39 40 41 42

export type OptionDescriptions<T> = {
	[P in keyof T]: Option<TypeName<T[P]>>;
};

type TypeName<T> =
	T extends boolean ? 'boolean' :
	T extends string ? 'string' :
	T extends string[] ? 'string[]' :
	T extends undefined ? 'undefined' :
	'unknown';

43
//_urls
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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
export const OPTIONS: OptionDescriptions<ParsedArgs> = {
	'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.") },
	'wait': { type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") },
	'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.") },
	'version': { type: 'boolean', cat: 'o', alias: 'v', description: localize('version', "Print version.") },
	'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
	'telemetry': { type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") },
	'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)") },

	'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
	'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.") },
	'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts.") },
	'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.") },

	'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") },
	'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.") },

	'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).") },

	'remote': { type: 'string' },
	'locate-extension': { type: 'string[]' },
	'extensionDevelopmentPath': { type: 'string[]' },
	'extensionTestsPath': { type: 'string' },
	'extension-development-confirm-save': { type: 'boolean' },
	'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' },
	'skip-getting-started': { type: 'boolean' },
	'skip-release-notes': { type: 'boolean' },
	'sticky-quickopen': { type: 'boolean' },
	'disable-restore-windows': { type: 'boolean' },
	'disable-telemetry': { type: 'boolean' },
	'disable-updates': { type: 'boolean' },
	'disable-crash-reporter': { type: 'boolean' },
	'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' },
	'trace-category-filter': { type: 'string' },
	'trace-options': { type: 'string' },
	'disable-inspect': { type: 'boolean' },

	'js-flags': { type: 'string' }, // chrome js flags
	'nolazy': { type: 'boolean' }, // node inspect

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

115 116 117 118 119 120 121 122 123 124
export interface ErrorReporter {
	onUnknownOption(id: string): void;
	onMultipleValues(id: string, usedValue: string): void;
}

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

125
export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, errorReporter: ErrorReporter = ignoringReporter): T {
M
Martin Aeschlimann 已提交
126 127 128
	const alias: { [key: string]: string } = {};
	const string: string[] = [];
	const boolean: string[] = [];
129 130 131 132
	for (let optionId in options) {
		const o = options[optionId];
		if (o.alias) {
			alias[optionId] = o.alias;
M
Martin Aeschlimann 已提交
133
		}
134

135
		if (o.type === 'string' || o.type === 'string[]') {
136
			string.push(optionId);
137 138 139
			if (o.deprecates) {
				string.push(o.deprecates);
			}
M
Martin Aeschlimann 已提交
140
		} else if (o.type === 'boolean') {
141
			boolean.push(optionId);
142 143 144
			if (o.deprecates) {
				boolean.push(o.deprecates);
			}
M
Martin Aeschlimann 已提交
145
		}
J
Joao Moreno 已提交
146
	}
M
Martin Aeschlimann 已提交
147
	// remote aliases to avoid confusion
148
	const parsedArgs = minimist(args, { string, boolean, alias });
M
Martin Aeschlimann 已提交
149 150 151

	const cleanedArgs: any = {};

152 153 154 155 156 157
	// https://github.com/microsoft/vscode/issues/58177
	cleanedArgs._ = parsedArgs._.filter(arg => arg.length > 0);
	delete parsedArgs._;

	for (let optionId in options) {
		const o = options[optionId];
M
Martin Aeschlimann 已提交
158 159 160
		if (o.alias) {
			delete parsedArgs[o.alias];
		}
M
Martin Aeschlimann 已提交
161

162
		let val = parsedArgs[optionId];
M
Martin Aeschlimann 已提交
163 164 165 166
		if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates)) {
			if (!val) {
				val = parsedArgs[o.deprecates];
			}
167 168
			delete parsedArgs[o.deprecates];
		}
M
Martin Aeschlimann 已提交
169 170

		if (val) {
171 172 173 174 175 176 177 178
			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 已提交
179
				}
180
			}
181
			cleanedArgs[optionId] = val;
182
		}
183
		delete parsedArgs[optionId];
M
Martin Aeschlimann 已提交
184
	}
J
Joao Moreno 已提交
185

M
Martin Aeschlimann 已提交
186
	for (let key in parsedArgs) {
187
		errorReporter.onUnknownOption(key);
M
Martin Aeschlimann 已提交
188
	}
189

M
Martin Aeschlimann 已提交
190
	return cleanedArgs;
J
Joao Moreno 已提交
191 192
}

193
function formatUsage(optionId: string, option: Option<any>) {
M
Martin Aeschlimann 已提交
194 195 196 197 198 199 200 201 202
	let args = '';
	if (option.args) {
		if (Array.isArray(option.args)) {
			args = ` <${option.args.join('> <')}>`;
		} else {
			args = ` <${option.args}>`;
		}
	}
	if (option.alias) {
203
		return `-${option.alias} --${optionId}${args}`;
M
Martin Aeschlimann 已提交
204
	}
205
	return `--${optionId}${args}`;
J
Joao Moreno 已提交
206 207
}

M
Martin Aeschlimann 已提交
208
// exported only for testing
209 210 211 212 213 214 215 216 217 218
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 已提交
219 220
	if (columns - argLength < 25) {
		// Use a condensed version on narrow terminals
221
		return usageTexts.reduce<string[]>((r, ut) => r.concat([`  ${ut[0]}`, `      ${ut[1]}`]), []);
D
Daniel Imms 已提交
222 223
	}
	let descriptionColumns = columns - argLength - 1;
M
Martin Aeschlimann 已提交
224
	let result: string[] = [];
225 226 227
	for (const ut of usageTexts) {
		let usage = ut[0];
		let wrappedDescription = wrapText(ut[1], descriptionColumns);
M
Martin Aeschlimann 已提交
228 229
		let keyPadding = indent(argLength - usage.length - 2/*left padding*/);
		result.push('  ' + usage + keyPadding + wrappedDescription[0]);
230
		for (let i = 1; i < wrappedDescription.length; i++) {
M
Martin Aeschlimann 已提交
231
			result.push(indent(argLength) + wrappedDescription[i]);
D
Daniel Imms 已提交
232
		}
233
	}
D
Daniel Imms 已提交
234
	return result;
J
Joao Moreno 已提交
235 236
}

M
Martin Aeschlimann 已提交
237 238 239 240
function indent(count: number): string {
	return (<any>' ').repeat(count);
}

J
Johannes Rieken 已提交
241
function wrapText(text: string, columns: number): string[] {
M
Martin Aeschlimann 已提交
242
	let lines: string[] = [];
D
Daniel Imms 已提交
243 244
	while (text.length) {
		let index = text.length < columns ? text.length : text.lastIndexOf(' ', columns);
D
Daniel Imms 已提交
245
		let line = text.slice(0, index).trim();
D
Daniel Imms 已提交
246 247 248 249 250 251
		text = text.slice(index);
		lines.push(line);
	}
	return lines;
}

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

M
Martin Aeschlimann 已提交
255 256 257 258
	let help = [`${productName} ${version}`];
	help.push('');
	help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`);
	help.push('');
259 260 261 262 263 264 265
	if (isPipeSupported) {
		if (os.platform() === 'win32') {
			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 已提交
266
	}
267 268 269 270 271 272 273 274 275
	const optionsByCategory: { [P in keyof typeof helpCategories]: OptionDescriptions<any> } = { e: {}, o: {}, t: {} };
	for (const optionId in options) {
		const o = options[optionId];
		if (o.description && o.cat) {
			optionsByCategory[o.cat][optionId] = o;
		}
	}

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

278
		let categoryOptions = optionsByCategory[key];
M
Martin Aeschlimann 已提交
279
		if (categoryOptions.length) {
280
			help.push(helpCategories[key]);
M
Martin Aeschlimann 已提交
281 282 283 284 285 286
			help.push(...formatOptions(categoryOptions, columns));
			help.push('');
		}
	}
	return help.join('\n');
}
B
Benjamin Pasero 已提交
287

M
Martin Aeschlimann 已提交
288 289
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}`;
290
}
M
Martin Aeschlimann 已提交
291

B
Benjamin Pasero 已提交
292 293 294 295 296 297 298 299 300 301 302 303 304

export function addArg(argv: string[], ...args: string[]): string[] {
	const endOfArgsMarkerIndex = argv.indexOf('--');
	if (endOfArgsMarkerIndex === -1) {
		argv.push(...args);
	} else {
		// if the we have an argument "--" (end of argument marker)
		// we cannot add arguments at the end. rather, we add
		// arguments before the "--" marker.
		argv.splice(endOfArgsMarkerIndex, 0, ...args);
	}

	return argv;
M
Martin Aeschlimann 已提交
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
}

export function createWaitMarkerFile(verbose?: boolean): string | undefined {
	const randomWaitMarkerPath = join(os.tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10));

	try {
		writeFileSync(randomWaitMarkerPath, '');
		if (verbose) {
			console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`);
		}
		return randomWaitMarkerPath;
	} catch (err) {
		if (verbose) {
			console.error(`Failed to create marker file for --wait: ${err}`);
		}
		return undefined;
	}
322
}