diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 1d7ad908f95..b2064a7698f 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -91,11 +91,14 @@ export interface IUserDataProfilesService { readonly onDidChangeProfiles: Event; readonly profiles: IUserDataProfile[]; + readonly onDidResetWorkspaces: Event; + createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise; updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise; setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise; - getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile; + getProfile(workspaceIdentifier: WorkspaceIdentifier, profileToUseIfNotSet: IUserDataProfile): IUserDataProfile; removeProfile(profile: IUserDataProfile): Promise; + resetWorkspaces(): Promise; } export function reviveProfile(profile: UriDto, scheme: string): IUserDataProfile { @@ -171,6 +174,9 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf protected readonly _onWillRemoveProfile = this._register(new Emitter()); readonly onWillRemoveProfile = this._onWillRemoveProfile.event; + private readonly _onDidResetWorkspaces = this._register(new Emitter()); + readonly onDidResetWorkspaces = this._onDidResetWorkspaces.event; + constructor( @IEnvironmentService protected readonly environmentService: IEnvironmentService, @IFileService protected readonly fileService: IFileService, @@ -194,6 +200,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf const profiles = this.enabled ? this.getStoredProfiles().map(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : []; let emptyWindow: IUserDataProfile | undefined; const workspaces = new ResourceMap(); + const defaultProfile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome); + profiles.unshift({ ...defaultProfile, isDefault: true, extensionsResource: this.defaultProfileShouldIncludeExtensionsResourceAlways || profiles.length > 0 ? defaultProfile.extensionsResource : undefined }); if (profiles.length) { const profileAssicaitions = this.getStoredProfileAssociations(); if (profileAssicaitions.workspaces) { @@ -211,17 +219,23 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation)); } } - const profile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome); - profiles.unshift({ ...profile, isDefault: true, extensionsResource: this.defaultProfileShouldIncludeExtensionsResourceAlways || profiles.length > 0 ? profile.extensionsResource : undefined }); this._profilesObject = { profiles, workspaces, emptyWindow }; } return this._profilesObject; } - getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { + getProfile(workspaceIdentifier: WorkspaceIdentifier, profileToUseIfNotSet: IUserDataProfile): IUserDataProfile { const workspace = this.getWorkspace(workspaceIdentifier); - const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow; - return profile ?? this.defaultProfile; + let profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow; + if (!profile) { + profile = profileToUseIfNotSet; + // Associate the profile to workspace only if there are user profiles + // If there are no profiles, workspaces are associated to default profile by default + if (this.profiles.length > 1) { + this.updateWorkspaceAssociation(workspaceIdentifier, profile); + } + } + return profile; } protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier { @@ -299,6 +313,13 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf this.updateWorkspaceAssociation(workspaceIdentifier); } + async resetWorkspaces(): Promise { + this.profilesObject.workspaces.clear(); + this.profilesObject.emptyWindow = undefined; + this.updateStoredProfileAssociations(); + this._onDidResetWorkspaces.fire(); + } + async removeProfile(profileToRemove: IUserDataProfile): Promise { if (!this.enabled) { throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); @@ -364,19 +385,19 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf this._onDidChangeProfiles.fire({ added, removed, updated, all: this.profiles }); } - private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) { + private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile): void { const workspace = this.getWorkspace(workspaceIdentifier); // Folder or Multiroot workspace if (URI.isUri(workspace)) { this.profilesObject.workspaces.delete(workspace); - if (newProfile && !newProfile.isDefault) { + if (newProfile) { this.profilesObject.workspaces.set(workspace, newProfile); } } // Empty Window else { - this.profilesObject.emptyWindow = !newProfile?.isDefault ? newProfile : undefined; + this.profilesObject.emptyWindow = newProfile; } this.updateStoredProfileAssociations(); diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts index eb662f36339..113012688aa 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { UriDto } from 'vs/base/common/types'; @@ -29,6 +29,8 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa private readonly _onDidChangeProfiles = this._register(new Emitter()); readonly onDidChangeProfiles = this._onDidChangeProfiles.event; + readonly onDidResetWorkspaces: Event; + constructor( profiles: UriDto[], @IMainProcessService mainProcessService: IMainProcessService, @@ -45,6 +47,7 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa this._profiles = e.all.map(profile => reviveProfile(profile, this.profilesHome.scheme)); this._onDidChangeProfiles.fire({ added, removed, updated, all: this.profiles }); })); + this.onDidResetWorkspaces = this.channel.listen('onDidResetWorkspaces'); } async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { @@ -65,6 +68,10 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa return reviveProfile(result, this.profilesHome.scheme); } - getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); } + resetWorkspaces(): Promise { + return this.channel.call('resetWorkspaces'); + } + + getProfile(workspaceIdentifier: WorkspaceIdentifier, profileToUseIfNotSet: IUserDataProfile): IUserDataProfile { throw new Error('Not implemented'); } } diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 61220857b74..cce402a89ba 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -118,8 +118,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this._config?.workspace; } - private _profile: IUserDataProfile | undefined; - get profile(): IUserDataProfile | undefined { if (!this._profile) { this._profile = revive(this._config?.profiles.current); } return this._profile; } + get profile(): IUserDataProfile | undefined { return this.config ? this.userDataProfilesService.getProfile(this.config.workspace ?? 'empty-window', revive(this.config.profiles.current)) : undefined; } get remoteAuthority(): string | undefined { return this._config?.remoteAuthority; } @@ -956,7 +955,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { configuration.editSessionId = this.environmentMainService.editSessionId; // set latest edit session id configuration.profiles = { all: this.userDataProfilesService.profiles, - current: this.userDataProfilesService.getProfile(configuration.workspace ?? 'empty-window'), + current: this.profile || this.userDataProfilesService.defaultProfile, }; // Load config diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 8452c1c5fc4..b287f30732d 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1326,7 +1326,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic profiles: { all: this.userDataProfilesService.profiles, - current: this.userDataProfilesService.getProfile(options.workspace ?? 'empty-window'), + current: this.userDataProfilesService.getProfile(options.workspace ?? 'empty-window', (options.windowToUse ?? this.getLastActiveWindow())?.profile ?? this.userDataProfilesService.defaultProfile), }, homeDir: this.environmentMainService.userHome.fsPath, diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 02ea6a2b6cb..b36b008adce 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -269,7 +269,7 @@ export class BrowserMain extends Disposable { // User Data Profiles const userDataProfilesService = new BrowserUserDataProfilesService(environmentService, fileService, uriIdentityService, logService); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - const userDataProfileService = new UserDataProfileService(userDataProfilesService.getProfile(isWorkspaceIdentifier(payload) || isSingleFolderWorkspaceIdentifier(payload) ? payload : 'empty-window'), userDataProfilesService); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.getProfile(isWorkspaceIdentifier(payload) || isSingleFolderWorkspaceIdentifier(payload) ? payload : 'empty-window', userDataProfilesService.defaultProfile), userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); // Long running services (workspace, config, storage) diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index 99090a700f0..92696e458c8 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -295,31 +295,6 @@ registerAction2(class SwitchProfileAction extends Action2 { } }); -registerAction2(class CleanupProfilesAction extends Action2 { - constructor() { - super({ - id: 'workbench.profiles.actions.cleanupProfiles', - title: { - value: localize('cleanup profile', "Cleanup Settings Profiles"), - original: 'Cleanup Profiles' - }, - category: CATEGORIES.Developer, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, - }); - } - - async run(accessor: ServicesAccessor) { - const userDataProfilesService = accessor.get(IUserDataProfilesService); - const fileService = accessor.get(IFileService); - const uriIdentityService = accessor.get(IUriIdentityService); - - const stat = await fileService.resolve(userDataProfilesService.profilesHome); - await Promise.all((stat.children || [])?.filter(child => child.isDirectory && userDataProfilesService.profiles.every(p => !uriIdentityService.extUri.isEqual(p.location, child.resource))) - .map(child => fileService.del(child.resource, { recursive: true }))); - } -}); - registerAction2(class ExportProfileAction extends Action2 { constructor() { super({ @@ -469,3 +444,50 @@ registerAction2(class ImportProfileAction extends Action2 { } }); + +// Developer Actions + +registerAction2(class CleanupProfilesAction extends Action2 { + constructor() { + super({ + id: 'workbench.profiles.actions.cleanupProfiles', + title: { + value: localize('cleanup profile', "Cleanup Settings Profiles"), + original: 'Cleanup Profiles' + }, + category: CATEGORIES.Developer, + f1: true, + precondition: PROFILES_ENABLEMENT_CONTEXT, + }); + } + + async run(accessor: ServicesAccessor) { + const userDataProfilesService = accessor.get(IUserDataProfilesService); + const fileService = accessor.get(IFileService); + const uriIdentityService = accessor.get(IUriIdentityService); + + const stat = await fileService.resolve(userDataProfilesService.profilesHome); + await Promise.all((stat.children || [])?.filter(child => child.isDirectory && userDataProfilesService.profiles.every(p => !uriIdentityService.extUri.isEqual(p.location, child.resource))) + .map(child => fileService.del(child.resource, { recursive: true }))); + } +}); + +registerAction2(class ResetWorkspacesAction extends Action2 { + constructor() { + super({ + id: 'workbench.profiles.actions.resetWorkspaces', + title: { + value: localize('reset workspaces', "Reset Workspace Settings Profiles Associations"), + original: 'Reset Workspace Settings Profiles Associations' + }, + category: CATEGORIES.Developer, + f1: true, + precondition: PROFILES_ENABLEMENT_CONTEXT, + }); + } + + async run(accessor: ServicesAccessor) { + const userDataProfilesService = accessor.get(IUserDataProfilesService); + return userDataProfilesService.resetWorkspaces(); + } +}); diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 7b124b207ca..719b6951865 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -28,6 +28,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse ) { super(); this._register(userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); + this._register(userDataProfilesService.onDidResetWorkspaces(() => this.onDidResetWorkspaces())); } private onDidChangeProfiles(e: DidChangeProfilesEvent): void { @@ -37,6 +38,13 @@ export class UserDataProfileManagementService extends Disposable implements IUse } } + private onDidResetWorkspaces(): void { + if (!this.userDataProfileService.currentProfile.isDefault) { + this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current settings profile has been removed. Please reload to switch back to default settings profile")); + return; + } + } + async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise { const profile = await this.userDataProfilesService.createProfile(name, useDefaultFlags, this.getWorkspaceIdentifier()); await this.enterProfile(profile, !!fromExisting);