* fix #168976

* code formatting

* review feedback

* clean up empty window associations

* use `instanceof` check for `Promise`

Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
This commit is contained in:
Sandeep Somavarapu
2022-12-20 07:06:17 +01:00
committed by GitHub
parent 4d4e903f2b
commit fef6e86202
12 changed files with 212 additions and 136 deletions
+2 -2
View File
@@ -105,7 +105,7 @@ import { IRequestService } from 'vs/platform/request/common/request';
import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService';
import { UserDataTransientProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataTransientProfilesHandler';
import { UserDataProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataProfilesHandler';
import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc';
import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
import { resolveMachineId } from 'vs/platform/telemetry/electron-main/telemetryUtils';
@@ -558,7 +558,7 @@ export class CodeApplication extends Disposable {
this._register(instantiationService.createInstance(ProxyAuthHandler));
// Transient profiles handler
this._register(instantiationService.createInstance(UserDataTransientProfilesHandler));
this._register(instantiationService.createInstance(UserDataProfilesHandler));
}
private setupSharedProcess(machineId: string): { sharedProcess: SharedProcess; sharedProcessReady: Promise<MessagePortClient>; sharedProcessClient: Promise<MessagePortClient> } {
@@ -13,7 +13,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
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, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { IAnyWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { ResourceMap } from 'vs/base/common/map';
import { IStringDictionary } from 'vs/base/common/collections';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
@@ -69,9 +69,6 @@ export function isUserDataProfile(thing: unknown): thing is IUserDataProfile {
export const PROFILES_ENABLEMENT_CONFIG = 'workbench.experimental.settingsProfiles.enabled';
export type EmptyWindowWorkspaceIdentifier = 'empty-window';
export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier | EmptyWindowWorkspaceIdentifier;
export type DidChangeProfilesEvent = { readonly added: readonly IUserDataProfile[]; readonly removed: readonly IUserDataProfile[]; readonly updated: readonly IUserDataProfile[]; readonly all: readonly IUserDataProfile[] };
export type WillCreateProfileEvent = {
@@ -107,13 +104,13 @@ export interface IUserDataProfilesService {
readonly onDidResetWorkspaces: Event<void>;
isEnabled(): boolean;
createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
createTransientProfile(workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile>;
createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile>;
createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile>;
updateProfile(profile: IUserDataProfile, options?: IUserDataProfileUpdateOptions,): Promise<IUserDataProfile>;
removeProfile(profile: IUserDataProfile): Promise<void>;
setProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier, profile: IUserDataProfile): Promise<void>;
setProfileForWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier, profile: IUserDataProfile): Promise<void>;
resetWorkspaces(): Promise<void>;
cleanUp(): Promise<void>;
@@ -159,7 +156,7 @@ export function toUserDataProfile(id: string, name: string, location: URI, optio
export type UserDataProfilesObject = {
profiles: IUserDataProfile[];
workspaces: ResourceMap<IUserDataProfile>;
emptyWindow?: IUserDataProfile;
emptyWindows: Map<string, IUserDataProfile>;
};
export type StoredUserDataProfile = {
@@ -171,7 +168,7 @@ export type StoredUserDataProfile = {
export type StoredProfileAssociations = {
workspaces?: IStringDictionary<string>;
emptyWindow?: string;
emptyWindows?: IStringDictionary<string>;
};
export class UserDataProfilesService extends Disposable implements IUserDataProfilesService {
@@ -203,7 +200,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
protected readonly transientProfilesObject: UserDataProfilesObject = {
profiles: [],
workspaces: new ResourceMap()
workspaces: new ResourceMap(),
emptyWindows: new Map()
};
constructor(
@@ -240,14 +238,14 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags }));
}
}
let emptyWindow: IUserDataProfile | undefined;
const workspaces = new ResourceMap<IUserDataProfile>();
const emptyWindows = new Map<string, IUserDataProfile>();
const defaultProfile = toUserDataProfile(hash(this.environmentService.userRoamingDataHome.path).toString(16), localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
profiles.unshift({ ...defaultProfile, extensionsResource: this.getDefaultProfileExtensionsLocation() ?? defaultProfile.extensionsResource, isDefault: true });
if (profiles.length) {
const profileAssicaitions = this.getStoredProfileAssociations();
if (profileAssicaitions.workspaces) {
for (const [workspacePath, profilePath] of Object.entries(profileAssicaitions.workspaces)) {
const profileAssociaitions = this.getStoredProfileAssociations();
if (profileAssociaitions.workspaces) {
for (const [workspacePath, profilePath] of Object.entries(profileAssociaitions.workspaces)) {
const workspace = URI.parse(workspacePath);
const profileLocation = URI.parse(profilePath);
const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation));
@@ -256,17 +254,22 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
}
}
}
if (profileAssicaitions.emptyWindow) {
const emptyWindowProfileLocation = URI.parse(profileAssicaitions.emptyWindow);
emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation));
if (profileAssociaitions.emptyWindows) {
for (const [windowId, profilePath] of Object.entries(profileAssociaitions.emptyWindows)) {
const profileLocation = URI.parse(profilePath);
const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation));
if (profile) {
emptyWindows.set(windowId, profile);
}
}
}
}
this._profilesObject = { profiles, workspaces, emptyWindow };
this._profilesObject = { profiles, workspaces, emptyWindows };
}
return this._profilesObject;
}
async createTransientProfile(workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
async createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
const namePrefix = `Temp`;
const nameRegEx = new RegExp(`${escapeRegExpCharacters(namePrefix)}\\s(\\d+)`);
let nameIndex = 0;
@@ -279,11 +282,11 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return this.createProfile(hash(generateUuid()).toString(16), name, { transient: true }, workspaceIdentifier);
}
async createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
async createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
return this.createProfile(hash(generateUuid()).toString(16), name, options, workspaceIdentifier);
}
async createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
async createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
@@ -375,8 +378,10 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
this.logService.error(error);
}
if (profile.id === this.profilesObject.emptyWindow?.id) {
this.profilesObject.emptyWindow = undefined;
for (const windowId of [...this.profilesObject.emptyWindows.keys()]) {
if (profile.id === this.profilesObject.emptyWindows.get(windowId)?.id) {
this.profilesObject.emptyWindows.delete(windowId);
}
}
for (const workspace of [...this.profilesObject.workspaces.keys()]) {
if (profile.id === this.profilesObject.workspaces.get(workspace)?.id) {
@@ -398,28 +403,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
}
}
getOrSetProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier, profileToSet: IUserDataProfile = this.defaultProfile): IUserDataProfile {
if (!this.enabled) {
return this.defaultProfile;
}
let profile = this.getProfileForWorkspace(workspaceIdentifier);
if (!profile) {
profile = profileToSet;
// 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.setProfileForWorkspaceSync(workspaceIdentifier, profile);
}
}
return profile;
}
async setProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier, profileToSet: IUserDataProfile): Promise<void> {
this.setProfileForWorkspaceSync(workspaceIdentifier, profileToSet);
}
setProfileForWorkspaceSync(workspaceIdentifier: WorkspaceIdentifier, profileToSet: IUserDataProfile): void {
async setProfileForWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier, profileToSet: IUserDataProfile): Promise<void> {
if (!this.enabled) {
throw new Error(`Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
@@ -432,7 +416,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
this.updateWorkspaceAssociation(workspaceIdentifier, profile);
}
unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier, transient?: boolean): void {
unsetWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier, transient?: boolean): void {
if (!this.enabled) {
throw new Error(`Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
@@ -442,9 +426,9 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
async resetWorkspaces(): Promise<void> {
this.transientProfilesObject.workspaces.clear();
this.transientProfilesObject.emptyWindow = undefined;
this.transientProfilesObject.emptyWindows.clear();
this.profilesObject.workspaces.clear();
this.profilesObject.emptyWindow = undefined;
this.profilesObject.emptyWindows.clear();
this.updateStoredProfileAssociations();
this._onDidResetWorkspaces.fire();
}
@@ -469,29 +453,29 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
await Promise.allSettled(unAssociatedTransientProfiles.map(p => this.removeProfile(p)));
}
private getProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile | undefined {
getProfileForWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier): IUserDataProfile | undefined {
const workspace = this.getWorkspace(workspaceIdentifier);
return URI.isUri(workspace) ? this.transientProfilesObject.workspaces.get(workspace) ?? this.profilesObject.workspaces.get(workspace) : this.transientProfilesObject.emptyWindow ?? this.profilesObject.emptyWindow;
return URI.isUri(workspace) ? this.transientProfilesObject.workspaces.get(workspace) ?? this.profilesObject.workspaces.get(workspace) : this.transientProfilesObject.emptyWindows.get(workspace) ?? this.profilesObject.emptyWindows.get(workspace);
}
protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier {
protected getWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier): URI | string {
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
return workspaceIdentifier.uri;
}
if (isWorkspaceIdentifier(workspaceIdentifier)) {
return workspaceIdentifier.configPath;
}
return 'empty-window';
return workspaceIdentifier.id;
}
private isProfileAssociatedToWorkspace(profile: IUserDataProfile): boolean {
if (this.uriIdentityService.extUri.isEqual(this.transientProfilesObject.emptyWindow?.location, profile.location)) {
if ([...this.transientProfilesObject.emptyWindows.values()].some(windowProfile => this.uriIdentityService.extUri.isEqual(windowProfile.location, profile.location))) {
return true;
}
if ([...this.transientProfilesObject.workspaces.values()].some(workspaceProfile => this.uriIdentityService.extUri.isEqual(workspaceProfile.location, profile.location))) {
return true;
}
if (this.uriIdentityService.extUri.isEqual(this.profilesObject.emptyWindow?.location, profile.location)) {
if ([...this.profilesObject.emptyWindows.values()].some(windowProfile => this.uriIdentityService.extUri.isEqual(windowProfile.location, profile.location))) {
return true;
}
if ([...this.profilesObject.workspaces.values()].some(workspaceProfile => this.uriIdentityService.extUri.isEqual(workspaceProfile.location, profile.location))) {
@@ -527,7 +511,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
this._onDidChangeProfiles.fire({ added, removed, updated, all: this.profiles });
}
private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile, transient?: boolean): void {
private updateWorkspaceAssociation(workspaceIdentifier: IAnyWorkspaceIdentifier, newProfile?: IUserDataProfile, transient?: boolean): void {
// Force transient if the new profile to associate is transient
transient = newProfile?.isTransient ? true : transient;
@@ -548,7 +532,10 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
}
// Empty Window
else {
profilesObject.emptyWindow = newProfile;
profilesObject.emptyWindows.delete(workspace);
if (newProfile) {
profilesObject.emptyWindows.set(workspace, newProfile);
}
}
if (!transient) {
@@ -561,8 +548,11 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
for (const [workspace, profile] of this.profilesObject.workspaces.entries()) {
workspaces[workspace.toString()] = profile.location.toString();
}
const emptyWindow = this.profilesObject.emptyWindow?.location.toString();
this.saveStoredProfileAssociations({ workspaces, emptyWindow });
const emptyWindows: IStringDictionary<string> = {};
for (const [windowId, profile] of this.profilesObject.emptyWindows.entries()) {
emptyWindows[windowId.toString()] = profile.location.toString();
}
this.saveStoredProfileAssociations({ workspaces, emptyWindows });
this._profilesObject = undefined;
}
@@ -11,15 +11,16 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instant
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 { IUserDataProfilesService, WorkspaceIdentifier, StoredUserDataProfile, StoredProfileAssociations, WillCreateProfileEvent, WillRemoveProfileEvent, IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUserDataProfilesService, StoredUserDataProfile, StoredProfileAssociations, WillCreateProfileEvent, WillRemoveProfileEvent, IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile';
import { IStringDictionary } from 'vs/base/common/collections';
import { IAnyWorkspaceIdentifier, IEmptyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
export const IUserDataProfilesMainService = refineServiceDecorator<IUserDataProfilesService, IUserDataProfilesMainService>(IUserDataProfilesService);
export interface IUserDataProfilesMainService extends IUserDataProfilesService {
getOrSetProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier, profileToSet?: IUserDataProfile): IUserDataProfile;
setProfileForWorkspaceSync(workspaceIdentifier: WorkspaceIdentifier, profileToSet: IUserDataProfile): void;
unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier, transient?: boolean): void;
getProfileForWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier): IUserDataProfile | undefined;
unsetWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier, transient?: boolean): void;
getAssociatedEmptyWindows(): IEmptyWorkspaceIdentifier[];
readonly onWillCreateProfile: Event<WillCreateProfileEvent>;
readonly onWillRemoveProfile: Event<WillRemoveProfileEvent>;
}
@@ -45,6 +46,14 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
}
}
getAssociatedEmptyWindows(): IEmptyWorkspaceIdentifier[] {
const emptyWindows: IEmptyWorkspaceIdentifier[] = [];
for (const id of this.profilesObject.emptyWindows.keys()) {
emptyWindows.push({ id });
}
return emptyWindows;
}
protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void {
if (storedProfiles.length) {
this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles);
@@ -54,7 +63,7 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
}
protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void {
if (storedProfileAssociations.emptyWindow || storedProfileAssociations.workspaces) {
if (storedProfileAssociations.emptyWindows || storedProfileAssociations.workspaces) {
this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, storedProfileAssociations);
} else {
this.stateMainService.removeItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY);
@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ILifecycleMainService, } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { ICodeWindow, LoadReason } from 'vs/platform/window/electron-main/window';
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
import { IAnyWorkspaceIdentifier, isEmptyWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
export class UserDataProfilesHandler extends Disposable {
constructor(
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@IUserDataProfilesMainService private readonly userDataProfilesService: IUserDataProfilesMainService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
) {
super();
this._register(lifecycleMainService.onWillLoadWindow(e => {
if (e.reason === LoadReason.LOAD) {
this.unsetProfileForWorkspace(e.window);
}
}));
this._register(lifecycleMainService.onBeforeCloseWindow(window => this.unsetProfileForWorkspace(window)));
this._register(new RunOnceScheduler(() => this.cleanUpEmptyWindowAssociations(), 30 * 1000 /* after 30s */)).schedule();
}
private async unsetProfileForWorkspace(window: ICodeWindow): Promise<void> {
const workspace = this.getWorkspace(window);
const profile = this.userDataProfilesService.getProfileForWorkspace(workspace);
if (profile && (isEmptyWorkspaceIdentifier(workspace) || profile.isTransient)) {
this.userDataProfilesService.unsetWorkspace(workspace, profile.isTransient);
if (profile.isTransient) {
await this.userDataProfilesService.cleanUpTransientProfiles();
}
}
}
private getWorkspace(window: ICodeWindow): IAnyWorkspaceIdentifier {
return window.openedWorkspace ?? toWorkspaceIdentifier(window.backupPath, window.isExtensionDevelopmentHost);
}
private cleanUpEmptyWindowAssociations(): void {
const associatedEmptyWindows = this.userDataProfilesService.getAssociatedEmptyWindows();
if (associatedEmptyWindows.length === 0) {
return;
}
const openedWorkspaces = this.windowsMainService.getWindows().map(window => this.getWorkspace(window));
for (const associatedEmptyWindow of associatedEmptyWindows) {
if (openedWorkspaces.some(openedWorkspace => openedWorkspace.id === associatedEmptyWindow.id)) {
continue;
}
this.userDataProfilesService.unsetWorkspace(associatedEmptyWindow, false);
}
}
}
@@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILifecycleMainService, } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { LoadReason } from 'vs/platform/window/electron-main/window';
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
export class UserDataTransientProfilesHandler extends Disposable {
constructor(
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@IUserDataProfilesMainService private readonly userDataProfilesService: IUserDataProfilesMainService,
) {
super();
this._register(lifecycleMainService.onWillLoadWindow(e => {
if (e.reason === LoadReason.LOAD) {
this.unsetTransientProfileForWorkspace(e.window.openedWorkspace ?? 'empty-window');
}
}));
this._register(lifecycleMainService.onBeforeCloseWindow(window => this.unsetTransientProfileForWorkspace(window.openedWorkspace ?? 'empty-window')));
}
private async unsetTransientProfileForWorkspace(workspace: WorkspaceIdentifier): Promise<void> {
const profile = this.userDataProfilesService.getOrSetProfileForWorkspace(workspace);
if (profile.isTransient) {
this.userDataProfilesService.unsetWorkspace(workspace, true);
await this.userDataProfilesService.cleanUpTransientProfiles();
}
}
}
@@ -10,7 +10,8 @@ import { URI, UriDto } from 'vs/base/common/uri';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions, reviveProfile, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
export class UserDataProfilesNativeService extends Disposable implements IUserDataProfilesService {
@@ -58,22 +59,22 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa
return this.enabled;
}
async createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
async createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createNamedProfile', [name, options, workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);
}
async createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
async createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [id, name, options, workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);
}
async createTransientProfile(workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
async createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createTransientProfile', [workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);
}
async setProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier, profile: IUserDataProfile): Promise<void> {
async setProfileForWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier, profile: IUserDataProfile): Promise<void> {
await this.channel.call<UriDto<IUserDataProfile>>('setProfileForWorkspace', [workspaceIdentifier, profile]);
}
@@ -70,4 +70,39 @@ suite('UserDataProfileMainService', () => {
assert.strictEqual(testObject.defaultProfile.isDefault, true);
});
test('when no profile is set', async () => {
await testObject.createNamedProfile('profile1');
assert.equal(testObject.getProfileForWorkspace({ id: 'id' }), undefined);
assert.equal(testObject.getProfileForWorkspace({ id: 'id', configPath: environmentService.userRoamingDataHome }), undefined);
assert.equal(testObject.getProfileForWorkspace({ id: 'id', uri: environmentService.userRoamingDataHome }), undefined);
});
test('set profile to a workspace', async () => {
const workspace = { id: 'id', configPath: environmentService.userRoamingDataHome };
const profile = await testObject.createNamedProfile('profile1');
testObject.setProfileForWorkspace(workspace, profile);
assert.deepStrictEqual(testObject.getProfileForWorkspace(workspace), profile);
});
test('set profile to a folder', async () => {
const workspace = { id: 'id', uri: environmentService.userRoamingDataHome };
const profile = await testObject.createNamedProfile('profile1');
testObject.setProfileForWorkspace(workspace, profile);
assert.deepStrictEqual(testObject.getProfileForWorkspace(workspace), profile);
});
test('set profile to a window', async () => {
const workspace = { id: 'id' };
const profile = await testObject.createNamedProfile('profile1');
testObject.setProfileForWorkspace(workspace, profile);
assert.deepStrictEqual(testObject.getProfileForWorkspace(workspace), profile);
});
});
@@ -35,7 +35,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { getMenuBarVisibility, getTitleBarStyle, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, useWindowControlsOverlay, WindowMinimumSize, zoomLevelToZoomFactor } from 'vs/platform/window/common/window';
import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadReason, defaultWindowState } from 'vs/platform/window/electron-main/window';
import { Color } from 'vs/base/common/color';
@@ -139,7 +139,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return profile;
}
return this.userDataProfilesService.getOrSetProfileForWorkspace(this.config.workspace ?? 'empty-window', this.userDataProfilesService.defaultProfile);
return this.userDataProfilesService.getProfileForWorkspace(this.config.workspace ?? toWorkspaceIdentifier(this.backupPath, this.isExtensionDevelopmentHost)) ?? this.userDataProfilesService.defaultProfile;
}
get remoteAuthority(): string | undefined { return this._config?.remoteAuthority; }
@@ -44,7 +44,7 @@ import { IOpenConfiguration, IOpenEmptyConfiguration, IWindowsCountChangedEvent,
import { findWindowOnExtensionDevelopmentPath, findWindowOnFile, findWindowOnWorkspaceOrFolder } from 'vs/platform/windows/electron-main/windowsFinder';
import { IWindowState, WindowsStateHandler } from 'vs/platform/windows/electron-main/windowsStateHandler';
import { IRecent } from 'vs/platform/workspaces/common/workspaces';
import { hasWorkspaceFileExtension, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { hasWorkspaceFileExtension, IAnyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { createEmptyWorkspaceIdentifier, getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/platform/workspaces/node/workspaces';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
@@ -1350,7 +1350,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
profiles: {
all: this.userDataProfilesMainService.profiles,
profile: window?.isExtensionDevelopmentHost && window?.profile ? window.profile : await this.resolveProfileForBrowserWindow(options)
// Set to default profile first and resolve and update the profile
// only after the workspace-backup is registered.
// Because, workspace identifier of an empty window is known only then.
profile: this.userDataProfilesMainService.defaultProfile
},
homeDir: this.environmentMainService.userHome.fsPath,
@@ -1453,19 +1456,19 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// first and only load the new configuration if that was
// not vetoed
if (window.isReady) {
this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(veto => {
this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(async veto => {
if (!veto) {
this.doOpenInBrowserWindow(window!, configuration, options);
await this.doOpenInBrowserWindow(window!, configuration, options);
}
});
} else {
this.doOpenInBrowserWindow(window, configuration, options);
await this.doOpenInBrowserWindow(window, configuration, options);
}
return window;
}
private doOpenInBrowserWindow(window: ICodeWindow, configuration: INativeWindowConfiguration, options: IOpenBrowserWindowOptions): void {
private async doOpenInBrowserWindow(window: ICodeWindow, configuration: INativeWindowConfiguration, options: IOpenBrowserWindowOptions): Promise<void> {
// Register window for backups unless the window
// is for extension development, where we do not
@@ -1498,27 +1501,34 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
}
if (this.userDataProfilesMainService.isEnabled()) {
const workspace = configuration.workspace ?? toWorkspaceIdentifier(configuration.backupPath, false);
const profilePromise = this.resolveProfileForBrowserWindow(options, workspace);
const profile = profilePromise instanceof Promise ? await profilePromise : profilePromise;
configuration.profiles.profile = profile;
if (!configuration.extensionDevelopmentPath) {
// Associate the configured profile to the workspace
// unless the window is for extension development,
// where we do not persist the associations
await this.userDataProfilesMainService.setProfileForWorkspace(workspace, profile);
}
}
// Load it
window.load(configuration);
}
private async resolveProfileForBrowserWindow(options: IOpenBrowserWindowOptions): Promise<IUserDataProfile> {
let profile: IUserDataProfile | undefined;
if (this.userDataProfilesMainService.isEnabled()) {
if (options.forceProfile) {
profile = this.userDataProfilesMainService.profiles.find(p => p.name === options.forceProfile) ?? await this.userDataProfilesMainService.createNamedProfile(options.forceProfile);
} else if (options.forceTempProfile) {
profile = await this.userDataProfilesMainService.createTransientProfile();
}
private resolveProfileForBrowserWindow(options: IOpenBrowserWindowOptions, workspace: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> | IUserDataProfile {
if (options.forceProfile) {
return this.userDataProfilesMainService.profiles.find(p => p.name === options.forceProfile) ?? this.userDataProfilesMainService.createNamedProfile(options.forceProfile);
}
if (profile) {
if (!options.cli?.extensionDevelopmentPath) {
this.userDataProfilesMainService.setProfileForWorkspaceSync(options.workspace ?? 'empty-window', profile);
}
} else {
profile = this.userDataProfilesMainService.getOrSetProfileForWorkspace(options.workspace ?? 'empty-window', this.userDataProfilesMainService.defaultProfile);
if (options.forceTempProfile) {
return this.userDataProfilesMainService.createTransientProfile();
}
return profile;
return this.userDataProfilesMainService.getProfileForWorkspace(workspace) ?? this.userDataProfilesMainService.defaultProfile;
}
private onWindowClosed(window: ICodeWindow): void {
@@ -140,6 +140,13 @@ export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleF
return typeof singleFolderIdentifier?.id === 'string' && URI.isUri(singleFolderIdentifier.uri);
}
export function isEmptyWorkspaceIdentifier(obj: unknown): obj is IEmptyWorkspaceIdentifier {
const emptyWorkspaceIdentifier = obj as IEmptyWorkspaceIdentifier | undefined;
return typeof emptyWorkspaceIdentifier?.id === 'string'
&& !isSingleFolderWorkspaceIdentifier(obj)
&& !isWorkspaceIdentifier(obj);
}
export const EXTENSION_DEVELOPMENT_EMPTY_WINDOW_WORKSPACE: IEmptyWorkspaceIdentifier = { id: 'ext-dev' };
export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined;
+2 -3
View File
@@ -22,7 +22,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas, connectionTokenCookieName } from 'vs/base/common/network';
import { IAnyWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IAnyWorkspaceIdentifier, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { onUnexpectedError } from 'vs/base/common/errors';
import { setFullscreen } from 'vs/base/browser/browser';
@@ -297,8 +297,7 @@ export class BrowserMain extends Disposable {
}
}
const lastActiveProfile = environmentService.lastActiveProfile ? userDataProfilesService.profiles.find(p => p.id === environmentService.lastActiveProfile) : undefined;
const currentProfile = userDataProfilesService.getOrSetProfileForWorkspace(isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace) ? workspace : 'empty-window', lastActiveProfile ?? userDataProfilesService.defaultProfile);
const currentProfile = userDataProfilesService.getProfileForWorkspace(workspace) ?? userDataProfilesService.defaultProfile;
const userDataProfileService = new UserDataProfileService(currentProfile, userDataProfilesService);
serviceCollection.set(IUserDataProfileService, userDataProfileService);
@@ -8,8 +8,8 @@ import { localize } from 'vs/nls';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IAnyWorkspaceIdentifier, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IHostService } from 'vs/workbench/services/host/browser/host';
@@ -113,7 +113,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse
this.telemetryService.publicLog2<ProfileManagementActionExecutedEvent, ProfileManagementActionExecutedClassification>('profileManagementActionExecuted', { id: 'switchProfile' });
}
private getWorkspaceIdentifier(): WorkspaceIdentifier {
private getWorkspaceIdentifier(): IAnyWorkspaceIdentifier {
const workspace = this.workspaceContextService.getWorkspace();
switch (this.workspaceContextService.getWorkbenchState()) {
case WorkbenchState.FOLDER:
@@ -121,7 +121,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse
case WorkbenchState.WORKSPACE:
return { configPath: workspace.configuration!, id: workspace.id };
}
return 'empty-window';
return { id: workspace.id };
}
private async enterProfile(profile: IUserDataProfile, preserveData: boolean, reloadMessage?: string): Promise<void> {