in patched-vscode/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts [247:543]
private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | URI | Mutable<IUserDataProfileTemplate>): Promise<void> {
type SaveProfileInfoClassification = {
owner: 'sandy081';
comment: 'Report when profile is about to be saved';
};
type CreateProfileInfoClassification = {
owner: 'sandy081';
comment: 'Report when profile is about to be created';
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Type of profile source' };
};
type CreateProfileInfoEvent = {
source: string | undefined;
};
const createProfileTelemetryData: CreateProfileInfoEvent = { source: source instanceof URI ? 'template' : isUserDataProfile(source) ? 'profile' : source ? 'external' : undefined };
if (profile) {
this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.startEdit');
} else {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.startCreate', createProfileTelemetryData);
}
const disposables = new DisposableStore();
const title = profile ? localize('save profile', "Edit {0} Profile...", profile.name) : localize('create new profle', "Create New Profile...");
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', "Profile name");
quickPick.value = profile?.name ?? (isUserDataProfileTemplate(source) ? this.generateProfileName(source.name) : '');
quickPick.canSelectMany = true;
quickPick.matchOnDescription = false;
quickPick.matchOnDetail = false;
quickPick.matchOnLabel = false;
quickPick.sortByLabel = false;
quickPick.hideCountBadge = true;
quickPick.ok = false;
quickPick.customButton = true;
quickPick.hideCheckAll = true;
quickPick.ignoreFocusOut = true;
quickPick.customLabel = profile ? localize('save', "Save") : localize('create', "Create");
quickPick.description = localize('customise the profile', "Choose what to configure in your Profile:");
quickPick.items = [...resources];
const update = () => {
quickPick.items = resources;
quickPick.selectedItems = resources.filter(item => item.picked);
};
update();
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.Warning;
return;
}
if (resources.every(resource => !resource.picked)) {
quickPick.validationMessage = localize('invalid configurations', "The profile should contain at least one configuration.");
quickPick.severity = Severity.Warning;
return;
}
quickPick.severity = Severity.Ignore;
quickPick.validationMessage = undefined;
};
disposables.add(quickPick.onDidChangeSelection(items => {
let needUpdate = false;
for (const resource of resources) {
resource.picked = items.includes(resource);
const description = resource.picked ? undefined : localize('use default profile', "Using Default Profile");
if (resource.description !== description) {
resource.description = description;
needUpdate = true;
}
}
if (needUpdate) {
update();
}
validate();
}));
disposables.add(quickPick.onDidChangeValue(validate));
let icon = DEFAULT_ICON;
if (profile?.icon) {
icon = ThemeIcon.fromId(profile.icon);
}
if (isUserDataProfileTemplate(source) && source.icon) {
icon = ThemeIcon.fromId(source.icon);
}
if (icon.id !== DEFAULT_ICON.id && !ICONS.some(({ id }) => id === icon.id) && !getAllCodicons().some(({ id }) => id === icon.id)) {
icon = DEFAULT_ICON;
}
let result: { name: string; items: ReadonlyArray<IQuickPickItem>; icon?: string | null } | undefined;
disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => {
const name = quickPick.value.trim();
if (!name) {
quickPick.validationMessage = localize('name required', "Profile name is required and must be a non-empty value.");
quickPick.severity = Severity.Error;
}
if (quickPick.validationMessage) {
return;
}
result = { name, items: quickPick.selectedItems, icon: icon.id === DEFAULT_ICON.id ? null : icon.id };
quickPick.hide();
quickPick.severity = Severity.Ignore;
quickPick.validationMessage = undefined;
}));
const domNode = DOM.$('.profile-edit-widget');
const profileIconContainer = DOM.$('.profile-icon-container');
DOM.append(profileIconContainer, DOM.$('.profile-icon-label', undefined, localize('icon', "Icon:")));
const profileIconElement = DOM.append(profileIconContainer, DOM.$(`.profile-icon${ThemeIcon.asCSSSelector(icon)}`));
profileIconElement.tabIndex = 0;
profileIconElement.role = 'button';
profileIconElement.ariaLabel = localize('select icon', "Icon: {0}", icon.id);
const iconSelectBox = disposables.add(this.instantiationService.createInstance(WorkbenchIconSelectBox, { icons: ICONS, inputBoxStyles: defaultInputBoxStyles }));
const dimension = new DOM.Dimension(486, 260);
iconSelectBox.layout(dimension);
let hoverWidget: IHoverWidget | undefined;
const updateIcon = (updated: ThemeIcon | undefined) => {
icon = updated ?? DEFAULT_ICON;
profileIconElement.className = `profile-icon ${ThemeIcon.asClassName(icon)}`;
profileIconElement.ariaLabel = localize('select icon', "Icon: {0}", icon.id);
};
disposables.add(iconSelectBox.onDidSelect(selectedIcon => {
if (icon.id !== selectedIcon.id) {
updateIcon(selectedIcon);
}
hoverWidget?.dispose();
profileIconElement.focus();
}));
const showIconSelectBox = () => {
iconSelectBox.clearInput();
hoverWidget = this.hoverService.showHover({
content: iconSelectBox.domNode,
target: profileIconElement,
position: {
hoverPosition: HoverPosition.BELOW,
},
persistence: {
sticky: true,
},
appearance: {
showPointer: true,
},
}, true);
if (hoverWidget) {
iconSelectBox.layout(dimension);
disposables.add(hoverWidget);
}
iconSelectBox.focus();
};
disposables.add(DOM.addDisposableListener(profileIconElement, DOM.EventType.CLICK, (e: MouseEvent) => {
DOM.EventHelper.stop(e, true);
showIconSelectBox();
}));
disposables.add(DOM.addDisposableListener(profileIconElement, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
DOM.EventHelper.stop(event, true);
showIconSelectBox();
}
}));
disposables.add(DOM.addDisposableListener(iconSelectBox.domNode, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Escape)) {
DOM.EventHelper.stop(event, true);
hoverWidget?.dispose();
profileIconElement.focus();
}
}));
if (!profile && !isUserDataProfileTemplate(source)) {
const profileTypeContainer = DOM.append(domNode, DOM.$('.profile-type-container'));
DOM.append(profileTypeContainer, DOM.$('.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 | URI })[] = [];
profileOptions.push({ text: localize('empty profile', "None") });
const templates = await this.userDataProfileManagementService.getBuiltinProfileTemplates();
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: URI.parse(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 (source instanceof URI) {
return option.source instanceof URI && this.uriIdentityService.extUri.isEqual(option.source, source);
} else if (isUserDataProfile(source)) {
return option.id === source.id;
}
return false;
});
return index > -1 ? index : 0;
};
const initialIndex = findOptionIndex();
const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox,
profileOptions,
initialIndex,
this.contextViewService,
defaultSelectBoxStyles,
{
useCustomDrawn: true,
ariaLabel: localize('copy profile from', "Copy profile from"),
}
));
selectBox.render(DOM.append(profileTypeContainer, DOM.$('.profile-type-select-container')));
if (profileOptions[initialIndex].source) {
quickPick.value = this.generateProfileName(profileOptions[initialIndex].text);
}
const updateOptions = () => {
const option = profileOptions[findOptionIndex()];
for (const resource of resources) {
resource.picked = option.source && !(option.source instanceof URI) ? !option.source?.useDefaultFlags?.[resource.id] : true;
}
updateIcon(!(option.source instanceof URI) && option.source?.icon ? ThemeIcon.fromId(option.source.icon) : undefined);
update();
};
updateOptions();
disposables.add(selectBox.onDidSelect(({ index }) => {
source = profileOptions[index].source;
updateOptions();
}));
}
DOM.append(domNode, profileIconContainer);
quickPick.widget = domNode;
quickPick.show();
await new Promise<void>((c, e) => {
disposables.add(quickPick.onDidHide(() => {
disposables.dispose();
c();
}));
});
if (!result) {
if (profile) {
this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.cancelEdit');
} else {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.cancelCreate', createProfileTelemetryData);
}
return;
}
try {
const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length === resources.length
? undefined
: {
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)
};
if (profile) {
await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, icon: result.icon, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags });
} else {
if (source instanceof URI) {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createFromTemplate', createProfileTelemetryData);
await this.importProfile(source, { mode: 'apply', name: result.name, useDefaultFlags, icon: result.icon ? result.icon : undefined });
} else if (isUserDataProfile(source)) {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createFromProfile', createProfileTelemetryData);
await this.createFromProfile(source, result.name, { useDefaultFlags, icon: result.icon ? result.icon : undefined });
} else if (isUserDataProfileTemplate(source)) {
source.name = result.name;
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createFromExternalTemplate', createProfileTelemetryData);
await this.createAndSwitch(source, false, true, { useDefaultFlags, icon: result.icon ? result.icon : undefined }, localize('create profile', "Create Profile"));
} else {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createEmptyProfile', createProfileTelemetryData);
await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags, icon: result.icon ? result.icon : undefined });
}
}
} catch (error) {
this.notificationService.error(error);
}
}