diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 7716e2b73dd99..1686b418ce951 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -6,9 +6,7 @@ import { Event } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; export const IConfigurationService = createDecorator('configurationService'); @@ -262,23 +260,6 @@ export function merge(base: any, add: any, overwrite: boolean): void { }); } -export function getConfigurationKeys(): string[] { - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - return Object.keys(properties); -} - -export function getDefaultValues(): any { - const valueTreeRoot: any = Object.create(null); - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - - for (let key in properties) { - let value = properties[key].default; - addToValueTree(valueTreeRoot, key, value, message => console.error(`Conflict in default settings: ${message}`)); - } - - return valueTreeRoot; -} - export function getMigratedSettingValue(configurationService: IConfigurationService, currentSettingName: string, legacySettingName: string): T { const setting = configurationService.inspect(currentSettingName); const legacySetting = configurationService.inspect(legacySettingName); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index f41d440c4f9ba..0e7e1bff8107f 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -13,7 +13,7 @@ import * as objects from 'vs/base/common/objects'; import { IExtUri } from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { addToValueTree, ConfigurationTarget, getConfigurationKeys, getConfigurationValue, getDefaultValues, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationUpdateOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { addToValueTree, ConfigurationTarget, getConfigurationValue, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationUpdateOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IFileService } from 'vs/platform/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -234,10 +234,17 @@ export class ConfigurationModel implements IConfigurationModel { export class DefaultConfigurationModel extends ConfigurationModel { - constructor() { - const contents = getDefaultValues(); - const keys = getConfigurationKeys(); + constructor(configurationDefaultsOverrides: IStringDictionary = {}) { + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const keys = Object.keys(properties); + const contents: any = Object.create(null); const overrides: IOverrides[] = []; + + for (const key in properties) { + const defaultOverrideValue = configurationDefaultsOverrides[key]; + const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default; + addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`)); + } for (const key of Object.keys(contents)) { if (OVERRIDE_PROPERTY_REGEX.test(key)) { overrides.push({ @@ -247,6 +254,7 @@ export class DefaultConfigurationModel extends ConfigurationModel { }); } } + super(contents, keys, overrides); } } @@ -587,21 +595,12 @@ export class Configuration { this._foldersConsolidatedConfigurations.delete(resource); } - compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel, keys: string[]): IConfigurationChange { - const overrides: [string, string[]][] = []; - for (const key of keys) { - for (const overrideIdentifier of overrideIdentifiersFromKey(key)) { - const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier); - const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier); - const keys = [ - ...toKeys.filter(key => fromKeys.indexOf(key) === -1), - ...fromKeys.filter(key => toKeys.indexOf(key) === -1), - ...fromKeys.filter(key => !objects.equals(this._defaultConfiguration.override(overrideIdentifier).getValue(key), defaults.override(overrideIdentifier).getValue(key))) - ]; - overrides.push([overrideIdentifier, keys]); - } + compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel): IConfigurationChange { + const { added, updated, removed, overrides } = compare(this._defaultConfiguration, defaults); + const keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updateDefaultConfiguration(defaults); } - this.updateDefaultConfiguration(defaults); return { keys, overrides }; } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 6dc55505e20f4..6538dbdf2cf48 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -55,6 +55,11 @@ export interface IConfigurationRegistry { */ deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void; + /** + * Return the registered configuration defaults overrides + */ + getConfigurationDefaultsOverrides(): IStringDictionary; + /** * Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values. * Property or default value changes are not allowed. @@ -65,13 +70,13 @@ export interface IConfigurationRegistry { * Event that fires whenver a configuration has been * registered. */ - onDidSchemaChange: Event; + readonly onDidSchemaChange: Event; /** * Event that fires whenver a configuration has been * registered. */ - onDidUpdateConfiguration: Event; + readonly onDidUpdateConfiguration: Event<{ properties: string[], defaultsOverrides?: boolean }>; /** * Returns all configuration nodes contributed to this registry. @@ -190,7 +195,7 @@ const contributionRegistry = Registry.as(JSONExtensio class ConfigurationRegistry implements IConfigurationRegistry { - private readonly defaultValues: IStringDictionary; + private readonly configurationDefaultsOverrides: IStringDictionary; private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode; private readonly configurationContributors: IConfigurationNode[]; private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema }; @@ -201,11 +206,11 @@ class ConfigurationRegistry implements IConfigurationRegistry { private readonly _onDidSchemaChange = new Emitter(); readonly onDidSchemaChange: Event = this._onDidSchemaChange.event; - private readonly _onDidUpdateConfiguration: Emitter = new Emitter(); - readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + private readonly _onDidUpdateConfiguration = new Emitter<{ properties: string[], defaultsOverrides?: boolean }>(); + readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event; constructor() { - this.defaultValues = {}; + this.configurationDefaultsOverrides = {}; this.defaultLanguageConfigurationOverridesNode = { id: 'defaultOverrides', title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"), @@ -229,7 +234,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); - this._onDidUpdateConfiguration.fire(properties); + this._onDidUpdateConfiguration.fire({ properties }); } public deregisterConfigurations(configurations: IConfigurationNode[]): void { @@ -237,7 +242,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); - this._onDidUpdateConfiguration.fire(properties); + this._onDidUpdateConfiguration.fire({ properties }); } public updateConfigurations({ add, remove }: { add: IConfigurationNode[], remove: IConfigurationNode[] }): void { @@ -247,7 +252,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); - this._onDidUpdateConfiguration.fire(distinct(properties)); + this._onDidUpdateConfiguration.fire({ properties: distinct(properties) }); } public registerDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void { @@ -259,10 +264,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { properties.push(key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { - this.defaultValues[key] = { ...(this.defaultValues[key] || {}), ...defaultConfiguration[key] }; + this.configurationDefaultsOverrides[key] = { ...(this.configurationDefaultsOverrides[key] || {}), ...defaultConfiguration[key] }; const property: IConfigurationPropertySchema = { type: 'object', - default: this.defaultValues[key], + default: this.configurationDefaultsOverrides[key], description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key), $ref: resourceLanguageSettingsSchemaId }; @@ -270,7 +275,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.configurationProperties[key] = property; this.defaultLanguageConfigurationOverridesNode.properties![key] = property; } else { - this.defaultValues[key] = defaultConfiguration[key]; + this.configurationDefaultsOverrides[key] = defaultConfiguration[key]; const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); @@ -282,7 +287,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.registerOverrideIdentifiers(overrideIdentifiers); this._onDidSchemaChange.fire(); - this._onDidUpdateConfiguration.fire(properties); + this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true }); } public deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary[]): void { @@ -290,7 +295,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { for (const defaultConfiguration of defaultConfigurations) { for (const key in defaultConfiguration) { properties.push(key); - delete this.defaultValues[key]; + delete this.configurationDefaultsOverrides[key]; if (OVERRIDE_PROPERTY_REGEX.test(key)) { delete this.configurationProperties[key]; delete this.defaultLanguageConfigurationOverridesNode.properties![key]; @@ -306,7 +311,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateOverridePropertyPatternKey(); this._onDidSchemaChange.fire(); - this._onDidUpdateConfiguration.fire(properties); + this._onDidUpdateConfiguration.fire({ properties, defaultsOverrides: true }); } public notifyConfigurationSchemaUpdated(...configurations: IConfigurationNode[]) { @@ -417,6 +422,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { return this.excludedConfigurationProperties; } + getConfigurationDefaultsOverrides(): IStringDictionary { + return this.configurationDefaultsOverrides; + } + private registerJSONConfiguration(configuration: IConfigurationNode) { const register = (configuration: IConfigurationNode) => { let properties = configuration.properties; @@ -476,6 +485,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { case ConfigurationScope.RESOURCE: case ConfigurationScope.LANGUAGE_OVERRIDABLE: delete resourceSettings.properties[key]; + delete this.resourceLanguageSettingsSchema.properties![key]; break; } } @@ -517,7 +527,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void { - let defaultValue = this.defaultValues[key]; + let defaultValue = this.configurationDefaultsOverrides[key]; if (types.isUndefined(defaultValue)) { defaultValue = property.default; } diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index d4eeb63f9407f..3bc96c77572fb 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -34,7 +34,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); - this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDidDefaultConfigurationChange(configurationProperties))); + this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(() => this.onDidDefaultConfigurationChange())); this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule())); } @@ -89,9 +89,9 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.trigger(change, previous, ConfigurationTarget.USER); } - private onDidDefaultConfigurationChange(keys: string[]): void { + private onDidDefaultConfigurationChange(): void { const previous = this.configuration.toData(); - const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel(), keys); + const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel()); this.trigger(change, previous, ConfigurationTarget.DEFAULT); } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 406f48b809219..3b0b466cc55d1 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -443,6 +443,43 @@ suite('CustomConfigurationModel', () => { }); }); +suite('CustomConfigurationModel', () => { + + test('Default configuration model uses overrides', () => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'a', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'a': { + 'description': 'a', + 'type': 'boolean', + 'default': false, + } + } + }); + assert.strictEqual(true, new DefaultConfigurationModel().getValue('a')); + }); + + test('Default configuration model uses overrides', () => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'a', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'a': { + 'description': 'a', + 'type': 'boolean', + 'default': false, + } + } + }); + assert.strictEqual(false, new DefaultConfigurationModel({ a: false }).getValue('a')); + }); +}); + suite('Configuration', () => { test('Test inspect for overrideIdentifiers', () => { @@ -488,9 +525,9 @@ suite('Configuration', () => { '[markdown]': { 'editor.wordWrap': 'off' } - }), ['editor.lineNumbers', '[markdown]']); + })); - assert.deepStrictEqual(actual, { keys: ['editor.lineNumbers', '[markdown]'], overrides: [['markdown', ['editor.wordWrap']]] }); + assert.deepStrictEqual(actual, { keys: ['[markdown]', 'editor.lineNumbers'], overrides: [['markdown', ['editor.wordWrap']]] }); }); @@ -853,7 +890,7 @@ suite('ConfigurationChangeEvent', () => { '[markdown]': { 'editor.wordWrap': 'off' } - }), ['editor.lineNumbers', '[markdown]']), + })), configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ '[json]': { 'editor.lineNumbers': 'relative' diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index 9eac534a91446..23571bd5f6aba 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -6,7 +6,9 @@ import { Emitter } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { getConfigurationKeys, getConfigurationValue, IConfigurationChangeEvent, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { getConfigurationValue, IConfigurationChangeEvent, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; export class TestConfigurationService implements IConfigurationService { public _serviceBrand: undefined; @@ -68,7 +70,7 @@ export class TestConfigurationService implements IConfigurationService { public keys() { return { - default: getConfigurationKeys(), + default: Object.keys(Registry.as(Extensions.Configuration).getConfigurationProperties()), user: Object.keys(this.configuration), workspace: [], workspaceFolder: [] diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index b40c1e90cfea7..810d350f1272f 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -53,6 +53,7 @@ export interface IEnvironmentService { untitledWorkspacesHome: URI; globalStorageHome: URI; workspaceStorageHome: URI; + cacheHome: URI; // --- settings sync userDataSyncHome: URI; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index d1efc97921bf3..1ee4818dbf9b6 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -57,6 +57,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get tmpDir(): URI { return URI.file(this.paths.tmpDir); } + @memoize + get cacheHome(): URI { return URI.file(this.userDataPath); } + @memoize get userRoamingDataHome(): URI { return this.appSettingsHome; } diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 5a12b9db7ec4d..77824674812df 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,13 +8,14 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX, windowSettings, resourceSettings, machineOverridableSettings } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IStringDictionary } from 'vs/base/common/collections'; +const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); const configurationRegistry = Registry.as(Extensions.Configuration); const configurationEntrySchema: IJSONSchema = { @@ -111,21 +112,34 @@ const configurationEntrySchema: IJSONSchema = { } }; +const configurationDefaultsSchemaId = 'vscode://schemas/settings/configurationDefaults'; +const configurationDefaultsSchema: IJSONSchema = { + type: 'object', + description: nls.localize('configurationDefaults.description', 'Contribute defaults for configurations'), + properties: {}, + patternProperties: { + [OVERRIDE_PROPERTY_PATTERN]: { + type: 'object', + default: {}, + $ref: resourceLanguageSettingsSchemaId, + } + }, + additionalProperties: false +}; +jsonRegistry.registerSchema(configurationDefaultsSchemaId, configurationDefaultsSchema); +configurationRegistry.onDidSchemaChange(() => { + configurationDefaultsSchema.properties = { + ...machineOverridableSettings.properties, + ...windowSettings.properties, + ...resourceSettings.properties + }; +}); + // BEGIN VSCode extension point `configurationDefaults` const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'configurationDefaults', jsonSchema: { - description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'), - type: 'object', - patternProperties: { - [OVERRIDE_PROPERTY_PATTERN]: { - type: 'object', - default: {}, - $ref: resourceLanguageSettingsSchemaId, - } - }, - errorMessage: nls.localize('config.property.defaultConfiguration.languageExpected', "Language selector expected (e.g. [\"java\"])"), - additionalProperties: false + $ref: configurationDefaultsSchemaId, } }); defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => { @@ -134,12 +148,17 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => { configurationRegistry.deregisterDefaultConfigurations(removedDefaultConfigurations); } if (added.length) { + const registeredProperties = configurationRegistry.getConfigurationProperties(); + const allowedScopes = [ConfigurationScope.MACHINE_OVERRIDABLE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]; const addedDefaultConfigurations = added.map>(extension => { const defaults: IStringDictionary = objects.deepClone(extension.value); for (const key of Object.keys(defaults)) { - if (!OVERRIDE_PROPERTY_REGEX.test(key) || typeof defaults[key] !== 'object') { - extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for language specific settings are supported.", key)); - delete defaults[key]; + if (!OVERRIDE_PROPERTY_REGEX.test(key)) { + const registeredPropertyScheme = registeredProperties[key]; + if (registeredPropertyScheme.scope && !allowedScopes.includes(registeredPropertyScheme.scope)) { + extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for machine-overridable, window, resource and language overridable scoped settings are supported.", key)); + delete defaults[key]; + } } } return defaults; @@ -273,7 +292,6 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { }); // END VSCode extension point `configuration` -const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { allowComments: true, allowTrailingCommas: true, diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 5589b239f8cd9..a0a9a1954c2ce 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -29,7 +29,7 @@ import { setFullscreen } from 'vs/base/browser/browser'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; +import { ConfigurationCache } from 'vs/workbench/services/configuration/common/configurationCache'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/browser/signService'; import type { IWorkbenchConstructionOptions, IWorkspace, IWorkbench } from 'vs/workbench/workbench.web.api'; @@ -356,7 +356,8 @@ class BrowserMain extends Disposable { } private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); + const configurationCache = new ConfigurationCache([Schemas.file, Schemas.userData, Schemas.tmp] /* Cache all non native resources */, environmentService, fileService); + const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts index f4a7ba21f2770..54d68a55d8f63 100644 --- a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts @@ -30,7 +30,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel'; -import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-sandbox/configurationCache'; +import { ConfigurationCache } from 'vs/workbench/services/configuration/common/configurationCache'; import { ISignService } from 'vs/platform/sign/common/sign'; import { basename } from 'vs/base/common/path'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -50,6 +50,7 @@ import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver import { safeStringify } from 'vs/base/common/objects'; import { ISharedProcessWorkerWorkbenchService, SharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService'; import { isMacintosh } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; export abstract class SharedDesktopMain extends Disposable { @@ -333,7 +334,8 @@ export abstract class SharedDesktopMain extends Disposable { } private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache: new ConfigurationCache(URI.file(environmentService.userDataPath), fileService) }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); + const configurationCache = new ConfigurationCache([Schemas.file, Schemas.userData] /* Cache all non native resources */, environmentService, fileService); + const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index fadca0d8c8e3f..5a7d8566db498 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,13 +9,13 @@ import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, DefaultConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { equals } from 'vs/base/common/objects'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { hash } from 'vs/base/common/hash'; @@ -24,6 +24,92 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStringDictionary } from 'vs/base/common/collections'; import { ResourceMap } from 'vs/base/common/map'; import { joinPath } from 'vs/base/common/resources'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { isObject } from 'vs/base/common/types'; + +export class DefaultConfiguration extends Disposable { + + private readonly configurationRegistry = Registry.as(Extensions.Configuration); + private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; + private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; + + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + + constructor( + private readonly configurationCache: IConfigurationCache, + environmentService: IWorkbenchEnvironmentService, + ) { + super(); + if (environmentService.options?.configurationDefaults) { + this.configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]); + } + } + + private _configurationModel: ConfigurationModel | undefined; + get configurationModel(): ConfigurationModel { + if (!this._configurationModel) { + this._configurationModel = new DefaultConfigurationModel(this.cachedConfigurationDefaultsOverrides); + } + return this._configurationModel; + } + + async initialize(): Promise { + await this.initializeCachedConfigurationDefaultsOverrides(); + this._register(this.configurationRegistry.onDidUpdateConfiguration(({ defaultsOverrides }) => this.onDidUpdateConfiguration(defaultsOverrides))); + return this.configurationModel; + } + + reload(): ConfigurationModel { + this.cachedConfigurationDefaultsOverrides = {}; + this._configurationModel = undefined; + this.updateCachedConfigurationDefaultsOverrides(); + return this.configurationModel; + } + + private initiaizeCachedConfigurationDefaultsOverridesPromise: Promise | undefined; + private initializeCachedConfigurationDefaultsOverrides(): Promise { + if (!this.initiaizeCachedConfigurationDefaultsOverridesPromise) { + this.initiaizeCachedConfigurationDefaultsOverridesPromise = (async () => { + try { + const content = await this.configurationCache.read(this.cacheKey); + if (content) { + this.cachedConfigurationDefaultsOverrides = JSON.parse(content); + } + } catch (error) { /* ignore */ } + this.cachedConfigurationDefaultsOverrides = isObject(this.cachedConfigurationDefaultsOverrides) ? this.cachedConfigurationDefaultsOverrides : {}; + })(); + } + return this.initiaizeCachedConfigurationDefaultsOverridesPromise; + } + + private onDidUpdateConfiguration(defaultsOverrides?: boolean): void { + this._configurationModel = undefined; + this._onDidChangeConfiguration.fire(this.configurationModel); + if (defaultsOverrides) { + this.updateCachedConfigurationDefaultsOverrides(); + } + } + + private async updateCachedConfigurationDefaultsOverrides(): Promise { + const cachedConfigurationDefaultsOverrides: IStringDictionary = {}; + const configurationDefaultsOverrides = this.configurationRegistry.getConfigurationDefaultsOverrides(); + for (const key of Object.keys(configurationDefaultsOverrides)) { + if (!OVERRIDE_PROPERTY_REGEX.test(key) && configurationDefaultsOverrides[key] !== undefined) { + cachedConfigurationDefaultsOverrides[key] = configurationDefaultsOverrides[key]; + } + } + try { + if (Object.keys(cachedConfigurationDefaultsOverrides).length) { + await this.configurationCache.write(this.cacheKey, JSON.stringify(cachedConfigurationDefaultsOverrides)); + } else { + await this.configurationCache.remove(this.cacheKey); + } + } catch (error) {/* Ignore error */ } + } + +} export class UserConfiguration extends Disposable { diff --git a/src/vs/workbench/services/configuration/browser/configurationCache.ts b/src/vs/workbench/services/configuration/browser/configurationCache.ts deleted file mode 100644 index 394e2bdbb2956..0000000000000 --- a/src/vs/workbench/services/configuration/browser/configurationCache.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; - -export class ConfigurationCache implements IConfigurationCache { - - needsCaching(resource: URI): boolean { - // Cache all non user data resources - return ![Schemas.file, Schemas.userData, Schemas.tmp].includes(resource.scheme); - } - - async read(key: ConfigurationKey): Promise { - return ''; - } - - async write(key: ConfigurationKey, content: string): Promise { - } - - async remove(key: ConfigurationKey): Promise { - } -} diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 5c0ae2f19c6fe..04dab49ec07c3 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -11,8 +11,8 @@ import { Disposable } 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 } from 'vs/platform/workspace/common/workspace'; -import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; 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 { Registry } from 'vs/platform/registry/common/platform'; @@ -20,7 +20,7 @@ import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resour import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceIdentifier, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; +import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration, DefaultConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { mark } from 'vs/base/common/performance'; @@ -35,6 +35,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { delta, distinct } from 'vs/base/common/arrays'; import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -50,7 +51,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; private initialized: boolean = false; - private defaultConfiguration: DefaultConfigurationModel; + private defaultConfiguration: DefaultConfiguration; private localUserConfiguration: UserConfiguration; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; @@ -102,20 +103,15 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat super(); this.configurationRegistry = Registry.as(Extensions.Configuration); - // register defaults before creating default configuration model - // so that the model is not required to be updated after registering - if (environmentService.options?.configurationDefaults) { - this.configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]); - } this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); - this.defaultConfiguration = new DefaultConfigurationModel(); + this.defaultConfiguration = new DefaultConfiguration(configurationCache, environmentService); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; this.logService = logService; - this._configuration = new Configuration(this.defaultConfiguration, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.cachedFolderConfigs = new ResourceMap(); this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService, uriIdentityService, logService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); @@ -138,7 +134,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat }); })); - this._register(this.configurationRegistry.onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(configurationModel => this.onDefaultConfigurationChanged(configurationModel))); this.workspaceEditingQueue = new Queue(); } @@ -350,6 +346,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } switch (target) { + case ConfigurationTarget.DEFAULT: + await this.reloadDefaultConfiguration(); + return; + case ConfigurationTarget.USER: const { local, remote } = await this.reloadUserConfiguration(); await this.loadConfiguration(local, remote); @@ -566,6 +566,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } private async initializeConfiguration(): Promise { + await this.defaultConfiguration.initialize(); + mark('code/willInitUserConfiguration'); const { local, remote } = await this.initializeUserConfiguration(); mark('code/didInitUserConfiguration'); @@ -580,6 +582,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return { local, remote }; } + private async reloadDefaultConfiguration(): Promise { + this.onDefaultConfigurationChanged(this.defaultConfiguration.reload()); + } + private async reloadUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { const [local, remote] = await Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]); return { local, remote }; @@ -630,7 +636,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, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); if (this.initialized) { const change = this._configuration.compare(currentConfiguration); @@ -654,11 +660,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } - private onDefaultConfigurationChanged(keys: string[]): void { - this.defaultConfiguration = new DefaultConfigurationModel(); + private onDefaultConfigurationChanged(configurationModel: ConfigurationModel): void { if (this.workspace) { const previousData = this._configuration.toData(); - const change = this._configuration.compareAndUpdateDefaultConfiguration(this.defaultConfiguration, keys); + const change = this._configuration.compareAndUpdateDefaultConfiguration(configurationModel); if (this.remoteUserConfiguration) { this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse()); this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reparse()); @@ -1101,5 +1106,16 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo } } +class ResetConfigurationDefaultsOverridesCache extends Disposable implements IWorkbenchContribution { + constructor( + @IConfigurationService configurationService: IConfigurationService, + @IExtensionService extensionService: IExtensionService, + ) { + super(); + extensionService.whenInstalledExtensionsRegistered().then(() => configurationService.reloadConfiguration(ConfigurationTarget.DEFAULT)); + } +} + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); +workbenchContributionsRegistry.registerWorkbenchContribution(ResetConfigurationDefaultsOverridesCache, LifecyclePhase.Ready); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 105b65223d717..af6490e619635 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -36,7 +36,7 @@ WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG export const USER_STANDALONE_CONFIGURATIONS = Object.create(null); USER_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${TASKS_CONFIGURATION_KEY}.json`; -export type ConfigurationKey = { type: 'user' | 'workspaces' | 'folder', key: string }; +export type ConfigurationKey = { type: 'defaults' | 'user' | 'workspaces' | 'folder', key: string }; export interface IConfigurationCache { diff --git a/src/vs/workbench/services/configuration/electron-sandbox/configurationCache.ts b/src/vs/workbench/services/configuration/common/configurationCache.ts similarity index 89% rename from src/vs/workbench/services/configuration/electron-sandbox/configurationCache.ts rename to src/vs/workbench/services/configuration/common/configurationCache.ts index 408b12c1daf70..9baf8514dac8d 100644 --- a/src/vs/workbench/services/configuration/electron-sandbox/configurationCache.ts +++ b/src/vs/workbench/services/configuration/common/configurationCache.ts @@ -5,22 +5,28 @@ import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { Queue } from 'vs/base/common/async'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export class ConfigurationCache implements IConfigurationCache { + private readonly cacheHome: URI; private readonly cachedConfigurations: Map = new Map(); - constructor(private readonly cacheHome: URI, private readonly fileService: IFileService) { + constructor( + private readonly donotCacheResourcesWithSchemes: string[], + environmentService: IEnvironmentService, + private readonly fileService: IFileService + ) { + this.cacheHome = environmentService.cacheHome; } needsCaching(resource: URI): boolean { // Cache all non native resources - return ![Schemas.file, Schemas.userData].includes(resource.scheme); + return !this.donotCacheResourcesWithSchemes.includes(resource.scheme); } read(key: ConfigurationKey): Promise { diff --git a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts new file mode 100644 index 0000000000000..4b7465b41dee1 --- /dev/null +++ b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Event } from 'vs/base/common/event'; +import { joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { DefaultConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; +import { ConfigurationKey, IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; +import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { TestEnvironmentService, TestProductService } from 'vs/workbench/test/browser/workbenchTestServices'; + +export class ConfigurationCache implements IConfigurationCache { + private readonly cache = new Map(); + needsCaching(resource: URI): boolean { return false; } + async read({ type, key }: ConfigurationKey): Promise { return this.cache.get(`${type}:${key}`) || ''; } + async write({ type, key }: ConfigurationKey, content: string): Promise { this.cache.set(`${type}:${key}`, content); } + async remove({ type, key }: ConfigurationKey): Promise { this.cache.delete(`${type}:${key}`); } +} + +suite('DefaultConfiguration', () => { + + const configurationRegistry = Registry.as(Extensions.Configuration); + const cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; + let configurationCache: ConfigurationCache; + + setup(() => { + configurationCache = new ConfigurationCache(); + configurationRegistry.registerConfiguration({ + 'id': 'test.configurationDefaultsOverride', + 'type': 'object', + 'properties': { + 'test.configurationDefaultsOverride': { + 'type': 'string', + 'default': 'defaultValue', + } + } + }); + }); + + teardown(() => { + configurationRegistry.deregisterConfigurations(configurationRegistry.getConfigurations()); + configurationRegistry.deregisterDefaultConfigurations([configurationRegistry.getConfigurationDefaultsOverrides()]); + }); + + test('configuration default overrides are read from environment', async () => { + const environmentService = new BrowserWorkbenchEnvironmentService({ logsPath: joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'logs'), workspaceId: '', configurationDefaults: { 'test.configurationDefaultsOverride': 'envOverrideValue' } }, TestProductService); + const testObject = new DefaultConfiguration(configurationCache, environmentService); + assert.deepStrictEqual(testObject.configurationModel.getValue('test.configurationDefaultsOverride'), 'envOverrideValue'); + }); + + test('configuration default overrides are read from cache', async () => { + await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + + const actual = await testObject.initialize(); + + assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'overrideValue'); + }); + + test('configuration default overrides read from cache override environment', async () => { + const environmentService = new BrowserWorkbenchEnvironmentService({ logsPath: joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'logs'), workspaceId: '', configurationDefaults: { 'test.configurationDefaultsOverride': 'envOverrideValue' } }, TestProductService); + await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); + const testObject = new DefaultConfiguration(configurationCache, environmentService); + + const actual = await testObject.initialize(); + + assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'overrideValue'); + }); + + test('configuration default overrides are read from cache when default configuration changed', async () => { + await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + configurationRegistry.registerConfiguration({ + 'id': 'test.configurationDefaultsOverride', + 'type': 'object', + 'properties': { + 'test.configurationDefaultsOverride1': { + 'type': 'string', + 'default': 'defaultValue', + } + } + }); + + const actual = await promise; + assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'overrideValue'); + }); + + test('configuration default overrides are not read from cache after reload', async () => { + await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + + await testObject.initialize(); + const actual = testObject.reload(); + + assert.deepStrictEqual(actual.getValue('test.configurationDefaultsOverride'), 'defaultValue'); + }); + + test('cache is reset after reload', async () => { + await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + + await testObject.initialize(); + testObject.reload(); + + assert.deepStrictEqual(await configurationCache.read(cacheKey), ''); + }); + + test('configuration default overrides are written in cache', async () => { + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + await testObject.initialize(); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + configurationRegistry.registerDefaultConfigurations([{ 'test.configurationDefaultsOverride': 'newoverrideValue' }]); + await promise; + + const actual = JSON.parse(await configurationCache.read(cacheKey)); + assert.deepStrictEqual(actual, { 'test.configurationDefaultsOverride': 'newoverrideValue' }); + }); + + test('configuration default overrides are removed from cache if there are no overrides', async () => { + const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + await testObject.initialize(); + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + configurationRegistry.registerConfiguration({ + 'id': 'test.configurationDefaultsOverride', + 'type': 'object', + 'properties': { + 'test.configurationDefaultsOverride1': { + 'type': 'string', + 'default': 'defaultValue', + } + } + }); + await promise; + + assert.deepStrictEqual(await configurationCache.read(cacheKey), ''); + }); + +}); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 5f062d88e8ba9..7bc9af9f71a92 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -14,7 +14,7 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationEditingService, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; +import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, USER_STANDALONE_CONFIGURATIONS, IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -36,7 +36,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; @@ -44,6 +43,13 @@ import { IUserConfigurationFileService, UserConfigurationFileService } from 'vs/ const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); +export class ConfigurationCache implements IConfigurationCache { + needsCaching(resource: URI): boolean { return false; } + async read(): Promise { return ''; } + async write(): Promise { } + async remove(): Promise { } +} + suite('ConfigurationEditingService', () => { let instantiationService: TestInstantiationService; 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 17f9dfb4c5137..495086226d028 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -40,7 +40,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { ConfigurationCache as BrowserConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; @@ -54,8 +53,11 @@ function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifie }; } -class ConfigurationCache extends BrowserConfigurationCache { - override needsCaching() { return false; } +export class ConfigurationCache implements IConfigurationCache { + needsCaching(resource: URI): boolean { return false; } + async read(): Promise { return ''; } + async write(): Promise { } + async remove(): Promise { } } const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -2191,44 +2193,6 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { }); -suite('ConfigurationService - Configuration Defaults', () => { - - const disposableStore: DisposableStore = new DisposableStore(); - - suiteSetup(() => { - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - 'id': '_test', - 'type': 'object', - 'properties': { - 'configurationService.defaultOverridesSetting': { - 'type': 'string', - 'default': 'isSet', - }, - } - }); - }); - - teardown(() => disposableStore.clear()); - - test('when default value is not overriden', () => { - const testObject = createConfigurationService({}); - assert.deepStrictEqual(testObject.getValue('configurationService.defaultOverridesSetting'), 'isSet'); - }); - - test('when default value is overriden', () => { - const testObject = createConfigurationService({ 'configurationService.defaultOverridesSetting': 'overriddenValue' }); - assert.deepStrictEqual(testObject.getValue('configurationService.defaultOverridesSetting'), 'overriddenValue'); - }); - - function createConfigurationService(configurationDefaults: Record): IConfigurationService { - const remoteAgentService = (workbenchInstantiationService(undefined, disposableStore)).createInstance(RemoteAgentService, null); - const environmentService = new BrowserWorkbenchEnvironmentService({ logsPath: joinPath(ROOT, 'logs'), workspaceId: '', configurationDefaults }, TestProductService); - const fileService = new FileService(new NullLogService()); - return disposableStore.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); - } - -}); - function getWorkspaceId(configPath: URI): string { let workspaceConfigPath = configPath.toString(); if (!isLinux) { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 502fdf0f5e590..f0a2a3eedc93f 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -128,6 +128,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } + @memoize + get cacheHome(): URI { return joinPath(this.userRoamingDataHome, 'caches'); } + @memoize get globalStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'globalStorage'); }