Path: blob/main/src/vs/workbench/browser/actions/workspaceActions.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { localize, localize2 } from '../../../nls.js';6import { ITelemetryData } from '../../../platform/telemetry/common/telemetry.js';7import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, hasWorkspaceFileExtension } from '../../../platform/workspace/common/workspace.js';8import { IWorkspaceEditingService } from '../../services/workspaces/common/workspaceEditing.js';9import { IEditorService } from '../../services/editor/common/editorService.js';10import { ICommandService } from '../../../platform/commands/common/commands.js';11import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID, SET_ROOT_FOLDER_COMMAND_ID } from './workspaceCommands.js';12import { IFileDialogService } from '../../../platform/dialogs/common/dialogs.js';13import { MenuRegistry, MenuId, Action2, registerAction2 } from '../../../platform/actions/common/actions.js';14import { EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, OpenFolderWorkspaceSupportContext, WorkbenchStateContext, WorkspaceFolderCountContext } from '../../common/contextkeys.js';15import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';16import { IHostService } from '../../services/host/browser/host.js';17import { KeyChord, KeyCode, KeyMod } from '../../../base/common/keyCodes.js';18import { ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';19import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';20import { IWorkspacesService } from '../../../platform/workspaces/common/workspaces.js';21import { KeybindingWeight } from '../../../platform/keybinding/common/keybindingsRegistry.js';22import { IsMacNativeContext } from '../../../platform/contextkey/common/contextkeys.js';23import { ILocalizedString } from '../../../platform/action/common/action.js';24import { Categories } from '../../../platform/action/common/actionCommonCategories.js';2526const workspacesCategory: ILocalizedString = localize2('workspaces', 'Workspaces');2728export class OpenFileAction extends Action2 {2930static readonly ID = 'workbench.action.files.openFile';3132constructor() {33super({34id: OpenFileAction.ID,35title: localize2('openFile', 'Open File...'),36category: Categories.File,37f1: true,38keybinding: {39when: IsMacNativeContext.toNegated(),40weight: KeybindingWeight.WorkbenchContrib,41primary: KeyMod.CtrlCmd | KeyCode.KeyO42}43});44}4546override async run(accessor: ServicesAccessor, data?: ITelemetryData): Promise<void> {47const fileDialogService = accessor.get(IFileDialogService);4849return fileDialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data });50}51}5253export class OpenFolderAction extends Action2 {5455static readonly ID = 'workbench.action.files.openFolder';5657constructor() {58super({59id: OpenFolderAction.ID,60title: localize2('openFolder', 'Open Folder...'),61category: Categories.File,62f1: true,63precondition: OpenFolderWorkspaceSupportContext,64keybinding: {65weight: KeybindingWeight.WorkbenchContrib,66primary: undefined,67linux: {68primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyO)69},70win: {71primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyO)72}73}74});75}7677override async run(accessor: ServicesAccessor, data?: ITelemetryData): Promise<void> {78const fileDialogService = accessor.get(IFileDialogService);7980return fileDialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });81}82}8384export class OpenFolderViaWorkspaceAction extends Action2 {8586// This action swaps the folders of a workspace with87// the selected folder and is a workaround for providing88// "Open Folder..." in environments that do not support89// this without having a workspace open (e.g. web serverless)9091static readonly ID = 'workbench.action.files.openFolderViaWorkspace';9293constructor() {94super({95id: OpenFolderViaWorkspaceAction.ID,96title: localize2('openFolder', 'Open Folder...'),97category: Categories.File,98f1: true,99precondition: ContextKeyExpr.and(OpenFolderWorkspaceSupportContext.toNegated(), WorkbenchStateContext.isEqualTo('workspace')),100keybinding: {101weight: KeybindingWeight.WorkbenchContrib,102primary: KeyMod.CtrlCmd | KeyCode.KeyO103}104});105}106107override run(accessor: ServicesAccessor): Promise<void> {108const commandService = accessor.get(ICommandService);109110return commandService.executeCommand(SET_ROOT_FOLDER_COMMAND_ID);111}112}113114export class OpenFileFolderAction extends Action2 {115116static readonly ID = 'workbench.action.files.openFileFolder';117static readonly LABEL: ILocalizedString = localize2('openFileFolder', 'Open...');118119constructor() {120super({121id: OpenFileFolderAction.ID,122title: OpenFileFolderAction.LABEL,123category: Categories.File,124f1: true,125precondition: ContextKeyExpr.and(IsMacNativeContext, OpenFolderWorkspaceSupportContext),126keybinding: {127weight: KeybindingWeight.WorkbenchContrib,128primary: KeyMod.CtrlCmd | KeyCode.KeyO129}130});131}132133override async run(accessor: ServicesAccessor, data?: ITelemetryData): Promise<void> {134const fileDialogService = accessor.get(IFileDialogService);135136return fileDialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });137}138}139140class OpenWorkspaceAction extends Action2 {141142static readonly ID = 'workbench.action.openWorkspace';143144constructor() {145super({146id: OpenWorkspaceAction.ID,147title: localize2('openWorkspaceAction', 'Open Workspace from File...'),148category: Categories.File,149f1: true,150precondition: EnterMultiRootWorkspaceSupportContext151});152}153154override async run(accessor: ServicesAccessor, data?: ITelemetryData): Promise<void> {155const fileDialogService = accessor.get(IFileDialogService);156157return fileDialogService.pickWorkspaceAndOpen({ telemetryExtraData: data });158}159}160161class CloseWorkspaceAction extends Action2 {162163static readonly ID = 'workbench.action.closeFolder';164165constructor() {166super({167id: CloseWorkspaceAction.ID,168title: localize2('closeWorkspace', 'Close Workspace'),169category: workspacesCategory,170f1: true,171precondition: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), EmptyWorkspaceSupportContext),172keybinding: {173weight: KeybindingWeight.WorkbenchContrib,174primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyF)175}176});177}178179override async run(accessor: ServicesAccessor): Promise<void> {180const hostService = accessor.get(IHostService);181const environmentService = accessor.get(IWorkbenchEnvironmentService);182183return hostService.openWindow({ forceReuseWindow: true, remoteAuthority: environmentService.remoteAuthority });184}185}186187class OpenWorkspaceConfigFileAction extends Action2 {188189static readonly ID = 'workbench.action.openWorkspaceConfigFile';190191constructor() {192super({193id: OpenWorkspaceConfigFileAction.ID,194title: localize2('openWorkspaceConfigFile', 'Open Workspace Configuration File'),195category: workspacesCategory,196f1: true,197precondition: WorkbenchStateContext.isEqualTo('workspace')198});199}200201override async run(accessor: ServicesAccessor): Promise<void> {202const contextService = accessor.get(IWorkspaceContextService);203const editorService = accessor.get(IEditorService);204205const configuration = contextService.getWorkspace().configuration;206if (configuration) {207await editorService.openEditor({ resource: configuration, options: { pinned: true } });208}209}210}211212export class AddRootFolderAction extends Action2 {213214static readonly ID = 'workbench.action.addRootFolder';215216constructor() {217super({218id: AddRootFolderAction.ID,219title: ADD_ROOT_FOLDER_LABEL,220category: workspacesCategory,221f1: true,222precondition: ContextKeyExpr.or(EnterMultiRootWorkspaceSupportContext, WorkbenchStateContext.isEqualTo('workspace'))223});224}225226override run(accessor: ServicesAccessor): Promise<void> {227const commandService = accessor.get(ICommandService);228229return commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID);230}231}232233export class RemoveRootFolderAction extends Action2 {234235static readonly ID = 'workbench.action.removeRootFolder';236237constructor() {238super({239id: RemoveRootFolderAction.ID,240title: localize2('globalRemoveFolderFromWorkspace', 'Remove Folder from Workspace...'),241category: workspacesCategory,242f1: true,243precondition: ContextKeyExpr.and(WorkspaceFolderCountContext.notEqualsTo('0'), ContextKeyExpr.or(EnterMultiRootWorkspaceSupportContext, WorkbenchStateContext.isEqualTo('workspace')))244});245}246247override async run(accessor: ServicesAccessor): Promise<void> {248const commandService = accessor.get(ICommandService);249const workspaceEditingService = accessor.get(IWorkspaceEditingService);250251const folder = await commandService.executeCommand<IWorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND_ID);252if (folder) {253await workspaceEditingService.removeFolders([folder.uri]);254}255}256}257258class SaveWorkspaceAsAction extends Action2 {259260static readonly ID = 'workbench.action.saveWorkspaceAs';261262constructor() {263super({264id: SaveWorkspaceAsAction.ID,265title: localize2('saveWorkspaceAsAction', 'Save Workspace As...'),266category: workspacesCategory,267f1: true,268precondition: EnterMultiRootWorkspaceSupportContext269});270}271272override async run(accessor: ServicesAccessor): Promise<void> {273const workspaceEditingService = accessor.get(IWorkspaceEditingService);274const contextService = accessor.get(IWorkspaceContextService);275276const configPathUri = await workspaceEditingService.pickNewWorkspacePath();277if (configPathUri && hasWorkspaceFileExtension(configPathUri)) {278switch (contextService.getWorkbenchState()) {279case WorkbenchState.EMPTY:280case WorkbenchState.FOLDER: {281const folders = contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri }));282return workspaceEditingService.createAndEnterWorkspace(folders, configPathUri);283}284case WorkbenchState.WORKSPACE:285return workspaceEditingService.saveAndEnterWorkspace(configPathUri);286}287}288}289}290291class DuplicateWorkspaceInNewWindowAction extends Action2 {292293static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow';294295constructor() {296super({297id: DuplicateWorkspaceInNewWindowAction.ID,298title: localize2('duplicateWorkspaceInNewWindow', 'Duplicate As Workspace in New Window'),299category: workspacesCategory,300f1: true,301precondition: EnterMultiRootWorkspaceSupportContext302});303}304305override async run(accessor: ServicesAccessor): Promise<void> {306const workspaceContextService = accessor.get(IWorkspaceContextService);307const workspaceEditingService = accessor.get(IWorkspaceEditingService);308const hostService = accessor.get(IHostService);309const workspacesService = accessor.get(IWorkspacesService);310const environmentService = accessor.get(IWorkbenchEnvironmentService);311312const folders = workspaceContextService.getWorkspace().folders;313const remoteAuthority = environmentService.remoteAuthority;314315const newWorkspace = await workspacesService.createUntitledWorkspace(folders, remoteAuthority);316await workspaceEditingService.copyWorkspaceSettings(newWorkspace);317318return hostService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true, remoteAuthority });319}320}321322// --- Actions Registration323324registerAction2(AddRootFolderAction);325registerAction2(RemoveRootFolderAction);326registerAction2(OpenFileAction);327registerAction2(OpenFolderAction);328registerAction2(OpenFolderViaWorkspaceAction);329registerAction2(OpenFileFolderAction);330registerAction2(OpenWorkspaceAction);331registerAction2(OpenWorkspaceConfigFileAction);332registerAction2(CloseWorkspaceAction);333registerAction2(SaveWorkspaceAsAction);334registerAction2(DuplicateWorkspaceInNewWindowAction);335336// --- Menu Registration337338MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {339group: '2_open',340command: {341id: OpenFileAction.ID,342title: localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")343},344order: 1,345when: IsMacNativeContext.toNegated()346});347348MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {349group: '2_open',350command: {351id: OpenFolderAction.ID,352title: localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")353},354order: 2,355when: OpenFolderWorkspaceSupportContext356});357358MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {359group: '2_open',360command: {361id: OpenFolderViaWorkspaceAction.ID,362title: localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")363},364order: 2,365when: ContextKeyExpr.and(OpenFolderWorkspaceSupportContext.toNegated(), WorkbenchStateContext.isEqualTo('workspace'))366});367368MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {369group: '2_open',370command: {371id: OpenFileFolderAction.ID,372title: localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")373},374order: 1,375when: ContextKeyExpr.and(IsMacNativeContext, OpenFolderWorkspaceSupportContext)376});377378MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {379group: '2_open',380command: {381id: OpenWorkspaceAction.ID,382title: localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace from File...")383},384order: 3,385when: EnterMultiRootWorkspaceSupportContext386});387388MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {389group: '3_workspace',390command: {391id: ADD_ROOT_FOLDER_COMMAND_ID,392title: localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...")393},394when: ContextKeyExpr.or(EnterMultiRootWorkspaceSupportContext, WorkbenchStateContext.isEqualTo('workspace')),395order: 1396});397398MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {399group: '3_workspace',400command: {401id: SaveWorkspaceAsAction.ID,402title: localize('miSaveWorkspaceAs', "Save Workspace As...")403},404order: 2,405when: EnterMultiRootWorkspaceSupportContext406});407408MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {409group: '3_workspace',410command: {411id: DuplicateWorkspaceInNewWindowAction.ID,412title: localize('duplicateWorkspace', "Duplicate Workspace")413},414order: 3,415when: EnterMultiRootWorkspaceSupportContext416});417418MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {419group: '6_close',420command: {421id: CloseWorkspaceAction.ID,422title: localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder")423},424order: 3,425when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), EmptyWorkspaceSupportContext)426});427428MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {429group: '6_close',430command: {431id: CloseWorkspaceAction.ID,432title: localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace")433},434order: 3,435when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), EmptyWorkspaceSupportContext)436});437438439