Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support profiles in empty window #153371

Merged
merged 1 commit into from Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/vs/platform/userDataProfile/common/userDataProfile.ts
Expand Up @@ -81,6 +81,9 @@ if (!isWeb) {
});
}

export type EmptyWindowWorkspaceIdentifier = 'empty-window';
export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier | EmptyWindowWorkspaceIdentifier;

export type DidChangeProfilesEvent = { readonly added: IUserDataProfile[]; readonly removed: IUserDataProfile[]; readonly all: IUserDataProfile[] };

export const IUserDataProfilesService = createDecorator<IUserDataProfilesService>('IUserDataProfilesService');
Expand All @@ -94,9 +97,9 @@ export interface IUserDataProfilesService {
readonly profiles: IUserDataProfile[];

newProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags): CustomUserDataProfile;
createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile>;
setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile>;
getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile;
createProfile(profile: IUserDataProfile, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<IUserDataProfile>;
getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile;
removeProfile(profile: IUserDataProfile): Promise<void>;
}

Expand Down Expand Up @@ -163,8 +166,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return { ...profile, isDefault: true, extensionsResource: extensions ? profile.extensionsResource : undefined };
}

createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> { throw new Error('Not implemented'); }
setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> { throw new Error('Not implemented'); }
getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); }
createProfile(profile: IUserDataProfile, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> { throw new Error('Not implemented'); }
setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<IUserDataProfile> { throw new Error('Not implemented'); }
getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); }
removeProfile(profile: IUserDataProfile): Promise<void> { throw new Error('Not implemented'); }
}
116 changes: 79 additions & 37 deletions src/vs/platform/userDataProfile/electron-main/userDataProfile.ts
Expand Up @@ -4,17 +4,17 @@
*--------------------------------------------------------------------------------------------*/

