diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 0f4cc81f9da..db8e52ceaf4 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -31,10 +31,17 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IUserDataProfilesEditor } from 'vs/workbench/contrib/userDataProfile/common/userDataProfile'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; export const OpenProfileMenu = new MenuId('OpenProfile'); +const CONFIG_ENABLE_NEW_PROFILES_UI = 'workbench.experimental.enableNewProfilesUI'; +const CONTEXT_ENABLE_NEW_PROFILES_UI = ContextKeyExpr.equals('config.workbench.experimental.enableNewProfilesUI', true); export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -54,6 +61,10 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IProductService private readonly productService: IProductService, @ILifecycleService private readonly lifecycleService: ILifecycleService, ) { super(); @@ -73,6 +84,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1); this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1))); + this.registerConfiguration(); this.registerEditor(); this.registerActions(); @@ -83,6 +95,29 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.reportWorkspaceProfileInfo(); } + private openProfilesEditor(): Promise { + return this.editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(this.instantiationService)); + } + + private isNewProfilesUIEnabled(): boolean { + return this.configurationService.getValue(CONFIG_ENABLE_NEW_PROFILES_UI) === true; + } + + private registerConfiguration(): void { + Registry.as(Extensions.Configuration) + .registerConfiguration({ + ...workbenchConfigurationNodeBase, + properties: { + [CONFIG_ENABLE_NEW_PROFILES_UI]: { + type: 'boolean', + description: localize('enable new profiles UI', "Enables the new profiles UI."), + default: this.productService.quality !== 'stable', + scope: ConfigurationScope.APPLICATION, + } + } + }); + } + private registerEditor(): void { Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( @@ -120,6 +155,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const getProfilesTitle = () => { return localize('profiles', "Profile ({0})", this.userDataProfileService.currentProfile.name); }; + const when = ContextKeyExpr.or(CONTEXT_ENABLE_NEW_PROFILES_UI.negate(), HAS_PROFILES_CONTEXT); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { get title() { return getProfilesTitle(); @@ -127,7 +163,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements submenu: ProfilesMenu, group: '2_configuration', order: 1, - when: HAS_PROFILES_CONTEXT, + when, }); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { get title() { @@ -136,7 +172,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements submenu: ProfilesMenu, group: '2_configuration', order: 1, - when: HAS_PROFILES_CONTEXT, + when, }); } @@ -269,6 +305,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private readonly currentprofileActionsDisposable = this._register(new MutableDisposable()); private registerCurrentProfilesActions(): void { this.currentprofileActionsDisposable.value = new DisposableStore(); + this.currentprofileActionsDisposable.value.add(this.registerEditCurrentProfileAction()); + this.currentprofileActionsDisposable.value.add(this.registerShowCurrentProfileContentsAction()); this.currentprofileActionsDisposable.value.add(this.registerExportCurrentProfileAction()); this.currentprofileActionsDisposable.value.add(this.registerImportProfileAction()); } @@ -287,12 +325,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements { id: MenuId.GlobalActivity, group: '2_configuration', - order: 1 + order: 1, + when: CONTEXT_ENABLE_NEW_PROFILES_UI, }, { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', - order: 1 + order: 1, + when: CONTEXT_ENABLE_NEW_PROFILES_UI, }, ] }); @@ -308,12 +348,69 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements id: 'workbench.profiles.actions.manageProfiles', category: Categories.Preferences, title: localize2('open profiles', "Open Profiles (UI)"), + precondition: CONTEXT_ENABLE_NEW_PROFILES_UI, }, })); return disposables; } + private registerEditCurrentProfileAction(): IDisposable { + const that = this; + return registerAction2(class RenameCurrentProfileAction extends Action2 { + constructor() { + const precondition = ContextKeyExpr.and(ContextKeyExpr.notEquals(CURRENT_PROFILE_CONTEXT.key, that.userDataProfilesService.defaultProfile.id), IS_CURRENT_PROFILE_TRANSIENT_CONTEXT.toNegated()); + super({ + id: `workbench.profiles.actions.editCurrentProfile`, + title: localize2('edit profile', "Edit Profile..."), + precondition, + f1: true, + menu: [ + { + id: ProfilesMenu, + group: '2_manage_current', + when: ContextKeyExpr.and(precondition, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()), + order: 2 + } + ] + }); + } + run(accessor: ServicesAccessor) { + if (that.isNewProfilesUIEnabled()) { + return that.openProfilesEditor(); + } else { + return that.userDataProfileImportExportService.editProfile(that.userDataProfileService.currentProfile); + } + } + }); + } + + private registerShowCurrentProfileContentsAction(): IDisposable { + const id = 'workbench.profiles.actions.showProfileContents'; + return registerAction2(class ShowProfileContentsAction extends Action2 { + constructor() { + super({ + id, + title: localize2('show profile contents', "Show Profile Contents"), + category: PROFILES_CATEGORY, + menu: [ + { + id: ProfilesMenu, + group: '2_manage_current', + order: 3, + when: CONTEXT_ENABLE_NEW_PROFILES_UI.negate() + } + ] + }); + } + + async run(accessor: ServicesAccessor) { + const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); + return userDataProfileImportExportService.showProfileContents(); + } + }); + } + private registerExportCurrentProfileAction(): IDisposable { const that = this; const disposables = new DisposableStore(); @@ -322,22 +419,37 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id, - title: localize2('export profile', "Export Profile ({0})...", that.userDataProfileService.currentProfile.name), + title: localize2('export profile', "Export Profile..."), category: PROFILES_CATEGORY, precondition: IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT.toNegated(), - f1: true, - menu: { - id: MenuId.MenubarShare - } + menu: [ + { + id: ProfilesMenu, + group: '4_import_export_profiles', + order: 1, + when: CONTEXT_ENABLE_NEW_PROFILES_UI.negate(), + }, { + id: MenuId.CommandPalette + } + ] }); } async run(accessor: ServicesAccessor) { - const editorGroupsService = accessor.get(IEditorGroupsService); - const instantiationService = accessor.get(IInstantiationService); - return editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(instantiationService)); + if (that.isNewProfilesUIEnabled()) { + return that.openProfilesEditor(); + } else { + return that.userDataProfileImportExportService.exportProfile2(); + } } })); + disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarShare, { + command: { + id, + title: localize2('export profile in share', "Export Profile ({0})...", that.userDataProfileService.currentProfile.name), + precondition: PROFILES_ENABLEMENT_CONTEXT, + }, + })); return disposables; } @@ -352,7 +464,17 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements title: localize2('import profile', "Import Profile..."), category: PROFILES_CATEGORY, precondition: IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT.toNegated(), - f1: true, + menu: [ + { + id: ProfilesMenu, + group: '4_import_export_profiles', + when: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()), + order: 2 + }, { + id: MenuId.CommandPalette, + when: PROFILES_ENABLEMENT_CONTEXT, + } + ] }); } @@ -454,6 +576,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } private registerCreateProfileAction(): void { + const that = this; this._register(registerAction2(class CreateProfileAction extends Action2 { constructor() { super({ @@ -462,13 +585,23 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements category: PROFILES_CATEGORY, precondition: PROFILES_ENABLEMENT_CONTEXT, f1: true, + menu: [ + { + id: ProfilesMenu, + group: '3_manage_profiles', + when: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()), + order: 1 + } + ] }); } async run(accessor: ServicesAccessor) { - const editorGroupsService = accessor.get(IEditorGroupsService); - const instantiationService = accessor.get(IInstantiationService); - return editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(instantiationService)); + if (that.isNewProfilesUIEnabled()) { + return that.openProfilesEditor(); + } else { + return that.userDataProfileImportExportService.createProfile(); + } } })); } @@ -482,6 +615,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements category: PROFILES_CATEGORY, f1: true, precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT), + menu: [ + { + id: ProfilesMenu, + group: '3_manage_profiles', + when: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, CONTEXT_ENABLE_NEW_PROFILES_UI.negate()), + order: 2 + } + ] }); } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 1d5217c67d3..6a28cadf448 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -80,6 +80,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import type { IHoverWidget } from 'vs/base/browser/ui/hover/hover'; import { IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService'; +import { ILogService } from 'vs/platform/log/common/log'; interface IUserDataProfileTemplate { readonly name: string; @@ -103,6 +104,7 @@ function isUserDataProfileTemplate(thing: unknown): thing is IUserDataProfileTem && (isUndefined(candidate.extensions) || typeof candidate.extensions === 'string')); } +const EXPORT_PROFILE_PREVIEW_VIEW = 'workbench.views.profiles.export.preview'; const IMPORT_PROFILE_PREVIEW_VIEW = 'workbench.views.profiles.import.preview'; export class UserDataProfileImportExportService extends Disposable implements IUserDataProfileImportExportService, IURLHandler { @@ -141,6 +143,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU @ITelemetryService private readonly telemetryService: ITelemetryService, @IContextViewService private readonly contextViewService: IContextViewService, @IHoverService private readonly hoverService: IHoverService, + @ILogService private readonly logService: ILogService, ) { super(); this.registerProfileContentHandler(Schemas.file, this.fileUserDataProfileContentHandler = instantiationService.createInstance(FileUserDataProfileContentHandler)); @@ -190,6 +193,15 @@ export class UserDataProfileImportExportService extends Disposable implements IU this.profileContentHandlers.delete(id); } + async exportProfile2(): Promise { + if (this.isProfileExportInProgressContextKey.get()) { + this.logService.warn('Profile export already in progress.'); + return; + } + + return this.showProfileContents(); + } + async importProfile(uri: URI, options?: IProfileImportOptions): Promise { if (this.isProfileImportInProgressContextKey.get()) { this.notificationService.warn('Profile import already in progress.'); @@ -226,6 +238,10 @@ export class UserDataProfileImportExportService extends Disposable implements IU return this.saveProfile(undefined, from); } + editProfile(profile: IUserDataProfile): Promise { + return this.saveProfile(profile); + } + async createFromProfile(from: IUserDataProfile, options: IUserDataProfileCreateOptions, token: CancellationToken): Promise { const disposables = new DisposableStore(); let creationPromise: CancelablePromise; @@ -643,6 +659,36 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } + async showProfileContents(): Promise { + const view = this.viewsService.getViewWithId(EXPORT_PROFILE_PREVIEW_VIEW); + if (view) { + this.viewsService.openView(view.id, true); + return; + } + const disposables = new DisposableStore(); + try { + const userDataProfilesExportState = disposables.add(this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile, undefined)); + const barrier = new Barrier(); + const exportAction = new BarrierAction(barrier, new Action('export', localize('export', "Export"), undefined, true, async () => { + exportAction.enabled = false; + try { + await this.doExportProfile(userDataProfilesExportState, EXPORT_PROFILE_PREVIEW_VIEW); + } catch (error) { + exportAction.enabled = true; + this.notificationService.error(error); + throw error; + } + }), this.notificationService); + const closeAction = new BarrierAction(barrier, new Action('close', localize('close', "Close")), this.notificationService); + await this.showProfilePreviewView(EXPORT_PROFILE_PREVIEW_VIEW, userDataProfilesExportState.profile.name, exportAction, closeAction, true, userDataProfilesExportState); + disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(e => barrier.open())); + await barrier.wait(); + await this.hideProfilePreviewView(EXPORT_PROFILE_PREVIEW_VIEW); + } finally { + disposables.dispose(); + } + } + async exportProfile(profile: IUserDataProfile): Promise { const disposables = new DisposableStore(); try { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 2e98469a163..e5c260e0510 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -101,8 +101,11 @@ export interface IUserDataProfileImportExportService { resolveProfileTemplate(uri: URI): Promise; exportProfile(profile: IUserDataProfile): Promise; + exportProfile2(): Promise; importProfile(uri: URI, options?: IProfileImportOptions): Promise; + showProfileContents(): Promise; createProfile(from?: IUserDataProfile | URI): Promise; + editProfile(profile: IUserDataProfile): Promise; createFromProfile(from: IUserDataProfile, options: IUserDataProfileCreateOptions, token: CancellationToken): Promise; createProfileFromTemplate(profileTemplate: IUserDataProfileTemplate, options: IUserDataProfileCreateOptions, token: CancellationToken): Promise; createTroubleshootProfile(): Promise;