diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index bd7c26a8616..918ec2e5e40 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -102,7 +102,6 @@ import { IExtensionsScannerService } from 'vs/platform/extensionManagement/commo import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -import { revive } from 'vs/base/common/marshalling'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; @@ -233,7 +232,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // User Data Profiles - const userDataProfilesService = this._register(new UserDataProfilesService(revive(this.configuration.profiles.default), revive(this.configuration.profiles.current), environmentService, fileService, logService)); + const userDataProfilesService = this._register(new UserDataProfilesService(this.configuration.defaultProfile, undefined, environmentService, fileService, logService)); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 28db082ca0d..d846dcb1304 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -68,6 +68,8 @@ import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/pol import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; /** * The main VS Code entry point. @@ -170,6 +172,10 @@ class CodeMain { const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // URI Identity + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); + // Logger services.set(ILoggerService, new LoggerService(logService, fileService)); @@ -178,7 +184,7 @@ class CodeMain { services.set(IStateMainService, stateMainService); // User Data Profiles - const userDataProfilesMainService = new UserDataProfilesMainService(stateMainService, environmentMainService, fileService, logService); + const userDataProfilesMainService = new UserDataProfilesMainService(stateMainService, uriIdentityService, environmentMainService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesMainService); // Policy @@ -244,10 +250,7 @@ class CodeMain { ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), // State service - stateMainService.init(), - - // User Data Profiles Service - userDataProfilesMainService.init(), + stateMainService.init().then(() => userDataProfilesMainService.init()), // Configuration service configurationService.initialize() diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 81bb26375b2..7da986b840b 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -242,7 +242,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { appRoot: this.environmentMainService.appRoot, codeCachePath: this.environmentMainService.codeCachePath, backupWorkspacesPath: this.environmentMainService.backupWorkspacesPath, - profiles: this.userDataProfilesService.serialize(), + defaultProfile: this.userDataProfilesService.defaultProfile, userEnv: this.userEnv, args: this.environmentMainService.args, logLevel: this.logService.getLevel(), diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index b5656e2d080..e445bc596d0 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -7,8 +7,9 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { LogLevel } from 'vs/platform/log/common/log'; -import { IUserDataProfilesDto } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; +import { UriDto } from 'vs/base/common/types'; export interface ISharedProcess { @@ -28,7 +29,7 @@ export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly backupWorkspacesPath: string; - readonly profiles: IUserDataProfilesDto; + readonly defaultProfile: UriDto; readonly policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; } diff --git a/src/vs/platform/storage/common/storageIpc.ts b/src/vs/platform/storage/common/storageIpc.ts index e61a2736999..90572b900ea 100644 --- a/src/vs/platform/storage/common/storageIpc.ts +++ b/src/vs/platform/storage/common/storageIpc.ts @@ -5,9 +5,10 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { UriDto } from 'vs/base/common/types'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; -import { IUserDataProfileDto, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ISerializedSingleFolderWorkspaceIdentifier, ISerializedWorkspaceIdentifier, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; export type Key = string; @@ -21,7 +22,7 @@ export interface IBaseSerializableStorageRequest { * workspace is provided. Can be undefined to denote * application scope. */ - readonly profile: IUserDataProfileDto | undefined; + readonly profile: UriDto | undefined; /** * Workspace to correlate storage. Can be undefined to @@ -46,7 +47,7 @@ abstract class BaseStorageDatabaseClient extends Disposable implements IStorageD constructor( protected channel: IChannel, - protected profile: IUserDataProfileDto | undefined, + protected profile: UriDto | undefined, protected workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined ) { super(); @@ -81,7 +82,7 @@ abstract class BaseProfileAwareStorageDatabaseClient extends BaseStorageDatabase private readonly _onDidChangeItemsExternal = this._register(new Emitter()); readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event; - constructor(channel: IChannel, profile: IUserDataProfileDto | undefined) { + constructor(channel: IChannel, profile: UriDto | undefined) { super(channel, profile, undefined); this.registerListeners(); @@ -119,7 +120,7 @@ class ApplicationStorageDatabaseClient extends BaseProfileAwareStorageDatabaseCl class GlobalStorageDatabaseClient extends BaseProfileAwareStorageDatabaseClient { - constructor(channel: IChannel, profile: IUserDataProfileDto) { + constructor(channel: IChannel, profile: UriDto) { super(channel, profile); } diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index b916e7510be..1d71b72f781 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -3,17 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce } from 'vs/base/common/arrays'; -import { Emitter, Event } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; import { Disposable } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { UriDto } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; + +export type ProfileOptions = { + settings?: boolean; + keybindings?: boolean; + tasks?: boolean; + snippets?: boolean; + extensions?: boolean; + uiState?: boolean; +}; + +export const DefaultOptions: ProfileOptions = { + settings: true, + keybindings: true, + tasks: true, + snippets: true, + extensions: true, + uiState: true +}; export interface IUserDataProfile { readonly id: string; @@ -28,30 +46,23 @@ export interface IUserDataProfile { readonly extensionsResource: URI | undefined; } -export type IUserDataProfileDto = UriDto; -export type IUserDataProfilesDto = { - readonly current: IUserDataProfileDto; - readonly default: IUserDataProfileDto; -}; - export const IUserDataProfilesService = createDecorator('IUserDataProfilesService'); export interface IUserDataProfilesService { readonly _serviceBrand: undefined; readonly profilesHome: URI; readonly defaultProfile: IUserDataProfile; - - readonly onDidChangeCurrentProfile: Event; readonly currentProfile: IUserDataProfile; - createProfile(name: string): IUserDataProfile; - setProfile(name: string): Promise; + newProfile(name: string, options?: ProfileOptions): IUserDataProfile; + createProfile(profile: IUserDataProfile, options: ProfileOptions, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise; + setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise; + getProfile(workspace: URI): IUserDataProfile; getAllProfiles(): Promise; - - serialize(): IUserDataProfilesDto; + removeProfile(profile: IUserDataProfile): Promise; } -function reviveProfile(profile: IUserDataProfile, scheme: string): IUserDataProfile { +export function reviveProfile(profile: UriDto, scheme: string): IUserDataProfile { return { id: profile.id, isDefault: profile.isDefault, @@ -69,74 +80,49 @@ function reviveProfile(profile: IUserDataProfile, scheme: string): IUserDataProf export class UserDataProfilesService extends Disposable implements IUserDataProfilesService { readonly _serviceBrand: undefined; - protected static DEFAULT_PROFILE_NAME = 'default'; + readonly profilesHome: URI; protected _currentProfile: IUserDataProfile; get currentProfile(): IUserDataProfile { return this._currentProfile; } - readonly profilesHome: URI; protected _defaultProfile: IUserDataProfile; get defaultProfile(): IUserDataProfile { return this._defaultProfile; } - private readonly _onDidChangeCurrentProfile = this._register(new Emitter()); - readonly onDidChangeCurrentProfile = this._onDidChangeCurrentProfile.event; - constructor( - defaultProfile: IUserDataProfile | undefined, - currentProfile: IUserDataProfile | undefined, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + defaultProfile: UriDto | undefined, + currentProfile: UriDto | undefined, + @IEnvironmentService protected readonly environmentService: IEnvironmentService, @IFileService protected readonly fileService: IFileService, @ILogService protected readonly logService: ILogService ) { super(); this.profilesHome = joinPath(this.environmentService.userRoamingDataHome, 'profiles'); - this._defaultProfile = defaultProfile ? reviveProfile(defaultProfile, this.profilesHome.scheme) : this.createProfile(undefined); + this._defaultProfile = defaultProfile ? reviveProfile(defaultProfile, this.profilesHome.scheme) : this.toUserDataProfile(localize('defaultProfile', "Default"), environmentService.userRoamingDataHome, { ...DefaultOptions, extensions: false }, true); this._currentProfile = currentProfile ? reviveProfile(currentProfile, this.profilesHome.scheme) : this._defaultProfile; } - createProfile(name: string | undefined): IUserDataProfile { - const isDefault = !name || name === UserDataProfilesService.DEFAULT_PROFILE_NAME; - const location = name && name !== UserDataProfilesService.DEFAULT_PROFILE_NAME ? joinPath(this.profilesHome, name) : this.environmentService.userRoamingDataHome; + newProfile(name: string, options: ProfileOptions = DefaultOptions): IUserDataProfile { + return this.toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), options, this.defaultProfile); + } + + protected toUserDataProfile(name: string, location: URI, options: ProfileOptions, defaultProfile: true | IUserDataProfile): IUserDataProfile { return { id: hash(location.toString()).toString(16), - isDefault, - name: name ?? UserDataProfilesService.DEFAULT_PROFILE_NAME, - location, - globalStorageHome: joinPath(location, 'globalStorage'), - settingsResource: joinPath(location, 'settings.json'), - keybindingsResource: joinPath(location, 'keybindings.json'), - tasksResource: joinPath(location, 'tasks.json'), - snippetsHome: joinPath(location, 'snippets'), - extensionsResource: name ? joinPath(location, 'extensions.json') : undefined + name: name, + location: location, + isDefault: defaultProfile === true, + globalStorageHome: defaultProfile === true || options.uiState ? joinPath(location, 'globalStorage') : defaultProfile.globalStorageHome, + settingsResource: defaultProfile === true || options.settings ? joinPath(location, 'settings.json') : defaultProfile.settingsResource, + keybindingsResource: defaultProfile === true || options.keybindings ? joinPath(location, 'keybindings.json') : defaultProfile.keybindingsResource, + tasksResource: defaultProfile === true || options.tasks ? joinPath(location, 'tasks.json') : defaultProfile.tasksResource, + snippetsHome: defaultProfile === true || options.snippets ? joinPath(location, 'snippets') : defaultProfile.snippetsHome, + extensionsResource: defaultProfile === true && !options.extensions ? undefined : joinPath(location, 'extensions.json'), }; } - async getAllProfiles(): Promise { - try { - const stat = await this.fileService.resolve(this.profilesHome); - const profiles = coalesce(stat.children?.map(stat => stat.isDirectory ? this.createProfile(stat.name) : undefined) ?? []); - if (profiles.length) { - profiles.unshift(this._defaultProfile); - } - return profiles; - } catch (error) { - if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - this.logService.error('Error while getting all profiles', error); - } - } - return []; - } - - protected createCurrentProfile(profile: string | undefined): IUserDataProfile { - return profile === UserDataProfilesService.DEFAULT_PROFILE_NAME ? this._defaultProfile : this.createProfile(profile); - } - - setProfile(name: string): Promise { throw new Error('Not implemented'); } - - serialize(): IUserDataProfilesDto { - return { - default: this.defaultProfile, - current: this.currentProfile - }; - } + getAllProfiles(): Promise { throw new Error('Not implemented'); } + createProfile(profile: IUserDataProfile, options: ProfileOptions, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { throw new Error('Not implemented'); } + setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { throw new Error('Not implemented'); } + getProfile(workspace: URI): IUserDataProfile { throw new Error('Not implemented'); } + removeProfile(profile: IUserDataProfile): Promise { throw new Error('Not implemented'); } } diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts index 1a13319dc3d..8c83a01a59d 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts @@ -3,18 +3,42 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ResourceMap } from 'vs/base/common/map'; +import { revive } from 'vs/base/common/marshalling'; +import { UriDto } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateMainService } from 'vs/platform/state/electron-main/state'; -import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { ProfileOptions, DefaultOptions, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; + +type UserDataProfiles = { + profiles: IUserDataProfile[]; + workspaces: ResourceMap; +}; + +type StoredUserDataProfile = { + name: string; + location: URI; + options: ProfileOptions; +}; + +type StoredWorkspaceInfo = { + workspace: URI; + profile: URI; +}; export class UserDataProfilesMainService extends UserDataProfilesService implements IUserDataProfilesService { - private static CURRENT_PROFILE_KEY = 'currentUserDataProfile'; + private static readonly PROFILES_KEY = 'userDataProfiles'; + private static readonly WORKSPACE_PROFILE_INFO_KEY = 'workspaceAndProfileInfo'; constructor( @IStateMainService private readonly stateMainService: IStateMainService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @ILogService logService: ILogService, @@ -22,23 +46,90 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme super(undefined, undefined, environmentService, fileService, logService); } - async init(): Promise { - const profileName = this.stateMainService.getItem(UserDataProfilesMainService.CURRENT_PROFILE_KEY); - if (profileName) { - const profiles = await this.getAllProfiles(); - const profile = profiles.find(p => p.name === profileName); - if (profile || (profileName === UserDataProfilesMainService.DEFAULT_PROFILE_NAME && profiles.length > 1)) { - this._defaultProfile = this.createProfile(UserDataProfilesService.DEFAULT_PROFILE_NAME); - this._currentProfile = profileName === UserDataProfilesMainService.DEFAULT_PROFILE_NAME ? this._defaultProfile : profile ?? this._defaultProfile; - } else { - this.stateMainService?.removeItem(UserDataProfilesMainService.CURRENT_PROFILE_KEY); - } + init(): void { + if (this.storedProfiles.length) { + this._defaultProfile = this.toUserDataProfile(this.defaultProfile.name, this.defaultProfile.location, DefaultOptions, true); } } - override async setProfile(name: string): Promise { - this.stateMainService?.setItem(UserDataProfilesMainService.CURRENT_PROFILE_KEY, name); + private _profiles: UserDataProfiles | undefined; + private get profiles(): UserDataProfiles { + if (!this._profiles) { + const profiles = this.storedProfiles.map(storedProfile => this.toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.options, this.defaultProfile)); + profiles.unshift(this.defaultProfile); + const workspaces = this.storedWorskpaceInfos.reduce((workspaces, workspaceProfileInfo) => { + const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, workspaceProfileInfo.profile)); + if (profile) { + workspaces.set(workspaceProfileInfo.workspace, profile); + } + return workspaces; + }, new ResourceMap()); + this._profiles = { profiles: profiles, workspaces: workspaces }; + } + return this._profiles; + } + + override async getAllProfiles(): Promise { + return this.profiles.profiles; + } + + override getProfile(workspace: URI): IUserDataProfile { + return this.profiles.workspaces.get(workspace) ?? this.defaultProfile; + } + + override async createProfile(profile: IUserDataProfile, options: ProfileOptions, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + profile = reviveProfile(profile, this.profilesHome.scheme); + if (this.storedProfiles.some(p => p.name === profile.name)) { + throw new Error(`Profile with name ${profile.name} already exists`); + } + const storedProfile: StoredUserDataProfile = { name: profile.name, location: profile.location, options }; + const storedProfiles = [...this.storedProfiles, storedProfile]; + this.storedProfiles = storedProfiles; + if (workspaceIdentifier) { + await this.setProfileForWorkspace(profile, workspaceIdentifier); + } + return this.profiles.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!; + } + + override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + profile = reviveProfile(profile, this.profilesHome.scheme); + const workspace = isSingleFolderWorkspaceIdentifier(workspaceIdentifier) ? workspaceIdentifier.uri : workspaceIdentifier.configPath; + const storedWorkspaceInfos = this.storedWorskpaceInfos.filter(info => !this.uriIdentityService.extUri.isEqual(info.workspace, workspace)); + if (!profile.isDefault) { + storedWorkspaceInfos.push({ workspace, profile: profile.location }); + } + this.storedWorskpaceInfos = storedWorkspaceInfos; + return this.profiles.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!; + } + + override async removeProfile(profile: IUserDataProfile): Promise { + if (profile.isDefault) { + throw new Error('Cannot remove default profile'); + } + profile = reviveProfile(profile, this.profilesHome.scheme); + if (!this.storedProfiles.some(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))) { + throw new Error(`Profile with name ${profile.name} does not exist`); + } + this.storedWorskpaceInfos = this.storedWorskpaceInfos.filter(p => !this.uriIdentityService.extUri.isEqual(p.profile, profile.location)); + this.storedProfiles = this.storedProfiles.filter(p => !this.uriIdentityService.extUri.isEqual(p.location, profile.location)); + } + + private get storedProfiles(): StoredUserDataProfile[] { + return revive(this.stateMainService.getItem[]>(UserDataProfilesMainService.PROFILES_KEY, [])); + } + + private set storedProfiles(storedProfiles: StoredUserDataProfile[]) { + this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles); + this._profiles = undefined; + } + + private get storedWorskpaceInfos(): StoredWorkspaceInfo[] { + return revive(this.stateMainService.getItem[]>(UserDataProfilesMainService.WORKSPACE_PROFILE_INFO_KEY, [])); + } + + private set storedWorskpaceInfos(storedWorkspaceInfos: StoredWorkspaceInfo[]) { + this.stateMainService.setItem(UserDataProfilesMainService.WORKSPACE_PROFILE_INFO_KEY, storedWorkspaceInfos); + this._profiles = undefined; } } - diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts index c28b57fe965..d2bcfd82043 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { UriDto } from 'vs/base/common/types'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IUserDataProfile, IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ProfileOptions, IUserDataProfile, IUserDataProfilesService, reviveProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; export class UserDataProfilesNativeService extends UserDataProfilesService implements IUserDataProfilesService { @@ -22,8 +24,23 @@ export class UserDataProfilesNativeService extends UserDataProfilesService imple super(defaultProfile, currentProfile, environmentService, fileService, logService); } - override setProfile(name: string): Promise { - return this.channel.call('setProfile', [name]); + override async createProfile(profile: IUserDataProfile, options: ProfileOptions, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + const result = await this.channel.call>('createProfile', [profile, options, workspaceIdentifier]); + return reviveProfile(result, this.profilesHome.scheme); + } + + override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + const result = await this.channel.call>('setProfileForWorkspace', [profile, workspaceIdentifier]); + return reviveProfile(result, this.profilesHome.scheme); + } + + override async getAllProfiles(): Promise { + const result = await this.channel.call[]>('getAllProfiles'); + return result.map(profile => reviveProfile(profile, this.profilesHome.scheme)); + } + + override removeProfile(profile: IUserDataProfile): Promise { + return this.channel.call('removeProfile', [profile]); } } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index c675d3e5b13..875bd45f761 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -6,6 +6,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { PerformanceMark } from 'vs/base/common/performance'; import { isLinux, isMacintosh, isNative, isWeb, isWindows } from 'vs/base/common/platform'; +import { UriDto } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -16,7 +17,7 @@ import { FileType } from 'vs/platform/files/common/files'; import { LogLevel } from 'vs/platform/log/common/log'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; -import { IUserDataProfilesDto } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; export const WindowMinimumSize = { @@ -283,7 +284,10 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native execPath: string; backupPath?: string; - profiles: IUserDataProfilesDto; + profiles: { + default: UriDto; + current: UriDto; + }; homeDir: string; tmpDir: string; diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 8ec5afe488f..423cb251878 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -40,7 +40,7 @@ import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electro import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadReason, defaultWindowState } from 'vs/platform/window/electron-main/window'; import { Color } from 'vs/base/common/color'; import { IPolicyService } from 'vs/platform/policy/common/policy'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { revive } from 'vs/base/common/marshalling'; export interface IWindowCreationOptions { @@ -156,6 +156,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { @ILogService private readonly logService: ILogService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IPolicyService private readonly policyService: IPolicyService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IFileService private readonly fileService: IFileService, @IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService, @IStorageMainService private readonly storageMainService: IStorageMainService, @@ -882,6 +883,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { configuration.isInitialStartup = false; // since this is a reload configuration.policiesData = this.policyService.serialize(); // set policies data again + configuration.profiles = { + default: this.userDataProfilesService.defaultProfile, + current: configuration.workspace ? this.userDataProfilesService.getProfile(isWorkspaceIdentifier(configuration.workspace) ? configuration.workspace.configPath : configuration.workspace.uri) : this.userDataProfilesService.defaultProfile, + }; // Load config this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 43375f06971..994c4002165 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1300,7 +1300,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // loading the window. backupPath: options.emptyWindowBackupInfo ? join(this.environmentMainService.backupHome, options.emptyWindowBackupInfo.backupFolder) : undefined, - profiles: this.userDataProfilesService.serialize(), + profiles: { + default: this.userDataProfilesService.defaultProfile, + current: options.workspace ? this.userDataProfilesService.getProfile(isWorkspaceIdentifier(options.workspace) ? options.workspace.configPath : options.workspace.uri) : this.userDataProfilesService.defaultProfile, + }, homeDir: this.environmentMainService.userHome.fsPath, tmpDir: this.environmentMainService.tmpDir.fsPath, diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts index 023f1495974..e4e9946c3c8 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts @@ -3,28 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { PROFILES_CATEGORY } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import '../common/profileActions'; -// import '../common/userDataProfileActions'; +import '../common/userDataProfileActions'; -class UserDataProfileStatusBarEntryContribution implements IWorkbenchContribution { +class UserDataProfileStatusBarEntryContribution extends Disposable implements IWorkbenchContribution { constructor( @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, - @IStatusbarService private readonly statusBarService: IStatusbarService + @IStatusbarService private readonly statusBarService: IStatusbarService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, ) { + super(); this.updateStatus(); + this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this.updateStatus())); } private async updateStatus(): Promise { const profiles = await this.userDataProfilesService.getAllProfiles(); - if (profiles.length) { + if (profiles.length > 1 && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY) { this.statusBarService.addEntry({ name: this.userDataProfilesService.currentProfile.name!, command: 'workbench.profiles.actions.switchProfile', diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index ea2911b9457..1c6f6b1ecf0 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -17,6 +17,11 @@ import { asJson, asText, IRequestService } from 'vs/platform/request/common/requ import { IUserDataProfileTemplate, isProfile, IUserDataProfileManagementService, IUserDataProfileWorkbenchService, PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; +import { CATEGORIES } from 'vs/workbench/common/actions'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; registerAction2(class SaveProfileAsAction extends Action2 { constructor() { @@ -27,7 +32,8 @@ registerAction2(class SaveProfileAsAction extends Action2 { original: 'Save Settings Profile As...' }, category: PROFILES_CATEGORY, - f1: true + f1: true, + precondition: ContextKeyExpr.and(IsDevelopmentContext, WorkbenchStateContext.notEqualsTo('empty')), }); } @@ -39,7 +45,7 @@ registerAction2(class SaveProfileAsAction extends Action2 { title: localize('save profile as', "Save Settings Profile As..."), }); if (name) { - await userDataProfileManagementService.createAndEnterProfile(name); + await userDataProfileManagementService.createAndEnterProfile(name, undefined, true); } } }); @@ -53,7 +59,8 @@ registerAction2(class SwitchProfileAction extends Action2 { original: 'Switch Settings Profile' }, category: PROFILES_CATEGORY, - f1: true + f1: true, + precondition: ContextKeyExpr.and(IsDevelopmentContext, WorkbenchStateContext.notEqualsTo('empty')), }); } @@ -85,7 +92,8 @@ registerAction2(class RemoveProfileAction extends Action2 { original: 'Remove Settings Profile' }, category: PROFILES_CATEGORY, - f1: true + f1: true, + precondition: ContextKeyExpr.and(IsDevelopmentContext, WorkbenchStateContext.notEqualsTo('empty')), }); } @@ -104,16 +112,70 @@ registerAction2(class RemoveProfileAction extends Action2 { } }); +registerAction2(class CleanupProfilesAction extends Action2 { + constructor() { + super({ + id: 'workbench.profiles.actions.cleanupProfiles', + title: { + value: localize('cleanup profile', "Cleanup Profiles"), + original: 'Cleanup Profiles' + }, + category: CATEGORIES.Developer, + f1: true, + precondition: IsDevelopmentContext, + }); + } + + async run(accessor: ServicesAccessor) { + const userDataProfilesService = accessor.get(IUserDataProfilesService); + const fileService = accessor.get(IFileService); + const uriIdentityService = accessor.get(IUriIdentityService); + + const allProfiles = await userDataProfilesService.getAllProfiles(); + const stat = await fileService.resolve(userDataProfilesService.profilesHome); + await Promise.all((stat.children || [])?.filter(child => child.isDirectory && allProfiles.every(p => !uriIdentityService.extUri.isEqual(p.location, child.resource))) + .map(child => fileService.del(child.resource, { recursive: true }))); + } +}); + +registerAction2(class CreateAndEnterEmptyProfileAction extends Action2 { + constructor() { + super({ + id: 'workbench.profiles.actions.createAndEnterEmptyProfile', + title: { + value: localize('create and enter empty profile', "Create and Enter Empty Profile..."), + original: 'Create and Enter Empty Profile...' + }, + category: PROFILES_CATEGORY, + f1: true, + precondition: ContextKeyExpr.and(IsDevelopmentContext, WorkbenchStateContext.notEqualsTo('empty')), + }); + } + + async run(accessor: ServicesAccessor) { + const quickInputService = accessor.get(IQuickInputService); + const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); + const name = await quickInputService.input({ + placeHolder: localize('name', "Profile name"), + title: localize('create and enter empty profile', "Create and Enter Empty Profile..."), + }); + if (name) { + await userDataProfileManagementService.createAndEnterProfile(name); + } + } +}); + registerAction2(class ExportProfileAction extends Action2 { constructor() { super({ - id: 'workbench.profiles.actions.exportProfile', + id: 'workbench.profiles.actions.exportProfile2', title: { - value: localize('export profile', "Export Settings Profile as a Template..."), - original: 'Export Settings as a Profile as a Template...' + value: localize('export profile', "Export Settings as a Profile (2)..."), + original: 'Export Settings as a Profile as a Profile (2)...' }, category: PROFILES_CATEGORY, - f1: true + f1: true, + precondition: ContextKeyExpr.and(IsDevelopmentContext, WorkbenchStateContext.notEqualsTo('empty')), }); } @@ -140,16 +202,17 @@ registerAction2(class ExportProfileAction extends Action2 { } }); -registerAction2(class CreateProfileFromTemplateAction extends Action2 { +registerAction2(class ImportProfileAction extends Action2 { constructor() { super({ - id: 'workbench.profiles.actions.createProfileFromTemplate', + id: 'workbench.profiles.actions.importProfile2', title: { - value: localize('create profile from template', "Create Settings Profile from Template..."), - original: 'Create Settings Profile from Template...' + value: localize('import profile', "Import Settings from a Profile (2)..."), + original: 'Import Settings from a Profile (2)...' }, category: PROFILES_CATEGORY, - f1: true + f1: true, + precondition: ContextKeyExpr.and(IsDevelopmentContext, WorkbenchStateContext.notEqualsTo('empty')), }); } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 27336f47e71..83afead166e 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -17,6 +17,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceContextService, IWorkspaceIdentifier, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionManagementServerService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { CreationOptions, IUserDataProfileManagementService, IUserDataProfileTemplate, PROFILES_CATEGORY } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @@ -42,38 +43,48 @@ export class UserDataProfileManagementService extends Disposable implements IUse @IHostService private readonly hostService: IHostService, @IDialogService private readonly dialogService: IDialogService, @IProgressService private readonly progressService: IProgressService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @ILogService logService: ILogService ) { super(); } - async createAndEnterProfile(name: string, options: CreationOptions = DefaultOptions): Promise { + async createAndEnterProfile(name: string, options: CreationOptions = DefaultOptions, fromExisting?: boolean): Promise { + const workspaceIdentifier = this.getWorkspaceIdentifier(); + if (!workspaceIdentifier) { + throw new Error(localize('cannotCreateProfileInEmptyWorkbench', "Cannot create a profile in an empty workspace")); + } const promises: Promise[] = []; - const newProfile = this.userDataProfilesService.createProfile(name); + const newProfile = this.userDataProfilesService.newProfile(name); await this.fileService.createFolder(newProfile.location); - if (options?.uiState) { - promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.globalStorageHome, newProfile.globalStorageHome)); - } - if (options?.settings) { - promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.settingsResource, newProfile.settingsResource)); - } - if (options?.extensions && newProfile.extensionsResource) { - promises.push((async () => { - const extensionsProfileResource = this.userDataProfilesService.currentProfile.extensionsResource ?? await this.createDefaultExtensionsProfile(joinPath(this.userDataProfilesService.defaultProfile.location, basename(newProfile.extensionsResource!))); - this.fileService.copy(extensionsProfileResource, newProfile.extensionsResource!); - })()); - } - if (options?.keybindings) { - promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.keybindingsResource, newProfile.keybindingsResource)); - } - if (options?.tasks) { - promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.tasksResource, newProfile.tasksResource)); - } - if (options?.snippets) { - promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.snippetsHome, newProfile.snippetsHome)); + if (fromExisting) { + if (options?.uiState) { + promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.globalStorageHome, newProfile.globalStorageHome)); + } + if (options?.settings) { + promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.settingsResource, newProfile.settingsResource)); + } + if (options?.extensions && newProfile.extensionsResource) { + promises.push((async () => { + const extensionsProfileResource = this.userDataProfilesService.currentProfile.extensionsResource ?? await this.createDefaultExtensionsProfile(joinPath(this.userDataProfilesService.defaultProfile.location, basename(newProfile.extensionsResource!))); + this.fileService.copy(extensionsProfileResource, newProfile.extensionsResource!); + })()); + } + if (options?.keybindings) { + promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.keybindingsResource, newProfile.keybindingsResource)); + } + if (options?.tasks) { + promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.tasksResource, newProfile.tasksResource)); + } + if (options?.snippets) { + promises.push(this.fileService.copy(this.userDataProfilesService.currentProfile.snippetsHome, newProfile.snippetsHome)); + } + } else { + promises.push(this.fileService.createFolder(newProfile.globalStorageHome)); } await Promise.allSettled(promises); - await this.doSwitchProfile(name); + await this.userDataProfilesService.createProfile(newProfile, options, workspaceIdentifier); + await this.enterProfile(); } async removeProfile(name: string): Promise { @@ -88,6 +99,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse if (!profile) { throw new Error(`Profile ${name} does not exist`); } + await this.userDataProfilesService.removeProfile(profile); if (profiles.length === 2) { await this.fileService.del(this.userDataProfilesService.profilesHome, { recursive: true }); } else { @@ -96,21 +108,30 @@ export class UserDataProfileManagementService extends Disposable implements IUse } async switchProfile(name: string): Promise { + const workspaceIdentifier = this.getWorkspaceIdentifier(); + if (!workspaceIdentifier) { + throw new Error(localize('cannotSwitchProfileInEmptyWorkbench', "Cannot switch a profile in an empty workspace")); + } const profiles = await this.userDataProfilesService.getAllProfiles(); const profile = profiles.find(p => p.name === name); if (!profile) { throw new Error(`Profile ${name} does not exist`); } - await this.doSwitchProfile(name); + await this.userDataProfilesService.setProfileForWorkspace(profile, workspaceIdentifier); + await this.enterProfile(); } async createAndEnterProfileFromTemplate(name: string, template: IUserDataProfileTemplate, options: CreationOptions = DefaultOptions): Promise { + const workspaceIdentifier = this.getWorkspaceIdentifier(); + if (!workspaceIdentifier) { + throw new Error(localize('cannotCreateProfileInEmptyWorkbench', "Cannot create a profile in an empty workspace")); + } await this.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('profiles.creating', "{0}: Creating...", PROFILES_CATEGORY), }, async progress => { const promises: Promise[] = []; - const newProfile = this.userDataProfilesService.createProfile(name); + const newProfile = this.userDataProfilesService.newProfile(name); await this.fileService.createFolder(newProfile.location); if (template.globalState) { // todo: create global state @@ -122,26 +143,30 @@ export class UserDataProfileManagementService extends Disposable implements IUse promises.push(this.fileService.writeFile(newProfile.extensionsResource, VSBuffer.fromString(template.extensions))); } await Promise.allSettled(promises); + await this.userDataProfilesService.createProfile(newProfile, options, workspaceIdentifier); }); - await this.doSwitchProfile(name); + await this.enterProfile(); } - async reset(): Promise { - if (this.userDataProfilesService.currentProfile.name !== this.userDataProfilesService.defaultProfile.name) { - throw new Error('Please switch to default profile to reset'); + private getWorkspaceIdentifier(): ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier | undefined { + const workspace = this.workspaceContextService.getWorkspace(); + switch (this.workspaceContextService.getWorkbenchState()) { + case WorkbenchState.FOLDER: + return { uri: workspace.folders[0].uri, id: workspace.id }; + case WorkbenchState.WORKSPACE: + return { configPath: workspace.configuration!, id: workspace.id }; } - await this.fileService.del(this.userDataProfilesService.profilesHome); + return undefined; } - private async doSwitchProfile(name: string): Promise { - await this.userDataProfilesService.setProfile(name); + private async enterProfile(): Promise { const result = await this.dialogService.confirm({ type: 'info', - message: localize('restart message', "Switching a profile requires restarting VS Code."), - primaryButton: localize('restart button', "&&Restart"), + message: localize('reload message', "Switching a profile requires reloading VS Code."), + primaryButton: localize('reload button', "&&Reload"), }); if (result.confirmed) { - await this.hostService.restart(); + await this.hostService.reload(); } } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 3d43ca85d1f..a63d81994c0 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -20,12 +20,11 @@ export const IUserDataProfileManagementService = createDecorator; + createAndEnterProfile(name: string, options?: CreationOptions, fromExisting?: boolean): Promise; createAndEnterProfileFromTemplate(name: string, template: IUserDataProfileTemplate, options?: CreationOptions): Promise; removeProfile(name: string): Promise; switchProfile(name: string): Promise; - reset(): Promise; } export interface IUserDataProfileTemplate {