import { Emitter, Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, reviveProfile, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { IUserDataProfile, IUserDataProfilesService, reviveProfile, PROFILES_ENABLEMENT_CONFIG, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { Promises } from 'vs/base/common/async';
import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile';
import { StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile';
import { IStringDictionary } from 'vs/base/common/collections';

export type WillCreateProfileEvent = {
profile: IUserDataProfile;
Expand All @@ -28,22 +28,11 @@ export type WillRemoveProfileEvent = {

export const IUserDataProfilesMainService = refineServiceDecorator<IUserDataProfilesService, IUserDataProfilesMainService>(IUserDataProfilesService);
export interface IUserDataProfilesMainService extends IUserDataProfilesService {
unsetWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<void>;
unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void>;
readonly onWillCreateProfile: Event<WillCreateProfileEvent>;
readonly onWillRemoveProfile: Event<WillRemoveProfileEvent>;
}

type StoredUserDataProfile = {
name: string;
location: URI;
useDefaultFlags?: UseDefaultProfileFlags;
};

type StoredWorkspaceInfo = {
workspace: URI;
profile: URI;
};

export class UserDataProfilesMainService extends UserDataProfilesService implements IUserDataProfilesMainService {

private readonly _onWillCreateProfile = this._register(new Emitter<WillCreateProfileEvent>());
Expand All @@ -62,7 +51,7 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
super(stateMainService, uriIdentityService, environmentService, fileService, logService);
}

override async createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> {
override async createProfile(profile: IUserDataProfile, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
Expand All @@ -84,35 +73,32 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
});
await Promises.settled(joiners);

const storedProfile: StoredUserDataProfile = { name: profile.name, location: profile.location, useDefaultFlags: profile.useDefaultFlags };
const storedProfiles = [...this.getStoredProfiles(), storedProfile];
this.setStoredProfiles(storedProfiles, [profile], []);
this.updateProfiles([profile], []);

if (workspaceIdentifier) {
await this.setProfileForWorkspace(profile, workspaceIdentifier);
}
return this.profilesObject.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!;

return this.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!;
}

override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> {
override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}

profile = reviveProfile(profile, this.profilesHome.scheme);
const workspace = this.getWorkspace(workspaceIdentifier);
const storedWorkspaceInfos = this.getStoredWorskpaceInfos().filter(info => !this.uriIdentityService.extUri.isEqual(info.workspace, workspace));
if (!profile.isDefault) {
storedWorkspaceInfos.push({ workspace, profile: profile.location });
}
this.setStoredWorskpaceInfos(storedWorkspaceInfos);
return this.profilesObject.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!;
this.updateWorkspaceAssociation(workspaceIdentifier, profile);

return this.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!;
}

async unsetWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<void> {
async unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
const workspace = this.getWorkspace(workspaceIdentifier);
this.setStoredWorskpaceInfos(this.getStoredWorskpaceInfos().filter(info => !this.uriIdentityService.extUri.isEqual(info.workspace, workspace)));

this.updateWorkspaceAssociation(workspaceIdentifier);
}

override async removeProfile(profile: IUserDataProfile): Promise<void> {
Expand All @@ -136,8 +122,17 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
});
await Promises.settled(joiners);

this.setStoredWorskpaceInfos(this.getStoredWorskpaceInfos().filter(p => !this.uriIdentityService.extUri.isEqual(p.profile, profile.location)));
this.setStoredProfiles(this.getStoredProfiles().filter(p => !this.uriIdentityService.extUri.isEqual(p.location, profile.location)), [], [profile]);
if (profile.id === this.profilesObject.emptyWindow?.id) {
this.profilesObject.emptyWindow = undefined;
}
for (const workspace of [...this.profilesObject.workspaces.keys()]) {
if (profile.id === this.profilesObject.workspaces.get(workspace)?.id) {
this.profilesObject.workspaces.delete(workspace);
}
}
this.saveStoredProfileAssociations();

this.updateProfiles([], [profile]);

try {
if (this.profiles.length === 2) {
Expand All @@ -150,15 +145,62 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
}
}

private setStoredProfiles(storedProfiles: StoredUserDataProfile[], added: IUserDataProfile[], removed: IUserDataProfile[]): void {
private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[]) {
const storedProfiles: StoredUserDataProfile[] = [];
for (const profile of [...this.profilesObject.profiles, ...added]) {
if (profile.isDefault) {
continue;
}
if (removed.some(p => profile.id === p.id)) {
continue;
}
storedProfiles.push({ location: profile.location, name: profile.name, useDefaultFlags: profile.useDefaultFlags });
}
this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles);
this._profilesObject = undefined;
this._onDidChangeProfiles.fire({ added, removed, all: this.profiles });
}

private setStoredWorskpaceInfos(storedWorkspaceInfos: StoredWorkspaceInfo[]) {
this.stateMainService.setItem(UserDataProfilesMainService.WORKSPACE_PROFILE_INFO_KEY, storedWorkspaceInfos);
private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) {
const workspace = this.getWorkspace(workspaceIdentifier);

// Folder or Multiroot workspace
if (URI.isUri(workspace)) {
this.profilesObject.workspaces.delete(workspace);
if (newProfile && !newProfile.isDefault) {
this.profilesObject.workspaces.set(workspace, newProfile);
}
}
// Empty Window
else {
this.profilesObject.emptyWindow = !newProfile?.isDefault ? newProfile : undefined;
}

this.saveStoredProfileAssociations();
}

private saveStoredProfileAssociations() {
const workspaces: IStringDictionary<string> = {};
for (const [workspace, profile] of this.profilesObject.workspaces.entries()) {
workspaces[workspace.toString()] = profile.location.toString();
}
const emptyWindow = this.profilesObject.emptyWindow?.location.toString();
this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, { workspaces, emptyWindow });
this._profilesObject = undefined;
}

protected override getStoredProfileAssociations(): StoredProfileAssociations {
const oldKey = 'workspaceAndProfileInfo';
const storedWorkspaceInfos = this.stateMainService.getItem<{ workspace: UriComponents; profile: UriComponents }[]>(oldKey, undefined);
if (storedWorkspaceInfos) {
this.stateMainService.removeItem(oldKey);
const workspaces = storedWorkspaceInfos.reduce<IStringDictionary<string>>((result, { workspace, profile }) => {
result[URI.revive(workspace).toString()] = URI.revive(profile).toString();
return result;
}, {});
this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, <StoredProfileAssociations>{ workspaces });
}
return super.getStoredProfileAssociations();
}

}
58 changes: 39 additions & 19 deletions src/vs/platform/userDataProfile/node/userDataProfile.ts
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IStringDictionary } from 'vs/base/common/collections';
import { ResourceMap } from 'vs/base/common/map';
import { revive } from 'vs/base/common/marshalling';
import { UriDto } from 'vs/base/common/types';
Expand All @@ -12,29 +13,30 @@ import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, toUserDataProfile, WorkspaceIdentifier, EmptyWindowWorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';

