diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 8c39f0d1e50..f451547d241 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -6,7 +6,7 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; -import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows'; +import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows'; import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc'; import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; @@ -187,14 +187,14 @@ export class CodeApplication extends Disposable { }); }); - let macOpenFileURIs: URI[] = []; + let macOpenFileURIs: IURIToOpen[] = []; let runningTimeout: any = null; app.on('open-file', (event: Event, path: string) => { this.logService.trace('App#open-file: ', path); event.preventDefault(); // Keep in array because more might come! - macOpenFileURIs.push(URI.file(path)); + macOpenFileURIs.push({ uri: URI.file(path) }); // Clear previous handler if any if (runningTimeout !== null) { @@ -606,7 +606,7 @@ export class CodeApplication extends Disposable { } if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - return this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), initialStartup: true }); // mac: open-file event received on startup + return this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => ({ uri: URI.file(file) })), initialStartup: true }); // mac: open-file event received on startup } return 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/windows.ts b/src/vs/code/electron-main/windows.ts index ef521c8b8ae..a12e590c8e7 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { basename, normalize, join, dirname } from 'path'; +import { basename, normalize, join, dirname, extname } from 'path'; import * as fs from 'fs'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; @@ -18,7 +18,7 @@ import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/pa import { ILifecycleService, UnloadReason, IWindowUnloadEvent, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, URIType } from 'vs/platform/windows/common/windows'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import product from 'vs/platform/node/product'; @@ -26,7 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; @@ -118,10 +118,6 @@ interface IFileInputs { remoteAuthority?: string; } -enum URIType { - FILE, FOLDER, WORKSPACE -} - interface IPathToOpen extends IPath { // the workspace for a Code instance to open @@ -485,7 +481,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 && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !hasArgs(openConfig.cli['workspace-uri']) && !(openConfig.urisToOpen && openConfig.urisToOpen.length); + let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !(openConfig.urisToOpen && openConfig.urisToOpen.length); let focusLastOpened = true; let focusLastWindow = true; @@ -847,7 +843,7 @@ export class WindowsManager implements IWindowsMainService { } // Extract paths: from CLI - else if (hasArgs(openConfig.cli._) || hasArgs(openConfig.cli['folder-uri']) || hasArgs(openConfig.cli['file-uri']) || hasArgs(openConfig.cli['workspace-uri'])) { + else if (hasArgs(openConfig.cli._) || hasArgs(openConfig.cli['folder-uri']) || hasArgs(openConfig.cli['file-uri'])) { windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli); isCommandLineOrAPICall = true; } @@ -884,19 +880,19 @@ export class WindowsManager implements IWindowsMainService { continue; } - const path = this.parseUri(pathToOpen, openConfig.forceOpenWorkspaceAsFile ? URIType.FILE : URIType.FOLDER, parseOptions); + const path = this.parseUri(pathToOpen.uri, pathToOpen.typeHint, parseOptions); if (path) { pathsToOpen.push(path); } else { // Warn about the invalid URI or path let message, detail; - if (pathToOpen.scheme === Schemas.file) { + if (pathToOpen.uri.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); + detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.uri.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()); + detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", pathToOpen.uri.toString()); } const options: Electron.MessageBoxOptions = { title: product.nameLong, @@ -920,7 +916,7 @@ export class WindowsManager implements IWindowsMainService { // folder uris const folderUris = asArray(cli['folder-uri']); for (let folderUri of folderUris) { - const path = this.parseUri(this.argToUri(folderUri), URIType.FOLDER, parseOptions); + const path = this.parseUri(this.argToUri(folderUri), 'folder', parseOptions); if (path) { pathsToOpen.push(path); } @@ -929,21 +925,12 @@ export class WindowsManager implements IWindowsMainService { // file uris const fileUris = asArray(cli['file-uri']); for (let fileUri of fileUris) { - const path = this.parseUri(this.argToUri(fileUri), URIType.FILE, parseOptions); + const path = this.parseUri(this.argToUri(fileUri), 'file'); if (path) { pathsToOpen.push(path); } } - const workspaceUris = asArray(cli['workspace-uri']); - for (let workspaceUri of workspaceUris) { - const path = this.parseUri(this.argToUri(workspaceUri), URIType.WORKSPACE, parseOptions); - if (path) { - pathsToOpen.push(path); - } - } - - // folder or file paths const cliArgs = asArray(cli._); for (let cliArg of cliArgs) { @@ -987,12 +974,12 @@ export class WindowsManager implements IWindowsMainService { const windowsToOpen: IPathToOpen[] = []; for (const openedWindow of openedWindows) { if (openedWindow.workspace) { // Workspaces - const pathToOpen = this.parseUri(openedWindow.workspace.configPath, URIType.WORKSPACE, { remoteAuthority: openedWindow.remoteAuthority }); + const pathToOpen = this.parseUri(openedWindow.workspace.configPath, 'file', { remoteAuthority: openedWindow.remoteAuthority }); if (pathToOpen && pathToOpen.workspace) { windowsToOpen.push(pathToOpen); } } else if (openedWindow.folderUri) { // Folders - const pathToOpen = this.parseUri(openedWindow.folderUri, URIType.FOLDER, { remoteAuthority: openedWindow.remoteAuthority }); + const pathToOpen = this.parseUri(openedWindow.folderUri, 'folder', { remoteAuthority: openedWindow.remoteAuthority }); if (pathToOpen && pathToOpen.folderUri) { windowsToOpen.push(pathToOpen); } @@ -1042,7 +1029,7 @@ export class WindowsManager implements IWindowsMainService { return null; } - private parseUri(uri: URI, type: URIType, options?: IPathParseOptions): IPathToOpen | null { + private parseUri(uri: URI, typeHint?: URIType, options: IPathParseOptions = {}): IPathToOpen | null { if (!uri || !uri.scheme) { return null; } @@ -1051,16 +1038,31 @@ export class WindowsManager implements IWindowsMainService { } // open remote if either specified in the cli or if it's a remotehost URI - const remoteAuthority = options && options.remoteAuthority || getRemoteAuthority(uri); + const remoteAuthority = options.remoteAuthority || getRemoteAuthority(uri); // normalize URI uri = normalizePath(uri); + + + // remove trailing slash const uriPath = uri.path; - if (uriPath.length > 2 && endsWith(uriPath, '/')) { - uri = uri.with({ path: uriPath.substr(0, uriPath.length - 1) }); + if (endsWith(uriPath, '/')) { + if (uriPath.length > 2) { + // only remove if the path has some content + uri = uri.with({ path: uriPath.substr(0, uriPath.length - 1) }); + } + if (!typeHint) { + typeHint = 'folder'; + } } - if (type === URIType.FILE) { - if (options && options.gotoLineMode) { + + // if there's no type hint + if (!typeHint && (extname(uri.path) === WORKSPACE_EXTENSION || options.gotoLineMode)) { + typeHint = 'file'; + } + + if (typeHint === 'file') { + if (options.gotoLineMode) { const parsedPath = parseLineAndColumnAware(uri.path); return { fileUri: uri.with({ path: parsedPath.path }), @@ -1069,15 +1071,16 @@ export class WindowsManager implements IWindowsMainService { remoteAuthority }; } + if (extname(uri.path) === WORKSPACE_EXTENSION && !options.forceOpenWorkspaceAsFile) { + return { + workspace: this.workspacesMainService.getWorkspaceIdentifier(uri), + remoteAuthority + }; + } return { fileUri: uri, remoteAuthority }; - } else if (type === URIType.WORKSPACE) { - return { - workspace: this.workspacesMainService.getWorkspaceIdentifier(uri), - remoteAuthority - }; } return { folderUri: uri, @@ -1085,21 +1088,20 @@ export class WindowsManager implements IWindowsMainService { }; } - private parsePath(anyPath: string, options?: IPathParseOptions): IPathToOpen | null { + private parsePath(anyPath: string, options: IPathParseOptions): IPathToOpen | null { if (!anyPath) { return null; } let parsedPath: IPathWithLineAndColumn; - const gotoLineMode = options && options.gotoLineMode; - if (options && options.gotoLineMode) { + if (options.gotoLineMode) { parsedPath = parseLineAndColumnAware(anyPath); anyPath = parsedPath.path; } // open remote if either specified in the cli even if it is a local file. TODO: Future idea: resolve in remote host context. - const remoteAuthority = options && options.remoteAuthority; + const remoteAuthority = options.remoteAuthority; const candidate = normalize(anyPath); try { @@ -1108,7 +1110,7 @@ export class WindowsManager implements IWindowsMainService { if (candidateStat.isFile()) { // Workspace (unless disabled via flag) - if (!options || !options.forceOpenWorkspaceAsFile) { + if (!options.forceOpenWorkspaceAsFile) { const workspace = this.workspacesMainService.resolveWorkspaceSync(candidate); if (workspace) { return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority }; @@ -1118,8 +1120,8 @@ export class WindowsManager implements IWindowsMainService { // File return { fileUri: URI.file(candidate), - lineNumber: gotoLineMode ? parsedPath.line : undefined, - columnNumber: gotoLineMode ? parsedPath.column : undefined, + lineNumber: options.gotoLineMode ? parsedPath.line : undefined, + columnNumber: options.gotoLineMode ? parsedPath.column : undefined, remoteAuthority }; } @@ -1201,7 +1203,6 @@ export class WindowsManager implements IWindowsMainService { } let folderUris = asArray(openConfig.cli['folder-uri']); let fileUris = asArray(openConfig.cli['file-uri']); - let workspaceUris = asArray(openConfig.cli['workspace-uri']); let cliArgs = openConfig.cli._; // Fill in previously opened workspace unless an explicit path is provided and we are not unit testing @@ -1219,7 +1220,7 @@ export class WindowsManager implements IWindowsMainService { if (workspaceToOpen.configPath.scheme === Schemas.file) { cliArgs = [fsPath(workspaceToOpen.configPath)]; } else { - workspaceUris = [workspaceToOpen.configPath.toString()]; + fileUris = [workspaceToOpen.configPath.toString()]; } } } @@ -1238,17 +1239,12 @@ export class WindowsManager implements IWindowsMainService { fileUris = []; } - if (workspaceUris.length && workspaceUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) { - workspaceUris = []; - } - openConfig.cli._ = cliArgs; openConfig.cli['folder-uri'] = folderUris; openConfig.cli['file-uri'] = fileUris; - openConfig.cli['workspace-uri'] = workspaceUris; // Open it - this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: !cliArgs.length && !folderUris.length && !fileUris.length && !workspaceUris.length, userEnv: openConfig.userEnv }); + this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: !cliArgs.length && !folderUris.length && !fileUris.length, userEnv: openConfig.userEnv }); } private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { @@ -1897,7 +1893,7 @@ class Dialogs { }); } - private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise { + private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise { // Ensure dialog options if (!options.dialogOptions) { @@ -1935,7 +1931,12 @@ class Dialogs { // Remember path in storage for next time this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); - return paths.map(path => URI.file(path)); + const result: IURIToOpen[] = []; + for (const path of paths) { + result.push({ uri: URI.file(path) }); + } + + return result; } return undefined; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index e13c736429f..23e84ca2f83 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -11,7 +11,6 @@ export interface ParsedArgs { _: string[]; 'folder-uri'?: string | string[]; 'file-uri'?: string | string[]; - 'workspace-uri'?: string | string[]; _urls?: string[]; help?: boolean; version?: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 7b0a9c431b8..d7e62cbfd82 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -40,7 +40,6 @@ export const options: Option[] = [ { 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: 'workspace-uri', type: 'string', cat: 'o', args: 'uri', description: localize('workspaceUri', "Opens a window with given workspace file") }, { id: 'extensions-dir', type: 'string', 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.") }, diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 7070576c1c1..fef0db5705c 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -424,7 +424,7 @@ export class Menubar { this.setMenu(submenu, item.submenu.items); menu.append(submenuItem); } else if (isMenubarMenuItemUriAction(item)) { - menu.append(this.createOpenRecentMenuItem(item.uri, item.label, item.id, item.id === 'openRecentFile')); + menu.append(this.createOpenRecentMenuItem(item.uri, item.label, item.id)); } else if (isMenubarMenuItemAction(item)) { if (item.id === 'workbench.action.showAboutDialog') { this.insertCheckForUpdatesItems(menu); @@ -462,8 +462,9 @@ export class Menubar { } } - private createOpenRecentMenuItem(uri: URI, label: string, commandId: string, isFile: boolean): Electron.MenuItem { + private createOpenRecentMenuItem(uri: URI, label: string, commandId: string): Electron.MenuItem { const revivedUri = URI.revive(uri); + const typeHint = commandId === 'openRecentFile' || commandId === 'openRecentWorkspace' ? 'file' : 'folder'; return new MenuItem(this.likeAction(commandId, { label, @@ -472,9 +473,9 @@ export class Menubar { const success = this.windowsMainService.open({ context: OpenContext.MENU, cli: this.environmentService.args, - urisToOpen: [revivedUri], + urisToOpen: [{ uri: revivedUri, typeHint }], forceNewWindow: openInNewWindow, - forceOpenWorkspaceAsFile: isFile + forceOpenWorkspaceAsFile: commandId === 'openRecentFile' }).length > 0; if (!success) { diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 8e643286d88..670fee0dbb5 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -149,7 +149,7 @@ export interface IWindowsService { toggleSharedProcess(): Promise; // Global methods - openWindow(windowId: number, paths: URI[], options?: IOpenSettings): Promise; + openWindow(windowId: number, uris: IURIToOpen[], options?: IOpenSettings): Promise; openNewWindow(options?: INewWindowOptions): Promise; showWindow(windowId: number): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; @@ -185,6 +185,13 @@ export interface IOpenSettings { args?: ParsedArgs; } +export type URIType = 'file' | 'folder'; + +export interface IURIToOpen { + uri: URI; + typeHint?: URIType; +} + export interface IWindowService { _serviceBrand: any; @@ -211,7 +218,7 @@ export interface IWindowService { getRecentlyOpened(): Promise; focusWindow(): Promise; closeWindow(): Promise; - openWindow(paths: URI[], options?: IOpenSettings): Promise; + openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise; isFocused(): Promise; setDocumentEdited(flag: boolean): Promise; isMaximized(): Promise; diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index 9c9cc78d419..179df0df102 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, IDevToolsOptions, IOpenSettings } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, IDevToolsOptions, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -92,8 +92,8 @@ export class WindowService extends Disposable implements IWindowService { return this.windowsService.enterWorkspace(this.windowId, path); } - openWindow(paths: URI[], options?: IOpenSettings): Promise { - return this.windowsService.openWindow(this.windowId, paths, options); + openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise { + return this.windowsService.openWindow(this.windowId, uris, options); } closeWindow(): Promise { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 19219a64149..6dff9714c47 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions } from 'vs/platform/windows/common/windows'; +import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen } from 'vs/platform/windows/common/windows'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -124,7 +124,7 @@ export interface IOpenConfiguration { readonly contextWindowId?: number; readonly cli: ParsedArgs; readonly userEnv?: IProcessEnvironment; - readonly urisToOpen?: URI[]; + readonly urisToOpen?: IURIToOpen[]; readonly preferNewWindow?: boolean; readonly forceNewWindow?: boolean; readonly forceNewTabbedWindow?: boolean; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 940cbe2d1cd..ea4c77c4259 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -9,7 +9,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import product from 'vs/platform/node/product'; -import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, INewWindowOptions, IOpenSettings } from 'vs/platform/windows/common/windows'; +import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, INewWindowOptions, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { shell, crashReporter, app, Menu, clipboard } from 'electron'; import { Event } from 'vs/base/common/event'; @@ -274,16 +274,16 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable }); } - async openWindow(windowId: number, paths: URI[], options?: IOpenSettings): Promise { + async openWindow(windowId: number, urisToOpen: IURIToOpen[], options?: IOpenSettings): Promise { this.logService.trace('windowsService#openWindow'); - if (!paths || !paths.length) { + if (!urisToOpen || !urisToOpen.length) { return undefined; } this.windowsMainService.open({ context: OpenContext.API, contextWindowId: windowId, - urisToOpen: paths, + urisToOpen: urisToOpen, cli: options && options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args, forceNewWindow: options && options.forceNewWindow, forceReuseWindow: options && options.forceReuseWindow, @@ -419,14 +419,14 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable // Catch file URLs if (uri.authority === Schemas.file && !!uri.path) { - this.openFileForURI(URI.file(uri.fsPath)); + this.openFileForURI({ uri }); return true; } return false; } - private openFileForURI(uri: URI): void { + private openFileForURI(uri: IURIToOpen): void { const cli = assign(Object.create(null), this.environmentService.args, { goto: true }); const urisToOpen = [uri]; diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/node/windowsIpc.ts index 2d7f1b963bf..12b741138c0 100644 --- a/src/vs/platform/windows/node/windowsIpc.ts +++ b/src/vs/platform/windows/node/windowsIpc.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions, INewWindowOptions } from 'vs/platform/windows/common/windows'; +import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions, INewWindowOptions, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; @@ -85,7 +85,7 @@ export class WindowsChannel implements IServerChannel { case 'minimizeWindow': return this.service.minimizeWindow(arg); case 'onWindowTitleDoubleClick': return this.service.onWindowTitleDoubleClick(arg); case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); - case 'openWindow': return this.service.openWindow(arg[0], arg[1] ? (arg[1]).map(r => URI.revive(r)) : arg[1], arg[2]); + case 'openWindow': return this.service.openWindow(arg[0], arg[1] ? (arg[1]).map(r => ({ uri: URI.revive(r.uri), typeHint: r.typeHint })) : arg[1], arg[2]); case 'openNewWindow': return this.service.openNewWindow(arg); case 'showWindow': return this.service.showWindow(arg); case 'getWindows': return this.service.getWindows(); @@ -275,8 +275,8 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('toggleSharedProcess'); } - openWindow(windowId: number, paths: URI[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): Promise { - return this.channel.call('openWindow', [windowId, paths, options]); + openWindow(windowId: number, uris: IURIToOpen[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): Promise { + return this.channel.call('openWindow', [windowId, uris, options]); } openNewWindow(options?: INewWindowOptions): Promise { diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 2b8311a2e3e..5e17cdde2ad 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -210,7 +210,7 @@ CommandsRegistry.registerCommand('_workbench.enterWorkspace', async function (ac const runningExtensions = await extensionService.getExtensions(); // If requested extension to disable is running, then reload window with given workspace if (disableExtensions && runningExtensions.some(runningExtension => disableExtensions.some(id => ExtensionIdentifier.equals(runningExtension.identifier, id)))) { - return windowService.openWindow([URI.file(workspace.fsPath)], { args: { _: [], 'disable-extension': disableExtensions } }); + return windowService.openWindow([{ uri: workspace, typeHint: 'file' }], { args: { _: [], 'disable-extension': disableExtensions } }); } } diff --git a/src/vs/workbench/api/node/apiCommands.ts b/src/vs/workbench/api/node/apiCommands.ts index 0e808dbfed4..967520f0107 100644 --- a/src/vs/workbench/api/node/apiCommands.ts +++ b/src/vs/workbench/api/node/apiCommands.ts @@ -62,7 +62,7 @@ export class OpenFolderAPICommand { uri = correctedUri; } - return executor.executeCommand('_files.windowOpen', { folderURIs: [uri], forceNewWindow }); + return executor.executeCommand('_files.windowOpen', { urisToOpen: [{ uri }], forceNewWindow }); } } CommandsRegistry.registerCommand(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute)); diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 005f6b766a8..03bea6bc3bd 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -19,6 +19,7 @@ import * as http from 'http'; import * as fs from 'fs'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { sanitizeProcessEnvironment } from 'vs/base/node/processes'; +import { IURIToOpen, URIType } from 'vs/platform/windows/common/windows'; const RENDERER_NO_PROCESS_ID = -1; @@ -618,18 +619,16 @@ class CLIServer { return this.ipcHandlePath; } - private toURIs(strs: string[]): URI[] { - const result: URI[] = []; + private collectURIToOpen(strs: string[], typeHint: URIType, result: IURIToOpen[]): void { if (Array.isArray(strs)) { for (const s of strs) { try { - result.push(URI.parse(s)); + result.push({ uri: URI.parse(s), typeHint }); } catch (e) { // ignore } } } - return result; } private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { @@ -642,7 +641,10 @@ class CLIServer { if (folderURIs && folderURIs.length && !forceReuseWindow) { forceNewWindow = true; } - this._commands.executeCommand('_files.windowOpen', { folderURIs: this.toURIs(folderURIs), fileURIs: this.toURIs(fileURIs), forceNewWindow, diffMode, addMode, forceReuseWindow }); + const urisToOpen: IURIToOpen[] = []; + this.collectURIToOpen(folderURIs, 'folder', urisToOpen); + this.collectURIToOpen(fileURIs, 'file', urisToOpen); + this._commands.executeCommand('_files.windowOpen', { urisToOpen, forceNewWindow, diffMode, addMode, forceReuseWindow }); } res.writeHead(200); res.end(); diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 6a8b00cb170..a8847e2cbb7 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -256,7 +256,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { return this.workspacesService.createUntitledWorkspace(folders).then(newWorkspace => { return this.workspaceEditingService.copyWorkspaceSettings(newWorkspace).then(() => { - return this.windowService.openWindow([newWorkspace.configPath], { forceNewWindow: true }); + return this.windowService.openWindow([{ uri: newWorkspace.configPath, typeHint: 'file' }], { forceNewWindow: true }); }); }); } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 5e5039eade3..2bdb8ba7ce6 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -6,7 +6,7 @@ import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { extname, basename, normalize } from 'vs/base/common/paths'; import { IFileService } from 'vs/platform/files/common/files'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -255,7 +255,7 @@ export class ResourcesDropHandler { } private handleWorkspaceFileDrop(fileOnDiskResources: URI[]): Promise { - const workspaceResources: { workspaces: URI[], folders: URI[] } = { + const workspaceResources: { workspaces: IURIToOpen[], folders: IURIToOpen[] } = { workspaces: [], folders: [] }; @@ -264,7 +264,7 @@ export class ResourcesDropHandler { // Check for Workspace if (extname(fileOnDiskResource.fsPath) === `.${WORKSPACE_EXTENSION}`) { - workspaceResources.workspaces.push(fileOnDiskResource); + workspaceResources.workspaces.push({ uri: fileOnDiskResource, typeHint: 'file' }); return undefined; } @@ -272,7 +272,7 @@ export class ResourcesDropHandler { // Check for Folder return this.fileService.resolveFile(fileOnDiskResource).then(stat => { if (stat.isDirectory) { - workspaceResources.folders.push(stat.resource); + workspaceResources.folders.push({ uri: stat.resource, typeHint: 'folder' }); } }, error => undefined); })).then(_ => { @@ -286,16 +286,16 @@ export class ResourcesDropHandler { // Pass focus to window this.windowService.focusWindow(); - let workspacesToOpen: Promise; + let workspacesToOpen: Promise; // Open in separate windows if we drop workspaces or just one folder if (workspaces.length > 0 || folders.length === 1) { - workspacesToOpen = Promise.resolve([...workspaces, ...folders].map(resources => resources)); + workspacesToOpen = Promise.resolve([...workspaces, ...folders]); } // Multiple folders: Create new workspace with folders and open else if (folders.length > 1) { - workspacesToOpen = this.workspacesService.createUntitledWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]); + workspacesToOpen = this.workspacesService.createUntitledWorkspace(folders).then(workspace => [{ uri: workspace.configPath, typeHint: 'file' }]); } // Open diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/parts/editor/editorWidgets.ts index 9b384758177..396d0d641b7 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/parts/editor/editorWidgets.ts @@ -159,7 +159,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit this._register(this.openWorkspaceButton.onClick(() => { const model = this.editor.getModel(); if (model) { - this.windowService.openWindow([model.uri]); + this.windowService.openWindow([{ uri: model.uri, typeHint: 'file' }]); } })); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 7d4ef576f04..003a6b34f4c 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { IMenubarMenu, IMenubarMenuItemAction, IMenubarMenuItemSubmenu, IMenubarKeybinding, IMenubarService, IMenubarData, MenubarMenuItem } from 'vs/platform/menubar/common/menubar'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWindowService, MenuBarVisibility, IWindowsService, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, IWindowsService, getTitleBarStyle, URIType } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -317,26 +317,34 @@ export class MenubarControl extends Disposable { return label; } - private createOpenRecentMenuAction(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, commandId: string, isFile: boolean): IAction & { uri: URI } { + private createOpenRecentMenuAction(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, isFile: boolean): IAction & { uri: URI } { let label: string; let uri: URI; + let commandId: string; + let typeHint: URIType | undefined; if (isSingleFolderWorkspaceIdentifier(workspace) && !isFile) { label = this.labelService.getWorkspaceLabel(workspace, { verbose: true }); uri = workspace; + commandId = 'openRecentFolder'; + typeHint = 'folder'; } else if (isWorkspaceIdentifier(workspace)) { label = this.labelService.getWorkspaceLabel(workspace, { verbose: true }); uri = workspace.configPath; + commandId = 'openRecentWorkspace'; + typeHint = 'file'; } else { uri = workspace; label = this.labelService.getUriLabel(uri); + commandId = 'openRecentFile'; + typeHint = 'file'; } const ret: IAction = new Action(commandId, label, undefined, undefined, (event) => { const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); - return this.windowService.openWindow([uri], { + return this.windowService.openWindow([{ uri, typeHint }], { forceNewWindow: openInNewWindow, forceOpenWorkspaceAsFile: isFile }); @@ -357,7 +365,7 @@ export class MenubarControl extends Disposable { if (workspaces.length > 0) { for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) { - result.push(this.createOpenRecentMenuAction(workspaces[i], 'openRecentWorkspace', false)); + result.push(this.createOpenRecentMenuAction(workspaces[i], false)); } result.push(new Separator()); @@ -365,7 +373,7 @@ export class MenubarControl extends Disposable { if (files.length > 0) { for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) { - result.push(this.createOpenRecentMenuAction(files[i], 'openRecentFile', false)); + result.push(this.createOpenRecentMenuAction(files[i], true)); } result.push(new Separator()); diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index d61cba9441d..1c67d1881cd 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -366,9 +366,9 @@ export abstract class BaseOpenRecentAction extends Action { }; }; - const runPick = (resource: URI, isFile: boolean, keyMods: IKeyMods) => { + const runPick = (uri: URI, isFile: boolean, keyMods: IKeyMods) => { const forceNewWindow = keyMods.ctrlCmd; - return this.windowService.openWindow([resource], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); + return this.windowService.openWindow([{ uri, typeHint: isFile ? 'file' : 'folder' }], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); }; const workspacePicks = recentWorkspaces.map(workspace => toPick(workspace, isSingleFolderWorkspaceIdentifier(workspace) ? FileKind.FOLDER : FileKind.ROOT_FOLDER, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 420a845ae97..db325ff5c60 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -876,7 +876,7 @@ export class ShowOpenedFileInNewWindow extends Action { public run(): Promise { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: Schemas.file /* todo@remote */ }); if (fileResource) { - this.windowService.openWindow([fileResource], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); + this.windowService.openWindow([{ uri: fileResource, typeHint: 'file' }], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); } else { this.notificationService.info(nls.localize('openFileToShowInNewWindow', "Open a file first to open in new window")); } diff --git a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts index 0bd9b93fcc5..a6ac5a7cc48 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as paths from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; import { toResource, IEditorCommandsContext } from 'vs/workbench/common/editor'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -76,17 +76,13 @@ export const ResourceSelectedForCompareContext = new RawContextKey('res export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); -export const openWindowCommand = (accessor: ServicesAccessor, input: Array | { fileURIs: URI[], folderURIs: URI[], forceNewWindow: boolean, forceReuseWindow?: boolean, diffMode?: boolean, addMode?: boolean }, forceNewWindow: boolean) => { +export const openWindowCommand = (accessor: ServicesAccessor, input: Array | { urisToOpen: IURIToOpen[], forceNewWindow: boolean, forceReuseWindow?: boolean, diffMode?: boolean, addMode?: boolean }, forceNewWindow: boolean) => { const windowService = accessor.get(IWindowService); + if (Array.isArray(input)) { - windowService.openWindow(input.map(p => typeof p === 'string' ? URI.file(p) : p), { forceNewWindow }); + windowService.openWindow(input.map(p => ({ uri: typeof p === 'string' ? URI.file(p) : p }), { forceNewWindow })); } else if (input) { - if (Array.isArray(input.folderURIs) && input.folderURIs.length) { - windowService.openWindow(input.folderURIs, { forceNewWindow: input.forceNewWindow, diffMode: input.diffMode, addMode: input.addMode, forceReuseWindow: input.forceReuseWindow }); - } - if (Array.isArray(input.fileURIs) && input.fileURIs.length) { - windowService.openWindow(input.fileURIs, { forceNewWindow: input.forceNewWindow, forceOpenWorkspaceAsFile: true, diffMode: input.diffMode, addMode: input.addMode, forceReuseWindow: input.forceReuseWindow }); - } + windowService.openWindow(input.urisToOpen, { forceNewWindow: input.forceNewWindow, diffMode: input.diffMode, addMode: input.addMode, forceReuseWindow: input.forceReuseWindow }); } }; diff --git a/src/vs/workbench/parts/stats/node/workspaceStats.ts b/src/vs/workbench/parts/stats/node/workspaceStats.ts index 74a0a4c503e..8843247d0a3 100644 --- a/src/vs/workbench/parts/stats/node/workspaceStats.ts +++ b/src/vs/workbench/parts/stats/node/workspaceStats.ts @@ -16,10 +16,11 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; -import { extname, join } from 'path'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { joinPath } from 'vs/base/common/resources'; +import { extname } from 'vs/base/common/paths'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -551,7 +552,7 @@ export class WorkspaceStats implements IWorkbenchContribution { this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ label: localize('openWorkspace', "Open Workspace"), - run: () => this.windowService.openWindow([URI.file(join(folder.fsPath, workspaceFile))]) + run: () => this.windowService.openWindow([{ uri: joinPath(folder, workspaceFile), typeHint: 'file' }]) }, doNotShowAgain]); } @@ -564,7 +565,7 @@ export class WorkspaceStats implements IWorkbenchContribution { workspaces.map(workspace => ({ label: workspace } as IQuickPickItem)), { placeHolder: localize('selectToOpen', "Select a workspace to open") }).then(pick => { if (pick) { - this.windowService.openWindow([URI.file(join(folder.fsPath, pick.label))]); + this.windowService.openWindow([{ uri: joinPath(folder, pick.label), typeHint: 'file' }]); } }); } diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index a33701370ad..b2e075386d3 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -14,7 +14,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService, URIType } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; @@ -344,15 +344,19 @@ class WelcomePage { return workspaces.map(workspace => { let label: string; let resource: URI; + let typeHint: URIType | undefined; if (isSingleFolderWorkspaceIdentifier(workspace)) { resource = workspace; label = this.labelService.getWorkspaceLabel(workspace); + typeHint = 'folder'; } else if (isWorkspaceIdentifier(workspace)) { label = this.labelService.getWorkspaceLabel(workspace); resource = workspace.configPath; + typeHint = 'file'; } else { label = getBaseLabel(workspace); resource = URI.file(workspace); + typeHint = 'file'; } const li = document.createElement('li'); @@ -389,7 +393,7 @@ class WelcomePage { id: 'openRecentFolder', from: telemetryFrom }); - this.windowService.openWindow([resource], { forceNewWindow: e.ctrlKey || e.metaKey }); + this.windowService.openWindow([{ uri: resource, typeHint }], { forceNewWindow: e.ctrlKey || e.metaKey }); e.preventDefault(); e.stopPropagation(); }); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index f902c08b46c..4dd9012e9ca 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -37,7 +37,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IWindowsService, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, MenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, MenuBarVisibility, IURIToOpen } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -1094,7 +1094,7 @@ export class TestWindowService implements IWindowService { return Promise.resolve(); } - openWindow(_paths: URI[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { + openWindow(_uris: IURIToOpen[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { return Promise.resolve(); } @@ -1300,7 +1300,7 @@ export class TestWindowsService implements IWindowsService { } // Global methods - openWindow(_windowId: number, _paths: URI[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { + openWindow(_windowId: number, _uris: IURIToOpen[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { return Promise.resolve(); }