diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 5c9916b3e2c..b5a1d51f900 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -768,7 +768,7 @@ export class ExtensionsScanner extends Disposable { } } - private async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { + async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { try { if (profileLocation) { const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation }); @@ -921,13 +921,9 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask { - const installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation, this.options.productVersion); - return installed.find(i => areSameExtensions(i.identifier, this.identifier)); - } - protected async doRun(token: CancellationToken): Promise { - const existingExtension = await this.getExistingExtension(); + const installed = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation, this.options.productVersion); + const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.identifier)); if (existingExtension) { this._operation = InstallOperation.Update; } @@ -1018,7 +1014,7 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask .sash-container, +.profiles-editor .monaco-split-view2.separator-border.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before { + top: 55px; +} + .profiles-editor .contents-container { padding: 0px 20px; height: 100%; @@ -42,8 +47,20 @@ padding: 0 4px; } +.profiles-editor .monaco-list-row .profile-tree-item-actions-container { + display: none; +} + +.profiles-editor .monaco-list-row.focused .profile-tree-item-actions-container, +.profiles-editor .monaco-list-row.selected .profile-tree-item-actions-container, +.profiles-editor .monaco-list-row:hover .profile-tree-item-actions-container { + flex: 1; + display: flex; + align-items: center; +} + .profiles-editor .sidebar-container .profiles-list { - margin-top: 10px; + margin-top: 15px; } .profiles-editor .sidebar-container .profiles-list .profile-list-item { @@ -68,6 +85,11 @@ opacity: 0.7; } +.profiles-editor .sidebar-container .profiles-list .profile-list-item .profile-tree-item-actions-container { + justify-content: flex-end; + margin-right: 10px; +} + .profiles-editor .hide { display: none !important; } @@ -82,18 +104,18 @@ flex: 1; display: flex; align-items: center; - font-size: large; + font-size: medium; } .profiles-editor .contents-container .profile-title-container .codicon { cursor: pointer; font-size: large; - padding: 6px; + padding: 4px; margin-right: 8px; } .profiles-editor .contents-container .profile-title-container .monaco-inputbox { - width: 330px; + width: 340px; } .profiles-editor .contents-container .profile-header .profile-button-container { @@ -102,25 +124,15 @@ } .profiles-editor .contents-container .profile-header .profile-button-container .monaco-button { - margin-right: 4px; + margin-left: 4px; } .profiles-editor .contents-container .profile-header .profile-actions-container { display: flex; - height: 28px; -} - -.profiles-editor .contents-container .profile-header .profile-actions-container .actions-container { - gap: 4px; -} - -.profiles-editor .contents-container .profile-header .profile-actions-container .actions-container .codicon { - font-size: 18px; - padding: 6px; } .profiles-editor .contents-container .profile-header .profile-actions-container .profile-button-container { - margin-left:6px; + margin-right: 6px; min-width: 120px; } @@ -129,6 +141,10 @@ padding-right: 10px; } +.profiles-editor .contents-container .profile-header .profile-actions-container .actions-container .action-label { + padding: 6px; +} + .profiles-editor .contents-container .profile-body { margin-top: 20px; } @@ -142,15 +158,15 @@ .profiles-editor .contents-container .profile-select-container > .monaco-select-box { cursor: pointer; - line-height: 17px; - padding: 2px 23px 2px 8px; + line-height: 18px; + padding: 0px 23px 0px 8px; border-radius: 2px; } .profiles-editor .contents-container .profile-copy-from-container { display: flex; align-items: center; - margin: 0px 0px 20px 28px; + margin: 0px 0px 20px 36px; } .profiles-editor .contents-container .profile-copy-from-container > .profile-copy-from-label { @@ -166,35 +182,60 @@ .profiles-editor .contents-container .profile-use-as-default-container { display: flex; align-items: center; - margin: 25px 20px 20px 28px; + margin: 0px 20px 15px 6px; cursor: pointer; } +.profiles-editor .contents-container .profile-use-as-default-container .profile-use-as-default-label { + margin-left: 2px; +} + .profiles-editor .contents-container .profile-contents-container { margin: 0px 0px 10px 20px; } +.profiles-editor .contents-container .profile-content-tree-header, +.profiles-editor .contents-container .profile-content-tree { + margin-left: 6px; + max-width: 450px; +} + +.profiles-editor .contents-container .profile-content-tree-header { + display: flex; + height: 24px; + align-items: center; + margin-bottom: 2px; + background-color: var(--vscode-keybindingTable-headerBackground); + font-weight: bold; +} + .profiles-editor .contents-container .profile-tree-item-container { display: flex; align-items: center; } -.profiles-editor .contents-container .profile-tree-item-container > .profile-resource-type-label { +.profiles-editor .contents-container .profile-content-tree-header :first-child { width: 200px; } +.profiles-editor .contents-container .profile-tree-item-container > .profile-resource-type-label, +.profiles-editor .contents-container .profile-tree-item-container.new-profile-resource-type-container > .profile-resource-type-label-container { + width: 170px; +} + +.profiles-editor .contents-container .profile-content-tree-header > .inherit-label, .profiles-editor .contents-container .profile-tree-item-container > .inherit-container { + width: 130px; display: flex; + justify-content: center; align-items: center; } -.profiles-editor .contents-container .profile-tree-item-container > .inherit-container > .inherit-label { - /* font-size: 0.9em; */ - opacity: 0.8; -} - -.profiles-editor .contents-container .profile-tree-item-container.new-profile-resource-type-container > .profile-resource-type-label-container { - width: 170px; +.profiles-editor .contents-container .profile-content-tree-header > .actions-label { + flex: 1; + display: flex; + justify-content: center; + align-items: center; } .profiles-editor .contents-container .profile-tree-item-container.new-profile-resource-type-container > .profile-select-container { @@ -206,3 +247,11 @@ font-size: 0.9em; opacity: 0.7; } + +.profiles-editor .contents-container .profile-tree-item-container.profile-resource-child-container > .monaco-icon-label { + width: 295px; +} + +.profiles-editor .contents-container .profile-tree-item-container .profile-tree-item-actions-container { + justify-content: center; +} diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index cdfb41bec61..0f4cc81f9da 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -34,6 +34,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; +export const OpenProfileMenu = new MenuId('OpenProfile'); + export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.userDataProfiles'; @@ -100,6 +102,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this._register(this.registerManageProfilesAction()); this._register(this.registerSwitchProfileAction()); + this.registerOpenProfileSubMenu(); this.registerProfilesActions(); this._register(this.userDataProfilesService.onDidChangeProfiles(() => this.registerProfilesActions())); @@ -137,15 +140,23 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); } + private registerOpenProfileSubMenu(): void { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: localize('open profile', "Open Profile"), + submenu: OpenProfileMenu, + group: '2_open', + order: 4, + when: HAS_PROFILES_CONTEXT, + }); + } + private readonly profilesDisposable = this._register(new MutableDisposable()); private registerProfilesActions(): void { this.profilesDisposable.value = new DisposableStore(); for (const profile of this.userDataProfilesService.profiles) { if (!profile.isTransient) { this.profilesDisposable.value.add(this.registerProfileEntryAction(profile)); - if (!profile.isDefault) { - this.profilesDisposable.value.add(this.registerNewWindowAction(profile)); - } + this.profilesDisposable.value.add(this.registerNewWindowAction(profile)); } } } @@ -189,19 +200,19 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } private registerNewWindowAction(profile: IUserDataProfile): IDisposable { - return registerAction2(class NewWindowAction extends Action2 { + const disposables = new DisposableStore(); + + const id = `workbench.action.openProfile.${profile.name.toLowerCase().replace('/\s+/', '_')}`; + + disposables.add(registerAction2(class NewWindowAction extends Action2 { constructor() { super({ - id: `workbench.action.${profile.name.toLowerCase().replace('/\s+/', '_')}.newWindow`, - title: { - ...localize2('newWindow', "New {0} Window", profile.name), - }, - f1: true, + id, + title: localize2('openShort', "{0}", profile.name), menu: { - id: MenuId.MenubarFileMenu, - group: '1_new', - order: 4 + id: OpenProfileMenu, + when: HAS_PROFILES_CONTEXT } }); } @@ -210,7 +221,18 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const hostService = accessor.get(IHostService); return hostService.openWindow({ remoteAuthority: null, forceProfile: profile.name }); } - }); + })); + + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id, + category: PROFILES_CATEGORY, + title: localize2('open', "Open {0} Profile", profile.name), + precondition: HAS_PROFILES_CONTEXT + }, + })); + + return disposables; } private registerSwitchProfileAction(): IDisposable { @@ -247,15 +269,12 @@ 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()); } private registerManageProfilesAction(): IDisposable { const disposables = new DisposableStore(); - const precondition = ContextKeyExpr.equals('config.workbench.experimental.enableNewProfilesUI', true); disposables.add(registerAction2(class ManageProfilesAction extends Action2 { constructor() { super({ @@ -265,23 +284,15 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements mnemonicTitle: localize({ key: 'miOpenProfiles', comment: ['&& denotes a mnemonic'] }, "&&Profiles"), }, menu: [ - { - id: ProfilesMenu, - group: '2_manage_current', - when: precondition, - order: 1 - }, { id: MenuId.GlobalActivity, group: '2_configuration', - order: 1, - when: ContextKeyExpr.and(HAS_PROFILES_CONTEXT.negate(), precondition), + order: 1 }, { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', - order: 1, - when: ContextKeyExpr.and(HAS_PROFILES_CONTEXT.negate(), precondition), + order: 1 }, ] }); @@ -297,66 +308,12 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements id: 'workbench.profiles.actions.manageProfiles', category: Categories.Preferences, title: localize2('open profiles', "Open Profiles (UI)"), - precondition: precondition, }, })); 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: precondition, - f1: true, - menu: [ - { - id: ProfilesMenu, - group: '2_manage_current', - when: ContextKeyExpr.and(precondition, ContextKeyExpr.notEquals('config.workbench.experimental.enableNewProfilesUI', true)), - order: 2 - } - ] - }); - } - run() { - 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: ContextKeyExpr.notEquals('config.workbench.experimental.enableNewProfilesUI', true) - } - ] - }); - } - - async run(accessor: ServicesAccessor) { - const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); - return userDataProfileImportExportService.showProfileContents(); - } - }); - } - private registerExportCurrentProfileAction(): IDisposable { const that = this; const disposables = new DisposableStore(); @@ -365,34 +322,22 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements constructor() { super({ id, - title: localize2('export profile', "Export Profile..."), + title: localize2('export profile', "Export Profile ({0})...", that.userDataProfileService.currentProfile.name), category: PROFILES_CATEGORY, precondition: IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT.toNegated(), - menu: [ - { - id: ProfilesMenu, - group: '4_import_export_profiles', - order: 1, - when: ContextKeyExpr.notEquals('config.workbench.experimental.enableNewProfilesUI', true), - }, { - id: MenuId.CommandPalette - } - ] + f1: true, + menu: { + id: MenuId.MenubarShare + } }); } async run(accessor: ServicesAccessor) { - const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); - return userDataProfileImportExportService.exportProfile(); + const editorGroupsService = accessor.get(IEditorGroupsService); + const instantiationService = accessor.get(IInstantiationService); + return editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(instantiationService)); } })); - 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; } @@ -407,17 +352,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements title: localize2('import profile', "Import Profile..."), category: PROFILES_CATEGORY, precondition: IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT.toNegated(), - menu: [ - { - id: ProfilesMenu, - group: '4_import_export_profiles', - when: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, ContextKeyExpr.notEquals('config.workbench.experimental.enableNewProfilesUI', true)), - order: 2 - }, { - id: MenuId.CommandPalette, - when: PROFILES_ENABLEMENT_CONTEXT, - } - ] + f1: true, }); } @@ -519,7 +454,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } private registerCreateProfileAction(): void { - const that = this; this._register(registerAction2(class CreateProfileAction extends Action2 { constructor() { super({ @@ -528,19 +462,13 @@ 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, ContextKeyExpr.notEquals('config.workbench.experimental.enableNewProfilesUI', true)), - order: 1 - } - ] }); } async run(accessor: ServicesAccessor) { - return that.userDataProfileImportExportService.createProfile(); + const editorGroupsService = accessor.get(IEditorGroupsService); + const instantiationService = accessor.get(IInstantiationService); + return editorGroupsService.activeGroup.openEditor(new UserDataProfilesEditorInput(instantiationService)); } })); } @@ -554,14 +482,6 @@ 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, ContextKeyExpr.notEquals('config.workbench.experimental.enableNewProfilesUI', true)), - order: 2 - } - ] }); } diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts index f236412049a..2f4c25dd78d 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts @@ -117,7 +117,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi layout: (width, _, height) => { sidebarView.style.width = `${width}px`; if (height && this.profilesList) { - const listHeight = height - 40 /* new profile button */ - 10 /* padding */; + const listHeight = height - 40 /* new profile button */ - 15 /* marginTop */; this.profilesList.getHTMLElement().style.height = `${listHeight}px`; this.profilesList.layout(listHeight, width); } @@ -136,9 +136,6 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi } }, Sizing.Distribute, undefined, true); - const borderColor = this.theme.getColor(profilesSashBorder)!; - this.splitView.style({ separatorBorder: borderColor }); - this.registerListeners(); this.userDataProfileManagementService.getBuiltinProfileTemplates().then(templates => { @@ -147,6 +144,11 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi }); } + override updateStyles(): void { + const borderColor = this.theme.getColor(profilesSashBorder)!; + this.splitView?.style({ separatorBorder: borderColor }); + } + private renderSidebar(parent: HTMLElement): void { // render New Profile Button this.renderNewProfileButton(append(parent, $('.new-profile-button'))); @@ -188,10 +190,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi getActions: () => { const actions: IAction[] = []; if (this.templates.length) { - actions.push(new SubmenuAction('from.template', localize('from template', "From Template"), - this.templates.map(template => new Action(`template:${template.url}`, template.name, undefined, true, async () => { - this.createNewProfile(URI.parse(template.url)); - })))); + actions.push(new SubmenuAction('from.template', localize('from template', "From Template"), this.getCreateFromTemplateActions())); actions.push(new Separator()); } actions.push(new Action('importProfile', localize('importProfile', "Import Profile..."), undefined, true, () => this.importProfile())); @@ -203,10 +202,16 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi supportIcons: true, ...defaultButtonStyles })); - button.label = `$(add) ${localize('newProfile', "New Profile")}`; + button.label = localize('newProfile', "New Profile"); this._register(button.onDidClick(e => this.createNewProfile())); } + private getCreateFromTemplateActions(): IAction[] { + return this.templates.map(template => new Action(`template:${template.url}`, template.name, undefined, true, async () => { + this.createNewProfile(URI.parse(template.url)); + })); + } + private registerListeners(): void { if (this.profilesList) { this._register(this.profilesList.onDidChangeSelection(e => { @@ -215,19 +220,43 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi this.profileWidget?.render(element); } })); - this._register(this.profilesList.onContextMenu(e => { + const actions: IAction[] = []; + if (!e.element) { + actions.push(...this.getTreeContextMenuActions()); + } if (e.element instanceof AbstractUserDataProfileElement) { + actions.push(...e.element.contextMenuActions); + } + if (actions.length) { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => e.element instanceof AbstractUserDataProfileElement ? e.element.contextMenuActions.slice(0) : [], + getActions: () => actions, getActionsContext: () => e.element }); } })); + this._register(this.profilesList.onMouseDblClick(e => { + if (!e.element) { + this.createNewProfile(); + } + })); } } + + private getTreeContextMenuActions(): IAction[] { + const actions: IAction[] = []; + actions.push(new Action('newProfile', localize('newProfile', "New Profile"), undefined, true, () => this.createNewProfile())); + const templateActions = this.getCreateFromTemplateActions(); + if (templateActions.length) { + actions.push(new SubmenuAction('from.template', localize('new from template', "New Profile From Template"), templateActions)); + } + actions.push(new Separator()); + actions.push(new Action('importProfile', localize('importProfile', "Import Profile..."), undefined, true, () => this.importProfile())); + return actions; + } + private async importProfile(): Promise { const disposables = new DisposableStore(); const quickPick = disposables.add(this.quickInputService.createQuickPick()); @@ -338,12 +367,14 @@ interface IProfileElementTemplateData { readonly icon: HTMLElement; readonly label: HTMLElement; readonly description: HTMLElement; + readonly actionBar: WorkbenchToolBar; readonly disposables: DisposableStore; + readonly elementDisposables: DisposableStore; } class ProfileElementDelegate implements IListVirtualDelegate { getHeight(element: AbstractUserDataProfileElement) { - return 30; + return 22; } getTemplateId() { return 'profileListElement'; } } @@ -352,24 +383,42 @@ class ProfileElementRenderer implements IListRenderer { + templateData.elementDisposables.add(element.onDidChange(e => { if (e.name) { templateData.label.textContent = element.name; } @@ -385,10 +434,16 @@ class ProfileElementRenderer implements IListRenderer; private _templates: IProfileTemplateInfo[] = []; @@ -471,6 +527,7 @@ class ProfileWidget extends Disposable { })); const actionsContainer = append(header, $('.profile-actions-container')); + this.buttonContainer = append(actionsContainer, $('.profile-button-container')); this.toolbar = this._register(instantiationService.createInstance(WorkbenchToolBar, actionsContainer, { @@ -478,7 +535,6 @@ class ProfileWidget extends Disposable { highlightToggledItems: true } )); - this.buttonContainer = append(actionsContainer, $('.profile-button-container')); const body = append(parent, $('.profile-body')); @@ -497,7 +553,7 @@ class ProfileWidget extends Disposable { this.copyFromSelectBox.render(append(this.copyFromContainer, $('.profile-select-container'))); this.useAsDefaultProfileContainer = append(body, $('.profile-use-as-default-container')); - const useAsDefaultProfileTitle = localize('enable for new windows', "Enable this profile for new windows"); + const useAsDefaultProfileTitle = localize('enable for new windows', "Use this profile for new windows"); this.useAsDefaultProfileCheckbox = this._register(new Checkbox(useAsDefaultProfileTitle, false, defaultCheckboxStyles)); append(this.useAsDefaultProfileContainer, this.useAsDefaultProfileCheckbox.domNode); const useAsDefaultProfileLabel = append(this.useAsDefaultProfileContainer, $('.profile-use-as-default-label', undefined, useAsDefaultProfileTitle)); @@ -512,6 +568,12 @@ class ProfileWidget extends Disposable { } })); + this.contentsTreeHeader = append(body, $('.profile-content-tree-header')); + append(this.contentsTreeHeader, + $(''), + $('.inherit-label', undefined, localize('default profile', "Use Default Profile")), + $('.actions-label', undefined, localize('actions', "Actions")), + ); const delegate = new ProfileResourceTreeElementDelegate(); this.resourcesTree = this._register(this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'ProfileEditor-ResourcesTree', @@ -551,7 +613,7 @@ class ProfileWidget extends Disposable { expandOnlyOnTwistieClick: true, renderIndentGuides: RenderIndentGuides.None, enableStickyScroll: false, - openOnSingleClick: false + openOnSingleClick: false, })); this._register(this.resourcesTree.onDidOpen(async (e) => { if (!e.browserEvent) { @@ -625,19 +687,7 @@ class ProfileWidget extends Disposable { } private renderSelectBox(): void { - const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true }; - this.copyFromOptions.push({ text: localize('empty profile', "None") }); - if (this._templates.length) { - this.copyFromOptions.push({ ...separator, decoratorRight: localize('from templates', "Profile Templates") }); - for (const template of this._templates) { - this.copyFromOptions.push({ text: template.name, id: template.url, source: URI.parse(template.url) }); - } - } - this.copyFromOptions.push({ ...separator, decoratorRight: localize('from existing profiles', "Existing Profiles") }); - for (const profile of this.userDataProfilesService.profiles) { - this.copyFromOptions.push({ text: profile.name, id: profile.id, source: profile }); - } - this.copyFromSelectBox.setOptions(this.copyFromOptions); + this.copyFromSelectBox.setOptions(this.getCopyFromOptions()); this._register(this.copyFromSelectBox.onDidSelect(option => { if (this._profileElement.value?.element instanceof NewProfileElement) { this._profileElement.value.element.copyFrom = this.copyFromOptions[option.index].source; @@ -662,7 +712,7 @@ class ProfileWidget extends Disposable { this.resourcesTree.setInput(profileElement); disposables.add(profileElement.onDidChange(e => { - if (e.flags || e.copyFrom || e.copyFlags) { + if (e.flags || e.copyFrom || e.copyFlags || e.disabled) { const viewState = this.resourcesTree.getViewState(); this.resourcesTree.setInput(profileElement, { ...viewState, @@ -671,49 +721,54 @@ class ProfileWidget extends Disposable { } })); - if (profileElement.primaryAction || profileElement.secondaryAction) { + const [primaryTitleButtons, secondatyTitleButtons] = profileElement.titleButtons; + if (primaryTitleButtons?.length || secondatyTitleButtons?.length) { this.buttonContainer.classList.remove('hide'); - if (profileElement.primaryAction) { - const button = disposables.add(new Button(this.buttonContainer, { - ...defaultButtonStyles - })); - button.label = profileElement.primaryAction.label; - button.enabled = profileElement.primaryAction.enabled; - disposables.add(button.onDidClick(() => this.editorProgressService.showWhile(profileElement.primaryAction!.run()))); - disposables.add(profileElement.primaryAction.onDidChange((e) => { - if (!isUndefined(e.enabled)) { - button.enabled = profileElement.primaryAction!.enabled; - } - })); - disposables.add(profileElement.onDidChange(e => { - if (e.message) { - button.setTitle(profileElement.message ?? profileElement.primaryAction!.label); - button.element.classList.toggle('error', !!profileElement.message); - } - })); + if (primaryTitleButtons?.length) { + for (const action of primaryTitleButtons) { + const button = disposables.add(new Button(this.buttonContainer, { + ...defaultButtonStyles + })); + button.label = action.label; + button.enabled = action.enabled; + disposables.add(button.onDidClick(() => this.editorProgressService.showWhile(action.run()))); + disposables.add(action.onDidChange((e) => { + if (!isUndefined(e.enabled)) { + button.enabled = action.enabled; + } + })); + disposables.add(profileElement.onDidChange(e => { + if (e.message) { + button.setTitle(profileElement.message ?? action.label); + button.element.classList.toggle('error', !!profileElement.message); + } + })); + } } - if (profileElement.secondaryAction) { - const button = disposables.add(new Button(this.buttonContainer, { - ...defaultButtonStyles, - secondary: true - })); - button.label = profileElement.secondaryAction.label; - button.enabled = profileElement.secondaryAction.enabled; - disposables.add(button.onDidClick(() => this.editorProgressService.showWhile(profileElement.secondaryAction!.run()))); - disposables.add(profileElement.secondaryAction.onDidChange((e) => { - if (!isUndefined(e.enabled)) { - button.enabled = profileElement.secondaryAction!.enabled; - } - })); + if (secondatyTitleButtons?.length) { + for (const action of secondatyTitleButtons) { + const button = disposables.add(new Button(this.buttonContainer, { + ...defaultButtonStyles, + secondary: true + })); + button.label = action.label; + button.enabled = action.enabled; + disposables.add(button.onDidClick(() => this.editorProgressService.showWhile(action.run()))); + disposables.add(action.onDidChange((e) => { + if (!isUndefined(e.enabled)) { + button.enabled = action.enabled; + } + })); + } } } else { this.buttonContainer.classList.add('hide'); } - this.toolbar.setActions(profileElement.titleActions[0].slice(0), profileElement.titleActions[1].slice(0)); + this.toolbar.setActions([], profileElement.titleActions[1].slice(0)); if (profileElement instanceof NewProfileElement) { this.nameInput.focus(); @@ -735,8 +790,10 @@ class ProfileWidget extends Disposable { this.iconElement.className = ThemeIcon.asClassName(ThemeIcon.fromId(DEFAULT_ICON.id)); } if (profileElement instanceof NewProfileElement) { + this.contentsTreeHeader.classList.add('hide'); this.useAsDefaultProfileContainer.classList.add('hide'); this.copyFromContainer.classList.remove('hide'); + this.copyFromOptions = this.getCopyFromOptions(); const id = profileElement.copyFrom instanceof URI ? profileElement.copyFrom.toString() : profileElement.copyFrom?.id; const index = id ? this.copyFromOptions.findIndex(option => option.id === id) @@ -750,11 +807,29 @@ class ProfileWidget extends Disposable { this.copyFromSelectBox.setEnabled(false); } } else if (profileElement instanceof UserDataProfileElement) { + this.contentsTreeHeader.classList.toggle('hide', profileElement.profile.isDefault); this.useAsDefaultProfileContainer.classList.remove('hide'); this.useAsDefaultProfileCheckbox.checked = profileElement.isNewWindowProfile; this.copyFromContainer.classList.add('hide'); } } + + private getCopyFromOptions(): (ISelectOptionItem & { id?: string; source?: IUserDataProfile | URI })[] { + const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true }; + const copyFromOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | URI })[] = []; + copyFromOptions.push({ text: localize('empty profile', "None") }); + if (this._templates.length) { + copyFromOptions.push({ ...separator, decoratorRight: localize('from templates', "Profile Templates") }); + for (const template of this._templates) { + copyFromOptions.push({ text: template.name, id: template.url, source: URI.parse(template.url) }); + } + } + copyFromOptions.push({ ...separator, decoratorRight: localize('from existing profiles', "Existing Profiles") }); + for (const profile of this.userDataProfilesService.profiles) { + copyFromOptions.push({ text: profile.name, id: profile.id, source: profile }); + } + return copyFromOptions; + } } @@ -774,7 +849,7 @@ class ProfileResourceTreeElementDelegate implements IListVirtualDelegateelement.element).resourceType)) { + const resourceType = (element.element).resourceType; + if (element.root.getFlag(resourceType)) { return true; } + if (!element.root.hasResource(resourceType)) { + return false; + } if (element.root.copyFrom === undefined) { return false; } - if (!element.root.getCopyFlag((element.element).resourceType)) { + if (!element.root.getCopyFlag(resourceType)) { return false; } } @@ -829,6 +908,7 @@ class ProfileResourceTreeDataSource implements IAsyncDataSource, index: number, templateData: IExistingProfileResourceTemplateData, height: number | undefined): void { @@ -906,12 +1001,14 @@ class ExistingProfileResourceTreeRenderer extends AbstractProfileResourceTreeRen templateData.label.textContent = this.getResourceTypeTitle(element.resourceType); if (root instanceof UserDataProfileElement && root.profile.isDefault) { - templateData.inheritContainer.classList.add('hide'); + templateData.checkbox.domNode.classList.add('hide'); } else { - templateData.inheritContainer.classList.remove('hide'); + templateData.checkbox.domNode.classList.remove('hide'); templateData.checkbox.checked = root.getFlag(element.resourceType); templateData.elementDisposables.add(templateData.checkbox.onChange(() => root.setFlag(element.resourceType, templateData.checkbox.checked))); } + + templateData.actionBar.setActions(element.action ? [element.action] : []); } } @@ -946,7 +1043,16 @@ class NewProfileResourceTreeRenderer extends AbstractProfileResourceTreeRenderer const selectContainer = append(container, $('.profile-select-container')); selectBox.render(selectContainer); - return { label, selectContainer, selectBox, disposables, elementDisposables: disposables.add(new DisposableStore()) }; + const actionsContainer = append(container, $('.profile-tree-item-actions-container')); + const actionBar = disposables.add(this.instantiationService.createInstance(WorkbenchToolBar, + actionsContainer, + { + hoverDelegate: disposables.add(createInstantHoverDelegate()), + highlightToggledItems: true + } + )); + + return { label, selectContainer, selectBox, actionBar, disposables, elementDisposables: disposables.add(new DisposableStore()) }; } renderElement({ element: profileResourceTreeElement }: ITreeNode, index: number, templateData: INewProfileResourceTemplateData, height: number | undefined): void { @@ -959,10 +1065,11 @@ class NewProfileResourceTreeRenderer extends AbstractProfileResourceTreeRenderer throw new Error('Invalid profile resource element'); } templateData.label.textContent = this.getResourceTypeTitle(element.resourceType); - if (root.copyFrom) { + if (root.copyFrom && root.hasResource(element.resourceType)) { + const copyFromName = root.getCopyFromName(); templateData.selectBox.setOptions([ { text: localize('empty', "Empty") }, - { text: localize('copy', "Copy") }, + { text: copyFromName ? localize('copy from', "Copy ({0})", copyFromName) : localize('copy', "Copy") }, { text: localize('default', "Use Default Profile") } ]); templateData.selectBox.select(root.getCopyFlag(element.resourceType) ? 1 : root.getFlag(element.resourceType) ? 2 : 0); @@ -981,6 +1088,7 @@ class NewProfileResourceTreeRenderer extends AbstractProfileResourceTreeRenderer })); } templateData.selectBox.setEnabled(!root.disabled); + templateData.actionBar.setActions(element.action ? [element.action] : []); } } @@ -993,7 +1101,7 @@ class ProfileResourceChildTreeItemRenderer extends AbstractProfileResourceTreeRe private readonly hoverDelegate: IHoverDelegate; constructor( - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); this.labels = instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER); @@ -1006,7 +1114,17 @@ class ProfileResourceChildTreeItemRenderer extends AbstractProfileResourceTreeRe const checkbox = disposables.add(new Checkbox('', false, defaultCheckboxStyles)); append(container, checkbox.domNode); const resourceLabel = disposables.add(this.labels.create(container, { hoverDelegate: this.hoverDelegate })); - return { checkbox, resourceLabel, disposables, elementDisposables: disposables.add(new DisposableStore()) }; + + const actionsContainer = append(container, $('.profile-tree-item-actions-container')); + const actionBar = disposables.add(this.instantiationService.createInstance(WorkbenchToolBar, + actionsContainer, + { + hoverDelegate: disposables.add(createInstantHoverDelegate()), + highlightToggledItems: true + } + )); + + return { checkbox, resourceLabel, actionBar, disposables, elementDisposables: disposables.add(new DisposableStore()) }; } renderElement({ element: profileResourceTreeElement }: ITreeNode, index: number, templateData: IProfileResourceChildTreeItemTemplateData, height: number | undefined): void { @@ -1038,6 +1156,7 @@ class ProfileResourceChildTreeItemRenderer extends AbstractProfileResourceTreeRe icon: element.icon, hideIcon: !element.resource && !element.icon, }); + templateData.actionBar.setActions(element.action ? [element.action] : []); } } diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts index f4a5c42a76b..110b69e5711 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts @@ -25,7 +25,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; import { generateUuid } from 'vs/base/common/uuid'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ITreeItemCheckboxState } from 'vs/workbench/common/views'; @@ -197,17 +197,22 @@ export abstract class AbstractUserDataProfileElement extends Disposable { ProfileResourceType.Snippets, ProfileResourceType.Extensions ]; - return resourceTypes.map(resourceType => ({ - handle: resourceType, - checkbox: undefined, - resourceType, - action: resourceType === ProfileResourceType.Settings - || resourceType === ProfileResourceType.Keybindings - || resourceType === ProfileResourceType.Tasks - ? new Action('_open', '', undefined, true, async () => { - const children = await this.getChildren(resourceType); - children[0]?.action?.run(); - }) : undefined + return Promise.all(resourceTypes.map>(async r => { + const children = (r === ProfileResourceType.Settings + || r === ProfileResourceType.Keybindings + || r === ProfileResourceType.Tasks) ? await this.getChildrenForResourceType(r) : []; + return { + handle: r, + checkbox: undefined, + resourceType: r, + action: children.length + ? new Action('_open', + localize('open', "Open to the Side"), + ThemeIcon.asClassName(Codicon.goToFile), + true, + () => children[0]?.action?.run()) + : undefined + }; })); } return this.getChildrenForResourceType(resourceType); @@ -247,7 +252,7 @@ export abstract class AbstractUserDataProfileElement extends Disposable { label: child.label?.label ?? '', resource: URI.revive(child.resourceUri), icon: child.themeIcon, - action: new Action('_openChild', '', undefined, true, async () => { + action: new Action('_openChild', localize('open', "Open to the Side"), ThemeIcon.asClassName(Codicon.goToFile), true, async () => { if (child.parent.type === ProfileResourceType.Extensions) { await this.commandService.executeCommand('extension.open', child.handle, undefined, true, undefined, true); } else if (child.resourceUri) { @@ -298,8 +303,7 @@ export abstract class AbstractUserDataProfileElement extends Disposable { }); } - abstract readonly primaryAction?: Action; - abstract readonly secondaryAction?: Action; + abstract readonly titleButtons: [Action[], Action[]]; abstract readonly titleActions: [IAction[], IAction[]]; abstract readonly contextMenuActions: IAction[]; @@ -310,11 +314,9 @@ export class UserDataProfileElement extends AbstractUserDataProfileElement { get profile(): IUserDataProfile { return this._profile; } - readonly primaryAction = undefined; - readonly secondaryAction = undefined; - constructor( private _profile: IUserDataProfile, + readonly titleButtons: [Action[], Action[]], readonly titleActions: [IAction[], IAction[]], readonly contextMenuActions: IAction[], @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @@ -388,11 +390,13 @@ const USER_DATA_PROFILE_TEMPLATE_PREVIEW_SCHEME = 'userdataprofiletemplateprevie export class NewProfileElement extends AbstractUserDataProfileElement { + private templatePromise: CancelablePromise | undefined; + private template: IUserDataProfileTemplate | null = null; + constructor( name: string, copyFrom: URI | IUserDataProfile | undefined, - readonly primaryAction: Action, - readonly secondaryAction: Action, + readonly titleButtons: [Action[], Action[]], readonly titleActions: [IAction[], IAction[]], readonly contextMenuActions: Action[], @IFileService private readonly fileService: IFileService, @@ -414,6 +418,7 @@ export class NewProfileElement extends AbstractUserDataProfileElement { ); this._copyFrom = copyFrom; this._copyFlags = this.getCopyFlagsFrom(copyFrom); + this.initialize(); this._register(this.fileService.registerProvider(USER_DATA_PROFILE_TEMPLATE_PREVIEW_SCHEME, this._register(new InMemoryFileSystemProvider()))); } @@ -425,6 +430,11 @@ export class NewProfileElement extends AbstractUserDataProfileElement { this._onDidChange.fire({ copyFrom: true }); this.flags = undefined; this.copyFlags = this.getCopyFlagsFrom(copyFrom); + if (copyFrom instanceof URI) { + this.templatePromise?.cancel(); + this.templatePromise = undefined; + } + this.initialize(); } } @@ -456,6 +466,76 @@ export class NewProfileElement extends AbstractUserDataProfileElement { } : undefined; } + private async initialize(): Promise { + this.disabled = true; + try { + if (this.copyFrom instanceof URI) { + await this.resolveTemplate(this.copyFrom); + if (this.template) { + this.name = this.template.name ?? ''; + this.icon = this.template.icon; + this.setCopyFlag(ProfileResourceType.Settings, !!this.template.settings); + this.setCopyFlag(ProfileResourceType.Keybindings, !!this.template.keybindings); + this.setCopyFlag(ProfileResourceType.Tasks, !!this.template.tasks); + this.setCopyFlag(ProfileResourceType.Snippets, !!this.template.snippets); + this.setCopyFlag(ProfileResourceType.Extensions, !!this.template.extensions); + } + return; + } + + if (isUserDataProfile(this.copyFrom)) { + this.name = `${this.copyFrom.name} (Copy)`; + this.icon = this.copyFrom.icon; + this.setCopyFlag(ProfileResourceType.Settings, true); + this.setCopyFlag(ProfileResourceType.Keybindings, true); + this.setCopyFlag(ProfileResourceType.Tasks, true); + this.setCopyFlag(ProfileResourceType.Snippets, true); + this.setCopyFlag(ProfileResourceType.Extensions, true); + return; + } + + this.name = localize('untitled', "Untitled"); + this.icon = undefined; + this.setCopyFlag(ProfileResourceType.Settings, false); + this.setCopyFlag(ProfileResourceType.Keybindings, false); + this.setCopyFlag(ProfileResourceType.Tasks, false); + this.setCopyFlag(ProfileResourceType.Snippets, false); + this.setCopyFlag(ProfileResourceType.Extensions, false); + } finally { + this.disabled = false; + } + } + + private async resolveTemplate(uri: URI): Promise { + if (!this.templatePromise) { + this.templatePromise = createCancelablePromise(async token => { + const template = await this.userDataProfileImportExportService.resolveProfileTemplate(uri); + if (!token.isCancellationRequested) { + this.template = template; + } + }); + } + return this.templatePromise; + } + + hasResource(resourceType: ProfileResourceType): boolean { + if (this.template) { + switch (resourceType) { + case ProfileResourceType.Settings: + return !!this.template.settings; + case ProfileResourceType.Keybindings: + return !!this.template.keybindings; + case ProfileResourceType.Snippets: + return !!this.template.snippets; + case ProfileResourceType.Tasks: + return !!this.template.tasks; + case ProfileResourceType.Extensions: + return !!this.template.extensions; + } + } + return true; + } + getCopyFlag(key: ProfileResourceType): boolean { return this.copyFlags?.[key] ?? false; } @@ -466,6 +546,16 @@ export class NewProfileElement extends AbstractUserDataProfileElement { this.copyFlags = flags; } + getCopyFromName(): string | undefined { + if (isUserDataProfile(this.copyFrom)) { + return this.copyFrom.name; + } + if (this.template) { + return this.template.name; + } + return undefined; + } + protected override async getChildrenForResourceType(resourceType: ProfileResourceType): Promise { if (this.getFlag(resourceType)) { return this.getChildrenFromProfile(this.userDataProfilesService.defaultProfile, resourceType); @@ -474,11 +564,11 @@ export class NewProfileElement extends AbstractUserDataProfileElement { return []; } if (this.copyFrom instanceof URI) { - const template = await this.userDataProfileImportExportService.resolveProfileTemplate(this.copyFrom); - if (!template) { + await this.resolveTemplate(this.copyFrom); + if (!this.template) { return []; } - return this.getChildrenFromProfileTemplate(template, resourceType); + return this.getChildrenFromProfileTemplate(this.template, resourceType); } if (this.copyFrom) { return this.getChildrenFromProfile(this.copyFrom, resourceType); @@ -614,54 +704,92 @@ export class UserDataProfilesEditorModel extends EditorModel { private createProfileElement(profile: IUserDataProfile): [UserDataProfileElement, DisposableStore] { const disposables = new DisposableStore(); - const activateAction = disposables.add(new Action('userDataProfile.activate', localize('active', "Active"), ThemeIcon.asClassName(Codicon.check), true, () => this.userDataProfileManagementService.switchProfile(profile))); - activateAction.checked = this.userDataProfileService.currentProfile.id === profile.id; - disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(() => activateAction.checked = this.userDataProfileService.currentProfile.id === profile.id)); - const copyFromProfileAction = disposables.add(new Action('userDataProfile.copyFromProfile', localize('copyFromProfile', "Save As..."), ThemeIcon.asClassName(Codicon.copy), true, () => this.createNewProfile(profile))); - const exportAction = disposables.add(new Action('userDataProfile.export', localize('export', "Export..."), ThemeIcon.asClassName(Codicon.export), true, () => this.exportProfile(profile))); - const deleteAction = disposables.add(new Action('userDataProfile.delete', localize('delete', "Delete"), ThemeIcon.asClassName(Codicon.trash), true, () => this.removeProfile(profile))); - const newWindowAction = disposables.add(new Action('userDataProfile.newWindow', localize('new window', "New {0} Window", profile.name), ThemeIcon.asClassName(Codicon.emptyWindow), true, () => this.openWindow(profile))); - const useAsNewWindowProfileAction = disposables.add(new Action('userDataProfile.useAsNewWindowProfile', localize('use as new window', "Enable for New Windows", profile.name), undefined, true, () => profileElement.toggleNewWindowProfile())); + const activateAction = disposables.add(new Action( + 'userDataProfile.activate', + localize('active', "Use for Current Window"), + ThemeIcon.asClassName(Codicon.check), + true, + () => this.userDataProfileManagementService.switchProfile(profile) + )); + + const copyFromProfileAction = disposables.add(new Action( + 'userDataProfile.copyFromProfile', + localize('copyFromProfile', "Duplicate..."), + ThemeIcon.asClassName(Codicon.copy), + true, () => this.createNewProfile(profile) + )); + + const exportAction = disposables.add(new Action( + 'userDataProfile.export', + localize('export', "Export..."), + ThemeIcon.asClassName(Codicon.export), + true, + () => this.exportProfile(profile) + )); + + const deleteAction = disposables.add(new Action( + 'userDataProfile.delete', + localize('delete', "Delete"), + ThemeIcon.asClassName(Codicon.trash), + true, + () => this.removeProfile(profile) + )); + + const newWindowAction = disposables.add(new Action( + 'userDataProfile.newWindow', + localize('open new window', "Open"), + ThemeIcon.asClassName(Codicon.emptyWindow), + true, + () => this.openWindow(profile) + )); + + const useAsNewWindowProfileAction = disposables.add(new Action( + 'userDataProfile.useAsNewWindowProfile', + localize('use as new window', "Use for New Windows", profile.name), + undefined, + true, + () => profileElement.toggleNewWindowProfile() + )); const titlePrimaryActions: IAction[] = []; - titlePrimaryActions.push(copyFromProfileAction); - titlePrimaryActions.push(exportAction); - if (!profile.isDefault) { - titlePrimaryActions.push(deleteAction); - } - + titlePrimaryActions.push(newWindowAction); const titleSecondaryActions: IAction[] = []; + titleSecondaryActions.push(copyFromProfileAction); + titleSecondaryActions.push(exportAction); + if (!profile.isDefault) { + titleSecondaryActions.push(new Separator()); + titleSecondaryActions.push(deleteAction); + } const secondaryActions: IAction[] = []; secondaryActions.push(activateAction); secondaryActions.push(useAsNewWindowProfileAction); secondaryActions.push(new Separator()); - secondaryActions.push(newWindowAction); - secondaryActions.push(new Separator()); secondaryActions.push(copyFromProfileAction); secondaryActions.push(exportAction); if (!profile.isDefault) { secondaryActions.push(new Separator()); secondaryActions.push(deleteAction); } + const profileElement = disposables.add(this.instantiationService.createInstance(UserDataProfileElement, profile, + [[newWindowAction], []], [titlePrimaryActions, titleSecondaryActions], secondaryActions, )); - useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; - useAsNewWindowProfileAction.label = profileElement.isNewWindowProfile ? localize('donot use as new window', "Disable for New Windows") : localize('use as new window', "Enable for New Windows"); + activateAction.checked = this.userDataProfileService.currentProfile.id === profile.id; + disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(() => + activateAction.checked = this.userDataProfileService.currentProfile.id === profile.id)); + useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; disposables.add(profileElement.onDidChange(e => { - if (e.name) { - newWindowAction.label = localize('new window', "New {0} Window", profileElement.name); - } if (e.newWindowProfile) { useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; - useAsNewWindowProfileAction.label = profileElement.isNewWindowProfile ? localize('donot use as new window', "Disable for New Windows") : localize('use as new window', "Enable for New Windows"); } })); + return [profileElement, disposables]; } @@ -686,7 +814,7 @@ export class UserDataProfilesEditorModel extends EditorModel { )); const previewProfileAction = disposables.add(new Action( 'userDataProfile.preview', - localize('preview', "Open Preview"), + localize('preview', "Preview"), ThemeIcon.asClassName(Codicon.openPreview), true, () => this.previewNewProfile(cancellationTokenSource.token) @@ -694,11 +822,8 @@ export class UserDataProfilesEditorModel extends EditorModel { this.newProfileElement = disposables.add(this.instantiationService.createInstance(NewProfileElement, localize('untitled', "Untitled"), copyFrom, - createAction, - cancelAction, - [[ - previewProfileAction - ], []], + [[createAction], [previewProfileAction, cancelAction]], + [[cancelAction], []], [], )); disposables.add(this.newProfileElement.onDidChange(e => { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 3afec944efd..d09cac70e2f 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -53,6 +53,8 @@ export interface IUserDataProfileManagementService { } export interface IUserDataProfileTemplate { + readonly name: string; + readonly icon?: string; readonly settings?: string; readonly keybindings?: string; readonly tasks?: string;