type UserDataProfilesObject = {
export type UserDataProfilesObject = {
profiles: IUserDataProfile[];
workspaces: ResourceMap<IUserDataProfile>;
emptyWindow?: IUserDataProfile;
};

type StoredUserDataProfile = {
export type StoredUserDataProfile = {
name: string;
location: URI;
useDefaultFlags?: UseDefaultProfileFlags;
};

type StoredWorkspaceInfo = {
workspace: URI;
profile: URI;
export type StoredProfileAssociations = {
workspaces?: IStringDictionary<string>;
emptyWindow?: string;
};

export class UserDataProfilesService extends BaseUserDataProfilesService implements IUserDataProfilesService {

protected static readonly PROFILES_KEY = 'userDataProfiles';
protected static readonly WORKSPACE_PROFILE_INFO_KEY = 'workspaceAndProfileInfo';
protected static readonly PROFILE_ASSOCIATIONS_KEY = 'profileAssociations';

protected enabled: boolean = false;

Expand All @@ -60,37 +62,55 @@ export class UserDataProfilesService extends BaseUserDataProfilesService impleme
}
if (!this._profilesObject) {
const profiles = this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags));
let emptyWindow: IUserDataProfile | undefined;
const workspaces = new ResourceMap<IUserDataProfile>();
if (profiles.length) {
profiles.unshift(this.createDefaultUserDataProfile(true));
for (const workspaceProfileInfo of this.getStoredWorskpaceInfos()) {
const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, workspaceProfileInfo.profile));
if (profile) {
workspaces.set(workspaceProfileInfo.workspace, profile);
const profileAssicaitions = this.getStoredProfileAssociations();
if (profileAssicaitions.workspaces) {
for (const [workspacePath, profilePath] of Object.entries(profileAssicaitions.workspaces)) {
const workspace = URI.parse(workspacePath);
const profileLocation = URI.parse(profilePath);
const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation));
if (profile) {
workspaces.set(workspace, profile);
}
}
}
if (profileAssicaitions.emptyWindow) {
const emptyWindowProfileLocation = URI.parse(profileAssicaitions.emptyWindow);
emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation));
}
}
this._profilesObject = { profiles, workspaces };
this._profilesObject = { profiles, workspaces, emptyWindow };
}
return this._profilesObject;
}

override get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; }

override getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile {
return this.profilesObject.workspaces.get(this.getWorkspace(workspaceIdentifier)) ?? this.defaultProfile;
override getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile {
const workspace = this.getWorkspace(workspaceIdentifier);
const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow;
return profile ?? this.defaultProfile;
}

protected getWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier) {
return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) ? workspaceIdentifier.uri : workspaceIdentifier.configPath;
protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier {
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
return workspaceIdentifier.uri;
}
if (isWorkspaceIdentifier(workspaceIdentifier)) {
return workspaceIdentifier.configPath;
}
return 'empty-window';
}

protected getStoredProfiles(): StoredUserDataProfile[] {
return revive(this.stateService.getItem<UriDto<StoredUserDataProfile>[]>(UserDataProfilesService.PROFILES_KEY, []));
}

protected getStoredWorskpaceInfos(): StoredWorkspaceInfo[] {
return revive(this.stateService.getItem<UriDto<StoredWorkspaceInfo>[]>(UserDataProfilesService.WORKSPACE_PROFILE_INFO_KEY, []));
protected getStoredProfileAssociations(): StoredProfileAssociations {
return revive(this.stateService.getItem<UriDto<StoredProfileAssociations>>(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, {}));
}

}
2 changes: 1 addition & 1 deletion src/vs/platform/windows/electron-main/window.ts
Expand Up @@ -906,7 +906,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: configuration.workspace ? this.userDataProfilesService.getProfile(configuration.workspace) : this.userDataProfilesService.defaultProfile,
current: this.userDataProfilesService.getProfile(configuration.workspace ?? 'empty-window'),
};

// Load config
Expand Down
Expand Up @@ -1302,7 +1302,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic

profiles: {
all: this.userDataProfilesService.profiles,
current: options.workspace ? this.userDataProfilesService.getProfile(options.workspace) : this.userDataProfilesService.defaultProfile,
current: this.userDataProfilesService.getProfile(options.workspace ?? 'empty-window'),
},

homeDir: this.environmentMainService.userHome.fsPath,
Expand Down