diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 99ba8dbcea0d2..c1c69a09f5f8b 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -659,7 +659,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor } export function isProfileUsingDefaultStorage(profile: IUserDataProfile): boolean { - return profile.isDefault || !!profile.useDefaultFlags?.uiState; + return profile.isDefault || !!profile.useDefaultFlags?.globalState; } export class InMemoryStorageService extends AbstractStorageService { diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 4fe9b821e04f5..83065021bdb47 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -22,17 +22,19 @@ import { generateUuid } from 'vs/base/common/uuid'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { isString } from 'vs/base/common/types'; +export const enum ProfileResourceType { + Settings = 'settings', + Keybindings = 'keybindings', + Snippets = 'snippets', + Tasks = 'tasks', + Extensions = 'extensions', + GlobalState = 'globalState', +} + /** * Flags to indicate whether to use the default profile or not. */ -export type UseDefaultProfileFlags = { - settings?: boolean; - keybindings?: boolean; - tasks?: boolean; - snippets?: boolean; - extensions?: boolean; - uiState?: boolean; -}; +export type UseDefaultProfileFlags = { [key in ProfileResourceType]?: boolean }; export interface IUserDataProfile { readonly id: string; @@ -135,19 +137,19 @@ export function reviveProfile(profile: UriDto, scheme: string) }; } -export function toUserDataProfile(id: string, name: string, location: URI, profilesCacheHome: URI, options?: IUserDataProfileOptions): IUserDataProfile { +export function toUserDataProfile(id: string, name: string, location: URI, profilesCacheHome: URI, options?: IUserDataProfileOptions, defaultProfile?: IUserDataProfile): IUserDataProfile { return { id, name, location, isDefault: false, shortName: options?.shortName, - globalStorageHome: joinPath(location, 'globalStorage'), - settingsResource: joinPath(location, 'settings.json'), - keybindingsResource: joinPath(location, 'keybindings.json'), - tasksResource: joinPath(location, 'tasks.json'), - snippetsHome: joinPath(location, 'snippets'), - extensionsResource: joinPath(location, 'extensions.json'), + globalStorageHome: defaultProfile && options?.useDefaultFlags?.globalState ? defaultProfile.globalStorageHome : joinPath(location, 'globalStorage'), + settingsResource: defaultProfile && options?.useDefaultFlags?.settings ? defaultProfile.settingsResource : joinPath(location, 'settings.json'), + keybindingsResource: defaultProfile && options?.useDefaultFlags?.keybindings ? defaultProfile.keybindingsResource : joinPath(location, 'keybindings.json'), + tasksResource: defaultProfile && options?.useDefaultFlags?.tasks ? defaultProfile.tasksResource : joinPath(location, 'tasks.json'), + snippetsHome: defaultProfile && options?.useDefaultFlags?.snippets ? defaultProfile.snippetsHome : joinPath(location, 'snippets'), + extensionsResource: defaultProfile && options?.useDefaultFlags?.extensions ? defaultProfile.extensionsResource : joinPath(location, 'extensions.json'), cacheHome: joinPath(profilesCacheHome, id), useDefaultFlags: options?.useDefaultFlags, isTransient: options?.transient @@ -235,7 +237,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf protected _profilesObject: UserDataProfilesObject | undefined; protected get profilesObject(): UserDataProfilesObject { if (!this._profilesObject) { - const profiles = []; + const defaultProfile = this.createDefaultProfile(); + const profiles = [defaultProfile]; if (this.enabled) { try { for (const storedProfile of this.getStoredProfiles()) { @@ -243,7 +246,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf this.logService.warn('Skipping the invalid stored profile', storedProfile.location || storedProfile.name); continue; } - profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags })); + profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags }, defaultProfile)); } } catch (error) { this.logService.error(error); @@ -251,8 +254,6 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf } const workspaces = new ResourceMap(); const emptyWindows = new Map(); - const defaultProfile = this.createDefaultProfile(); - profiles.unshift({ ...defaultProfile, extensionsResource: this.getDefaultProfileExtensionsLocation() ?? defaultProfile.extensionsResource, isDefault: true }); if (profiles.length) { try { const profileAssociaitions = this.getStoredProfileAssociations(); @@ -283,7 +284,8 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf } private createDefaultProfile() { - return toUserDataProfile('__default__profile__', localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome, this.profilesCacheHome); + const defaultProfile = toUserDataProfile('__default__profile__', localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome, this.profilesCacheHome); + return { ...defaultProfile, extensionsResource: this.getDefaultProfileExtensionsLocation() ?? defaultProfile.extensionsResource, isDefault: true }; } async createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise { @@ -330,7 +332,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf return existing; } - const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), this.profilesCacheHome, options); + const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), this.profilesCacheHome, options, this.defaultProfile); await this.fileService.createFolder(profile.location); const joiners: Promise[] = []; diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts index 14195e24c2987..dbcd309c77e48 100644 --- a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts +++ b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts @@ -178,4 +178,52 @@ suite('UserDataProfileService (Common)', () => { assert.deepStrictEqual(testObject.profiles[1].id, profile.id); }); + test('profile using default profile for settings', async () => { + const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { settings: true } }); + + assert.strictEqual(profile.isDefault, false); + assert.deepStrictEqual(profile.useDefaultFlags, { settings: true }); + assert.strictEqual(profile.settingsResource.toString(), testObject.defaultProfile.settingsResource.toString()); + }); + + test('profile using default profile for keybindings', async () => { + const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { keybindings: true } }); + + assert.strictEqual(profile.isDefault, false); + assert.deepStrictEqual(profile.useDefaultFlags, { keybindings: true }); + assert.strictEqual(profile.keybindingsResource.toString(), testObject.defaultProfile.keybindingsResource.toString()); + }); + + test('profile using default profile for snippets', async () => { + const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { snippets: true } }); + + assert.strictEqual(profile.isDefault, false); + assert.deepStrictEqual(profile.useDefaultFlags, { snippets: true }); + assert.strictEqual(profile.snippetsHome.toString(), testObject.defaultProfile.snippetsHome.toString()); + }); + + test('profile using default profile for tasks', async () => { + const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { tasks: true } }); + + assert.strictEqual(profile.isDefault, false); + assert.deepStrictEqual(profile.useDefaultFlags, { tasks: true }); + assert.strictEqual(profile.tasksResource.toString(), testObject.defaultProfile.tasksResource.toString()); + }); + + test('profile using default profile for global state', async () => { + const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { globalState: true } }); + + assert.strictEqual(profile.isDefault, false); + assert.deepStrictEqual(profile.useDefaultFlags, { globalState: true }); + assert.strictEqual(profile.globalStorageHome.toString(), testObject.defaultProfile.globalStorageHome.toString()); + }); + + test('profile using default profile for extensions', async () => { + const profile = await testObject.createNamedProfile('name', { useDefaultFlags: { extensions: true } }); + + assert.strictEqual(profile.isDefault, false); + assert.deepStrictEqual(profile.useDefaultFlags, { extensions: true }); + assert.strictEqual(profile.extensionsResource.toString(), testObject.defaultProfile.extensionsResource.toString()); + }); + }); diff --git a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts index 6a8afbcd53c08..9f179f239af52 100644 --- a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts +++ b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts @@ -29,7 +29,7 @@ interface IUserDataProfilesManifestResourcePreview extends IResourcePreview { export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - protected readonly version: number = 1; + protected readonly version: number = 2; readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'profiles.json'); readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); @@ -190,7 +190,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i for (const profile of local.added) { promises.push((async () => { this.logService.trace(`${this.syncResourceLogLabel}: Creating '${profile.name}' profile...`); - await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName }); + await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags }); this.logService.info(`${this.syncResourceLogLabel}: Created profile '${profile.name}'.`); })()); } @@ -224,7 +224,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i for (const profile of remote?.added || []) { const collection = await this.userDataSyncStoreService.createCollection(this.syncHeaders); addedCollections.push(collection); - remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName }); + remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags }); } } else { this.logService.info(`${this.syncResourceLogLabel}: Could not create remote profiles as there are too many profiles.`); @@ -235,7 +235,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i for (const profile of remote?.updated || []) { const profileToBeUpdated = remoteProfiles.find(({ id }) => profile.id === id); if (profileToBeUpdated) { - remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { id: profile.id, name: profile.name, collection: profileToBeUpdated.collection, shortName: profile.shortName }); + remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { ...profileToBeUpdated, id: profile.id, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags }); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index a0b8cddc6f60c..d64a2dff8e86f 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -21,7 +21,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; export function getDisallowedIgnoredSettings(): string[] { const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); @@ -325,6 +325,7 @@ export interface ISyncUserDataProfile { readonly collection: string; readonly name: string; readonly shortName?: string; + readonly useDefaultFlags?: UseDefaultProfileFlags; } export type ISyncExtension = ILocalSyncExtension | IRemoteSyncExtension; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 8612189e0f7ff..32cbc4dba51b1 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -622,6 +622,10 @@ class ProfileSynchronizer extends Disposable { if (syncResource === SyncResource.WorkspaceState) { return; } + if (syncResource !== SyncResource.Profiles && this.profile.useDefaultFlags?.[syncResource]) { + this.logService.debug(`Skipping syncing ${syncResource} in ${this.profile.name} because it is already synced by default profile`); + return; + } const disposables = new DisposableStore(); const synchronizer = disposables.add(this.createSynchronizer(syncResource)); disposables.add(synchronizer.onDidChangeStatus(() => this.updateStatus())); diff --git a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts index 43e2534b810a2..068718a3f859b 100644 --- a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts @@ -100,7 +100,7 @@ suite('UserDataProfilesManifestSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); const profiles = getLocalProfiles(testClient); - assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined }]); + assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: undefined }]); }); test('first time sync when profiles exists', async () => { @@ -113,7 +113,7 @@ suite('UserDataProfilesManifestSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); const profiles = getLocalProfiles(testClient); - assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined }, { id: '2', name: 'name 2', shortName: undefined }]); + assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: undefined }, { id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); @@ -132,7 +132,7 @@ suite('UserDataProfilesManifestSync', () => { assert.deepStrictEqual(testObject.conflicts.conflicts, []); const profiles = getLocalProfiles(testClient); - assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined }]); + assert.deepStrictEqual(profiles, [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: undefined }]); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); @@ -149,10 +149,10 @@ suite('UserDataProfilesManifestSync', () => { await testObject.sync(await testClient.getResourceManifest()); assert.strictEqual(testObject.status, SyncStatus.Idle); assert.deepStrictEqual(testObject.conflicts.conflicts, []); - assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: 'short 1' }, { id: '2', name: 'name 2', shortName: undefined }]); + assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: 'short 1', useDefaultFlags: undefined }, { id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]); await client2.sync(); - assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 1', shortName: 'short 1' }, { id: '2', name: 'name 2', shortName: undefined }]); + assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 1', shortName: 'short 1', useDefaultFlags: undefined }, { id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); @@ -169,10 +169,10 @@ suite('UserDataProfilesManifestSync', () => { await testObject.sync(await testClient.getResourceManifest()); assert.strictEqual(testObject.status, SyncStatus.Idle); assert.deepStrictEqual(testObject.conflicts.conflicts, []); - assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 2', shortName: '2' }]); + assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 2', shortName: '2', useDefaultFlags: undefined }]); await client2.sync(); - assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 2', shortName: '2' }]); + assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '1', name: 'name 2', shortName: '2', useDefaultFlags: undefined }]); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); @@ -190,10 +190,10 @@ suite('UserDataProfilesManifestSync', () => { await testObject.sync(await testClient.getResourceManifest()); assert.strictEqual(testObject.status, SyncStatus.Idle); assert.deepStrictEqual(testObject.conflicts.conflicts, []); - assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '2', name: 'name 2', shortName: undefined }]); + assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]); await client2.sync(); - assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '2', name: 'name 2', shortName: undefined }]); + assert.deepStrictEqual(getLocalProfiles(client2), [{ id: '2', name: 'name 2', shortName: undefined, useDefaultFlags: undefined }]); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); @@ -201,6 +201,22 @@ suite('UserDataProfilesManifestSync', () => { assert.deepStrictEqual(actual, [{ id: '2', name: 'name 2', collection: '2' }]); }); + test('sync profile that uses default profile', async () => { + await client2.instantiationService.get(IUserDataProfilesService).createProfile('1', 'name 1', { useDefaultFlags: { keybindings: true } }); + await client2.sync(); + + await testObject.sync(await testClient.getResourceManifest()); + assert.strictEqual(testObject.status, SyncStatus.Idle); + assert.deepStrictEqual(testObject.conflicts.conflicts, []); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseRemoteProfiles(content!); + assert.deepStrictEqual(actual, [{ id: '1', name: 'name 1', collection: '1', useDefaultFlags: { keybindings: true } }]); + + assert.deepStrictEqual(getLocalProfiles(testClient), [{ id: '1', name: 'name 1', shortName: undefined, useDefaultFlags: { keybindings: true } }]); + }); + function parseRemoteProfiles(content: string): ISyncUserDataProfile[] { const syncData: ISyncData = JSON.parse(content); return JSON.parse(syncData.content); @@ -209,7 +225,7 @@ suite('UserDataProfilesManifestSync', () => { function getLocalProfiles(client: UserDataSyncClient): { id: string; name: string; shortName?: string }[] { return client.instantiationService.get(IUserDataProfilesService).profiles .slice(1).sort((a, b) => a.name.localeCompare(b.name)) - .map(profile => ({ id: profile.id, name: profile.name, shortName: profile.shortName })); + .map(profile => ({ id: profile.id, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags })); } diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 5c450eec8b0c4..42cfe4a1e4716 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -682,4 +682,100 @@ suite('UserDataSyncService', () => { } }); + test('test sync when there are local profile that uses default profile', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncService); + await (await testObject.createSyncTask(null)).run(); + target.reset(); + + // Do changes in the client + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + const userDataProfilesService = client.instantiationService.get(IUserDataProfilesService); + await userDataProfilesService.createNamedProfile('1', { useDefaultFlags: { settings: true } }); + await fileService.writeFile(userDataProfilesService.defaultProfile.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); + await fileService.writeFile(userDataProfilesService.defaultProfile.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); + await fileService.writeFile(joinPath(userDataProfilesService.defaultProfile.snippetsHome, 'html.json'), VSBuffer.fromString(`{}`)); + await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); + + // Sync from the client + await (await testObject.createSyncTask(null)).run(); + + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Settings + { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, + // Keybindings + { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, + // Snippets + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } }, + // Global state + { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } }, + // Profiles + { type: 'POST', url: `${target.url}/v1/collection`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/profiles`, headers: { 'If-Match': '0' } }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/keybindings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/snippets/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/tasks/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/globalState/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/extensions/latest`, headers: {} }, + ]); + }); + + test('test sync when there is a remote profile that uses default profile', async () => { + const target = new UserDataSyncTestServer(); + + // Sync from first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + + // Sync from test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await (await testObject.createSyncTask(null)).run(); + + // Do changes in first client and sync + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + const userDataProfilesService = client.instantiationService.get(IUserDataProfilesService); + await userDataProfilesService.createNamedProfile('1', { useDefaultFlags: { keybindings: true } }); + await fileService.writeFile(userDataProfilesService.defaultProfile.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); + await fileService.writeFile(userDataProfilesService.defaultProfile.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); + await fileService.writeFile(joinPath(userDataProfilesService.defaultProfile.snippetsHome, 'html.json'), VSBuffer.fromString(`{ "a": "changed" }`)); + await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + + // Sync from test client + target.reset(); + await (await testObject.createSyncTask(null)).run(); + + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Settings + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: { 'If-None-Match': '1' } }, + // Keybindings + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: { 'If-None-Match': '1' } }, + // Snippets + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: { 'If-None-Match': '1' } }, + // Global state + { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: { 'If-None-Match': '1' } }, + // Profiles + { type: 'GET', url: `${target.url}/v1/resource/profiles/latest`, headers: { 'If-None-Match': '0' } }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/settings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/snippets/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/tasks/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/globalState/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/collection/1/resource/extensions/latest`, headers: {} }, + ]); + + }); + }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index fbf7362e401d3..f0f2c01fbbda5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -78,6 +78,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStringDictionary } from 'vs/base/common/collections'; import { CONTEXT_KEYBINDINGS_EDITOR } from 'vs/workbench/contrib/preferences/common/preferences'; import { DeprecatedExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -472,6 +473,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, ) { super(); const hasGalleryContext = CONTEXT_HAS_GALLERY.bindTo(contextKeyService); @@ -515,27 +517,31 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi // Global actions private registerGlobalActions(): void { - this._register(MenuRegistry.appendMenuItems([{ - id: MenuId.MenubarPreferencesMenu, - item: { + const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.extensions + ? `${title} (${localize('default profile', "Default Profile")})` + : title; + const registerOpenExtensionsActionDisposables = this._register(new DisposableStore()); + const registerOpenExtensionsAction = () => { + registerOpenExtensionsActionDisposables.clear(); + registerOpenExtensionsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { command: { id: VIEWLET_ID, - title: localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions") + title: getTitle(localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions")) }, group: '2_configuration', order: 3 - } - }, { - id: MenuId.GlobalActivity, - item: { + })); + registerOpenExtensionsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { command: { id: VIEWLET_ID, - title: localize('showExtensions', "Extensions") + title: getTitle(localize('showExtensions', "Extensions")) }, group: '2_configuration', order: 3 - } - }])); + })); + }; + registerOpenExtensionsAction(); + this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenExtensionsAction())); this.registerExtensionAction({ id: 'workbench.extensions.action.installExtensions', diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index ef83f66b5985e..744a8a219efdd 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -12,6 +12,7 @@ import 'vs/css!./media/preferences'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest'; import * as nls from 'vs/nls'; +import { Event } from 'vs/base/common/event'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -161,37 +162,46 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } private registerSettingsActions() { - registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_COMMAND_OPEN_SETTINGS, - title: nls.localize('settings', "Settings"), - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - when: null, - primary: KeyMod.CtrlCmd | KeyCode.Comma, - }, - menu: { - id: MenuId.GlobalActivity, - group: '2_configuration', - order: 1 - } - }); - } - run(accessor: ServicesAccessor, args: string | IOpenSettingsActionOptions) { - // args takes a string for backcompat - const opts = typeof args === 'string' ? { query: args } : sanitizeOpenSettingsArgs(args); - return accessor.get(IPreferencesService).openSettings(opts); - } - }); - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { - id: SETTINGS_COMMAND_OPEN_SETTINGS, - title: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings") - }, - group: '2_configuration', - order: 1 - }); + const registerOpenSettingsActionDisposables = this._register(new DisposableStore()); + const registerOpenSettingsAction = () => { + registerOpenSettingsActionDisposables.clear(); + const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.settings + ? `${title} (${nls.localize('default profile', "Default Profile")})` + : title; + registerOpenSettingsActionDisposables.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_COMMAND_OPEN_SETTINGS, + title: { + value: getTitle(nls.localize('settings', "Settings")), + mnemonicTitle: getTitle(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings")), + original: 'Settings' + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: null, + primary: KeyMod.CtrlCmd | KeyCode.Comma, + }, + menu: [{ + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 1 + }, { + id: MenuId.MenubarPreferencesMenu, + group: '2_configuration', + order: 1 + }], + }); + } + run(accessor: ServicesAccessor, args: string | IOpenSettingsActionOptions) { + // args takes a string for backcompat + const opts = typeof args === 'string' ? { query: args } : sanitizeOpenSettingsArgs(args); + return accessor.get(IPreferencesService).openSettings(opts); + } + })); + }; + registerOpenSettingsAction(); + this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenSettingsAction())); registerAction2(class extends Action2 { constructor() { super({ @@ -780,13 +790,19 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon private registerKeybindingsActions() { const that = this; const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; - const registerOpenGlobalKeybindingsActionDisposable = this._register(new MutableDisposable()); + const registerOpenGlobalKeybindingsActionDisposables = this._register(new DisposableStore()); const registerOpenGlobalKeybindingsAction = () => { - registerOpenGlobalKeybindingsActionDisposable.value = registerAction2(class extends Action2 { + registerOpenGlobalKeybindingsActionDisposables.clear(); + const id = 'workbench.action.openGlobalKeybindings'; + const shortTitle = !that.userDataProfileService.currentProfile.isDefault && that.userDataProfileService.currentProfile.useDefaultFlags?.keybindings + ? nls.localize('keyboardShortcutsFromDefault', "Keyboard Shortcuts ({0})", nls.localize('default profile', "Default Profile")) + : nls.localize('keyboardShortcuts', "Keyboard Shortcuts"); + registerOpenGlobalKeybindingsActionDisposables.add(registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.openGlobalKeybindings', + id, title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, + shortTitle, category, icon: preferencesOpenSettingsIcon, keybinding: { @@ -801,6 +817,11 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon when: ResourceContextKey.Resource.isEqualTo(that.userDataProfileService.currentProfile.keybindingsResource.toString()), group: 'navigation', order: 1, + }, + { + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 3 } ] }); @@ -809,26 +830,18 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon const query = typeof args === 'string' ? args : undefined; return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query }); } - }); + })); + registerOpenGlobalKeybindingsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id, + title: shortTitle, + }, + group: '2_configuration', + order: 3 + })); }; registerOpenGlobalKeybindingsAction(); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => registerOpenGlobalKeybindingsAction())); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - command: { - id: 'workbench.action.openGlobalKeybindings', - title: { value: nls.localize('Keyboard Shortcuts', "Keyboard Shortcuts"), original: 'Keyboard Shortcuts' } - }, - group: '2_configuration', - order: 3 - }); - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { - id: 'workbench.action.openGlobalKeybindings', - title: { value: nls.localize('Keyboard Shortcuts', "Keyboard Shortcuts"), original: 'Keyboard Shortcuts' } - }, - group: '2_configuration', - order: 3 - }); + this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenGlobalKeybindingsAction())); registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 7105545524c24..76f3f201b1c49 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -602,7 +602,7 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc } private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { - if (!this.userDataProfileService.currentProfile.isDefault) { + if (!this.userDataProfileService.currentProfile.isDefault && !this.userDataProfileService.currentProfile.useDefaultFlags?.settings) { if (isEqual(this.userDataProfilesService.defaultProfile.settingsResource, this.settingsEditorModel.uri) && configuration.scope !== ConfigurationScope.APPLICATION) { // If we're in the default profile setting file, and the setting is not // application-scoped, fade it out. diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 18e922c5a28f8..7f8948aae61c7 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -4,18 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { isValidBasename } from 'vs/base/common/extpath'; +import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; import { extname } from 'vs/base/common/path'; import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; import * as nls from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; @@ -221,81 +224,97 @@ async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileS await textFileService.write(pick.filepath, contents); } -export class ConfigureSnippets extends SnippetsAction { - - constructor() { - super({ - id: 'workbench.action.openSnippets', - title: { - value: nls.localize('openSnippet.label', "Configure User Snippets"), - original: 'Configure User Snippets' - }, - shortTitle: { - value: nls.localize('userSnippets', "User Snippets"), - mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), - original: 'User Snippets' - }, - f1: true, - menu: [ - { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 4 }, - { id: MenuId.GlobalActivity, group: '2_configuration', order: 4 }, - ] - }); +export class ConfigureSnippetsActions extends Disposable implements IWorkbenchContribution { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + ) { + super(); + const disposable = this._register(new MutableDisposable()); + disposable.value = this.registerAction(); + this._register(Event.any(userDataProfileService.onDidChangeCurrentProfile, userDataProfileService.onDidUpdateCurrentProfile)(() => disposable.value = this.registerAction())); } - async run(accessor: ServicesAccessor): Promise { - - const snippetService = accessor.get(ISnippetsService); - const quickInputService = accessor.get(IQuickInputService); - const opener = accessor.get(IOpenerService); - const languageService = accessor.get(ILanguageService); - const userDataProfileService = accessor.get(IUserDataProfileService); - const workspaceService = accessor.get(IWorkspaceContextService); - const fileService = accessor.get(IFileService); - const textFileService = accessor.get(ITextFileService); - const labelService = accessor.get(ILabelService); - - const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService); - const existing: QuickPickInput[] = picks.existing; - - type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; - const globalSnippetPicks: SnippetPick[] = [{ - scope: nls.localize('new.global_scope', 'global'), - label: nls.localize('new.global', "New Global Snippets file..."), - uri: userDataProfileService.currentProfile.snippetsHome - }]; - - const workspaceSnippetPicks: SnippetPick[] = []; - for (const folder of workspaceService.getWorkspace().folders) { - workspaceSnippetPicks.push({ - scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name), - label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name), - uri: folder.toResource('.vscode') - }); - } + private registerAction(): IDisposable { + const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.snippets + ? `${title} (${nls.localize('default', "Default Profile")})` + : title; + return registerAction2(class extends SnippetsAction { + constructor() { + super({ + id: 'workbench.action.openSnippets', + title: { + value: nls.localize('openSnippet.label', "Configure User Snippets"), + original: 'Configure User Snippets' + }, + shortTitle: { + value: getTitle(nls.localize('userSnippets', "User Snippets")), + mnemonicTitle: getTitle(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets")), + original: 'User Snippets' + }, + f1: true, + menu: [ + { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 4 }, + { id: MenuId.GlobalActivity, group: '2_configuration', order: 4 }, + ] + }); + } - if (existing.length > 0) { - existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") }); - existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); - } else { - existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); - } + async run(accessor: ServicesAccessor): Promise { + + const snippetService = accessor.get(ISnippetsService); + const quickInputService = accessor.get(IQuickInputService); + const opener = accessor.get(IOpenerService); + const languageService = accessor.get(ILanguageService); + const userDataProfileService = accessor.get(IUserDataProfileService); + const workspaceService = accessor.get(IWorkspaceContextService); + const fileService = accessor.get(IFileService); + const textFileService = accessor.get(ITextFileService); + const labelService = accessor.get(ILabelService); + + const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService); + const existing: QuickPickInput[] = picks.existing; + + type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; + const globalSnippetPicks: SnippetPick[] = [{ + scope: nls.localize('new.global_scope', 'global'), + label: nls.localize('new.global', "New Global Snippets file..."), + uri: userDataProfileService.currentProfile.snippetsHome + }]; + + const workspaceSnippetPicks: SnippetPick[] = []; + for (const folder of workspaceService.getWorkspace().folders) { + workspaceSnippetPicks.push({ + scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name), + label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name), + uri: folder.toResource('.vscode') + }); + } - const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), { - placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"), - matchOnDescription: true - }); + if (existing.length > 0) { + existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") }); + existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); + } else { + existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); + } - if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); - } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); - } else if (ISnippetPick.is(pick)) { - if (pick.hint) { - await createLanguageSnippetFile(pick, fileService, textFileService); - } - return opener.open(pick.filepath); - } + const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), { + placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"), + matchOnDescription: true + }); + + if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); + } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); + } else if (ISnippetPick.is(pick)) { + if (pick.hint) { + await createLanguageSnippetFile(pick, fileService, textFileService); + } + return opener.open(pick.filepath); + } + } + }); } } diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 3739f02ea9781..b78f735e1fcfb 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -11,7 +11,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { ConfigureSnippets } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; +import { ConfigureSnippetsActions } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; import { InsertSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/insertSnippet'; import { SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet'; @@ -31,12 +31,12 @@ registerSingleton(ISnippetsService, SnippetsService, InstantiationType.Delayed); registerAction2(InsertSnippetAction); CommandsRegistry.registerCommandAlias('editor.action.showSnippets', 'editor.action.insertSnippet'); registerAction2(SurroundWithSnippetEditorAction); -registerAction2(ConfigureSnippets); registerAction2(ApplyFileSnippetAction); // workbench contribs -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(SnippetCodeActions, LifecyclePhase.Restored); +const workbenchContribRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContribRegistry.registerWorkbenchContribution(ConfigureSnippetsActions, LifecyclePhase.Restored); +workbenchContribRegistry.registerWorkbenchContribution(SnippetCodeActions, LifecyclePhase.Restored); // config Registry diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index e5032fab2817d..fa2b3ec19f23d 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -5,7 +5,8 @@ import * as nls from 'vs/nls'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -39,6 +40,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); @@ -351,6 +353,50 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { }, when: TaskExecutionSupportedContext }); + +class UserTasksGlobalActionContribution extends Disposable implements IWorkbenchContribution { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + ) { + super(); + this.registerActions(); + } + + private registerActions() { + const registerOpenUserTasksActionDisposables = this._register(new DisposableStore()); + const registerOpenSettingsAction = () => { + registerOpenUserTasksActionDisposables.clear(); + const id = 'workbench.action.tasks.openUserTasks'; + const userTasksTitle = nls.localize('userTasks', "User Tasks"); + const title = !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.tasks + ? `${userTasksTitle} (${nls.localize('default profile', "Default Profile")})` + : userTasksTitle; + registerOpenUserTasksActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + command: { + id, + title + }, + when: TaskExecutionSupportedContext, + group: '2_configuration', + order: 4 + })); + registerOpenUserTasksActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id, + title + }, + when: TaskExecutionSupportedContext, + group: '2_configuration', + order: 4 + })); + }; + registerOpenSettingsAction(); + this._register(Event.any(this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile)(() => registerOpenSettingsAction())); + } +} +workbenchRegistry.registerWorkbenchContribution(UserTasksGlobalActionContribution, LifecyclePhase.Restored); + // MenuRegistry.addCommand( { id: 'workbench.action.tasks.rebuild', title: nls.localize('RebuildAction.label', 'Run Rebuild Task'), category: tasksCategory }); // MenuRegistry.addCommand( { id: 'workbench.action.tasks.clean', title: nls.localize('CleanAction.label', 'Run Clean Task'), category: tasksCategory }); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 8b71b54770a48..1e49cf39e4926 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -11,7 +11,7 @@ import { localize } from 'vs/nls'; import { Action2, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { RenameProfileAction } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfileActions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -31,18 +31,18 @@ import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; -const CREATE_EMPTY_PROFILE_ACTION_ID = 'workbench.profiles.actions.createEmptyProfile'; -const CREATE_EMPTY_PROFILE_ACTION_TITLE = { - value: localize('create empty profile', "Create an Empty Profile..."), - original: 'Create an Empty Profile...' -}; - const CREATE_FROM_CURRENT_PROFILE_ACTION_ID = 'workbench.profiles.actions.createFromCurrentProfile'; const CREATE_FROM_CURRENT_PROFILE_ACTION_TITLE = { value: localize('save profile as', "Create from Current Profile..."), original: 'Create from Current Profile...' }; +const CREATE_NEW_PROFILE_ACTION_ID = 'workbench.profiles.actions.createNewProfile'; +const CREATE_NEW_PROFILE_ACTION_TITLE = { + value: localize('create new profile', "Create New Profile..."), + original: 'Create New Profile...' +}; + interface IProfileTemplateInfo { readonly name: string; readonly url: string; @@ -110,6 +110,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerCreateEmptyProfileAction(); this.registerCreateFromCurrentProfileAction(); + this.registerCreateNewProfileAction(); this.registerCreateProfileAction(); this.registerDeleteProfileAction(); this.registerCreateProfileFromTemplatesAction(); @@ -415,7 +416,26 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } run(accessor: ServicesAccessor) { - return that.createProfile(true); + return that.createFromCurrentProfile(); + } + })); + } + + private registerCreateNewProfileAction(): void { + const that = this; + this._register(registerAction2(class CreateFromCurrentProfileAction extends Action2 { + constructor() { + super({ + id: CREATE_NEW_PROFILE_ACTION_ID, + title: CREATE_NEW_PROFILE_ACTION_TITLE, + category: PROFILES_CATEGORY, + f1: true, + precondition: PROFILES_ENABLEMENT_CONTEXT + }); + } + + run() { + return that.createNewProfile(); } })); } @@ -425,8 +445,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this._register(registerAction2(class CreateEmptyProfileAction extends Action2 { constructor() { super({ - id: CREATE_EMPTY_PROFILE_ACTION_ID, - title: CREATE_EMPTY_PROFILE_ACTION_TITLE, + id: 'workbench.profiles.actions.createEmptyProfile', + title: { + value: localize('create empty profile', "Create an Empty Profile..."), + original: 'Create an Empty Profile...' + }, category: PROFILES_CATEGORY, f1: true, precondition: PROFILES_ENABLEMENT_CONTEXT @@ -434,36 +457,130 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } run(accessor: ServicesAccessor) { - return that.createProfile(false); + return that.createEmptyProfile(); } })); } - private async createProfile(fromExisting: boolean): Promise { - const name = await this.quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title: fromExisting ? localize('create from current profle', "Create from Current Profile...") : localize('create empty profile', "Create an Empty Profile..."), - validateInput: async (value: string) => { - if (this.userDataProfilesService.profiles.some(p => p.name === value)) { - return localize('profileExists', "Profile with name {0} already exists.", value); + private async createEmptyProfile(): Promise { + const name = await this.getNameForProfile(localize('create empty profile', "Create an Empty Profile...")); + if (!name) { + return; + } + try { + await this.userDataProfileManagementService.createAndEnterProfile(name, undefined); + } catch (error) { + this.notificationService.error(error); + } + } + + private async createFromCurrentProfile(): Promise { + const name = await this.getNameForProfile(localize('create from current profle', "Create from Current Profile...")); + if (!name) { + return; + } + try { + await this.userDataProfileImportExportService.createFromCurrentProfile(name); + } catch (error) { + this.notificationService.error(error); + } + } + + private async createNewProfile(): Promise { + const title = localize('create new profle', "Create New Profile..."); + + const name = await this.getNameForProfile(title); + if (!name) { + return; + } + + const settings: IQuickPickItem = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: true }; + const keybindings: IQuickPickItem = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: false }; + const snippets: IQuickPickItem = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: true }; + const tasks: IQuickPickItem = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: true }; + const extensions: IQuickPickItem = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: true }; + const resources = [settings, keybindings, snippets, tasks, extensions]; + + const quickPick = this.quickInputService.createQuickPick(); + quickPick.title = title; + quickPick.hideInput = true; + quickPick.canSelectMany = true; + quickPick.ok = false; + quickPick.customButton = true; + quickPick.hideCheckAll = true; + quickPick.ignoreFocusOut = true; + quickPick.customLabel = localize('create', "Create Profile"); + quickPick.description = localize('customise the profile', "Choose the customizations you want to include in the new profile."); + + const disposables = new DisposableStore(); + const update = () => { + quickPick.items = resources; + quickPick.selectedItems = resources.filter(item => item.picked); + }; + update(); + + disposables.add(quickPick.onDidChangeSelection(items => { + let needUpdate = false; + for (const resource of resources) { + resource.picked = items.includes(resource); + const description = resource.picked ? undefined : localize('use default profile', "Use Default Profile"); + if (resource.description !== description) { + resource.description = description; + needUpdate = true; } - return undefined; } + if (needUpdate) { + update(); + } + })); + + let result: ReadonlyArray | undefined; + disposables.add(quickPick.onDidCustom(async () => { + if (resources.some(resource => quickPick.selectedItems.includes(resource))) { + result = quickPick.selectedItems; + quickPick.hide(); + } + })); + quickPick.show(); + + await new Promise((c, e) => { + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); }); - if (!name) { + + if (!result) { return; } + try { - if (fromExisting) { - await this.userDataProfileImportExportService.createFromCurrentProfile(name); - } else { - await this.userDataProfileManagementService.createAndEnterProfile(name); - } + const useDefaultFlags: UseDefaultProfileFlags | undefined = result.length !== resources.length ? { + settings: !result.includes(settings), + keybindings: !result.includes(keybindings), + snippets: !result.includes(snippets), + tasks: !result.includes(tasks), + extensions: !result.includes(extensions) + } : undefined; + await this.userDataProfileManagementService.createAndEnterProfile(name, { useDefaultFlags }); } catch (error) { this.notificationService.error(error); } } + private async getNameForProfile(title: string): Promise { + return this.quickInputService.input({ + placeHolder: localize('name', "Profile name"), + title, + validateInput: async (value: string) => { + if (this.userDataProfilesService.profiles.some(p => p.name === value)) { + return localize('profileExists', "Profile with name {0} already exists.", value); + } + return undefined; + } + }); + } + private registerCreateProfileAction(): void { const that = this; this._register(registerAction2(class CreateProfileAction extends Action2 { @@ -492,11 +609,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const commandService = accessor.get(ICommandService); const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); const quickPickItems: QuickPickItem[] = [{ - id: CREATE_EMPTY_PROFILE_ACTION_ID, - label: localize('empty', "Empty Profile"), + id: CREATE_NEW_PROFILE_ACTION_ID, + label: localize('new profile', "New Profile..."), }, { id: CREATE_FROM_CURRENT_PROFILE_ACTION_ID, - label: localize('using current', "Using Current Profile"), + label: localize('using current', "From Current Profile..."), }]; const profileTemplateQuickPickItems = await that.getProfileTemplatesQuickPickItems(); if (profileTemplateQuickPickItems.length) { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 153cb26e39d47..7e68eba7dd704 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -156,7 +156,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private createApplicationConfiguration(): void { this.applicationConfigurationDisposables.clear(); - if (this.userDataProfileService.currentProfile.isDefault) { + if (this.userDataProfileService.currentProfile.isDefault || this.userDataProfileService.currentProfile.useDefaultFlags?.settings) { this.applicationConfiguration = null; } else { this.applicationConfiguration = this.applicationConfigurationDisposables.add(this._register(new UserConfiguration(this.userDataProfilesService.defaultProfile.settingsResource, undefined, [ConfigurationScope.APPLICATION], this.fileService, this.uriIdentityService, this.logService))); diff --git a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts index 3940f2a06b73a..b14dfc7264d81 100644 --- a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts @@ -13,10 +13,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface IProfileExtension { identifier: IExtensionIdentifier; @@ -275,6 +275,7 @@ export abstract class ExtensionsResourceTreeItem implements IProfileResourceTree return extensions.length > 0; } + abstract isFromDefaultProfile(): boolean; abstract getContent(): Promise; protected abstract getExtensions(): Promise; @@ -290,6 +291,10 @@ export class ExtensionsResourceExportTreeItem extends ExtensionsResourceTreeItem super(); } + isFromDefaultProfile(): boolean { + return !this.profile.isDefault && !!this.profile.useDefaultFlags?.extensions; + } + protected getExtensions(): Promise { return this.instantiationService.createInstance(ExtensionsResource, this.extensionsDisabled).getLocalExtensions(this.profile); } @@ -309,6 +314,10 @@ export class ExtensionsResourceImportTreeItem extends ExtensionsResourceTreeItem super(); } + isFromDefaultProfile(): boolean { + return false; + } + protected getExtensions(): Promise { return this.instantiationService.createInstance(ExtensionsResource, false).getProfileExtensions(this.content); } diff --git a/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts index 7d610f2815677..e25c4d7ac4fe9 100644 --- a/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts @@ -9,11 +9,11 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IStorageEntry, IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface IGlobalState { storage: IStringDictionary; @@ -114,6 +114,7 @@ export abstract class GlobalStateResourceTreeItem implements IProfileResourceTre } abstract getContent(): Promise; + abstract isFromDefaultProfile(): boolean; } export class GlobalStateResourceExportTreeItem extends GlobalStateResourceTreeItem { @@ -135,6 +136,10 @@ export class GlobalStateResourceExportTreeItem extends GlobalStateResourceTreeIt return this.instantiationService.createInstance(GlobalStateResource).getContent(this.profile); } + isFromDefaultProfile(): boolean { + return !this.profile.isDefault && !!this.profile.useDefaultFlags?.globalState; + } + } export class GlobalStateResourceImportTreeItem extends GlobalStateResourceTreeItem { @@ -150,4 +155,8 @@ export class GlobalStateResourceImportTreeItem extends GlobalStateResourceTreeIt return this.content; } + isFromDefaultProfile(): boolean { + return false; + } + } diff --git a/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts index 3c3f6b9be6a86..c417caf58d204 100644 --- a/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts @@ -6,10 +6,10 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { platform, Platform } from 'vs/base/common/platform'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; @@ -94,6 +94,10 @@ export class KeybindingsResourceTreeItem implements IProfileResourceTreeItem { @IInstantiationService private readonly instantiationService: IInstantiationService ) { } + isFromDefaultProfile(): boolean { + return !this.profile.isDefault && !!this.profile.useDefaultFlags?.keybindings; + } + async getChildren(): Promise { return [{ handle: this.profile.keybindingsResource.toString(), diff --git a/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts index f0d212d138bb3..870e1a1693788 100644 --- a/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts @@ -8,11 +8,11 @@ import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platf import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; @@ -136,4 +136,8 @@ export class SettingsResourceTreeItem implements IProfileResourceTreeItem { return this.instantiationService.createInstance(SettingsResource).getContent(this.profile); } + isFromDefaultProfile(): boolean { + return !this.profile.isDefault && !!this.profile.useDefaultFlags?.settings; + } + } diff --git a/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts index 82de3f6e6ca46..f1b9ef57f995d 100644 --- a/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts @@ -11,10 +11,10 @@ import { localize } from 'vs/nls'; import { FileOperationError, FileOperationResult, IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface ISnippetsContent { snippets: IStringDictionary; @@ -146,5 +146,10 @@ export class SnippetsResourceTreeItem implements IProfileResourceTreeItem { return this.instantiationService.createInstance(SnippetsResource).getContent(this.profile, this.excludedSnippets); } + isFromDefaultProfile(): boolean { + return !this.profile.isDefault && !!this.profile.useDefaultFlags?.snippets; + } + + } diff --git a/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts index 2fc82df17e837..176ee56e0dabd 100644 --- a/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts @@ -8,10 +8,10 @@ import { localize } from 'vs/nls'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface ITasksResourceContent { tasks: string | null; @@ -115,4 +115,9 @@ export class TasksResourceTreeItem implements IProfileResourceTreeItem { return this.instantiationService.createInstance(TasksResource).getContent(this.profile); } + isFromDefaultProfile(): boolean { + return !this.profile.isDefault && !!this.profile.useDefaultFlags?.tasks; + } + + } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index eb091f8a9c560..96cfd32fd6945 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -10,7 +10,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { INotificationService } from 'vs/platform/notification/common/notification'; import { Emitter, Event } from 'vs/base/common/event'; import * as DOM from 'vs/base/browser/dom'; -import { IUserDataProfileImportExportService, PROFILE_FILTER, PROFILE_EXTENSION, IUserDataProfileContentHandler, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, PROFILES_TITLE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, PROFILES_CATEGORY, IUserDataProfileManagementService, ProfileResourceType, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ISaveProfileResult, IProfileImportOptions, PROFILE_URL_AUTHORITY, toUserDataProfileUri } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileImportExportService, PROFILE_FILTER, PROFILE_EXTENSION, IUserDataProfileContentHandler, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, PROFILES_TITLE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, PROFILES_CATEGORY, IUserDataProfileManagementService, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ISaveProfileResult, IProfileImportOptions, PROFILE_URL_AUTHORITY, toUserDataProfileUri } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IDialogService, IFileDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -18,7 +18,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -878,34 +878,23 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT private readonly _onDidChangeRoots = this._register(new Emitter()); readonly onDidChangeRoots = this._onDidChangeRoots.event; - private readonly descriptions = new Map(); - constructor( @IQuickInputService protected readonly quickInputService: IQuickInputService, ) { super(); } - private _canSelect = true; - get canSelect(): boolean { - return this._canSelect; - } - set canSelect(canSelect: boolean) { - this._canSelect = canSelect; - this.rootsPromise = undefined; - } - - setDescription(type: ProfileResourceType, description: string | undefined): void { - if (description) { - this.descriptions.set(type, description); - } else { - this.descriptions.delete(type); - } - } - async getChildren(element?: ITreeItem): Promise { if (element) { - return (element).getChildren(); + const children = await (element).getChildren(); + if (children) { + for (const child of children) { + child.checkbox = child.parent.checkbox && child.checkbox + ? { isChecked: child.parent.checkbox.isChecked ? child.checkbox.isChecked : false } + : undefined; + } + } + return children; } else { this.rootsPromise = undefined; this._onDidChangeRoots.fire(); @@ -920,12 +909,10 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT this.rootsPromise = (async () => { this.roots = await this.fetchRoots(); for (const root of this.roots) { - if (this.canSelect) { - root.checkbox = { isChecked: true, tooltip: localize('select', "Select {0}", root.label.label) }; - } else { - root.checkbox = undefined; + root.checkbox = { isChecked: !root.isFromDefaultProfile(), tooltip: localize('select', "Select {0}", root.label.label) }; + if (root.isFromDefaultProfile()) { + root.description = localize('from default', "From Default Profile"); } - root.description = this.descriptions.get(root.type); } return this.roots; })(); diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts index 0c05c284536ac..e946a573bdd61 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts @@ -10,7 +10,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Barrier, Promises } from 'vs/base/common/async'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataInitializer } from 'vs/workbench/services/userData/browser/userDataInit'; -import { IProfileResourceInitializer, IUserDataProfileService, IUserDataProfileTemplate, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResourceInitializer, IUserDataProfileService, IUserDataProfileTemplate } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { SettingsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/settingsResource'; import { GlobalStateResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/globalStateResource'; import { KeybindingsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/keybindingsResource'; @@ -22,6 +22,7 @@ import { isString } from 'vs/base/common/types'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; +import { ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; export class UserDataProfileInitializer implements IUserDataInitializer { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 87b0b2edc5351..99b78b05bab8a 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfileUpdateOptions } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfileUpdateOptions, ProfileResourceType } from 'vs/platform/userDataProfile/common/userDataProfile'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; @@ -92,15 +92,6 @@ export interface IUserDataProfileImportExportService { setProfile(profile: IUserDataProfileTemplate): Promise; } -export const enum ProfileResourceType { - Settings = 'settings', - Keybindings = 'keybindings', - Snippets = 'snippets', - Tasks = 'tasks', - Extensions = 'extensions', - GlobalState = 'globalState', -} - export interface IProfileResourceInitializer { initialize(content: string): Promise; } @@ -113,6 +104,7 @@ export interface IProfileResource { export interface IProfileResourceTreeItem extends ITreeItem { readonly type: ProfileResourceType; readonly label: ITreeItemLabel; + isFromDefaultProfile(): boolean; getChildren(): Promise; getContent(): Promise; }