diff --git a/package.json b/package.json index fc044a6c295..99898b90f42 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.40.0", - "distro": "c4406f67d23097ea3ddc5e388290c06ad37021eb", + "distro": "6864b7d5658c713e1a5d54baefafe5ff90818aee", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 233b4fd4c25..ae0d5174b90 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -206,6 +206,8 @@ class WorkspaceProvider implements IWorkspaceProvider { static QUERY_PARAM_FOLDER = 'folder'; static QUERY_PARAM_WORKSPACE = 'workspace'; + static QUERY_PARAM_PAYLOAD = 'payload'; + constructor( public readonly workspace: IWorkspace, public readonly payload: object @@ -216,27 +218,7 @@ class WorkspaceProvider implements IWorkspaceProvider { return; // return early if workspace and environment is not changing and we are reusing window } - // Empty - let targetHref: string | undefined = undefined; - if (!workspace) { - targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW}=true`; - } - - // Folder - else if (isFolderToOpen(workspace)) { - targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${workspace.folderUri.path}`; - } - - // Workspace - else if (isWorkspaceToOpen(workspace)) { - targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${workspace.workspaceUri.path}`; - } - - // Environment - if (options?.payload) { - targetHref += `&payload=${encodeURIComponent(JSON.stringify(options.payload))}`; - } - + const targetHref = this.createTargetUrl(workspace, options); if (targetHref) { if (options?.reuse) { window.location.href = targetHref; @@ -250,6 +232,32 @@ class WorkspaceProvider implements IWorkspaceProvider { } } + private createTargetUrl(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): string | undefined { + + // Empty + let targetHref: string | undefined = undefined; + if (!workspace) { + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW}=true`; + } + + // Folder + else if (isFolderToOpen(workspace)) { + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${encodeURIComponent(workspace.folderUri.toString())}`; + } + + // Workspace + else if (isWorkspaceToOpen(workspace)) { + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${encodeURIComponent(workspace.workspaceUri.toString())}`; + } + + // Append payload if any + if (options?.payload) { + targetHref += `&${WorkspaceProvider.QUERY_PARAM_PAYLOAD}=${encodeURIComponent(JSON.stringify(options.payload))}`; + } + + return targetHref; + } + private isSame(workspaceA: IWorkspace, workspaceB: IWorkspace): boolean { if (!workspaceA || !workspaceB) { return workspaceA === workspaceB; // both empty @@ -276,32 +284,49 @@ class WorkspaceProvider implements IWorkspaceProvider { throw new Error('Missing web configuration element'); } - const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute); - - // Determine workspace to open + // Find workspace to open and payload + let foundWorkspace = false; let workspace: IWorkspace; - if (options.folderUri) { - workspace = { folderUri: URI.revive(options.folderUri) }; - } else if (options.workspaceUri) { - workspace = { workspaceUri: URI.revive(options.workspaceUri) }; - } else { - workspace = undefined; - } - - // Find payload let payload = Object.create(null); - if (document.location.search) { - const query = document.location.search.substring(1); - const vars = query.split('&'); - for (let p of vars) { - const pair = p.split('='); - if (pair.length === 2) { - const [key, value] = pair; - if (key === 'payload') { - payload = JSON.parse(decodeURIComponent(value)); - break; - } - } + + const query = new URL(document.location.href).searchParams; + query.forEach((value, key) => { + switch (key) { + + // Folder + case WorkspaceProvider.QUERY_PARAM_FOLDER: + workspace = { folderUri: URI.parse(value) }; + foundWorkspace = true; + break; + + // Workspace + case WorkspaceProvider.QUERY_PARAM_WORKSPACE: + workspace = { workspaceUri: URI.parse(value) }; + foundWorkspace = true; + break; + + // Empty + case WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW: + workspace = undefined; + foundWorkspace = true; + break; + + // Payload + case WorkspaceProvider.QUERY_PARAM_PAYLOAD: + payload = JSON.parse(value); + break; + } + }); + + // If no workspace is provided through the URL, check for config attribute from server + const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute); + if (!foundWorkspace) { + if (options.folderUri) { + workspace = { folderUri: URI.revive(options.folderUri) }; + } else if (options.workspaceUri) { + workspace = { workspaceUri: URI.revive(options.workspaceUri) }; + } else { + workspace = undefined; } } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index b7d74392423..0834621124a 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -13,7 +13,7 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { WorkbenchStateContext, SupportsWorkspacesContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; @@ -22,6 +22,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; export class OpenFileAction extends Action { @@ -195,14 +196,74 @@ export class GlobalRemoveRootFolderAction extends Action { } } +export class SaveWorkspaceAsAction extends Action { + + static readonly ID = 'workbench.action.saveWorkspaceAs'; + static readonly LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); + + constructor( + id: string, + label: string, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService + + ) { + super(id, label); + } + + async run(): Promise { + const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); + if (configPathUri) { + switch (this.contextService.getWorkbenchState()) { + case WorkbenchState.EMPTY: + case WorkbenchState.FOLDER: + const folders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); + return this.workspaceEditingService.createAndEnterWorkspace(folders, configPathUri); + case WorkbenchState.WORKSPACE: + return this.workspaceEditingService.saveAndEnterWorkspace(configPathUri); + } + } + } +} + +export class DuplicateWorkspaceInNewWindowAction extends Action { + + static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow'; + static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window"); + + constructor( + id: string, + label: string, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, + @IHostService private readonly hostService: IHostService, + @IWorkspacesService private readonly workspacesService: IWorkspacesService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(id, label); + } + + async run(): Promise { + const folders = this.workspaceContextService.getWorkspace().folders; + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + + const newWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); + await this.workspaceEditingService.copyWorkspaceSettings(newWorkspace); + + return this.hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); + } +} + // --- Actions Registration const registry = Registry.as(Extensions.WorkbenchActions); const workspacesCategory = nls.localize('workspaces', "Workspaces"); -registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, SupportsWorkspacesContext); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory, SupportsWorkspacesContext); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory, SupportsWorkspacesContext); +registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); // --- Menu Registration @@ -216,8 +277,16 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { id: ADD_ROOT_FOLDER_COMMAND_ID, title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") }, - order: 1, - when: SupportsWorkspacesContext + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '3_workspace', + command: { + id: SaveWorkspaceAsAction.ID, + title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") + }, + order: 2 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { @@ -246,5 +315,5 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") }, order: 3, - when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), SupportsWorkspacesContext) + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace')) }); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 819ae22534a..d45fe4e74e7 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -38,8 +38,6 @@ export const RemoteConnectionState = new RawContextKey<'' | 'initializing' | 'di export const HasMacNativeTabsContext = new RawContextKey('hasMacNativeTabs', false); -export const SupportsWorkspacesContext = new RawContextKey('supportsWorkspaces', true); - export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); @@ -107,11 +105,6 @@ export class WorkbenchContextKeysHandler extends Disposable { // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); - // Workspaces Support - // - web: only if already in workspace state - // - desktop: always - SupportsWorkspacesContext.bindTo(this.contextKeyService).set(isWeb ? this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE : true); - // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); this.activeEditorIsSaveable = ActiveEditorIsSaveableContext.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 51194632b7d..26ccbe6f9ea 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -48,6 +48,7 @@ import { toLocalISOString } from 'vs/base/common/date'; import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider'; import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider'; import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; +import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; class BrowserMain extends Disposable { @@ -292,7 +293,7 @@ class BrowserMain extends Disposable { // Multi-root workspace if (workspace && isWorkspaceToOpen(workspace)) { - return { id: hash(workspace.workspaceUri.toString()).toString(16), configPath: workspace.workspaceUri }; + return getWorkspaceIdentifier(workspace.workspaceUri); } // Single-folder workspace diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index e54b26b0fd2..313a667124d 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -23,7 +23,7 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { SupportsWorkspacesContext, IsWebContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { IsWebContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ActiveEditorIsSaveableContext } from 'vs/workbench/common/editor'; @@ -496,7 +496,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: ADD_ROOT_FOLDER_COMMAND_ID, title: ADD_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext, SupportsWorkspacesContext) + when: ContextKeyExpr.and(ExplorerRootContext) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -506,7 +506,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: REMOVE_ROOT_FOLDER_COMMAND_ID, title: REMOVE_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext, SupportsWorkspacesContext) + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { diff --git a/src/vs/workbench/electron-browser/actions/workspaceActions.ts b/src/vs/workbench/electron-browser/actions/workspaceActions.ts deleted file mode 100644 index 6647ac84756..00000000000 --- a/src/vs/workbench/electron-browser/actions/workspaceActions.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Action } from 'vs/base/common/actions'; -import * as nls from 'vs/nls'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; - -export class SaveWorkspaceAsAction extends Action { - - static readonly ID = 'workbench.action.saveWorkspaceAs'; - static readonly LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); - - constructor( - id: string, - label: string, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService - - ) { - super(id, label); - } - - async run(): Promise { - const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); - if (configPathUri) { - switch (this.contextService.getWorkbenchState()) { - case WorkbenchState.EMPTY: - case WorkbenchState.FOLDER: - const folders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); - return this.workspaceEditingService.createAndEnterWorkspace(folders, configPathUri); - case WorkbenchState.WORKSPACE: - return this.workspaceEditingService.saveAndEnterWorkspace(configPathUri); - } - } - } -} - -export class DuplicateWorkspaceInNewWindowAction extends Action { - - static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow'; - static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window"); - - constructor( - id: string, - label: string, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, - @IHostService private readonly hostService: IHostService, - @IWorkspacesService private readonly workspacesService: IWorkspacesService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService - ) { - super(id, label); - } - - async run(): Promise { - const folders = this.workspaceContextService.getWorkspace().folders; - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - - const newWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); - await this.workspaceEditingService.copyWorkspaceSettings(newWorkspace); - - return this.hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); - } -} diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index b1398d6415f..211d8227a96 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -13,12 +13,11 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { ToggleSharedProcessAction, ToggleDevToolsAction, ConfigureRuntimeArgumentsAction } from 'vs/workbench/electron-browser/actions/developerActions'; import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; -import { SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction } from 'vs/workbench/electron-browser/actions/workspaceActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; +import { IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -66,14 +65,6 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat }); })(); - // Actions: Workspaces - (function registerWorkspaceActions(): void { - const workspacesCategory = nls.localize('workspaces', "Workspaces"); - - registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, SupportsWorkspacesContext); - registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory, SupportsWorkspacesContext); - })(); - // Actions: macOS Native Tabs (function registerMacOSNativeTabsActions(): void { if (isMacintosh) { @@ -115,16 +106,6 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat // Menu (function registerMenu(): void { - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: SaveWorkspaceAsAction.ID, - title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") - }, - order: 2, - when: SupportsWorkspacesContext - }); - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 003830f4248..a5092d5eb86 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -91,6 +91,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); this.argvResource = joinPath(this.userRoamingDataHome, 'argv.json'); this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS); + this.untitledWorkspacesHome = joinPath(this.userRoamingDataHome, 'Workspaces'); this.configuration.backupWorkspaceResource = joinPath(this.backupHome, options.workspaceId); this.configuration.connectionToken = options.connectionToken || getCookieValue('vscode-tkn'); @@ -99,8 +100,6 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment break: false }; - this.untitledWorkspacesHome = URI.from({ scheme: Schemas.untitled, path: 'Workspaces' }); - // Fill in selected extra environmental properties if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { const environment = serializableToMap(options.workspaceProvider.payload); diff --git a/src/vs/workbench/services/workspaces/browser/workspaces.ts b/src/vs/workbench/services/workspaces/browser/workspaces.ts new file mode 100644 index 00000000000..9234604c9cb --- /dev/null +++ b/src/vs/workbench/services/workspaces/browser/workspaces.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { URI } from 'vs/base/common/uri'; +import { hash } from 'vs/base/common/hash'; + +export function getWorkspaceIdentifier(workspacePath: URI): IWorkspaceIdentifier { + return { + id: hash(workspacePath.toString()).toString(16), + configPath: workspacePath + }; +} diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index 24240836def..b396d6ae5d3 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -4,13 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IEnterWorkspaceResult, IRecentlyOpened, restoreRecentlyOpened, IRecent, isRecentFile, isRecentFolder, toStoreData } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IEnterWorkspaceResult, IRecentlyOpened, restoreRecentlyOpened, IRecent, isRecentFile, isRecentFolder, toStoreData, IStoredWorkspaceFolder, getStoredWorkspaceFolder, WORKSPACE_EXTENSION, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; +import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { joinPath } from 'vs/base/common/resources'; +import { VSBuffer } from 'vs/base/common/buffer'; export class BrowserWorkspacesService extends Disposable implements IWorkspacesService { @@ -25,6 +31,9 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @ILogService private readonly logService: ILogService, + @IHostService private readonly hostService: IHostService, + @IFileService private readonly fileService: IFileService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { super(); @@ -113,20 +122,41 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS //#region Workspace Management - enterWorkspace(path: URI): Promise { - throw new Error('Untitled workspaces are currently unsupported in Web'); + async enterWorkspace(path: URI): Promise { + + // Open workspace in same window + await this.hostService.openWindow([{ workspaceUri: path }], { forceReuseWindow: true }); + + return { + workspace: await this.getWorkspaceIdentifier(path) + }; } - createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { - throw new Error('Untitled workspaces are currently unsupported in Web'); + async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString(); + const newUntitledWorkspacePath = joinPath(this.environmentService.untitledWorkspacesHome, `${randomId}.${WORKSPACE_EXTENSION}`); + + // Build array of workspace folders to store + const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; + if (folders) { + for (const folder of folders) { + storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, this.environmentService.untitledWorkspacesHome)); + } + } + + // Store at untitled workspaces location + const storedWorkspace: IStoredWorkspace = { folders: storedWorkspaceFolder, remoteAuthority }; + await this.fileService.writeFile(newUntitledWorkspacePath, VSBuffer.fromString(JSON.stringify(storedWorkspace, null, '\t'))); + + return this.getWorkspaceIdentifier(newUntitledWorkspacePath); } deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { - throw new Error('Untitled workspaces are currently unsupported in Web'); + return this.fileService.del(workspace.configPath); } - getWorkspaceIdentifier(workspacePath: URI): Promise { - throw new Error('Untitled workspaces are currently unsupported in Web'); + async getWorkspaceIdentifier(workspacePath: URI): Promise { + return getWorkspaceIdentifier(workspacePath); } //#endregion