argv.ts 14.0 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as minimist from '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 12
import { statSync, readFileSync } from 'fs';
import { writeFileSync, readdirSync } from 'vs/base/node/pfs';
J
Joao Moreno 已提交
13

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

M
Martin Aeschlimann 已提交
18 19 20 21
class HelpCategories {
	o = localize('optionsUpperCase', "Options");
	e = localize('extensionsManagement', "Extensions Management");
	t = localize('troubleshooting', "Troubleshooting");
J
Joao Moreno 已提交
22
}
J
Joao Moreno 已提交
23

M
Martin Aeschlimann 已提交
24 25
export interface Option {
	id: string;
P
Peng Lyu 已提交
26
	type: 'boolean' | 'string';
M
Martin Aeschlimann 已提交
27
	alias?: string;
28
	deprecates?: string; // old deprecated id
M
Martin Aeschlimann 已提交
29 30 31 32
	args?: string | string[];
	description?: string;
	cat?: keyof HelpCategories;
}
J
Joao Moreno 已提交
33

M
Martin Aeschlimann 已提交
34 35 36 37 38 39 40 41 42 43 44
export const options: Option[] = [
	{ id: 'diff', type: 'boolean', cat: 'o', alias: 'd', args: ['file', 'file'], description: localize('diff', "Compare two files with each other.") },
	{ id: 'add', type: 'boolean', cat: 'o', alias: 'a', args: 'folder', description: localize('add', "Add folder(s) to the last active window.") },
	{ id: '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.") },
	{ id: 'new-window', type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") },
	{ id: 'reuse-window', type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") },
	{ id: 'wait', type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") },
	{ id: 'locale', type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
	{ id: '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.") },
	{ id: 'version', type: 'boolean', cat: 'o', alias: 'v', description: localize('version', "Print version.") },
	{ id: 'help', type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
L
Logan Ramos 已提交
45
	{ id: 'telemetry', type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") },
M
Martin Aeschlimann 已提交
46 47 48
	{ id: 'folder-uri', type: 'string', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") },
	{ id: 'file-uri', type: 'string', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") },

49
	{ id: 'extensions-dir', type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
M
Martin Aeschlimann 已提交
50 51
	{ id: 'list-extensions', type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
	{ id: 'show-versions', type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
S
Sandeep Somavarapu 已提交
52
	{ id: '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.") },
M
Martin Aeschlimann 已提交
53 54 55 56 57 58 59
	{ id: 'uninstall-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
	{ id: '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.") },

	{ id: 'verbose', type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") },
	{ id: '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'.") },
	{ id: 'status', type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") },
	{ id: 'prof-startup', type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") },
60
	{ id: 'disable-extensions', type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") },
M
Martin Aeschlimann 已提交
61 62
	{ id: 'disable-extension', type: 'string', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") },

63 64
	{ id: '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.") },
	{ id: '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.") },
M
Martin Aeschlimann 已提交
65
	{ id: 'disable-gpu', type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") },
P
Peng Lyu 已提交
66
	{ id: 'max-memory', type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") },
M
Martin Aeschlimann 已提交
67 68

	{ id: 'remote', type: 'string' },
69
	{ id: 'locate-extension', type: 'string' },
M
Martin Aeschlimann 已提交
70 71 72
	{ id: 'extensionDevelopmentPath', type: 'string' },
	{ id: 'extensionTestsPath', type: 'string' },
	{ id: 'debugId', type: 'string' },
73
	{ id: 'inspect-search', type: 'string', deprecates: 'debugSearch' },
74
	{ id: 'inspect-brk-search', type: 'string', deprecates: 'debugBrkSearch' },
M
Martin Aeschlimann 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
	{ id: 'export-default-configuration', type: 'string' },
	{ id: 'install-source', type: 'string' },
	{ id: 'driver', type: 'string' },
	{ id: 'logExtensionHostCommunication', type: 'boolean' },
	{ id: 'skip-getting-started', type: 'boolean' },
	{ id: 'skip-release-notes', type: 'boolean' },
	{ id: 'sticky-quickopen', type: 'boolean' },
	{ id: 'disable-restore-windows', type: 'boolean' },
	{ id: 'disable-telemetry', type: 'boolean' },
	{ id: 'disable-updates', type: 'boolean' },
	{ id: 'disable-crash-reporter', type: 'boolean' },
	{ id: 'skip-add-to-recently-opened', type: 'boolean' },
	{ id: 'unity-launch', type: 'boolean' },
	{ id: 'open-url', type: 'boolean' },
	{ id: 'nolazy', type: 'boolean' },
	{ id: 'issue', type: 'boolean' },
	{ id: 'file-write', type: 'boolean' },
	{ id: 'file-chmod', type: 'boolean' },
	{ id: 'driver-verbose', type: 'boolean' },
	{ id: 'force', type: 'boolean' },
	{ id: 'trace-category-filter', type: 'string' },
	{ id: 'trace-options', type: 'string' },
97
	{ id: 'prof-code-loading', type: 'boolean' },
98
	{ id: '_', type: 'string' }
M
Martin Aeschlimann 已提交
99 100 101 102 103 104 105
];

export function parseArgs(args: string[], isOptionSupported = (_: Option) => true): ParsedArgs {
	const alias: { [key: string]: string } = {};
	const string: string[] = [];
	const boolean: string[] = [];
	for (let o of options) {
106 107 108 109
		if (isOptionSupported(o)) {
			if (o.alias) {
				alias[o.id] = o.alias;
			}
M
Martin Aeschlimann 已提交
110
		}
111

M
Martin Aeschlimann 已提交
112 113
		if (o.type === 'string') {
			string.push(o.id);
114 115 116
			if (o.deprecates) {
				string.push(o.deprecates);
			}
M
Martin Aeschlimann 已提交
117 118
		} else if (o.type === 'boolean') {
			boolean.push(o.id);
119 120 121
			if (o.deprecates) {
				boolean.push(o.deprecates);
			}
M
Martin Aeschlimann 已提交
122
		}
J
Joao Moreno 已提交
123
	}
M
Martin Aeschlimann 已提交
124 125 126 127 128 129
	// remote aliases to avoid confusion
	const parsedArgs = minimist(args, { string, boolean, alias }) as ParsedArgs;
	for (let o of options) {
		if (o.alias) {
			delete parsedArgs[o.alias];
		}
130
		if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates) && !parsedArgs[o.id]) {
131 132 133
			parsedArgs[o.id] = parsedArgs[o.deprecates];
			delete parsedArgs[o.deprecates];
		}
M
Martin Aeschlimann 已提交
134 135
	}
	return parsedArgs;
J
Joao Moreno 已提交
136 137
}

M
Martin Aeschlimann 已提交
138 139 140 141 142 143 144 145 146 147 148 149 150
function formatUsage(option: Option) {
	let args = '';
	if (option.args) {
		if (Array.isArray(option.args)) {
			args = ` <${option.args.join('> <')}>`;
		} else {
			args = ` <${option.args}>`;
		}
	}
	if (option.alias) {
		return `-${option.alias} --${option.id}${args}`;
	}
	return `--${option.id}${args}`;
J
Joao Moreno 已提交
151 152
}

M
Martin Aeschlimann 已提交
153 154 155 156
// exported only for testing
export function formatOptions(docOptions: Option[], columns: number): string[] {
	let usageTexts = docOptions.map(formatUsage);
	let argLength = Math.max.apply(null, usageTexts.map(k => k.length)) + 2/*left padding*/ + 1/*right padding*/;
D
Daniel Imms 已提交
157 158
	if (columns - argLength < 25) {
		// Use a condensed version on narrow terminals
M
Martin Aeschlimann 已提交
159
		return docOptions.reduce<string[]>((r, o, i) => r.concat([`  ${usageTexts[i]}`, `      ${o.description}`]), []);
D
Daniel Imms 已提交
160 161
	}
	let descriptionColumns = columns - argLength - 1;
M
Martin Aeschlimann 已提交
162
	let result: string[] = [];
M
Martin Aeschlimann 已提交
163 164 165 166 167
	docOptions.forEach((o, i) => {
		let usage = usageTexts[i];
		let wrappedDescription = wrapText(o.description!, descriptionColumns);
		let keyPadding = indent(argLength - usage.length - 2/*left padding*/);
		result.push('  ' + usage + keyPadding + wrappedDescription[0]);
168
		for (let i = 1; i < wrappedDescription.length; i++) {
M
Martin Aeschlimann 已提交
169
			result.push(indent(argLength) + wrappedDescription[i]);
D
Daniel Imms 已提交
170 171 172
		}
	});
	return result;
J
Joao Moreno 已提交
173 174
}

M
Martin Aeschlimann 已提交
175 176 177 178
function indent(count: number): string {
	return (<any>' ').repeat(count);
}

J
Johannes Rieken 已提交
179
function wrapText(text: string, columns: number): string[] {
M
Martin Aeschlimann 已提交
180
	let lines: string[] = [];
D
Daniel Imms 已提交
181 182
	while (text.length) {
		let index = text.length < columns ? text.length : text.lastIndexOf(' ', columns);
D
Daniel Imms 已提交
183
		let line = text.slice(0, index).trim();
D
Daniel Imms 已提交
184 185 186 187 188 189
		text = text.slice(index);
		lines.push(line);
	}
	return lines;
}

M
Martin Aeschlimann 已提交
190
export function buildHelpMessage(productName: string, executableName: string, version: string, isOptionSupported = (_: Option) => true, isPipeSupported = true): string {
M
Martin Aeschlimann 已提交
191
	const columns = (process.stdout).isTTY && (process.stdout).columns || 80;
J
Joao Moreno 已提交
192

M
Martin Aeschlimann 已提交
193
	let categories = new HelpCategories();
194

M
Martin Aeschlimann 已提交
195 196 197 198
	let help = [`${productName} ${version}`];
	help.push('');
	help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`);
	help.push('');
199 200 201 202 203 204 205
	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 已提交
206 207 208 209 210 211 212 213 214 215 216
	}
	for (let key in categories) {
		let categoryOptions = options.filter(o => !!o.description && o.cat === key && isOptionSupported(o));
		if (categoryOptions.length) {
			help.push(categories[key]);
			help.push(...formatOptions(categoryOptions, columns));
			help.push('');
		}
	}
	return help.join('\n');
}
B
Benjamin Pasero 已提交
217

M
Martin Aeschlimann 已提交
218 219
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}`;
220
}
M
Martin Aeschlimann 已提交
221

L
Logan Ramos 已提交
222
export function buildTelemetryMessage(appRoot: string, extensionsPath: string): string {
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
	// Gets all the directories inside the extension directory
	const dirs = readdirSync(extensionsPath).filter(files => {
		// This handles case where broken symbolic links can cause statSync to throw and error
		try {
			return statSync(join(extensionsPath, files)).isDirectory();
		} catch {
			return false;
		}
	});
	const telemetryJsonFolders: string[] = [];
	dirs.forEach((dir) => {
		const files = readdirSync(join(extensionsPath, dir)).filter(file => file === 'telemetry.json');
		// We know it contains a telemetry.json file so we add it to the list of folders which have one
		if (files.length === 1) {
			telemetryJsonFolders.push(dir);
		}
	});
	const mergedTelemetry = Object.create(null);
	// Simple function to merge the telemetry into one json object
	const mergeTelemetry = (contents: string, dirName: string) => {
		const telemetryData = JSON.parse(contents);
		mergedTelemetry[dirName] = telemetryData;
	};
	telemetryJsonFolders.forEach((folder) => {
		const contents = readFileSync(join(extensionsPath, folder, 'telemetry.json')).toString();
		mergeTelemetry(contents, folder);
	});
	let contents = readFileSync(join(appRoot, 'telemetry-core.json')).toString();
	mergeTelemetry(contents, 'vscode-core');
	contents = readFileSync(join(appRoot, 'telemetry-extensions.json')).toString();
	mergeTelemetry(contents, 'vscode-extensions');
	return JSON.stringify(mergedTelemetry, null, 4);
L
Logan Ramos 已提交
255 256
}

M
Martin Aeschlimann 已提交
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
/**
 * Converts an argument into an array
 * @param arg a argument value. Can be undefined, an entry or an array
 */
export function asArray(arg: string | string[] | undefined): string[] {
	if (arg) {
		if (Array.isArray(arg)) {
			return arg;
		}
		return [arg];
	}
	return [];
}

/**
 * Returns whether an argument is present.
 */
export function hasArgs(arg: string | string[] | undefined): boolean {
	if (arg) {
		if (Array.isArray(arg)) {
			return !!arg.length;
		}
		return true;
	}
	return false;
282
}
B
Benjamin Pasero 已提交
283 284 285 286 287 288 289 290 291 292 293 294 295

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 已提交
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
}

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;
	}
313
}