From 8b8feed4ad6f4b7058a35ffbcf934444e7dac43a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Aug 2018 17:54:54 +0200 Subject: [PATCH] polish cli args parsing --- src/vs/code/electron-main/app.ts | 13 ++-- src/vs/code/electron-main/launch.ts | 4 +- src/vs/code/electron-main/windows.ts | 100 +++++++++++++++++---------- src/vs/code/node/args.ts | 15 +++- src/vs/code/node/windowsFinder.ts | 53 +++++++------- 5 files changed, 112 insertions(+), 73 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index e6c6f9fb91d..6919e7bafb1 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -65,6 +65,7 @@ import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService import { MenubarChannel } from 'vs/platform/menubar/common/menubarIpc'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; import { CodeMenu } from 'vs/code/electron-main/menus'; +import { hasArgs } from 'vs/code/node/args'; export class CodeApplication { @@ -467,16 +468,16 @@ export class CodeApplication { // Open our first window const macOpenFiles = (global).macOpenFiles as string[]; const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; - const cliArgs = args._ || []; - const folderURIs = asArray(args['folder-uri']); - const fileURIs = asArray(args['file-uri']); + const hasCliArgs = hasArgs(args._); + const hasFolderURIs = hasArgs(args['folder-uri']); + const hasFileURIs = hasArgs(args['file-uri']); - if (args['new-window'] && !cliArgs.length && !folderURIs.length && !fileURIs.length) { + if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths - } else if (macOpenFiles && macOpenFiles.length && !cliArgs.length && !folderURIs.length && !fileURIs.length) { + } else if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), initialStartup: true }); // mac: open-file event received on startup } else { - this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!cliArgs.length && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli + this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli } } diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index bf3c3d45c2a..b3bf915f9a9 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -20,7 +20,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import URI, { UriComponents } from 'vs/base/common/uri'; import { BrowserWindow } from 'electron'; import { Event } from 'vs/base/common/event'; -import { asArray } from 'vs/code/node/args'; +import { hasArgs } from 'vs/code/node/args'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); @@ -179,7 +179,7 @@ export class LaunchService implements ILaunchService { } // Start without file/folder arguments - else if (args._.length === 0 && !asArray(args['folder-uri'].length && !asArray(args['file-uri'].length))) { + else if (!hasArgs(args._) && !hasArgs(args['folder-uri']) && !hasArgs(args['file-uri'])) { let openNewWindow = false; // Force new window diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index fea7ce16394..7a450e46ca6 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -14,7 +14,7 @@ import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStateService } from 'vs/platform/state/common/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { asArray } from 'vs/code/node/args'; +import { asArray, hasArgs } from 'vs/code/node/args'; import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron'; import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleService, UnloadReason, IWindowUnloadEvent } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; @@ -423,7 +423,7 @@ export class WindowsManager implements IWindowsMainService { // Make sure to pass focus to the most relevant of the windows if we open multiple if (usedWindows.length > 1) { - let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !asArray(openConfig.cli['file-uri']).length && !asArray(openConfig.cli['folder-uri']).length && !asArray(openConfig.urisToOpen).length; + let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !hasArgs(openConfig.urisToOpen); let focusLastOpened = true; let focusLastWindow = true; @@ -795,7 +795,7 @@ export class WindowsManager implements IWindowsMainService { } // Extract paths: from CLI - else if (openConfig.cli._.length > 0 || asArray(openConfig.cli['folder-uri']).length > 0 || asArray(openConfig.cli['file-uri']).length > 0) { + else if (hasArgs(openConfig.cli._) || hasArgs(openConfig.cli['folder-uri']) || hasArgs(openConfig.cli['file-uri'])) { windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli); isCommandLineOrAPICall = true; } @@ -824,53 +824,74 @@ export class WindowsManager implements IWindowsMainService { } private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPath[] { - let pathsToOpen = openConfig.urisToOpen.map(pathToOpen => { - const path = this.parseUri(pathToOpen, openConfig.forceOpenWorkspaceAsFile, { gotoLineMode: openConfig.cli && openConfig.cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile }); + let pathsToOpen = []; + let parseOptions = { gotoLineMode: openConfig.cli && openConfig.cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile }; + for (const pathToOpen of openConfig.urisToOpen) { + if (!pathToOpen) { + continue; + } - // Warn if the requested path to open does not exist - if (!path) { + const path = this.parseUri(pathToOpen, openConfig.forceOpenWorkspaceAsFile, parseOptions); + if (path) { + pathsToOpen.push(path); + } else { + // Warn about the invalid URI or path + + let message, detail; + if (pathToOpen.scheme === Schemas.file) { + message = localize('pathNotExistTitle', "Path does not exist"); + detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.fsPath); + } else { + message = localize('uriInvalidTitle', "URI can not be opened"); + detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", pathToOpen.toString()); + } const options: Electron.MessageBoxOptions = { title: product.nameLong, type: 'info', buttons: [localize('ok', "OK")], - message: localize('pathNotExistTitle', "Path does not exist"), - detail: localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.scheme === Schemas.file ? pathToOpen.fsPath : pathToOpen.path), + message, + detail, noLink: true }; this.dialogs.showMessageBox(options, this.getFocusedWindow()); } - - return path; - }); - - // get rid of nulls - pathsToOpen = arrays.coalesce(pathsToOpen); - + } return pathsToOpen; } private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] { const pathsToOpen = []; + const parseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto }; // folder uris const folderUris = asArray(cli['folder-uri']); - if (folderUris.length) { - pathsToOpen.push(...arrays.coalesce(folderUris.map(candidate => this.parseUri(this.parseUriArg(candidate), false, { ignoreFileNotFound: true, gotoLineMode: cli.goto })))); + for (let folderUri of folderUris) { + const path = this.parseUri(this.argToUri(folderUri), false, parseOptions); + if (path) { + pathsToOpen.push(path); + } } // file uris const fileUris = asArray(cli['file-uri']); - if (fileUris.length) { - pathsToOpen.push(...arrays.coalesce(fileUris.map(candidate => this.parseUri(this.parseUriArg(candidate), true, { ignoreFileNotFound: true, gotoLineMode: cli.goto })))); + for (let fileUri of fileUris) { + const path = this.parseUri(this.argToUri(fileUri), true, parseOptions); + if (path) { + pathsToOpen.push(path); + } } // folder or file paths - if (cli._ && cli._.length) { - pathsToOpen.push(...arrays.coalesce(cli._.map(candidate => this.parsePath(candidate, { ignoreFileNotFound: true, gotoLineMode: cli.goto })))); + const cliArgs = asArray(cli._); + for (let cliArg of cliArgs) { + const path = this.parsePath(cliArg, parseOptions); + if (path) { + pathsToOpen.push(path); + } } - if (pathsToOpen.length > 0) { + if (pathsToOpen.length) { return pathsToOpen; } @@ -974,29 +995,34 @@ export class WindowsManager implements IWindowsMainService { return restoreWindows; } - private parseUriArg(arg: string): URI { - // Do not support if user has passed folder path on Windows - if (isWindows && /^([a-z])\:(.*)$/i.test(arg)) { - return null; + private argToUri(arg: string): URI { + try { + let uri = URI.parse(arg); + if (!uri.scheme) { + console.log(`Invalid URI string, scheme missing: ${arg}`); + return null; + } + return uri; + } catch (e) { + console.log(`Invalid URI string, scheme missing: ${e.message}`); } - return URI.parse(arg); + return null; } - private parseUri(anyUri: URI, isFile: boolean, options?: { ignoreFileNotFound?: boolean, gotoLineMode?: boolean, forceOpenWorkspaceAsFile?: boolean; }): IPathToOpen { - if (!anyUri || !anyUri.scheme) { + private parseUri(uri: URI, isFile: boolean, options?: { ignoreFileNotFound?: boolean, gotoLineMode?: boolean, forceOpenWorkspaceAsFile?: boolean; }): IPathToOpen { + if (!uri || !uri.scheme) { return null; } - - if (anyUri.scheme === Schemas.file) { - return this.parsePath(anyUri.fsPath, options); + if (uri.scheme === Schemas.file) { + return this.parsePath(uri.fsPath, options); } if (isFile) { return { - fileUri: anyUri + fileUri: uri }; } return { - folderUri: anyUri + folderUri: uri }; } @@ -1135,11 +1161,11 @@ export class WindowsManager implements IWindowsMainService { cliArgs = []; } - if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.parseUriArg(uri)))) { + if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) { folderUris = []; } - if (fileUris.length && fileUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.parseUriArg(uri)))) { + if (fileUris.length && fileUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) { fileUris = []; } diff --git a/src/vs/code/node/args.ts b/src/vs/code/node/args.ts index 8f1a5a9fc9c..feff5c5b405 100644 --- a/src/vs/code/node/args.ts +++ b/src/vs/code/node/args.ts @@ -6,7 +6,7 @@ 'use strict'; /** - * Converts an arument into to an array + * Converts an argument into an array * @param arg a argument value. Can be undefined, en entry or an array */ export function asArray(arg: T | T[] | undefined): T[] { @@ -17,4 +17,17 @@ export function asArray(arg: T | T[] | undefined): T[] { return [arg]; } return []; +} + +/** + * Returns whether an argument is present. + */ +export function hasArgs(arg: T | T[] | undefined): boolean { + if (arg) { + if (Array.isArray(arg)) { + return !!arg.length; + } + return true; + } + return false; } \ No newline at end of file diff --git a/src/vs/code/node/windowsFinder.ts b/src/vs/code/node/windowsFinder.ts index c4cc2e87c44..fa5fba59882 100644 --- a/src/vs/code/node/windowsFinder.ts +++ b/src/vs/code/node/windowsFinder.ts @@ -8,7 +8,7 @@ import * as platform from 'vs/base/common/platform'; import * as paths from 'vs/base/common/paths'; import { OpenContext } from 'vs/platform/windows/common/windows'; -import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import URI from 'vs/base/common/uri'; import { hasToIgnoreCase, isEqual, isEqualOrParent } from 'vs/base/common/resources'; @@ -70,51 +70,50 @@ export function getLastActiveWindow(windows: W[]): W { } export function findWindowOnWorkspace(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W { - return windows.filter(window => { - - // match on folder - if (isSingleFolderWorkspaceIdentifier(workspace)) { - if (window.openedFolderUri && isEqual(window.openedFolderUri, workspace, hasToIgnoreCase(window.openedFolderUri))) { - return true; + if (isSingleFolderWorkspaceIdentifier(workspace)) { + for (const window of windows) { + // match on folder + if (isSingleFolderWorkspaceIdentifier(workspace)) { + if (window.openedFolderUri && isEqual(window.openedFolderUri, workspace, hasToIgnoreCase(window.openedFolderUri))) { + return window; + } } } - - // match on workspace - else { + } else if (isWorkspaceIdentifier(workspace)) { + for (const window of windows) { + // match on workspace if (window.openedWorkspace && window.openedWorkspace.id === workspace.id) { - return true; + return window; } } - - return false; - })[0]; + } + return null; } export function findWindowOnExtensionDevelopmentPath(windows: W[], extensionDevelopmentPath: string): W { - return windows.filter(window => { - + for (const window of windows) { // match on extension development path if (paths.isEqual(window.extensionDevelopmentPath, extensionDevelopmentPath, !platform.isLinux /* ignorecase */)) { - return true; + return window; } - - return false; - })[0]; + } + return null; } export function findWindowOnWorkspaceOrFolderUri(windows: W[], uri: URI): W { - return windows.filter(window => { - + if (!uri) { + return null; + } + for (const window of windows) { // check for workspace config path if (window.openedWorkspace && isEqual(URI.file(window.openedWorkspace.configPath), uri, !platform.isLinux /* ignorecase */)) { - return true; + return window; } // check for folder path if (window.openedFolderUri && isEqual(window.openedFolderUri, uri, hasToIgnoreCase(uri))) { - return true; + return window; } - - return false; - })[0]; + } + return null; }