From b9bf66bbb2a63be071c62e14ce13e87acf1dc595 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Sep 2019 10:49:40 +0200 Subject: [PATCH] web - limit workspace actions --- src/vs/code/electron-main/app.ts | 6 +- src/vs/code/electron-main/window.ts | 3 +- src/vs/code/electron-main/windows.ts | 4 +- .../electron-main/historyMainService.ts | 3 +- .../launch/electron-main/launchService.ts | 2 +- .../storage/browser/storageService.ts | 2 +- .../platform/workspaces/common/workspaces.ts | 23 +-- .../{node => electron-main}/workspacesIpc.ts | 3 +- .../electron-main/workspacesMainService.ts | 28 ++- .../browser/actions/workspaceActions.ts | 175 ++++++------------ src/vs/workbench/browser/contextkeys.ts | 6 +- .../workbench/browser/web.simpleservices.ts | 27 +-- .../files/browser/fileActions.contribution.ts | 7 +- .../actions/workspaceActions.ts | 97 ++++++++++ .../electron-browser/desktop.contribution.ts | 21 +-- .../workspace/browser/workspacesService.ts | 27 +++ .../electron-browser/workspacesService.ts | 3 + src/vs/workbench/workbench.desktop.main.ts | 4 +- src/vs/workbench/workbench.web.main.ts | 1 + 19 files changed, 236 insertions(+), 206 deletions(-) rename src/vs/platform/workspaces/{node => electron-main}/workspacesIpc.ts (88%) create mode 100644 src/vs/workbench/electron-browser/actions/workspaceActions.ts create mode 100644 src/vs/workbench/services/workspace/browser/workspacesService.ts rename src/vs/{platform/workspaces => workbench/services/workspace}/electron-browser/workspacesService.ts (91%) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 381ce92c9a1a8..1da45597f35c9 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -39,8 +39,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; import { URI } from 'vs/base/common/uri'; -import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc'; -import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { WorkspacesChannel } from 'vs/platform/workspaces/electron-main/workspacesIpc'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { getMachineId } from 'vs/base/node/id'; import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32'; import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; @@ -69,7 +69,7 @@ import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainSe import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; import { URLService } from 'vs/platform/url/node/urlService'; -import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { WorkspacesMainService, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { statSync } from 'fs'; import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc'; import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 0ccf51f097806..13224fe747035 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -17,7 +17,8 @@ import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, g import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import * as perf from 'vs/base/common/performance'; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 94745e3b84204..383604d2c87d1 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -25,7 +25,7 @@ import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/ import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService, IRecent } from 'vs/platform/history/common/history'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } 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'; @@ -36,7 +36,7 @@ import { exists, dirExists } from 'vs/base/node/pfs'; import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; -import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { getWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 1febed0c265ab..e71c67be003ef 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -12,7 +12,8 @@ import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IHistoryMainService, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/history/common/history'; import { ThrottledDelayer } from 'vs/base/common/async'; import { isEqual as areResourcesEqual, dirname, originalFSPath, basename } from 'vs/base/common/resources'; diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 876d2198d1fce..bb3e497e3d288 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -12,7 +12,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { OpenContext, IWindowSettings } from 'vs/platform/windows/common/windows'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { whenDeleted } from 'vs/base/node/pfs'; -import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron'; diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 3de0f8f072664..67f3e6ac6d021 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -150,7 +150,7 @@ export class BrowserStorageService extends Disposable implements IStorageService } async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { - // TODO@ben implement storage migration in web + throw new Error('Migrating storage is currently unsupported in Web'); } close(): void { diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index a1b5c0aadb30f..3f0352cf73927 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -5,7 +5,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; -import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { URI, UriComponents } from 'vs/base/common/uri'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; @@ -19,9 +18,6 @@ import { toSlashes } from 'vs/base/common/extpath'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -export const IWorkspacesMainService = createDecorator('workspacesMainService'); -export const IWorkspacesService = createDecorator('workspacesService'); - export const WORKSPACE_EXTENSION = 'code-workspace'; export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }]; export const UNTITLED_WORKSPACE_NAME = 'workspace.json'; @@ -95,23 +91,10 @@ export interface IUntitledWorkspaceInfo { remoteAuthority?: string; } -export interface IWorkspacesMainService extends IWorkspacesService { - _serviceBrand: undefined; - - onUntitledWorkspaceDeleted: Event; - - createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; - - resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; - - isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; - - deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; - - getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; -} +export const IWorkspacesService = createDecorator('workspacesService'); export interface IWorkspacesService { + _serviceBrand: undefined; createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; @@ -276,4 +259,4 @@ export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolea return false; } return true; -} \ No newline at end of file +} diff --git a/src/vs/platform/workspaces/node/workspacesIpc.ts b/src/vs/platform/workspaces/electron-main/workspacesIpc.ts similarity index 88% rename from src/vs/platform/workspaces/node/workspacesIpc.ts rename to src/vs/platform/workspaces/electron-main/workspacesIpc.ts index 61989e2eeb967..96fd72dc80b8d 100644 --- a/src/vs/platform/workspaces/node/workspacesIpc.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesIpc.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index e23da4f94ac27..6eaaba5d823d6 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; @@ -18,12 +18,38 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { originalFSPath, isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export interface IStoredWorkspace { folders: IStoredWorkspaceFolder[]; remoteAuthority?: string; } +export const IWorkspacesMainService = createDecorator('workspacesMainService'); + +export interface IWorkspacesMainService { + + _serviceBrand: undefined; + + onUntitledWorkspaceDeleted: Event; + + createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; + + resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; + + isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; + + deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; + + getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; + + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; + + deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; + + getWorkspaceIdentifier(workspacePath: URI): Promise; +} + export class WorkspacesMainService extends Disposable implements IWorkspacesMainService { _serviceBrand: undefined; diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 1d6ee2b796fed..8818bc1131c1d 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -5,24 +5,22 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; 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 { INotificationService } from 'vs/platform/notification/common/notification'; import { Schemas } from 'vs/base/common/network'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { toResource } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { WorkbenchStateContext, SupportsWorkspacesContext } 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'; export class OpenFileAction extends Action { @@ -102,7 +100,6 @@ export namespace OpenLocalFolderCommand { } } - export class OpenFileFolderAction extends Action { static readonly ID = 'workbench.action.files.openFileFolder'; @@ -134,84 +131,6 @@ export namespace OpenLocalFileFolderCommand { } } -export class AddRootFolderAction extends Action { - - static readonly ID = 'workbench.action.addRootFolder'; - static LABEL = ADD_ROOT_FOLDER_LABEL; - - constructor( - id: string, - label: string, - @ICommandService private readonly commandService: ICommandService - ) { - super(id, label); - } - - run(): Promise { - return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID); - } -} - -export class GlobalRemoveRootFolderAction extends Action { - - static readonly ID = 'workbench.action.removeRootFolder'; - static LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."); - - constructor( - id: string, - label: string, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ICommandService private readonly commandService: ICommandService - ) { - super(id, label); - } - - async run(): Promise { - const state = this.contextService.getWorkbenchState(); - - // Workspace / Folder - if (state === WorkbenchState.WORKSPACE || state === WorkbenchState.FOLDER) { - const folder = await this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); - if (folder) { - await this.workspaceEditingService.removeFolders([folder.uri]); - } - } - - return true; - } -} - -export class SaveWorkspaceAsAction extends Action { - - static readonly ID = 'workbench.action.saveWorkspaceAs'; - static 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 OpenWorkspaceAction extends Action { static readonly ID = 'workbench.action.openWorkspace'; @@ -230,93 +149,103 @@ export class OpenWorkspaceAction extends Action { } } -export class CloseWorkspaceAction extends Action { +export class OpenWorkspaceConfigFileAction extends Action { - static readonly ID = 'workbench.action.closeFolder'; - static LABEL = nls.localize('closeWorkspace', "Close Workspace"); + static readonly ID = 'workbench.action.openWorkspaceConfigFile'; + static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File"); constructor( id: string, label: string, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IEditorService private readonly editorService: IEditorService ) { super(id, label); - } - run(): Promise { - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close.")); + this.enabled = !!this.workspaceContextService.getWorkspace().configuration; + } - return Promise.resolve(undefined); + run(): Promise { + const configuration = this.workspaceContextService.getWorkspace().configuration; + if (configuration) { + return this.editorService.openEditor({ resource: configuration }); } - - return this.windowService.closeWorkspace(); + return Promise.resolve(); } } -export class OpenWorkspaceConfigFileAction extends Action { +export class AddRootFolderAction extends Action { - static readonly ID = 'workbench.action.openWorkspaceConfigFile'; - static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File"); + static readonly ID = 'workbench.action.addRootFolder'; + static LABEL = ADD_ROOT_FOLDER_LABEL; constructor( id: string, label: string, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IEditorService private readonly editorService: IEditorService + @ICommandService private readonly commandService: ICommandService ) { super(id, label); - - this.enabled = !!this.workspaceContextService.getWorkspace().configuration; } run(): Promise { - const configuration = this.workspaceContextService.getWorkspace().configuration; - if (configuration) { - return this.editorService.openEditor({ resource: configuration }); - } - return Promise.resolve(); + return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID); } } -export class DuplicateWorkspaceInNewWindowAction extends Action { +export class GlobalRemoveRootFolderAction extends Action { - static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow'; - static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window"); + static readonly ID = 'workbench.action.removeRootFolder'; + static LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."); constructor( id: string, label: string, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, - @IWindowService private readonly windowService: IWindowService, - @IWorkspacesService private readonly workspacesService: IWorkspacesService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ICommandService private readonly commandService: ICommandService ) { super(id, label); } async run(): Promise { - const folders = this.workspaceContextService.getWorkspace().folders; - const remoteAuthority = this.environmentService.configuration.remoteAuthority; + const state = this.contextService.getWorkbenchState(); - const newWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); - await this.workspaceEditingService.copyWorkspaceSettings(newWorkspace); + // Workspace / Folder + if (state === WorkbenchState.WORKSPACE || state === WorkbenchState.FOLDER) { + const folder = await this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); + if (folder) { + await this.workspaceEditingService.removeFolders([folder.uri]); + } + } - return this.windowService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); + return true; } } -// --- Menu Registration +// --- 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); + +// --- Menu Registration + CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); }); +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '3_workspace', + command: { + id: ADD_ROOT_FOLDER_COMMAND_ID, + title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") + }, + order: 1, + when: SupportsWorkspacesContext +}); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OpenWorkspaceConfigFileAction.ID, diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index c637469533c15..34640af8e1e21 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -141,8 +141,10 @@ export class WorkbenchContextKeysHandler extends Disposable { // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); - // File Pickers - SupportsWorkspacesContext.bindTo(this.contextKeyService); + // 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); diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 28a8ee424ac0e..f434f18446b27 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService, IOpenSettings, IWindowSettings } from 'vs/platform/windows/common/windows'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened, IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -609,28 +609,3 @@ export class SimpleWindowsService implements IWindowsService { registerSingleton(IWindowsService, SimpleWindowsService); //#endregion - -//#region Workspaces - -export class SimpleWorkspacesService implements IWorkspacesService { - - _serviceBrand: undefined; - - createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { - return Promise.resolve(undefined); - } - - getWorkspaceIdentifier(workspacePath: URI): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } -} - -registerSingleton(IWorkspacesService, SimpleWorkspacesService); - -//#endregion diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 9a335b9a22bf3..ff484b1f19cfe 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -47,7 +47,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, const workspacesCategory = nls.localize('workspaces', "Workspaces"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, SupportsWorkspacesContext); +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); // Commands CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand); @@ -520,7 +520,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: REMOVE_ROOT_FOLDER_COMMAND_ID, title: REMOVE_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext, SupportsWorkspacesContext) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -640,8 +640,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { id: OpenWorkspaceAction.ID, title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") }, - order: 3, - when: SupportsWorkspacesContext + order: 3 }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { diff --git a/src/vs/workbench/electron-browser/actions/workspaceActions.ts b/src/vs/workbench/electron-browser/actions/workspaceActions.ts new file mode 100644 index 0000000000000..c01580637dd6c --- /dev/null +++ b/src/vs/workbench/electron-browser/actions/workspaceActions.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; + +export class SaveWorkspaceAsAction extends Action { + + static readonly ID = 'workbench.action.saveWorkspaceAs'; + static 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, + @IWindowService private readonly windowService: IWindowService, + @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.windowService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); + } +} + +export class CloseWorkspaceAction extends Action { + + static readonly ID = 'workbench.action.closeFolder'; + static LABEL = nls.localize('closeWorkspace', "Close Workspace"); + + constructor( + id: string, + label: string, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @INotificationService private readonly notificationService: INotificationService, + @IWindowService private readonly windowService: IWindowService + ) { + super(id, label); + } + + run(): Promise { + if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close.")); + + return Promise.resolve(undefined); + } + + return this.windowService.closeWorkspace(); + } +} diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index f76e8ca3a2237..d80487841b90f 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, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { ToggleSharedProcessAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; -import { AddRootFolderAction, GlobalRemoveRootFolderAction, SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } 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 { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; @@ -31,7 +30,7 @@ import { IWindowService, IWindowsService } from 'vs/platform/windows/common/wind (function registerFileActions(): void { const fileCategory = nls.localize('file', "File"); - registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory, SupportsWorkspacesContext); })(); // Actions: View @@ -78,10 +77,8 @@ import { IWindowService, IWindowsService } from 'vs/platform/windows/common/wind (function registerWorkspaceActions(): void { 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); 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); + registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory, SupportsWorkspacesContext); })(); // Actions: macOS Native Tabs @@ -133,16 +130,6 @@ import { IWindowService, IWindowsService } from 'vs/platform/windows/common/wind order: 2 }); - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: ADD_ROOT_FOLDER_COMMAND_ID, - title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") - }, - order: 1, - when: SupportsWorkspacesContext - }); - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '3_workspace', command: { @@ -171,7 +158,7 @@ import { IWindowService, IWindowsService } from 'vs/platform/windows/common/wind title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") }, order: 3, - when: WorkbenchStateContext.isEqualTo('workspace') + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), SupportsWorkspacesContext) }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { diff --git a/src/vs/workbench/services/workspace/browser/workspacesService.ts b/src/vs/workbench/services/workspace/browser/workspacesService.ts new file mode 100644 index 0000000000000..01cadaf783086 --- /dev/null +++ b/src/vs/workbench/services/workspace/browser/workspacesService.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { URI } from 'vs/base/common/uri'; + +export class WorkspacesService implements IWorkspacesService { + + _serviceBrand: undefined; + + async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + throw new Error('Untitled workspaces are currently unsupported in Web'); + } + + async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + throw new Error('Untitled workspaces are currently unsupported in Web'); + } + + async getWorkspaceIdentifier(workspacePath: URI): Promise { + throw new Error('Untitled workspaces are currently unsupported in Web'); + } +} + +registerSingleton(IWorkspacesService, WorkspacesService, true); diff --git a/src/vs/platform/workspaces/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts similarity index 91% rename from src/vs/platform/workspaces/electron-browser/workspacesService.ts rename to src/vs/workbench/services/workspace/electron-browser/workspacesService.ts index 6c7f5fb824e43..a205d676926cf 100644 --- a/src/vs/platform/workspaces/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts @@ -7,6 +7,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class WorkspacesService implements IWorkspacesService { @@ -30,3 +31,5 @@ export class WorkspacesService implements IWorkspacesService { return this.channel.call('getWorkspaceIdentifier', configPath).then(reviveWorkspaceIdentifier); } } + +registerSingleton(IWorkspacesService, WorkspacesService, true); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 014864a31e84e..8cfb27e7fa170 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -48,6 +48,7 @@ import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/credentials/node/credentialsService'; import 'vs/workbench/services/url/electron-browser/urlService'; +import 'vs/workbench/services/workspace/electron-browser/workspacesService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -65,8 +66,6 @@ import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; @@ -78,7 +77,6 @@ registerSingleton(ISharedProcessService, SharedProcessService, true); registerSingleton(IWindowsService, WindowsService); registerSingleton(IUpdateService, UpdateService); registerSingleton(IIssueService, IssueService); -registerSingleton(IWorkspacesService, WorkspacesService); registerSingleton(IMenubarService, MenubarService); //#endregion diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 0c012cca0d730..8c8a470aa8a9c 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -40,6 +40,7 @@ import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/services/update/browser/updateService'; import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; +import 'vs/workbench/services/workspace/browser/workspacesService'; import 'vs/workbench/browser/web.simpleservices'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';