profiles editor: incorporate feedback (#214490)

This commit is contained in:
Sandeep Somavarapu 2024-06-06 21:55:49 +02:00 committed by GitHub
parent 6e4ace33dd
commit dfedd9ba12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 505 additions and 294 deletions

View file

@ -768,7 +768,7 @@ export class ExtensionsScanner extends Disposable {
}
}
private async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise<ILocalExtension> {
async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise<ILocalExtension> {
try {
if (profileLocation) {
const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation });
@ -921,13 +921,9 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask<ILocalExtensio
this.identifier = this.extensionKey.identifier;
}
private async getExistingExtension(): Promise<ILocalExtension | undefined> {
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<ILocalExtension> {
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<ILocalExtensio
throw toExtensionManagementError(error, ExtensionManagementErrorCode.AddToProfile);
}
const result = await this.getExistingExtension();
const result = await this.extensionsScanner.scanLocalExtension(local.location, ExtensionType.User, this.options.profileLocation);
if (!result) {
throw new ExtensionManagementError('Cannot find the installed extension', ExtensionManagementErrorCode.InstalledExtensionNotFound);
}

View file

@ -15,6 +15,11 @@
height: 100%;
}
.profiles-editor .monaco-split-view2 > .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;
}

View file

@ -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<DisposableStore>());
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<DisposableStore>());
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
}
]
});
}

View file

@ -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<void> {
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<AbstractUserDataProfileElement> {
getHeight(element: AbstractUserDataProfileElement) {
return 30;
return 22;
}
getTemplateId() { return 'profileListElement'; }
}
@ -352,24 +383,42 @@ class ProfileElementRenderer implements IListRenderer<AbstractUserDataProfileEle
readonly templateId = 'profileListElement';
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
) { }
renderTemplate(container: HTMLElement): IProfileElementTemplateData {
const disposables = new DisposableStore();
const elementDisposables = new DisposableStore();
container.classList.add('profile-list-item');
const icon = append(container, $('.profile-list-item-icon'));
const label = append(container, $('.profile-list-item-label'));
const description = append(container, $('.profile-list-item-description'));
append(description, $(`span${ThemeIcon.asCSSSelector(Codicon.check)}`));
append(description, $('span', undefined, localize('activeProfile', "Active")));
return { label, icon, description, disposables: new DisposableStore() };
append(description, $('span', undefined, localize('activeProfile', "In use")));
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, icon, description, actionBar, disposables, elementDisposables };
}
renderElement(element: AbstractUserDataProfileElement, index: number, templateData: IProfileElementTemplateData, height: number | undefined) {
templateData.disposables.clear();
templateData.elementDisposables.clear();
templateData.label.textContent = element.name;
templateData.label.classList.toggle('new-profile', element instanceof NewProfileElement);
templateData.icon.className = ThemeIcon.asClassName(element.icon ? ThemeIcon.fromId(element.icon) : DEFAULT_ICON);
templateData.description.classList.toggle('hide', !element.active);
if (element.onDidChange) {
templateData.disposables.add(element.onDidChange(e => {
templateData.elementDisposables.add(element.onDidChange(e => {
if (e.name) {
templateData.label.textContent = element.name;
}
@ -385,10 +434,16 @@ class ProfileElementRenderer implements IListRenderer<AbstractUserDataProfileEle
}
}));
}
templateData.actionBar.setActions(element.titleActions[0] ?? []);
}
disposeElement(element: AbstractUserDataProfileElement, index: number, templateData: IProfileElementTemplateData, height: number | undefined): void {
templateData.elementDisposables.clear();
}
disposeTemplate(templateData: IProfileElementTemplateData): void {
templateData.disposables.dispose();
templateData.elementDisposables.dispose();
}
}
@ -405,6 +460,7 @@ class ProfileWidget extends Disposable {
private readonly copyFromSelectBox: SelectBox;
private copyFromOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | URI })[] = [];
private readonly contentsTreeHeader: HTMLElement;
private readonly resourcesTree: WorkbenchAsyncDataTree<AbstractUserDataProfileElement, ProfileResourceTreeElement>;
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<AbstractUserDataProfileElement, ProfileResourceTreeElement>,
'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 IListVirtualDelegate<Profile
return ExistingProfileResourceTreeRenderer.TEMPLATE_ID;
}
getHeight(element: ProfileResourceTreeElement) {
return 30;
return 22;
}
}
@ -793,13 +868,17 @@ class ProfileResourceTreeDataSource implements IAsyncDataSource<AbstractUserData
return false;
}
if (element.root instanceof NewProfileElement) {
if (element.root.getFlag((<IProfileResourceTypeElement>element.element).resourceType)) {
const resourceType = (<IProfileResourceTypeElement>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((<IProfileResourceTypeElement>element.element).resourceType)) {
if (!element.root.getCopyFlag(resourceType)) {
return false;
}
}
@ -829,6 +908,7 @@ class ProfileResourceTreeDataSource implements IAsyncDataSource<AbstractUserData
interface IProfileResourceTemplateData {
readonly disposables: DisposableStore;
readonly elementDisposables: DisposableStore;
readonly actionBar: WorkbenchToolBar;
}
interface IExistingProfileResourceTemplateData extends IProfileResourceTemplateData {
@ -882,6 +962,12 @@ class ExistingProfileResourceTreeRenderer extends AbstractProfileResourceTreeRen
readonly templateId = ExistingProfileResourceTreeRenderer.TEMPLATE_ID;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
}
renderTemplate(parent: HTMLElement): IExistingProfileResourceTemplateData {
const disposables = new DisposableStore();
const container = append(parent, $('.profile-tree-item-container.existing-profile-resource-type-container'));
@ -890,8 +976,17 @@ class ExistingProfileResourceTreeRenderer extends AbstractProfileResourceTreeRen
const inheritContainer = append(container, $('.inherit-container'));
const checkbox = disposables.add(new Checkbox('', false, defaultCheckboxStyles));
append(inheritContainer, checkbox.domNode);
append(inheritContainer, $('.inherit-label', undefined, localize('default profile', "Use Default Profile")));
return { checkbox, label, inheritContainer, 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, label, inheritContainer, actionBar, disposables, elementDisposables: disposables.add(new DisposableStore()) };
}
renderElement({ element: profileResourceTreeElement }: ITreeNode<ProfileResourceTreeElement, void>, 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<ProfileResourceTreeElement, void>, 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<ProfileResourceTreeElement, void>, 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] : []);
}
}

View file

@ -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<IProfileResourceTypeElement>(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<Promise<IProfileResourceTypeElement>>(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<void> | 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<void> {
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<void> {
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<IProfileChildElement[]> {
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 => {

View file

@ -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;