diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index f2ebd926da2..c14ee2080ae 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -78,6 +78,10 @@ "fileMatch": "%APP_SETTINGS_HOME%/settings.json", "url": "vscode://schemas/settings/user" }, + { + "fileMatch": "%APP_SETTINGS_HOME%/profiles/*/settings.json", + "url": "vscode://schemas/settings/profile" + }, { "fileMatch": "%MACHINE_SETTINGS_HOME%/settings.json", "url": "vscode://schemas/settings/machine" diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index ca638d805a3..5e96676ba40 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -522,7 +522,7 @@ export class StandaloneConfigurationService implements IConfigurationService { private readonly _configuration: Configuration; constructor() { - this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); } getValue(): T; @@ -584,6 +584,7 @@ export class StandaloneConfigurationService implements IConfigurationService { return { defaults: emptyModel, policy: emptyModel, + application: emptyModel, user: emptyModel, workspace: emptyModel, folders: [] diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 8f96f193df4..5c27c738322 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -74,6 +74,7 @@ export interface IConfigurationChangeEvent { export interface IConfigurationValue { readonly defaultValue?: T; + readonly applicationValue?: T; readonly userValue?: T; readonly userLocalValue?: T; readonly userRemoteValue?: T; @@ -84,6 +85,7 @@ export interface IConfigurationValue { readonly value?: T; readonly default?: { value?: T; override?: T }; + readonly application?: { value?: T; override?: T }; readonly user?: { value?: T; override?: T }; readonly userLocal?: { value?: T; override?: T }; readonly userRemote?: { value?: T; override?: T }; @@ -166,6 +168,7 @@ export interface IOverrides { export interface IConfigurationData { defaults: IConfigurationModel; policy: IConfigurationModel; + application: IConfigurationModel; user: IConfigurationModel; workspace: IConfigurationModel; folders: [UriComponents, IConfigurationModel][]; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index dc52d443f9a..1df830b7b91 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -95,6 +95,9 @@ export class ConfigurationModel implements IConfigurationModel { const keys = [...this.keys]; for (const other of others) { + if (other.isEmpty()) { + continue; + } this.mergeContents(contents, other.contents); for (const otherOverride of other.overrides) { @@ -455,6 +458,7 @@ export class Configuration { constructor( private _defaultConfiguration: ConfigurationModel, private _policyConfiguration: ConfigurationModel, + private _applicationConfiguration: ConfigurationModel, private _localUserConfiguration: ConfigurationModel, private _remoteUserConfiguration: ConfigurationModel = new ConfigurationModel(), private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), @@ -499,6 +503,7 @@ export class Configuration { const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key); const policyValue = this._policyConfiguration.isEmpty() ? undefined : this._policyConfiguration.freeze().getValue(key); + const applicationValue = this.applicationConfiguration.isEmpty() ? undefined : this.applicationConfiguration.freeze().getValue(key); const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key); const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key); const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key); @@ -511,6 +516,7 @@ export class Configuration { return { defaultValue, policyValue, + applicationValue, userValue, userLocalValue, userRemoteValue, @@ -521,6 +527,7 @@ export class Configuration { default: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, policy: policyValue !== undefined ? { value: policyValue } : undefined, + application: applicationValue !== undefined ? { value: applicationValue, override: overrides.overrideIdentifier ? this.applicationConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, user: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userLocal: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userRemote: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, @@ -557,6 +564,12 @@ export class Configuration { this._policyConfiguration = policyConfiguration; } + updateApplicationConfiguration(applicationConfiguration: ConfigurationModel): void { + this._applicationConfiguration = applicationConfiguration; + this._workspaceConsolidatedConfiguration = null; + this._foldersConsolidatedConfigurations.clear(); + } + updateLocalUserConfiguration(localUserConfiguration: ConfigurationModel): void { this._localUserConfiguration = localUserConfiguration; this._userConfiguration = null; @@ -618,6 +631,15 @@ export class Configuration { return { keys, overrides: [] }; } + compareAndUpdateApplicationConfiguration(application: ConfigurationModel): IConfigurationChange { + const { added, updated, removed, overrides } = compare(this.applicationConfiguration, application); + const keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updateApplicationConfiguration(application); + } + return { keys, overrides }; + } + compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): IConfigurationChange { const { added, updated, removed, overrides } = compare(this.localUserConfiguration, user); const keys = [...added, ...updated, ...removed]; @@ -669,6 +691,10 @@ export class Configuration { return this._defaultConfiguration; } + get applicationConfiguration(): ConfigurationModel { + return this._applicationConfiguration; + } + private _userConfiguration: ConfigurationModel | null = null; get userConfiguration(): ConfigurationModel { if (!this._userConfiguration) { @@ -726,7 +752,7 @@ export class Configuration { private getWorkspaceConsolidatedConfiguration(): ConfigurationModel { if (!this._workspaceConsolidatedConfiguration) { - this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this.userConfiguration, this._workspaceConfiguration, this._memoryConfiguration); + this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this.applicationConfiguration, this.userConfiguration, this._workspaceConfiguration, this._memoryConfiguration); if (this._freeze) { this._workspaceConfiguration = this._workspaceConfiguration.freeze(); } @@ -774,6 +800,11 @@ export class Configuration { overrides: this._policyConfiguration.overrides, keys: this._policyConfiguration.keys }, + application: { + contents: this.applicationConfiguration.contents, + overrides: this.applicationConfiguration.overrides, + keys: this.applicationConfiguration.keys + }, user: { contents: this.userConfiguration.contents, overrides: this.userConfiguration.overrides, @@ -822,13 +853,14 @@ export class Configuration { static parse(data: IConfigurationData): Configuration { const defaultConfiguration = this.parseConfigurationModel(data.defaults); const policyConfiguration = this.parseConfigurationModel(data.policy); + const applicationConfiguration = this.parseConfigurationModel(data.application); const userConfiguration = this.parseConfigurationModel(data.user); const workspaceConfiguration = this.parseConfigurationModel(data.workspace); const folders: ResourceMap = data.folders.reduce((result, value) => { result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1])); return result; }, new ResourceMap()); - return new Configuration(defaultConfiguration, policyConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); + return new Configuration(defaultConfiguration, policyConfiguration, applicationConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); } private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 99da4050566..8a4ccfe329d 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -38,7 +38,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.defaultConfiguration = this._register(new DefaultConfiguration()); this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); - this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); + this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties))); @@ -48,7 +48,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe async initialize(): Promise { const [defaultModel, policyModel, userModel] = await Promise.all([this.defaultConfiguration.initialize(), this.policyConfiguration.initialize(), this.userConfiguration.loadConfiguration()]); - this.configuration = new Configuration(defaultModel, policyModel, userModel); + this.configuration = new Configuration(defaultModel, policyModel, new ConfigurationModel(), userModel); } getConfigurationData(): IConfigurationData { diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 7f52ca02443..d1775c8ea6d 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -497,7 +497,7 @@ suite('Configuration', () => { test('Test update value', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateValue('a', 2); @@ -507,7 +507,7 @@ suite('Configuration', () => { test('Test update value after inspect', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.inspect('a', {}, undefined); testObject.updateValue('a', 2); @@ -516,7 +516,7 @@ suite('Configuration', () => { }); test('Test compare and update default configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on', })); @@ -532,8 +532,25 @@ suite('Configuration', () => { }); + test('Test compare and update application configuration', () => { + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + testObject.updateApplicationConfiguration(toConfigurationModel({ + 'update.mode': 'on', + })); + + const actual = testObject.compareAndUpdateApplicationConfiguration(toConfigurationModel({ + 'update.mode': 'none', + '[typescript]': { + 'editor.wordWrap': 'off' + } + })); + + assert.deepStrictEqual(actual, { keys: ['[typescript]', 'update.mode',], overrides: [['typescript', ['editor.wordWrap']]] }); + + }); + test('Test compare and update user configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateLocalUserConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -556,7 +573,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -579,7 +596,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -602,7 +619,7 @@ suite('Configuration', () => { }); test('Test compare and delete workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -628,7 +645,7 @@ suite('Configuration', () => { suite('ConfigurationChangeEvent', () => { test('changeEvent affecting keys with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 1, 'workbench.editor.enablePreview': false, @@ -654,7 +671,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting keys when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 2, 'workbench.editor.enablePreview': true, @@ -683,7 +700,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'files.autoSave': 'off', '[markdown]': { @@ -725,7 +742,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.editor.enablePreview': true, '[markdown]': { @@ -792,7 +809,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting workspace folders', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })); configuration.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); configuration.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true })); @@ -882,7 +899,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent - all', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); const data = configuration.toData(); const change = mergeChanges( @@ -977,7 +994,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting tasks and launches', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'launch': { 'configuraiton': {} @@ -1000,7 +1017,7 @@ suite('ConfigurationChangeEvent', () => { suite('AllKeysConfigurationChangeEvent', () => { test('changeEvent', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', '[markdown]': { diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index c30ae792e98..15b35fbf036 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -141,7 +141,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf protected createDefaultUserDataProfile(extensions: boolean): IUserDataProfile { const profile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome); - return { ...profile, extensionsResource: extensions ? profile.extensionsResource : undefined }; + return { ...profile, isDefault: true, extensionsResource: extensions ? profile.extensionsResource : undefined }; } createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { throw new Error('Not implemented'); } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 0bd5f8e9752..2e9c5b7ada8 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -257,12 +257,12 @@ export class ExtHostConfigProvider { key, defaultValue: config.policy?.value ?? config.default?.value, - globalValue: config.user?.value, + globalValue: config.user?.value ?? config.application?.value, workspaceValue: config.workspace?.value, workspaceFolderValue: config.workspaceFolder?.value, defaultLanguageValue: config.default?.override, - globalLanguageValue: config.user?.override, + globalLanguageValue: config.user?.override ?? config.application?.override, workspaceLanguageValue: config.workspace?.override, workspaceFolderLanguageValue: config.workspaceFolder?.override, diff --git a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts index 16472f6c6d7..b0886289593 100644 --- a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts +++ b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts @@ -44,6 +44,7 @@ suite('ExtHostConfiguration', function () { return { defaults: new ConfigurationModel(contents), policy: new ConfigurationModel(), + application: new ConfigurationModel(), user: new ConfigurationModel(contents), workspace: new ConfigurationModel(), folders: [], @@ -281,6 +282,7 @@ suite('ExtHostConfiguration', function () { } }, ['editor.wordWrap']), policy: new ConfigurationModel(), + application: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -331,6 +333,7 @@ suite('ExtHostConfiguration', function () { } }, ['editor.wordWrap']), policy: new ConfigurationModel(), + application: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -409,6 +412,7 @@ suite('ExtHostConfiguration', function () { } }, ['editor.wordWrap']), policy: new ConfigurationModel(), + application: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -513,6 +517,7 @@ suite('ExtHostConfiguration', function () { } }), policy: new ConfigurationModel(), + application: new ConfigurationModel(), user: toConfigurationModel({ 'editor.wordWrap': 'bounded', '[typescript]': { @@ -554,6 +559,59 @@ suite('ExtHostConfiguration', function () { assert.deepStrictEqual(actual.languageIds, ['markdown', 'typescript']); }); + test('application is not set in inspect', () => { + + const testObject = new ExtHostConfigProvider( + new class extends mock() { }, + createExtHostWorkspace(), + { + defaults: new ConfigurationModel({ + 'editor': { + 'wordWrap': 'off', + 'lineNumbers': 'on', + 'fontSize': '12px' + } + }, ['editor.wordWrap']), + policy: new ConfigurationModel(), + application: new ConfigurationModel({ + 'editor': { + 'wordWrap': 'on' + } + }, ['editor.wordWrap']), + user: new ConfigurationModel({ + 'editor': { + 'wordWrap': 'auto', + 'lineNumbers': 'off' + } + }, ['editor.wordWrap']), + workspace: new ConfigurationModel({}, []), + folders: [], + configurationScopes: [] + }, + new NullLogService() + ); + + let actual = testObject.getConfiguration().inspect('editor.wordWrap')!; + assert.strictEqual(actual.defaultValue, 'off'); + assert.strictEqual(actual.globalValue, 'auto'); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(testObject.getConfiguration().get('editor.wordWrap'), 'auto'); + + actual = testObject.getConfiguration().inspect('editor.lineNumbers')!; + assert.strictEqual(actual.defaultValue, 'on'); + assert.strictEqual(actual.globalValue, 'off'); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(testObject.getConfiguration().get('editor.lineNumbers'), 'off'); + + actual = testObject.getConfiguration().inspect('editor.fontSize')!; + assert.strictEqual(actual.defaultValue, '12px'); + assert.strictEqual(actual.globalValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(testObject.getConfiguration().get('editor.fontSize'), '12px'); + }); test('getConfiguration vs get', function () { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index b7b89c030d7..db27fd2a452 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -14,13 +14,14 @@ import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workben import { ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, LOCAL_MACHINE_PROFILE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRegistry, IExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices'; @@ -166,7 +167,8 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { parent: SettingsTreeGroupElement, inspectResult: IInspectResult, isWorkspaceTrusted: boolean, - private readonly languageService: ILanguageService + private readonly languageService: ILanguageService, + private readonly userDataProfileService: IUserDataProfileService, ) { super(sanitizeId(parent.id + '_' + setting.key)); this.setting = setting; @@ -362,8 +364,13 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return REMOTE_MACHINE_SCOPES.indexOf(this.setting.scope) !== -1; } - if (configTarget === ConfigurationTarget.USER_LOCAL && isRemote) { - return LOCAL_MACHINE_SCOPES.indexOf(this.setting.scope) !== -1; + if (configTarget === ConfigurationTarget.USER_LOCAL) { + if (!this.userDataProfileService.currentProfile.isDefault) { + return LOCAL_MACHINE_PROFILE_SCOPES.indexOf(this.setting.scope) !== -1; + } + if (isRemote) { + return LOCAL_MACHINE_SCOPES.indexOf(this.setting.scope) !== -1; + } } return true; @@ -450,7 +457,8 @@ export class SettingsTreeModel { protected _viewState: ISettingsEditorViewState, private _isWorkspaceTrusted: boolean, @IWorkbenchConfigurationService private readonly _configurationService: IWorkbenchConfigurationService, - @ILanguageService private readonly _languageService: ILanguageService + @ILanguageService private readonly _languageService: ILanguageService, + @IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService, ) { } @@ -548,7 +556,7 @@ export class SettingsTreeModel { private createSettingsTreeSettingElement(setting: ISetting, parent: SettingsTreeGroupElement): SettingsTreeSettingElement { const inspectResult = inspectSetting(setting.key, this._viewState.settingsTarget, this._viewState.languageFilter, this._configurationService); - const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._languageService); + const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._languageService, this._userDataProfileService); const nameElements = this._treeElementsBySettingName.get(setting.key) || []; nameElements.push(element); @@ -791,9 +799,10 @@ export class SearchResultModel extends SettingsTreeModel { isWorkspaceTrusted: boolean, @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, - @ILanguageService languageService: ILanguageService + @ILanguageService languageService: ILanguageService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, ) { - super(viewState, isWorkspaceTrusted, configurationService, languageService); + super(viewState, isWorkspaceTrusted, configurationService, languageService, userDataProfileService); this.update({ id: 'searchResultModel', label: '' }); } diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index 82cee39c1e6..ef854a2857f 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -123,7 +123,7 @@ registerAction2(class RemoveProfileAction extends Action2 { const userDataProfilesService = accessor.get(IUserDataProfilesService); const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); - const profiles = userDataProfilesService.profiles.filter(p => p.name !== userDataProfileService.currentProfile.name && p.name !== userDataProfilesService.defaultProfile.name); + const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id && !p.isDefault); if (profiles.length) { const pick = await quickInputService.pick(profiles.map(profile => ({ label: profile.name, profile })), { placeHolder: localize('pick profile', "Select Settings Profile") }); if (pick) { @@ -153,8 +153,9 @@ registerAction2(class SwitchProfileAction extends Action2 { const userDataProfilesService = accessor.get(IUserDataProfilesService); const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); - if (userDataProfilesService.profiles) { - const picks: Array = userDataProfilesService.profiles.map(profile => ({ + const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id); + if (profiles.length) { + const picks: Array = profiles.map(profile => ({ label: profile.name!, description: profile.name === userDataProfileService.currentProfile.name ? localize('current', "Current") : undefined, profile diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 6039018ed45..e3c1a130ed4 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -27,7 +27,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { isObject } from 'vs/base/common/types'; import { DefaultConfiguration as BaseDefaultConfiguration } from 'vs/platform/configuration/common/configurations'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class DefaultConfiguration extends BaseDefaultConfiguration { @@ -124,34 +123,32 @@ export class UserConfiguration extends Disposable { private readonly userConfigurationChangeDisposable = this._register(new MutableDisposable()); private readonly reloadConfigurationScheduler: RunOnceScheduler; - private readonly configurationParseOptions: ConfigurationParseOptions; + private configurationParseOptions: ConfigurationParseOptions; get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; } constructor( + private settingsResource: URI, + private tasksResource: URI | undefined, scopes: ConfigurationScope[] | undefined, - private readonly userDataProfileService: IUserDataProfileService, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, ) { super(); this.configurationParseOptions = { scopes, skipRestricted: false }; - this.userConfiguration.value = new UserSettings(this.userDataProfileService.currentProfile.settingsResource, scopes, uriIdentityService.extUri, this.fileService); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.onDidChangeCurrentProfile()))); + this.userConfiguration.value = new UserSettings(settingsResource, scopes, uriIdentityService.extUri, this.fileService); this.userConfigurationChangeDisposable.value = this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule()); - this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); + this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.userConfiguration.value!.loadConfiguration().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); } - private async onDidChangeCurrentProfile(): Promise { - await this.resetUserConfiguration(); - this.reloadConfigurationScheduler.schedule(); - } - - private async resetUserConfiguration(): Promise { - const folder = this.uriIdentityService.extUri.dirname(this.userDataProfileService.currentProfile.settingsResource); - const standAloneConfigurationResources: [string, URI][] = [[TASKS_CONFIGURATION_KEY, this.userDataProfileService.currentProfile.tasksResource]]; - const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.userDataProfileService.currentProfile.settingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService); + async reset(settingsResource: URI, tasksResource: URI | undefined, scopes: ConfigurationScope[] | undefined): Promise { + this.settingsResource = settingsResource; + this.tasksResource = tasksResource; + this.configurationParseOptions = { scopes, skipRestricted: false }; + const folder = this.uriIdentityService.extUri.dirname(this.settingsResource); + const standAloneConfigurationResources: [string, URI][] = this.tasksResource ? [[TASKS_CONFIGURATION_KEY, this.tasksResource]] : []; + const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.settingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService); const configurationModel = await fileServiceBasedConfiguration.loadConfiguration(); this.userConfiguration.value = fileServiceBasedConfiguration; @@ -171,7 +168,7 @@ export class UserConfiguration extends Disposable { if (this.hasTasksLoaded) { return this.userConfiguration.value!.loadConfiguration(); } - return this.resetUserConfiguration(); + return this.reset(this.settingsResource, this.tasksResource, this.configurationParseOptions.scopes); } reparse(): ConfigurationModel { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 0d227fc057d..f1ff4db821f 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; import { equals } from 'vs/base/common/objects'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Queue, Barrier, runWhenIdle, Promises } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; @@ -15,7 +15,7 @@ import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChang import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry'; import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, useSlashForPath, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; @@ -40,8 +40,15 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { isUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; + +function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { + return userDataProfile.isDefault + ? hasRemote ? LOCAL_MACHINE_SCOPES : undefined + : hasRemote ? LOCAL_MACHINE_PROFILE_SCOPES : PROFILE_SCOPES; +} class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -59,15 +66,13 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private initialized: boolean = false; private readonly defaultConfiguration: DefaultConfiguration; private readonly policyConfiguration: IPolicyConfiguration; - private localUserConfiguration: UserConfiguration; - private remoteUserConfiguration: RemoteUserConfiguration | null = null; - private workspaceConfiguration: WorkspaceConfiguration; + private applicationConfiguration: UserConfiguration | null = null; + private readonly applicationConfigurationDisposables: DisposableStore; + private readonly localUserConfiguration: UserConfiguration; + private readonly remoteUserConfiguration: RemoteUserConfiguration | null = null; + private readonly workspaceConfiguration: WorkspaceConfiguration; private cachedFolderConfigs: ResourceMap; - private workspaceEditingQueue: Queue; - - private readonly logService: ILogService; - private readonly fileService: IFileService; - private readonly uriIdentityService: IUriIdentityService; + private readonly workspaceEditingQueue: Queue; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; @@ -102,11 +107,11 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat constructor( { remoteAuthority, configurationCache }: { remoteAuthority?: string; configurationCache: IConfigurationCache }, environmentService: IWorkbenchEnvironmentService, - userDataProfileService: IUserDataProfileService, - fileService: IFileService, + private readonly userDataProfileService: IUserDataProfileService, + private readonly fileService: IFileService, remoteAgentService: IRemoteAgentService, - uriIdentityService: IUriIdentityService, - logService: ILogService, + private readonly uriIdentityService: IUriIdentityService, + private readonly logService: ILogService, policyService: IPolicyService ) { super(); @@ -118,12 +123,11 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.configurationCache = configurationCache; - this.fileService = fileService; - this.uriIdentityService = uriIdentityService; - this.logService = logService; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); + this.applicationConfigurationDisposables = this._register(new DisposableStore()); + this.createApplicationConfiguration(); + this.localUserConfiguration = this._register(new UserConfiguration(userDataProfileService.currentProfile.settingsResource, userDataProfileService.currentProfile.tasksResource, getLocalUserConfigurationScopes(userDataProfileService.currentProfile, !!remoteAuthority), fileService, uriIdentityService, logService)); this.cachedFolderConfigs = new ResourceMap(); - this.localUserConfiguration = this._register(new UserConfiguration(remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, userDataProfileService, fileService, uriIdentityService, logService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); if (remoteAuthority) { const remoteUserConfiguration = this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, fileService, uriIdentityService, remoteAgentService)); @@ -146,10 +150,21 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties, defaults }) => this.onDefaultConfigurationChanged(defaults, properties))); this._register(this.policyConfiguration.onDidChangeConfiguration(configurationModel => this.onPolicyConfigurationChanged(configurationModel))); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => this.onUserDataProfileChanged(e))); this.workspaceEditingQueue = new Queue(); } + private createApplicationConfiguration(): void { + this.applicationConfigurationDisposables.clear(); + if (this.userDataProfileService.currentProfile.isDefault) { + this.applicationConfiguration = null; + } else { + this.applicationConfiguration = this.applicationConfigurationDisposables.add(this._register(new UserConfiguration(this.userDataProfileService.defaultProfile.settingsResource, undefined, [ConfigurationScope.APPLICATION], this.fileService, this.uriIdentityService, this.logService))); + this.applicationConfigurationDisposables.add(this.applicationConfiguration.onDidChangeConfiguration(configurationModel => this.onApplicationConfigurationChanged(configurationModel))); + } + } + // Workspace Context Service Impl public async getCompleteWorkspace(): Promise { @@ -347,9 +362,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat async reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise { if (target === undefined) { this.reloadDefaultConfiguration(); + const application = await this.reloadApplicationConfiguration(true); const { local, remote } = await this.reloadUserConfiguration(); await this.reloadWorkspaceConfiguration(); - await this.loadConfiguration(local, remote); + await this.loadConfiguration(application, local, remote); return; } @@ -365,7 +381,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat case ConfigurationTarget.USER: { const { local, remote } = await this.reloadUserConfiguration(); - await this.loadConfiguration(local, remote); + await this.loadConfiguration(this._configuration.applicationConfiguration, local, remote); return; } case ConfigurationTarget.USER_LOCAL: @@ -579,8 +595,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private async initializeConfiguration(): Promise { await this.defaultConfiguration.initialize(); - const [, user] = await Promise.all([ + const [, application, user] = await Promise.all([ this.policyConfiguration.initialize(), + this.initializeApplicationConfiguration(), (async () => { mark('code/willInitUserConfiguration'); const result = await this.initializeUserConfiguration(); @@ -590,10 +607,14 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat ]); mark('code/willInitWorkspaceConfiguration'); - await this.loadConfiguration(user.local, user.remote); + await this.loadConfiguration(application, user.local, user.remote); mark('code/didInitWorkspaceConfiguration'); } + private async initializeApplicationConfiguration(): Promise { + return this.applicationConfiguration ? this.applicationConfiguration.initialize() : Promise.resolve(new ConfigurationModel()); + } + private async initializeUserConfiguration(): Promise<{ local: ConfigurationModel; remote: ConfigurationModel }> { const [local, remote] = await Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]); return { local, remote }; @@ -603,6 +624,17 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.onDefaultConfigurationChanged(this.defaultConfiguration.reload()); } + private async reloadApplicationConfiguration(donotTrigger?: boolean): Promise { + if (!this.applicationConfiguration) { + return new ConfigurationModel(); + } + const model = await this.applicationConfiguration.reload(); + if (!donotTrigger) { + this.onApplicationConfigurationChanged(model); + } + return model; + } + private async reloadUserConfiguration(): Promise<{ local: ConfigurationModel; remote: ConfigurationModel }> { const [local, remote] = await Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]); return { local, remote }; @@ -641,7 +673,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return this.onWorkspaceFolderConfigurationChanged(folder); } - private async loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise { + private async loadConfiguration(applicationConfigurationModel: ConfigurationModel, userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise { // reset caches this.cachedFolderConfigs = new ResourceMap(); @@ -653,7 +685,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, applicationConfigurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); if (this.initialized) { const change = this._configuration.compare(currentConfiguration); @@ -677,6 +709,21 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } + private onUserDataProfileChanged(e: DidChangeUserDataProfileEvent): void { + const promises: Promise[] = []; + promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration))); + if (e.previous.isDefault !== e.profile.isDefault) { + this.createApplicationConfiguration(); + if (this.applicationConfiguration) { + promises.push(this.reloadApplicationConfiguration(true)); + } + } + e.join((async () => { + const [localUser, application] = await Promise.all(promises); + await this.loadConfiguration(application ?? this._configuration.applicationConfiguration, localUser, this._configuration.remoteUserConfiguration); + })()); + } + private onDefaultConfigurationChanged(configurationModel: ConfigurationModel, properties?: string[]): void { if (this.workspace) { const previousData = this._configuration.toData(); @@ -711,6 +758,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.triggerConfigurationChange(change, previous, ConfigurationTarget.DEFAULT); } + private onApplicationConfigurationChanged(applicationConfiguration: ConfigurationModel): void { + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdateApplicationConfiguration(applicationConfiguration); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); + } + private onLocalUserConfigurationChanged(userConfiguration: ConfigurationModel): void { const previous = { data: this._configuration.toData(), workspace: this.workspace }; const change = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration); @@ -916,7 +969,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat await this.configurationEditingService.writeConfiguration(editableConfigurationTarget, { key, value }, { scopes: overrides, donotNotifyError }); switch (editableConfigurationTarget) { case EditableConfigurationTarget.USER_LOCAL: - return this.reloadLocalUserConfiguration().then(() => undefined); + if (this.applicationConfiguration && this.configurationRegistry.getConfigurationProperties()[key].scope === ConfigurationScope.APPLICATION) { + await this.reloadApplicationConfiguration(); + } else { + await this.reloadLocalUserConfiguration(); + } + return; case EditableConfigurationTarget.USER_REMOTE: return this.reloadRemoteUserConfiguration().then(() => undefined); case EditableConfigurationTarget.WORKSPACE: @@ -1049,6 +1107,19 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo } : allSettingsSchema; + const profileSettingsSchema: IJSONSchema = { + properties: { + ...machineSettings.properties, + ...machineOverridableSettings.properties, + ...windowSettings.properties, + ...resourceSettings.properties + }, + patternProperties: allSettings.patternProperties, + additionalProperties: true, + allowTrailingCommas: true, + allowComments: true + }; + const machineSettingsSchema: IJSONSchema = { properties: { ...machineSettings.properties, @@ -1094,6 +1165,7 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo allowComments: true }); jsonRegistry.registerSchema(userSettingsSchemaId, userSettingsSchema); + jsonRegistry.registerSchema(profileSettingsSchemaId, profileSettingsSchema); jsonRegistry.registerSchema(machineSettingsSchemaId, machineSettingsSchema); if (WorkbenchState.WORKSPACE === this.workspaceContextService.getWorkbenchState()) { diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 311dd5ab4f5..0b625da7565 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -17,13 +17,16 @@ export const FOLDER_SETTINGS_PATH = `${FOLDER_CONFIG_FOLDER_NAME}/${FOLDER_SETTI export const defaultSettingsSchemaId = 'vscode://schemas/settings/default'; export const userSettingsSchemaId = 'vscode://schemas/settings/user'; +export const profileSettingsSchemaId = 'vscode://schemas/settings/profile'; export const machineSettingsSchemaId = 'vscode://schemas/settings/machine'; export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; export const launchSchemaId = 'vscode://schemas/launch'; export const tasksSchemaId = 'vscode://schemas/tasks'; -export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]; +export const PROFILE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]; +export const LOCAL_MACHINE_PROFILE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]; +export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ...LOCAL_MACHINE_PROFILE_SCOPES]; export const REMOTE_MACHINE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE, ConfigurationScope.MACHINE_OVERRIDABLE]; export const WORKSPACE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE, ConfigurationScope.MACHINE_OVERRIDABLE]; export const FOLDER_SCOPES = [ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE, ConfigurationScope.MACHINE_OVERRIDABLE]; diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 74e507c17b1..74e0400e97b 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -576,7 +576,7 @@ export class ConfigurationEditingService { const standaloneConfigurationMap = target === EditableConfigurationTarget.USER_LOCAL ? USER_STANDALONE_CONFIGURATIONS : WORKSPACE_STANDALONE_CONFIGURATIONS; const standaloneConfigurationKeys = Object.keys(standaloneConfigurationMap); for (const key of standaloneConfigurationKeys) { - const resource = this.getConfigurationFileResource(target, key, standaloneConfigurationMap[key], overrides.resource); + const resource = this.getConfigurationFileResource(target, key, standaloneConfigurationMap[key], overrides.resource, undefined); // Check for prefix if (config.key === key) { @@ -594,12 +594,14 @@ export class ConfigurationEditingService { } const key = config.key; + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + const configurationScope = configurationProperties[key]?.scope; let jsonPath = overrides.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key]; if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { - return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, undefined, '', null)), target }; + return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, undefined, '', null, configurationScope)), target }; } - const resource = this.getConfigurationFileResource(target, undefined, FOLDER_SETTINGS_PATH, overrides.resource); + const resource = this.getConfigurationFileResource(target, undefined, FOLDER_SETTINGS_PATH, overrides.resource, configurationScope); if (this.isWorkspaceConfigurationResource(resource)) { jsonPath = ['settings', ...jsonPath]; } @@ -611,11 +613,14 @@ export class ConfigurationEditingService { return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath); } - private getConfigurationFileResource(target: EditableConfigurationTarget, standAloneConfigurationKey: string | undefined, relativePath: string, resource: URI | null | undefined): URI | null { + private getConfigurationFileResource(target: EditableConfigurationTarget, standAloneConfigurationKey: string | undefined, relativePath: string, resource: URI | null | undefined, scope: ConfigurationScope | undefined): URI | null { if (target === EditableConfigurationTarget.USER_LOCAL) { if (standAloneConfigurationKey === TASKS_CONFIGURATION_KEY) { return this.userDataProfileService.currentProfile.tasksResource; } else { + if (scope === ConfigurationScope.APPLICATION && !this.userDataProfileService.currentProfile.isDefault) { + return this.userDataProfileService.defaultProfile.settingsResource; + } return this.userDataProfileService.currentProfile.settingsResource; } } diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 807e96399df..95eabebc306 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -99,6 +99,7 @@ export class Configuration extends BaseConfiguration { constructor( defaults: ConfigurationModel, policy: ConfigurationModel, + application: ConfigurationModel, localUser: ConfigurationModel, remoteUser: ConfigurationModel, workspaceConfiguration: ConfigurationModel, @@ -106,7 +107,7 @@ export class Configuration extends BaseConfiguration { memoryConfiguration: ConfigurationModel, memoryConfigurationByResource: ResourceMap, private readonly _workspace?: Workspace) { - super(defaults, policy, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); + super(defaults, policy, application, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); } override getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index f9094264c4a..44f199c15e1 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -68,7 +68,7 @@ export class ConfigurationCache implements IConfigurationCache { const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); function aUserDataProfileService(environmentService: IEnvironmentService): IUserDataProfileService { - const profile = toUserDataProfile('temp', environmentService.userRoamingDataHome); + const profile = { ...toUserDataProfile('temp', environmentService.userRoamingDataHome), isDefault: true }; return new UserDataProfileService(profile, profile); } @@ -683,7 +683,7 @@ suite('WorkspaceService - Initialization', () => { suite('WorkspaceConfigurationService - Folder', () => { - let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService; + let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); const disposables: DisposableStore = new DisposableStore(); @@ -751,7 +751,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const folder = joinPath(ROOT, 'a'); await fileService.createFolder(folder); - const instantiationService = workbenchInstantiationService(undefined, disposables); + instantiationService = workbenchInstantiationService(undefined, disposables); environmentService = TestEnvironmentService; environmentService.policyFile = joinPath(folder, 'policies.json'); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); @@ -1028,6 +1028,7 @@ suite('WorkspaceConfigurationService - Folder', () => { test('inspect', async () => { let actual = testObject.inspect('something.missing'); assert.strictEqual(actual.defaultValue, undefined); + assert.strictEqual(actual.application, undefined); assert.strictEqual(actual.userValue, undefined); assert.strictEqual(actual.workspaceValue, undefined); assert.strictEqual(actual.workspaceFolderValue, undefined); @@ -1035,6 +1036,7 @@ suite('WorkspaceConfigurationService - Folder', () => { actual = testObject.inspect('configurationService.folder.testSetting'); assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.application, undefined); assert.strictEqual(actual.userValue, undefined); assert.strictEqual(actual.workspaceValue, undefined); assert.strictEqual(actual.workspaceFolderValue, undefined); @@ -1044,6 +1046,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await testObject.reloadConfiguration(); actual = testObject.inspect('configurationService.folder.testSetting'); assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.application, undefined); assert.strictEqual(actual.userValue, 'userValue'); assert.strictEqual(actual.workspaceValue, undefined); assert.strictEqual(actual.workspaceFolderValue, undefined); @@ -1053,6 +1056,7 @@ suite('WorkspaceConfigurationService - Folder', () => { await testObject.reloadConfiguration(); actual = testObject.inspect('configurationService.folder.testSetting'); assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.application, undefined); assert.strictEqual(actual.userValue, 'userValue'); assert.strictEqual(actual.workspaceValue, 'workspaceValue'); assert.strictEqual(actual.workspaceFolderValue, undefined); @@ -1370,6 +1374,164 @@ suite('WorkspaceConfigurationService - Folder', () => { }); }); +suite('WorkspaceConfigurationService - Profiles', () => { + + let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService; + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + const disposables: DisposableStore = new DisposableStore(); + + suiteSetup(() => { + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.profiles.applicationSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.APPLICATION + }, + 'configurationService.profiles.testSetting': { + 'type': 'string', + 'default': 'isSet', + }, + 'configurationService.profiles.applicationSetting2': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.APPLICATION + }, + 'configurationService.profiles.testSetting2': { + 'type': 'string', + 'default': 'isSet', + }, + } + }); + }); + + setup(async () => { + const logService = new NullLogService(); + fileService = disposables.add(new FileService(logService)); + const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + fileService.registerProvider(ROOT.scheme, fileSystemProvider); + + const folder = joinPath(ROOT, 'a'); + await fileService.createFolder(folder); + + instantiationService = workbenchInstantiationService(undefined, disposables); + environmentService = TestEnvironmentService; + environmentService.policyFile = joinPath(folder, 'policies.json'); + const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); + instantiationService.stub(IRemoteAgentService, remoteAgentService); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); + userDataProfileService = new UserDataProfileService({ ...toUserDataProfile('default', environmentService.userRoamingDataHome), isDefault: true }, toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp'))); + instantiationService.stub(IUserDataProfileService, userDataProfileService); + workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); + instantiationService.stub(IFileService, fileService); + instantiationService.stub(IWorkspaceContextService, testObject); + instantiationService.stub(IConfigurationService, testObject); + instantiationService.stub(IEnvironmentService, environmentService); + + await fileService.writeFile(userDataProfileService.defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting2": "applicationValue", "configurationService.profiles.testSetting2": "userValue" }')); + await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting2": "profileValue", "configurationService.profiles.testSetting2": "profileValue" }')); + await workspaceService.initialize(convertToWorkspacePayload(folder)); + instantiationService.stub(IKeybindingEditingService, instantiationService.createInstance(KeybindingsEditingService)); + instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); + instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); + workspaceService.acquireInstantiationService(instantiationService); + }); + + teardown(() => disposables.clear()); + + test('initialize', async () => { + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting2'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting2'), 'profileValue'); + }); + + test('inspect', async () => { + let actual = testObject.inspect('something.missing'); + assert.strictEqual(actual.defaultValue, undefined); + assert.strictEqual(actual.application, undefined); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, undefined); + + actual = testObject.inspect('configurationService.profiles.applicationSetting'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.application, undefined); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'isSet'); + + await fileService.writeFile(userDataProfileService.defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "applicationValue" }')); + await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue" }')); + await testObject.reloadConfiguration(); + actual = testObject.inspect('configurationService.profiles.applicationSetting'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.applicationValue, 'applicationValue'); + assert.strictEqual(actual.userValue, undefined); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'applicationValue'); + + await fileService.writeFile(userDataProfileService.defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "applicationValue" }')); + await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.testSetting": "profileValue" }')); + await testObject.reloadConfiguration(); + actual = testObject.inspect('configurationService.profiles.testSetting'); + assert.strictEqual(actual.defaultValue, 'isSet'); + assert.strictEqual(actual.applicationValue, undefined); + assert.strictEqual(actual.userValue, 'profileValue'); + assert.strictEqual(actual.workspaceValue, undefined); + assert.strictEqual(actual.workspaceFolderValue, undefined); + assert.strictEqual(actual.value, 'profileValue'); + }); + + test('update application scope setting', async () => { + await testObject.updateValue('configurationService.profiles.applicationSetting', 'applicationValue'); + + assert.deepStrictEqual(JSON.parse((await fileService.readFile(userDataProfileService.defaultProfile.settingsResource)).value.toString()), { 'configurationService.profiles.applicationSetting': 'applicationValue', 'configurationService.profiles.applicationSetting2': 'applicationValue', 'configurationService.profiles.testSetting2': 'userValue' }); + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting'), 'applicationValue'); + }); + + test('update normal setting', async () => { + await testObject.updateValue('configurationService.profiles.testSetting', 'profileValue'); + + assert.deepStrictEqual(JSON.parse((await fileService.readFile(userDataProfileService.currentProfile.settingsResource)).value.toString()), { 'configurationService.profiles.testSetting': 'profileValue', 'configurationService.profiles.testSetting2': 'profileValue', 'configurationService.profiles.applicationSetting2': 'profileValue' }); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue'); + }); + + test('switch to default profile', async () => { + await fileService.writeFile(userDataProfileService.defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "applicationValue", "configurationService.profiles.testSetting": "userValue" }')); + await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue", "configurationService.profiles.testSetting": "profileValue" }')); + await testObject.reloadConfiguration(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await userDataProfileService.updateCurrentProfile(userDataProfileService.defaultProfile, false); + + const changeEvent = await promise; + assert.deepStrictEqual(changeEvent.affectedKeys, ['configurationService.profiles.testSetting']); + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'userValue'); + }); + + test('switch to non default profile', async () => { + await fileService.writeFile(userDataProfileService.defaultProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "applicationValue", "configurationService.profiles.testSetting": "userValue" }')); + await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue", "configurationService.profiles.testSetting": "profileValue" }')); + await testObject.reloadConfiguration(); + + const profile = toUserDataProfile('custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2')); + await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue2", "configurationService.profiles.testSetting": "profileValue2" }')); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await userDataProfileService.updateCurrentProfile(profile, false); + + const changeEvent = await promise; + assert.deepStrictEqual(changeEvent.affectedKeys, ['configurationService.profiles.testSetting']); + assert.strictEqual(testObject.getValue('configurationService.profiles.applicationSetting'), 'applicationValue'); + assert.strictEqual(testObject.getValue('configurationService.profiles.testSetting'), 'profileValue2'); + }); + +}); + suite('WorkspaceConfigurationService-Multiroot', () => { let workspaceContextService: IWorkspaceContextService, jsonEditingServce: IJSONEditingService, testObject: WorkspaceService, fileService: IFileService, environmentService: BrowserWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService; diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 7f391a11ad3..72486f21370 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -155,14 +155,14 @@ suite('Workspace Configuration', () => { test('Test compare same configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); @@ -176,14 +176,14 @@ suite('Workspace Configuration', () => { test('Test compare different configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.enableTabs': true, '[typescript]': { 'editor.insertSpaces': true } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.fontSize': 11 })); diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index c126b7e8963..ab2dc871caa 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -100,6 +100,9 @@ export class UserDataProfileManagementService extends Disposable implements IUse if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { throw new Error(`Profile ${profile.name} does not exist`); } + if (this.userDataProfileService.currentProfile.id === profile.id) { + return; + } await this.userDataProfilesService.setProfileForWorkspace(profile, workspaceIdentifier); await this.enterProfile(profile, false); } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 70af1be98b4..dc408d8ea6d 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -12,6 +12,7 @@ import { IUserDataProfile, UseDefaultProfileFlags } from 'vs/platform/userDataPr export interface DidChangeUserDataProfileEvent { readonly preserveData: boolean; + readonly previous: IUserDataProfile; readonly profile: IUserDataProfile; join(promise: Promise): void; } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index ad47afa8c31..99746a5ad64 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -31,10 +31,15 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi } async updateCurrentProfile(userDataProfile: IUserDataProfile, preserveData: boolean): Promise { + if (this._currentProfile.id === userDataProfile.id) { + return; + } + const previous = this._currentProfile; this._currentProfile = userDataProfile; const joiners: Promise[] = []; this._onDidChangeCurrentProfile.fire({ preserveData, + previous, profile: userDataProfile, join(promise) { joiners.push(promise);