diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index f9829e3d98f..c8158ad9b50 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1326,7 +1326,6 @@ export class QuickInputController extends Disposable { rightActionBar.domNode.classList.add('quick-input-right-action-bar'); const headerContainer = dom.append(container, $('.quick-input-header')); - const description1 = dom.append(container, $('.quick-input-description')); const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); checkAll.type = 'checkbox'; @@ -1379,6 +1378,8 @@ export class QuickInputController extends Disposable { const widget = dom.append(container, $('.quick-input-html-widget')); widget.tabIndex = -1; + const description1 = dom.append(container, $('.quick-input-description')); + const listId = this.idPrefix + 'list'; const list = this._register(new QuickInputList(container, listId, this.options)); inputBox.setAttribute('aria-controls', listId); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css b/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css new file mode 100644 index 00000000000..899cf1a074a --- /dev/null +++ b/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.profile-type-widget { + display: flex; + margin: 0px 6px 8px 11px; + align-items: center; + justify-content: space-between; + font-size: 12px; +} + +.profile-type-widget>.profile-type-select-container { + overflow: hidden; + padding-left: 10px; + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.profile-type-widget>.profile-type-select-container>.monaco-select-box { + cursor: pointer; + line-height: 17px; + padding: 2px 23px 2px 8px; + border-radius: 2px; +} diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 9b2002bdfe2..3c8e7b62c30 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -3,20 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/userDataProfileCreateWidget'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { RenameProfileAction } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfileActions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { IQuickInputService, IQuickPickItem, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { URI } from 'vs/base/common/uri'; @@ -31,32 +30,18 @@ import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; import Severity from 'vs/base/common/severity'; - -const CREATE_EMPTY_PROFILE_ACTION_ID = 'workbench.profiles.actions.createEmptyProfile'; -const CREATE_EMPTY_PROFILE_ACTION_TITLE = { - value: localize('create empty profile', "Create an Empty Profile..."), - original: 'Create an Empty Profile...' -}; - -const CREATE_FROM_CURRENT_PROFILE_ACTION_ID = 'workbench.profiles.actions.createFromCurrentProfile'; -const CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE = { - value: localize('save profile as', "Save Current Profile As..."), - original: 'Save Current Profile As...' -}; - -const CREATE_NEW_PROFILE_ACTION_ID = 'workbench.profiles.actions.createNewProfile'; -const CREATE_NEW_PROFILE_ACTION_TITLE = { - value: localize('create new profile', "Create New Profile..."), - original: 'Create New Profile...' -}; +import { $, append } from 'vs/base/browser/dom'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { isString } from 'vs/base/common/types'; interface IProfileTemplateInfo { readonly name: string; readonly url: string; } -type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; - export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly currentProfileContext: IContextKey; @@ -77,6 +62,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @ILifecycleService private readonly lifecycleService: ILifecycleService, @IProductService private readonly productService: IProductService, @IRequestService private readonly requestService: IRequestService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextViewService private readonly contextViewService: IContextViewService, @ILogService private readonly logService: ILogService, ) { super(); @@ -115,9 +102,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerCurrentProfilesActions(); this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => this.registerCurrentProfilesActions())); - this.registerCreateEmptyProfileAction(); this.registerCreateFromCurrentProfileAction(); - this.registerCreateNewProfileAction(); this.registerCreateProfileAction(); this.registerDeleteProfileAction(); @@ -214,21 +199,21 @@ 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.registerRenameCurrentProfileAction()); + 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 registerRenameCurrentProfileAction(): IDisposable { + private registerEditCurrentProfileAction(): IDisposable { const that = this; return registerAction2(class RenameCurrentProfileAction extends Action2 { constructor() { super({ - id: `workbench.profiles.actions.renameCurrentProfile`, + id: `workbench.profiles.actions.editCurrentProfile`, title: { - value: localize('rename profile', "Rename..."), - original: `Rename...` + value: localize('edit profile', "Edit..."), + original: `Edit...` }, menu: [ { @@ -240,8 +225,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements ] }); } - async run(accessor: ServicesAccessor) { - accessor.get(ICommandService).executeCommand(RenameProfileAction.ID, that.userDataProfileService.currentProfile); + run() { + return that.saveProfile(that.userDataProfileService.currentProfile); } }); } @@ -413,8 +398,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this._register(registerAction2(class CreateFromCurrentProfileAction extends Action2 { constructor() { super({ - id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, - title: CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE, + id: 'workbench.profiles.actions.createFromCurrentProfile', + title: { + value: localize('save profile as', "Save Current Profile As..."), + original: 'Save Current Profile As...' + }, category: PROFILES_CATEGORY, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT @@ -422,86 +410,35 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } run(accessor: ServicesAccessor) { - return that.createFromCurrentProfile(); + return that.saveProfile(undefined, that.userDataProfileService.currentProfile); } })); } - private registerCreateNewProfileAction(): void { - const that = this; - this._register(registerAction2(class CreateFromCurrentProfileAction extends Action2 { - constructor() { - super({ - id: CREATE_NEW_PROFILE_ACTION_ID, - title: CREATE_NEW_PROFILE_ACTION_TITLE, - category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT - }); - } + private async saveProfile(profile: IUserDataProfile): Promise; + private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise; + private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise { - run() { - return that.createNewProfile(); - } - })); - } + type CreateProfileInfoClassification = { + owner: 'sandy081'; + comment: 'Report when profile is about to be created'; + }; + this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.startCreate'); - private registerCreateEmptyProfileAction(): void { - const that = this; - this._register(registerAction2(class CreateEmptyProfileAction extends Action2 { - constructor() { - super({ - id: CREATE_EMPTY_PROFILE_ACTION_ID, - title: CREATE_EMPTY_PROFILE_ACTION_TITLE, - category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT - }); - } + const disposables = new DisposableStore(); + const title = profile ? localize('save profile', "Edit Profile...") : localize('create new profle', "Create New Profile..."); - run(accessor: ServicesAccessor) { - return that.createEmptyProfile(); - } - })); - } - - private async createEmptyProfile(): Promise { - const name = await this.getNameForProfile(localize('create empty profile', "Create an Empty Profile...")); - if (!name) { - return; - } - try { - await this.userDataProfileManagementService.createAndEnterProfile(name, undefined); - } catch (error) { - this.notificationService.error(error); - } - } - - private async createFromCurrentProfile(): Promise { - const name = await this.getNameForProfile(localize('create from current profle', "Create from Current Profile...")); - if (!name) { - return; - } - try { - await this.userDataProfileImportExportService.SaveCurrentProfileAs(name); - } catch (error) { - this.notificationService.error(error); - } - } - - private async createNewProfile(): Promise { - const title = localize('create new profle', "Create New Profile..."); - - const settings: IQuickPickItem = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: true }; - const keybindings: IQuickPickItem = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: true }; - const snippets: IQuickPickItem = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: true }; - const tasks: IQuickPickItem = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: true }; - const extensions: IQuickPickItem = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: true }; + const settings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: profile?.useDefaultFlags?.settings }; + const keybindings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: profile?.useDefaultFlags?.keybindings }; + const snippets: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: profile?.useDefaultFlags?.snippets }; + const tasks: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: profile?.useDefaultFlags?.tasks }; + const extensions: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: profile?.useDefaultFlags?.extensions }; const resources = [settings, keybindings, snippets, tasks, extensions]; const quickPick = this.quickInputService.createQuickPick(); quickPick.title = title; - quickPick.placeholder = localize('name placeholder', "Name the new profile"); + quickPick.placeholder = localize('name placeholder', "Profile name"); + quickPick.value = profile?.name ?? ''; quickPick.canSelectMany = true; quickPick.matchOnDescription = false; quickPick.matchOnDetail = false; @@ -512,42 +449,109 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements quickPick.customButton = true; quickPick.hideCheckAll = true; quickPick.ignoreFocusOut = true; - quickPick.customLabel = localize('create', "Create Profile"); - quickPick.description = localize('customise the profile', "Choose what to configure in the profile. Unselected items are shared from the default profile."); - quickPick.items = resources; - quickPick.selectedItems = resources.filter(item => item.picked); + quickPick.customLabel = profile ? localize('save', "Save") : localize('create', "Create"); + quickPick.description = localize('customise the profile', "Select configurations to share from the Default profile:"); + quickPick.items = [...resources]; + + const updateSelection = () => { + quickPick.selectedItems = resources.filter(item => item.picked); + }; + updateSelection(); + + const validate = () => { + if (!profile && this.userDataProfilesService.profiles.some(p => p.name === quickPick.value)) { + quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", quickPick.value); + quickPick.severity = Severity.Error; + return; + } + if (resources.every(resource => resource.picked)) { + quickPick.validationMessage = localize('cannot share all', "Cannot share all configurations from the Default Profile."); + quickPick.severity = Severity.Error; + return; + } + quickPick.severity = Severity.Ignore; + quickPick.validationMessage = undefined; + }; - const disposables = new DisposableStore(); disposables.add(quickPick.onDidChangeSelection(items => { for (const resource of resources) { resource.picked = items.includes(resource); } + validate(); })); - disposables.add(quickPick.onDidChangeValue(value => { - if (this.userDataProfilesService.profiles.some(p => p.name === value)) { - quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", value); - quickPick.severity = Severity.Error; - } else { - quickPick.severity = Severity.Ignore; - quickPick.validationMessage = undefined; - } - })); + disposables.add(quickPick.onDidChangeValue(validate)); let result: { name: string; items: ReadonlyArray } | undefined; - disposables.add(quickPick.onDidCustom(async () => { + disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => { if (!quickPick.value) { quickPick.validationMessage = localize('name required', "Provide a name for the new profile"); quickPick.severity = Severity.Error; + } + if (quickPick.validationMessage) { return; } - if (resources.some(resource => quickPick.selectedItems.includes(resource))) { - result = { name: quickPick.value, items: quickPick.selectedItems }; - quickPick.hide(); - } + result = { name: quickPick.value, items: quickPick.selectedItems }; + quickPick.hide(); quickPick.severity = Severity.Ignore; quickPick.validationMessage = undefined; })); + + if (!profile) { + const domNode = $('.profile-type-widget'); + append(domNode, $('.profile-type-create-label', undefined, localize('create from', "Copy from:"))); + const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true }; + const profileOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | string })[] = []; + profileOptions.push({ text: localize('empty profile', "None") }); + const templates = await this.getProfileTemplatesFromProduct(); + if (templates.length) { + profileOptions.push({ ...separator, decoratorRight: localize('from templates', "Profile Templates") }); + for (const template of templates) { + profileOptions.push({ text: template.name, id: template.url, source: template.url }); + } + } + profileOptions.push({ ...separator, decoratorRight: localize('from existing profiles', "Existing Profiles") }); + for (const profile of this.userDataProfilesService.profiles) { + profileOptions.push({ text: profile.name, id: profile.id, source: profile }); + } + + const findOptionIndex = () => { + const index = profileOptions.findIndex(option => { + if (isString(source)) { + return option.id === source; + } else if (source) { + return option.id === source.id; + } + return false; + }); + return index > -1 ? index : 0; + }; + + const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox, profileOptions, findOptionIndex(), this.contextViewService, defaultSelectBoxStyles, { useCustomDrawn: true })); + selectBox.render(append(domNode, $('.profile-type-select-container'))); + quickPick.widget = domNode; + + const updateOptions = () => { + const index = findOptionIndex(); + if (index <= 0) { + return; + } + const option = profileOptions[index]; + if (!isString(option.source)) { + for (const resource of resources) { + resource.picked = option.source?.useDefaultFlags?.[resource.id]; + } + updateSelection(); + } + }; + + updateOptions(); + disposables.add(selectBox.onDidSelect(({ index }) => { + source = profileOptions[index].source; + updateOptions(); + })); + } + quickPick.show(); await new Promise((c, e) => { @@ -558,37 +562,36 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); if (!result) { + this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.cancelCreate'); return; } - const { name, items } = result; + this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.successCreate'); + try { - const useDefaultFlags: UseDefaultProfileFlags | undefined = items.length !== resources.length ? { - settings: !items.includes(settings), - keybindings: !items.includes(keybindings), - snippets: !items.includes(snippets), - tasks: !items.includes(tasks), - extensions: !items.includes(extensions) + const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length ? { + settings: result.items.includes(settings), + keybindings: result.items.includes(keybindings), + snippets: result.items.includes(snippets), + tasks: result.items.includes(tasks), + extensions: result.items.includes(extensions) } : undefined; - await this.userDataProfileManagementService.createAndEnterProfile(name, { useDefaultFlags }); + if (profile) { + await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags }); + } else { + if (isString(source)) { + await this.userDataProfileImportExportService.importProfile(URI.parse(source), { mode: 'apply', name: result.name, useDefaultFlags }); + } else if (source) { + await this.userDataProfileImportExportService.createFromProfile(source, result.name, { useDefaultFlags }); + } else { + await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags }); + } + } } catch (error) { this.notificationService.error(error); } } - private async getNameForProfile(title: string): Promise { - return this.quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title, - validateInput: async (value: string) => { - if (this.userDataProfilesService.profiles.some(p => p.name === value)) { - return localize('profileExists', "Profile with name {0} already exists.", value); - } - return undefined; - } - }); - } - private registerCreateProfileAction(): void { const that = this; this._register(registerAction2(class CreateProfileAction extends Action2 { @@ -601,6 +604,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }, category: PROFILES_CATEGORY, precondition: PROFILES_ENABLEMENT_CONTEXT, + f1: true, menu: [ { id: ProfilesMenu, @@ -613,52 +617,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } async run(accessor: ServicesAccessor) { - const quickInputService = accessor.get(IQuickInputService); - const commandService = accessor.get(ICommandService); - const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); - const quickPickItems: QuickPickItem[] = [{ - id: CREATE_EMPTY_PROFILE_ACTION_ID, - label: localize('empty profile', "Empty Profile..."), - }, { - id: CREATE_NEW_PROFILE_ACTION_ID, - label: localize('new profile', "New Profile..."), - }, { - type: 'separator', - }, { - id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, - label: localize('using current', "Save Current Profile As..."), - }]; - const profileTemplateQuickPickItems = await that.getProfileTemplatesQuickPickItems(); - if (profileTemplateQuickPickItems.length) { - quickPickItems.push({ - type: 'separator', - label: localize('templates', "Profile Templates") - }, ...profileTemplateQuickPickItems); - } - const pick = await quickInputService.pick(quickPickItems, - { - hideInput: true, - canPickMany: false, - title: localize('create profile title', "Create Profile...") - }); - if (pick) { - if (pick.id) { - return commandService.executeCommand(pick.id); - } - if ((pick).url) { - type ProfileCreationFromTemplateActionClassification = { - owner: 'sandy081'; - comment: 'Report profile creation from template action'; - profileName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Name of the profile created from template' }; - }; - type ProfileCreationFromTemplateActionEvent = { - profileName: string; - }; - that.telemetryService.publicLog2('profileCreationAction:builtinTemplate', { profileName: (pick).name }); - const uri = URI.parse((pick).url); - return userDataProfileImportExportService.importProfile(uri, { mode: 'apply' }); - } - } + return that.saveProfile(); } })); } @@ -736,18 +695,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements })); } - private async getProfileTemplatesQuickPickItems(): Promise { - const quickPickItems: IProfileTemplateQuickPickItem[] = []; - const profileTemplates = await this.getProfileTemplatesFromProduct(); - for (const template of profileTemplates) { - quickPickItems.push({ - label: template.name, - ...template - }); - } - return quickPickItems; - } - private async getProfileTemplatesFromProduct(): Promise { if (this.productService.profileTemplatesUrl) { try { diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 3fe9085063c..dbfc7ae2f6e 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -18,7 +18,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, ProfileResourceType, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -209,11 +209,11 @@ export class UserDataProfileImportExportService extends Disposable implements IU return; } if (mode === 'preview') { - await this.previewProfile(profileTemplate); + await this.previewProfile(profileTemplate, options); } else if (mode === 'apply') { - await this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); + await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); } else if (mode === 'both') { - await this.importAndPreviewProfile(uri, profileTemplate); + await this.importAndPreviewProfile(uri, profileTemplate, options); } } finally { disposables.dispose(); @@ -249,11 +249,11 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - async SaveCurrentProfileAs(name: string): Promise { - const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile); + async createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise { + const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, profile); try { const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, undefined); - await this.createAndSwitch(profileTemplate, false, true, localize('save profile as', "Save Profile As")); + await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); } finally { userDataProfilesExportState.dispose(); } @@ -269,7 +269,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU sticky: true, }, async progress => { const reportProgress = (message: string) => progress.report({ message: localize('troubleshoot profile progress', "Setting up Troubleshoot Profile: {0}", message) }); - const profile = await this.createProfile(profileTemplate, true, false, reportProgress); + const profile = await this.createProfile(profileTemplate, true, false, { useDefaultFlags: this.userDataProfileService.currentProfile.useDefaultFlags }, reportProgress); if (profile) { reportProgress(localize('progress extensions', "Applying Extensions...")); await this.instantiationService.createInstance(ExtensionsResource).copy(this.userDataProfileService.currentProfile, profile, true); @@ -362,21 +362,21 @@ export class UserDataProfileImportExportService extends Disposable implements IU return profileTemplate; } - private async importAndPreviewProfile(uri: URI, profileTemplate: IUserDataProfileTemplate): Promise { + private async importAndPreviewProfile(uri: URI, profileTemplate: IUserDataProfileTemplate, options: IUserDataProfileOptions | undefined): Promise { const disposables = new DisposableStore(); try { const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); - const importedProfile = await this.createAndSwitch(profileTemplate, true, false, localize('preview profile', "Preview Profile")); + const importedProfile = await this.createAndSwitch(profileTemplate, true, false, options, localize('preview profile', "Preview Profile")); if (!importedProfile) { return; } const barrier = new Barrier(); - const importAction = this.getCreateAction(barrier, userDataProfileImportState); + const importAction = this.getCreateAction(barrier, userDataProfileImportState, options); const primaryAction = isWeb ? new Action('importInDesktop', localize('import in desktop', "Create Profile in {0}", this.productService.nameLong), undefined, true, async () => this.openerService.open(uri, { openExternal: true })) : importAction; @@ -435,15 +435,15 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private async previewProfile(profileTemplate: IUserDataProfileTemplate): Promise { + private async previewProfile(profileTemplate: IUserDataProfileTemplate, options: IUserDataProfileOptions | undefined): Promise { const disposables = new DisposableStore(); try { const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); if (userDataProfileImportState.isEmpty()) { - await this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); + await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); } else { const barrier = new Barrier(); - const importAction = this.getCreateAction(barrier, userDataProfileImportState); + const importAction = this.getCreateAction(barrier, userDataProfileImportState, options); await this.showProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW, profileTemplate.name, importAction, new BarrierAction(barrier, new Action('cancel', localize('cancel', "Cancel")), this.notificationService), false, userDataProfileImportState); await barrier.wait(); await this.hideProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW); @@ -453,16 +453,16 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private getCreateAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState): IAction { + private getCreateAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState, options: IUserDataProfileOptions | undefined): IAction { const importAction = new BarrierAction(barrier, new Action('title', localize('import', "Create Profile"), undefined, true, async () => { importAction.enabled = false; const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); - return this.createAndSwitch(profileTemplate, false, true, localize('create profile', "Create Profile")); + return this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); }), this.notificationService); return importAction; } - private async createAndSwitch(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, title: string): Promise { + private async createAndSwitch(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, options: IUserDataProfileOptions | undefined, title: string): Promise { return this.progressService.withProgress({ location: ProgressLocation.Notification, delay: 500, @@ -471,7 +471,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU title = `${title} (${profileTemplate.name})`; progress.report({ message: title }); const reportProgress = (message: string) => progress.report({ message: `${title}: ${message}` }); - const profile = await this.createProfile(profileTemplate, temporaryProfile, extensions, reportProgress); + const profile = await this.createProfile(profileTemplate, temporaryProfile, extensions, options, reportProgress); if (profile) { reportProgress(localize('switching profile', "Switching Profile...")); await this.userDataProfileManagementService.switchProfile(profile); @@ -480,8 +480,8 @@ export class UserDataProfileImportExportService extends Disposable implements IU }); } - private async createProfile(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, progress: (message: string) => void): Promise { - const profile = await this.getProfileToImport(profileTemplate, temporaryProfile); + private async createProfile(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, options: IUserDataProfileOptions | undefined, progress: (message: string) => void): Promise { + const profile = await this.getProfileToImport(profileTemplate, temporaryProfile, options); if (!profile) { return undefined; } @@ -569,12 +569,12 @@ export class UserDataProfileImportExportService extends Disposable implements IU return result?.id; } - private async getProfileToImport(profileTemplate: IUserDataProfileTemplate, temp: boolean): Promise { + private async getProfileToImport(profileTemplate: IUserDataProfileTemplate, temp: boolean, options: IUserDataProfileOptions | undefined): Promise { const profileName = profileTemplate.name; const profile = this.userDataProfilesService.profiles.find(p => p.name === profileName); if (profile) { if (temp) { - return this.userDataProfilesService.createNamedProfile(`${profileName} ${this.getProfileNameIndex(profileName)}`, { shortName: profileTemplate.shortName, transient: temp }); + return this.userDataProfilesService.createNamedProfile(`${profileName} ${this.getProfileNameIndex(profileName)}`, { ...options, shortName: profileTemplate.shortName, transient: temp }); } enum ImportProfileChoice { @@ -625,7 +625,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU } return this.userDataProfilesService.createNamedProfile(name); } else { - return this.userDataProfilesService.createNamedProfile(profileName, { shortName: profileTemplate.shortName, transient: temp }); + return this.userDataProfilesService.createNamedProfile(profileName, { ...options, shortName: profileTemplate.shortName, transient: temp }); } } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index bd126081ab0..3c56ae564f2 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -73,7 +73,8 @@ export function toUserDataProfileUri(path: string, productService: IProductServi }); } -export interface IProfileImportOptions { +export interface IProfileImportOptions extends IUserDataProfileOptions { + readonly name?: string; readonly mode?: 'preview' | 'apply' | 'both'; } @@ -87,7 +88,7 @@ export interface IUserDataProfileImportExportService { exportProfile(): Promise; importProfile(uri: URI, options?: IProfileImportOptions): Promise; showProfileContents(): Promise; - SaveCurrentProfileAs(name: string): Promise; + createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise; createTroubleshootProfile(): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; }