/*--------------------------------------------------------------------------------------------- * 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'; import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; /** * This code is also used by standalone cli's. Avoid adding any other dependencies. */ class HelpCategories { o = localize('optionsUpperCase', "Options"); e = localize('extensionsManagement', "Extensions Management"); t = localize('troubleshooting', "Troubleshooting"); } export interface Option { id: string; type: 'boolean' | 'string'; alias?: string; deprecates?: string; // old deprecated id args?: string | string[]; description?: string; cat?: keyof HelpCategories; } 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.") }, { 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)") }, { id: 'extensions-dir', type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, { 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.") }, { id: 'install-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts.") }, { 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-modules', type: 'boolean', alias: 'p', cat: 't', description: localize('prof-modules', "Capture performance markers while loading JS modules and print them with 'F1 > Developer: Startup Performance") }, { id: 'prof-startup', type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") }, { id: 'disable-extensions', type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, { id: 'disable-extension', type: 'string', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, { 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.") }, { id: 'disable-gpu', type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, { id: 'upload-logs', type: 'string', cat: 't', description: localize('uploadLogs', "Uploads logs from current session to a secure endpoint.") }, { id: 'max-memory', type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") }, { id: 'remote', type: 'string' }, { id: 'extensionDevelopmentPath', type: 'string' }, { id: 'extensionTestsPath', type: 'string' }, { id: 'debugId', type: 'string' }, { id: 'inspect-search', type: 'string', deprecates: 'debugSearch' }, { id: 'inspect-brk-search', type: 'string', deprecates: 'debugBrkSearch' }, { 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' }, { id: 'prof-code-loading', type: 'boolean' }, { id: 'nodeless', type: 'boolean' } // TODO@ben revisit electron5 nodeless support ]; 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) { if (isOptionSupported(o)) { if (o.alias) { alias[o.id] = o.alias; } } if (o.type === 'string') { string.push(o.id); if (o.deprecates) { string.push(o.deprecates); } } else if (o.type === 'boolean') { boolean.push(o.id); if (o.deprecates) { boolean.push(o.deprecates); } } } // 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]; } if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates) && !parsedArgs[o.id]) { parsedArgs[o.id] = parsedArgs[o.deprecates]; delete parsedArgs[o.deprecates]; } } return parsedArgs; } 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}`; } // 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*/; if (columns - argLength < 25) { // Use a condensed version on narrow terminals return docOptions.reduce((r, o, i) => r.concat([` ${usageTexts[i]}`, ` ${o.description}`]), []); } let descriptionColumns = columns - argLength - 1; let result: string[] = []; 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]); for (let i = 1; i < wrappedDescription.length; i++) { result.push(indent(argLength) + wrappedDescription[i]); } }); return result; } function indent(count: number): string { return (' ').repeat(count); } function wrapText(text: string, columns: number): string[] { let lines: string[] = []; while (text.length) { let index = text.length < columns ? text.length : text.lastIndexOf(' ', columns); let line = text.slice(0, index).trim(); text = text.slice(index); lines.push(line); } return lines; } export function buildHelpMessage(productName: string, executableName: string, version: string, isOptionSupported = (_: Option) => true): string { const columns = (process.stdout).isTTY && (process.stdout).columns || 80; let categories = new HelpCategories(); let help = [`${productName} ${version}`]; help.push(''); help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`); help.push(''); 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(''); 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'); } 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}`; } /** * 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; } 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; }