diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 157300bb64b..6c167b29b50 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -11,7 +11,7 @@ import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, R import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import product from 'vs/platform/product/node/product'; import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -574,7 +574,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json'); // Config (combination of process.argv and window configuration) - const environment = parseArgs(process.argv); + const environment = parseArgs(process.argv, OPTIONS); const config = objects.assign(environment, windowConfiguration); for (const key in config) { const configValue = (config as any)[key]; diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index afa7acafa94..b847c638090 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -5,7 +5,7 @@ import { spawn, ChildProcess, SpawnOptions } from 'child_process'; import { assign } from 'vs/base/common/objects'; -import { buildHelpMessage, buildVersionMessage, addArg, createWaitMarkerFile } from 'vs/platform/environment/node/argv'; +import { buildHelpMessage, buildVersionMessage, addArg, createWaitMarkerFile, OPTIONS } from 'vs/platform/environment/node/argv'; import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/node/product'; @@ -47,7 +47,7 @@ export async function main(argv: string[]): Promise { // Help if (args.help) { const executable = `${product.applicationName}${os.platform() === 'win32' ? '.exe' : ''}`; - console.log(buildHelpMessage(product.nameLong, executable, pkg.version)); + console.log(buildHelpMessage(product.nameLong, executable, pkg.version, OPTIONS)); } // Version Info diff --git a/src/vs/code/test/node/argv.test.ts b/src/vs/code/test/node/argv.test.ts index 6ac49bc9988..59911ebc41e 100644 --- a/src/vs/code/test/node/argv.test.ts +++ b/src/vs/code/test/node/argv.test.ts @@ -4,29 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { formatOptions, Option, addArg } from 'vs/platform/environment/node/argv'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; suite('formatOptions', () => { - function o(id: keyof ParsedArgs, description: string): Option { + function o(description: string): Option { return { - id, description, type: 'string' + description, type: 'string' }; } test('Text should display small columns correctly', () => { assert.deepEqual( - formatOptions([ - o('add', 'bar') - ], 80), + formatOptions({ + 'add': o('bar') + }, 80), [' --add bar'] ); assert.deepEqual( - formatOptions([ - o('add', 'bar'), - o('wait', 'ba'), - o('trace', 'b') - ], 80), + formatOptions({ + 'add': o('bar'), + 'wait': o('ba'), + 'trace': o('b') + }, 80), [ ' --add bar', ' --wait ba', @@ -36,9 +35,9 @@ suite('formatOptions', () => { test('Text should wrap', () => { assert.deepEqual( - formatOptions([ - o('add', ('bar ').repeat(9)) - ], 40), + formatOptions({ + 'add': o(('bar ').repeat(9)) + }, 40), [ ' --add bar bar bar bar bar bar bar bar', ' bar' @@ -47,9 +46,9 @@ suite('formatOptions', () => { test('Text should revert to the condensed view when the terminal is too narrow', () => { assert.deepEqual( - formatOptions([ - o('add', ('bar ').repeat(9)) - ], 30), + formatOptions({ + 'add': o(('bar ').repeat(9)) + }, 30), [ ' --add', ' bar bar bar bar bar bar bar bar bar ' diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 384fbaf9a7d..16891cc90cd 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -11,7 +11,7 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI as Uri, URI } from 'vs/base/common/uri'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupWorkspacesFormat, ISerializedWorkspace, IWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; @@ -32,7 +32,7 @@ suite('BackupMainService', () => { const backupHome = path.join(parentDir, 'Backups'); const backupWorkspacesPath = path.join(backupHome, 'workspaces.json'); - const environmentService = new EnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new EnvironmentService(parseArgs(process.argv, OPTIONS), process.execPath); class TestBackupMainService extends BackupMainService { diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 413381155d4..f60b09ad56f 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -25,7 +25,7 @@ export interface ParsedArgs { 'reuse-window'?: boolean; locale?: string; 'user-data-dir'?: string; - 'prof-startup'?: string; + 'prof-startup'?: boolean; 'prof-startup-prefix'?: string; 'prof-append-timers'?: string; verbose?: boolean; @@ -61,8 +61,8 @@ export interface ParsedArgs { 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; 'install-source'?: string; - 'disable-updates'?: string; - 'disable-crash-reporter'?: string; + 'disable-updates'?: boolean; + 'disable-crash-reporter'?: boolean; 'skip-add-to-recently-opened'?: boolean; 'max-memory'?: string; 'file-write'?: boolean; @@ -76,7 +76,7 @@ export interface ParsedArgs { 'force'?: boolean; 'gitCredential'?: string; // node flags - 'js-flags'?: boolean; + 'js-flags'?: string; 'disable-gpu'?: boolean; 'nolazy'?: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index c3cbae52fea..e639efb6f59 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -20,84 +20,96 @@ const helpCategories = { t: localize('troubleshooting', "Troubleshooting") }; -export interface Option { - id: keyof ParsedArgs; - type: 'boolean' | 'string' | 'string[]'; +export interface Option { + type: OptionType; alias?: string; deprecates?: string; // old deprecated id args?: string | string[]; description?: string; cat?: keyof typeof helpCategories; } -//_urls -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: 'telemetry', type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, - { 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: 'category', type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") }, - { 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.") }, - { 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.") }, +export type OptionDescriptions = { + [P in keyof T]: Option>; +}; - { 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") }, - { 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.") }, +type OptionTypeName = + T extends boolean ? 'boolean' : + T extends string ? 'string' : + T extends string[] ? 'string[]' : + T extends undefined ? 'undefined' : + 'unknown'; - { 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: 'max-memory', type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") }, +export const OPTIONS: OptionDescriptions = { + '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)") }, - { id: 'remote', type: 'string' }, - { id: 'locate-extension', type: 'string[]' }, - { id: 'extensionDevelopmentPath', type: 'string[]' }, - { id: 'extensionTestsPath', type: 'string' }, - { id: 'extension-development-confirm-save', type: 'boolean' }, - { 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: '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: 'disable-inspect', type: 'boolean' }, + '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.") }, - { id: 'js-flags', type: 'string' }, // chrome js flags - { id: 'nolazy', type: 'boolean' }, // node inspect -]; + '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 +}; export interface ErrorReporter { onUnknownOption(id: string): void; @@ -109,24 +121,23 @@ const ignoringReporter: ErrorReporter = { onMultipleValues: () => { } }; -export function parseArgs(args: string[], isOptionSupported = (_: Option) => true, errorReporter: ErrorReporter = ignoringReporter): ParsedArgs { +export function parseArgs(args: string[], options: OptionDescriptions, errorReporter: ErrorReporter = ignoringReporter): T { 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; - } + for (let optionId in options) { + const o = options[optionId]; + if (o.alias) { + alias[optionId] = o.alias; } if (o.type === 'string' || o.type === 'string[]') { - string.push(o.id); + string.push(optionId); if (o.deprecates) { string.push(o.deprecates); } } else if (o.type === 'boolean') { - boolean.push(o.id); + boolean.push(optionId); if (o.deprecates) { boolean.push(o.deprecates); } @@ -137,12 +148,17 @@ export function parseArgs(args: string[], isOptionSupported = (_: Option) => tru const cleanedArgs: any = {}; - for (const o of options) { + // 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]; if (o.alias) { delete parsedArgs[o.alias]; } - let val = parsedArgs[o.id]; + let val = parsedArgs[optionId]; if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates)) { if (!val) { val = parsedArgs[o.deprecates]; @@ -151,29 +167,21 @@ export function parseArgs(args: string[], isOptionSupported = (_: Option) => tru } if (val) { - if (isOptionSupported(o)) { - 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(o.id, val); - } + 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); } - cleanedArgs[o.id] = val; - } else { - errorReporter.onUnknownOption(o.id); } + cleanedArgs[optionId] = val; } - delete parsedArgs[o.id]; + delete parsedArgs[optionId]; } - // https://github.com/microsoft/vscode/issues/58177 - cleanedArgs._ = parsedArgs._.filter(arg => arg.length > 0); - delete parsedArgs._; - for (let key in parsedArgs) { errorReporter.onUnknownOption(key); } @@ -181,7 +189,7 @@ export function parseArgs(args: string[], isOptionSupported = (_: Option) => tru return cleanedArgs; } -function formatUsage(option: Option) { +function formatUsage(optionId: string, option: Option) { let args = ''; if (option.args) { if (Array.isArray(option.args)) { @@ -191,30 +199,37 @@ function formatUsage(option: Option) { } } if (option.alias) { - return `-${option.alias} --${option.id}${args}`; + return `-${option.alias} --${optionId}${args}`; } - return `--${option.id}${args}`; + return `--${optionId}${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*/; +export function formatOptions(options: OptionDescriptions, 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*/; if (columns - argLength < 25) { // Use a condensed version on narrow terminals - return docOptions.reduce((r, o, i) => r.concat([` ${usageTexts[i]}`, ` ${o.description}`]), []); + return usageTexts.reduce((r, ut) => r.concat([` ${ut[0]}`, ` ${ut[1]}`]), []); } let descriptionColumns = columns - argLength - 1; let result: string[] = []; - docOptions.forEach((o, i) => { - let usage = usageTexts[i]; - let wrappedDescription = wrapText(o.description!, descriptionColumns); + for (const ut of usageTexts) { + let usage = ut[0]; + let wrappedDescription = wrapText(ut[1], 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; } @@ -233,7 +248,7 @@ function wrapText(text: string, columns: number): string[] { return lines; } -export function buildHelpMessage(productName: string, executableName: string, version: string, isOptionSupported = (_: Option) => true, isPipeSupported = true): string { +export function buildHelpMessage(productName: string, executableName: string, version: string, options: OptionDescriptions, isPipeSupported = true): string { const columns = (process.stdout).isTTY && (process.stdout).columns || 80; let help = [`${productName} ${version}`]; @@ -248,11 +263,23 @@ export function buildHelpMessage(productName: string, executableName: string, ve } help.push(''); } - for (let helpCategoryKey in helpCategories) { + const optionsByCategory: { [P in keyof typeof helpCategories]?: OptionDescriptions } = {}; + for (const optionId in options) { + const o = options[optionId]; + if (o.description && o.cat) { + let optionsByCat = optionsByCategory[o.cat]; + if (!optionsByCat) { + optionsByCategory[o.cat] = optionsByCat = {}; + } + optionsByCat[optionId] = o; + } + } + + for (let helpCategoryKey in optionsByCategory) { const key = helpCategoryKey; - let categoryOptions = options.filter(o => !!o.description && o.cat === key && isOptionSupported(o)); - if (categoryOptions.length) { + let categoryOptions = optionsByCategory[key]; + if (categoryOptions) { help.push(helpCategories[key]); help.push(...formatOptions(categoryOptions, columns)); help.push(''); diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index 74191bb01df..f656fa743b8 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -8,7 +8,7 @@ import { firstIndex } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { ParsedArgs } from '../common/environment'; import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; -import { parseArgs, ErrorReporter } from 'vs/platform/environment/node/argv'; +import { parseArgs, ErrorReporter, OPTIONS } from 'vs/platform/environment/node/argv'; function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): ParsedArgs { const errorReporter: ErrorReporter = { @@ -20,7 +20,7 @@ function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): Parse } }; - const args = parseArgs(cmdLineArgs, undefined, reportWarnings ? errorReporter : undefined); + const args = parseArgs(cmdLineArgs, OPTIONS, reportWarnings ? errorReporter : undefined); if (args.goto) { args._.forEach(arg => assert(/^(\w:)?[^:]+(:\d*){0,2}$/.test(arg), localize('gotoValidation', "Arguments in `--goto` mode should be in the format of `FILE(:LINE(:CHARACTER))`."))); } diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index 39d83f414b3..d30dc8a688b 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -5,13 +5,13 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { parseExtensionHostPort, parseUserDataDir } from 'vs/platform/environment/node/environmentService'; suite('EnvironmentService', () => { test('parseExtensionHostPort when built', () => { - const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a), true); + const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a, OPTIONS), true); assert.deepEqual(parse([]), { port: null, break: false, debugId: undefined }); assert.deepEqual(parse(['--debugPluginHost']), { port: null, break: false, debugId: undefined }); @@ -28,7 +28,7 @@ suite('EnvironmentService', () => { }); test('parseExtensionHostPort when unbuilt', () => { - const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a), false); + const parse = (a: string[]) => parseExtensionHostPort(parseArgs(a, OPTIONS), false); assert.deepEqual(parse([]), { port: 5870, break: false, debugId: undefined }); assert.deepEqual(parse(['--debugPluginHost']), { port: 5870, break: false, debugId: undefined }); @@ -45,7 +45,7 @@ suite('EnvironmentService', () => { }); test('userDataPath', () => { - const parse = (a: string[], b: { cwd: () => string, env: { [key: string]: string } }) => parseUserDataDir(parseArgs(a), b); + const parse = (a: string[], b: { cwd: () => string, env: { [key: string]: string } }) => parseUserDataDir(parseArgs(a, OPTIONS), b); assert.equal(parse(['--user-data-dir', './dir'], { cwd: () => '/foo', env: {} }), path.resolve('/foo/dir'), 'should use cwd when --user-data-dir is specified'); @@ -55,11 +55,11 @@ suite('EnvironmentService', () => { // https://github.com/microsoft/vscode/issues/78440 test('careful with boolean file names', function () { - let actual = parseArgs(['-r', 'arg.txt']); + let actual = parseArgs(['-r', 'arg.txt'], OPTIONS); assert(actual['reuse-window']); assert.deepEqual(actual._, ['arg.txt']); - actual = parseArgs(['-r', 'true.txt']); + actual = parseArgs(['-r', 'true.txt'], OPTIONS); assert(actual['reuse-window']); assert.deepEqual(actual._, ['true.txt']); }); diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index f579807f76c..aa652515737 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as os from 'os'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { join } from 'vs/base/common/path'; import { mkdirp, RimRafMode, rimraf } from 'vs/base/node/pfs'; @@ -51,7 +51,7 @@ suite('Extension Gallery Service', () => { test('marketplace machine id', () => { const args = ['--user-data-dir', marketplaceHome]; - const environmentService = new EnvironmentService(parseArgs(args), process.execPath); + const environmentService = new EnvironmentService(parseArgs(args, OPTIONS), process.execPath); return resolveMarketplaceHeaders(pkg.version, environmentService, fileService).then(headers => { assert.ok(isUUID(headers['X-Market-User-Id'])); @@ -61,4 +61,4 @@ suite('Extension Gallery Service', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index 5bea35cbefc..3db9cd642a6 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import * as objects from 'vs/base/common/objects'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import { BrowserWindow, ipcMain, screen, Event, dialog } from 'electron'; import { ILaunchService } from 'vs/platform/launch/electron-main/launchService'; @@ -372,7 +372,7 @@ export class IssueService implements IIssueService { } function toLauchUrl(pathToHtml: string, windowConfiguration: T): string { - const environment = parseArgs(process.argv); + const environment = parseArgs(process.argv, OPTIONS); const config = objects.assign(environment, windowConfiguration); for (const keyValue of Object.keys(config)) { const key = keyValue as keyof typeof config; diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index 6cb2f835d76..e2c984125b9 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -12,7 +12,7 @@ import { tmpdir } from 'os'; import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs'; import { NullLogService } from 'vs/platform/log/common/log'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage'; suite('StorageService', () => { @@ -86,7 +86,7 @@ suite('StorageService', () => { class StorageTestEnvironmentService extends EnvironmentService { constructor(private workspaceStorageFolderPath: string, private _extensionsPath: string) { - super(parseArgs(process.argv), process.execPath); + super(parseArgs(process.argv, OPTIONS), process.execPath); } get workspaceStorageHome(): string { @@ -117,4 +117,4 @@ suite('StorageService', () => { await storage.close(); await rimraf(storageDir, RimRafMode.MOVE); }); -}); \ No newline at end of file +}); diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index 16b3f1d60d7..2cad1c5f1f4 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -9,7 +9,7 @@ import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { WORKSPACE_EXTENSION, IWorkspaceIdentifier, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -47,7 +47,7 @@ suite('WorkspacesMainService', () => { return service.createUntitledWorkspaceSync(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData))); } - const environmentService = new TestEnvironmentService(parseArgs(process.argv), process.execPath); + const environmentService = new TestEnvironmentService(parseArgs(process.argv, OPTIONS), process.execPath); const logService = new NullLogService(); let service: TestWorkspacesMainService; diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts index b4f37b1b276..80809f4d14d 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -21,7 +21,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; @@ -49,7 +49,7 @@ const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(u class TestBackupEnvironmentService extends WorkbenchEnvironmentService { constructor(backupPath: string) { - super({ ...parseArgs(process.argv), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath); + super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath); } } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 11587ed86d2..c3fc2b97536 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -11,7 +11,7 @@ import * as fs from 'fs'; import * as json from 'vs/base/common/json'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import * as uuid from 'vs/base/common/uuid'; @@ -46,7 +46,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file class TestEnvironmentService extends WorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index ffd0eccd172..3fb4263663b 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -11,7 +11,7 @@ import * as os from 'os'; import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import * as pfs from 'vs/base/node/pfs'; import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -51,7 +51,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ class TestEnvironmentService extends WorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 4ae911b01ff..ef234672a77 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -46,14 +46,14 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { URI } from 'vs/base/common/uri'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; class TestEnvironmentService extends WorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 4014dc4da45..689930dc68b 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -30,7 +30,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextFileStreamContent, ITextFileService, IResourceEncoding, IReadTextFileOptions } from 'vs/workbench/services/textfile/common/textfiles'; -import { parseArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -91,7 +91,7 @@ export function createFileInput(instantiationService: IInstantiationService, res return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } -export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv) as IWindowConfiguration, process.execPath); +export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); export class TestContextService implements IWorkspaceContextService { public _serviceBrand: undefined;