Path: blob/main/src/vs/workbench/services/configuration/browser/configuration.ts
5252 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { URI } from '../../../../base/common/uri.js';6import { Event, Emitter } from '../../../../base/common/event.js';7import * as errors from '../../../../base/common/errors.js';8import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable, DisposableStore } from '../../../../base/common/lifecycle.js';9import { RunOnceScheduler } from '../../../../base/common/async.js';10import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult, FileOperation, FileOperationEvent } from '../../../../platform/files/common/files.js';11import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from '../../../../platform/configuration/common/configurationModels.js';12import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from '../common/configurationModels.js';13import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, APPLY_ALL_PROFILES_SETTING, APPLICATION_SCOPES, MCP_CONFIGURATION_KEY } from '../common/configuration.js';14import { IStoredWorkspaceFolder } from '../../../../platform/workspaces/common/workspaces.js';15import { WorkbenchState, IWorkspaceFolder, IWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js';16import { ConfigurationScope, Extensions, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from '../../../../platform/configuration/common/configurationRegistry.js';17import { equals } from '../../../../base/common/objects.js';18import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';19import { hash } from '../../../../base/common/hash.js';20import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';21import { ILogService } from '../../../../platform/log/common/log.js';22import { IStringDictionary } from '../../../../base/common/collections.js';23import { joinPath } from '../../../../base/common/resources.js';24import { Registry } from '../../../../platform/registry/common/platform.js';25import { isEmptyObject, isObject } from '../../../../base/common/types.js';26import { DefaultConfiguration as BaseDefaultConfiguration } from '../../../../platform/configuration/common/configurations.js';27import { IJSONEditingService } from '../common/jsonEditing.js';28import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';29import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js';3031export class DefaultConfiguration extends BaseDefaultConfiguration {3233static readonly DEFAULT_OVERRIDES_CACHE_EXISTS_KEY = 'DefaultOverridesCacheExists';3435private readonly configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);36private cachedConfigurationDefaultsOverrides: IStringDictionary<unknown> = {};37private readonly cacheKey: ConfigurationKey;3839constructor(40cacheScope: string,41private readonly configurationCache: IConfigurationCache,42environmentService: IBrowserWorkbenchEnvironmentService,43logService: ILogService,44) {45super(logService);46this.cacheKey = { type: 'defaults', key: `${cacheScope}-configurationDefaultsOverrides` };47if (environmentService.options?.configurationDefaults) {48this.configurationRegistry.registerDefaultConfigurations([{ overrides: environmentService.options.configurationDefaults as IStringDictionary<IStringDictionary<unknown>> }]);49}50}5152protected override getConfigurationDefaultOverrides(): IStringDictionary<unknown> {53return this.cachedConfigurationDefaultsOverrides;54}5556override async initialize(): Promise<ConfigurationModel> {57await this.initializeCachedConfigurationDefaultsOverrides();58return super.initialize();59}6061override reload(): ConfigurationModel {62this.cachedConfigurationDefaultsOverrides = {};63this.updateCachedConfigurationDefaultsOverrides();64return super.reload();65}6667hasCachedConfigurationDefaultsOverrides(): boolean {68return !isEmptyObject(this.cachedConfigurationDefaultsOverrides);69}7071private initiaizeCachedConfigurationDefaultsOverridesPromise: Promise<void> | undefined;72private initializeCachedConfigurationDefaultsOverrides(): Promise<void> {73if (!this.initiaizeCachedConfigurationDefaultsOverridesPromise) {74this.initiaizeCachedConfigurationDefaultsOverridesPromise = (async () => {75try {76// Read only when the cache exists77if (localStorage.getItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY)) {78const content = await this.configurationCache.read(this.cacheKey);79if (content) {80this.cachedConfigurationDefaultsOverrides = JSON.parse(content);81}82}83} catch (error) { /* ignore */ }84this.cachedConfigurationDefaultsOverrides = isObject(this.cachedConfigurationDefaultsOverrides) ? this.cachedConfigurationDefaultsOverrides : {};85})();86}87return this.initiaizeCachedConfigurationDefaultsOverridesPromise;88}8990protected override onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void {91super.onDidUpdateConfiguration(properties, defaultsOverrides);92if (defaultsOverrides) {93this.updateCachedConfigurationDefaultsOverrides();94}95}9697private async updateCachedConfigurationDefaultsOverrides(): Promise<void> {98const cachedConfigurationDefaultsOverrides: IStringDictionary<unknown> = {};99const defaultConfigurations = this.configurationRegistry.getRegisteredDefaultConfigurations();100for (const defaultConfiguration of defaultConfigurations) {101if (defaultConfiguration.donotCache) {102continue;103}104for (const [key, value] of Object.entries(defaultConfiguration.overrides)) {105if (!OVERRIDE_PROPERTY_REGEX.test(key) && value !== undefined) {106const existingValue = cachedConfigurationDefaultsOverrides[key];107if (isObject(existingValue) && isObject(value)) {108cachedConfigurationDefaultsOverrides[key] = { ...existingValue, ...value };109} else {110cachedConfigurationDefaultsOverrides[key] = value;111}112}113}114}115try {116if (Object.keys(cachedConfigurationDefaultsOverrides).length) {117localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes');118await this.configurationCache.write(this.cacheKey, JSON.stringify(cachedConfigurationDefaultsOverrides));119} else {120localStorage.removeItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY);121await this.configurationCache.remove(this.cacheKey);122}123} catch (error) {/* Ignore error */ }124}125126}127128export class ApplicationConfiguration extends UserSettings {129130private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());131readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;132133private readonly reloadConfigurationScheduler: RunOnceScheduler;134135constructor(136userDataProfilesService: IUserDataProfilesService,137fileService: IFileService,138uriIdentityService: IUriIdentityService,139logService: ILogService,140) {141super(userDataProfilesService.defaultProfile.settingsResource, { scopes: APPLICATION_SCOPES, skipUnregistered: true }, uriIdentityService.extUri, fileService, logService);142this._register(this.onDidChange(() => this.reloadConfigurationScheduler.schedule()));143this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));144}145146async initialize(): Promise<ConfigurationModel> {147return this.loadConfiguration();148}149150override async loadConfiguration(): Promise<ConfigurationModel> {151const model = await super.loadConfiguration();152const value = model.getValue<string[]>(APPLY_ALL_PROFILES_SETTING);153const allProfilesSettings = Array.isArray(value) ? value : [];154return this.parseOptions.include || allProfilesSettings.length155? this.reparse({ ...this.parseOptions, include: allProfilesSettings })156: model;157}158}159160export class UserConfiguration extends Disposable {161162private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());163readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;164165private readonly userConfiguration = this._register(new MutableDisposable<UserSettings | FileServiceBasedConfiguration>());166private readonly userConfigurationChangeDisposable = this._register(new MutableDisposable<IDisposable>());167private readonly reloadConfigurationScheduler: RunOnceScheduler;168169get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; }170171constructor(172private settingsResource: URI,173private tasksResource: URI | undefined,174private mcpResource: URI | undefined,175private configurationParseOptions: ConfigurationParseOptions,176private readonly fileService: IFileService,177private readonly uriIdentityService: IUriIdentityService,178private readonly logService: ILogService,179) {180super();181this.userConfiguration.value = new UserSettings(settingsResource, this.configurationParseOptions, uriIdentityService.extUri, this.fileService, logService);182this.userConfigurationChangeDisposable.value = this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule());183this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.userConfiguration.value!.loadConfiguration().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));184}185186async reset(settingsResource: URI, tasksResource: URI | undefined, mcpResource: URI | undefined, configurationParseOptions: ConfigurationParseOptions): Promise<ConfigurationModel> {187this.settingsResource = settingsResource;188this.tasksResource = tasksResource;189this.mcpResource = mcpResource;190this.configurationParseOptions = configurationParseOptions;191return this.doReset();192}193194private async doReset(settingsConfiguration?: ConfigurationModel): Promise<ConfigurationModel> {195const folder = this.uriIdentityService.extUri.dirname(this.settingsResource);196const standAloneConfigurationResources: [string, URI][] = [];197if (this.tasksResource) {198standAloneConfigurationResources.push([TASKS_CONFIGURATION_KEY, this.tasksResource]);199}200if (this.mcpResource) {201standAloneConfigurationResources.push([MCP_CONFIGURATION_KEY, this.mcpResource]);202}203const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.settingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService);204const configurationModel = await fileServiceBasedConfiguration.loadConfiguration(settingsConfiguration);205this.userConfiguration.value = fileServiceBasedConfiguration;206207// Check for value because userConfiguration might have been disposed.208if (this.userConfigurationChangeDisposable.value) {209this.userConfigurationChangeDisposable.value = this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule());210}211212return configurationModel;213}214215async initialize(): Promise<ConfigurationModel> {216return this.userConfiguration.value!.loadConfiguration();217}218219async reload(settingsConfiguration?: ConfigurationModel): Promise<ConfigurationModel> {220if (this.hasTasksLoaded) {221return this.userConfiguration.value!.loadConfiguration();222}223return this.doReset(settingsConfiguration);224}225226reparse(parseOptions?: Partial<ConfigurationParseOptions>): ConfigurationModel {227this.configurationParseOptions = { ...this.configurationParseOptions, ...parseOptions };228return this.userConfiguration.value!.reparse(this.configurationParseOptions);229}230231getRestrictedSettings(): string[] {232return this.userConfiguration.value!.getRestrictedSettings();233}234}235236class FileServiceBasedConfiguration extends Disposable {237238private readonly allResources: URI[];239private _folderSettingsModelParser: ConfigurationModelParser;240private _folderSettingsParseOptions: ConfigurationParseOptions;241private _standAloneConfigurations: ConfigurationModel[];242private _cache: ConfigurationModel;243244private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());245readonly onDidChange: Event<void> = this._onDidChange.event;246247constructor(248name: string,249private readonly settingsResource: URI,250private readonly standAloneConfigurationResources: [string, URI][],251configurationParseOptions: ConfigurationParseOptions,252private readonly fileService: IFileService,253private readonly uriIdentityService: IUriIdentityService,254private readonly logService: ILogService,255) {256super();257this.allResources = [this.settingsResource, ...this.standAloneConfigurationResources.map(([, resource]) => resource)];258this._register(combinedDisposable(...this.allResources.map(resource => combinedDisposable(259this.fileService.watch(uriIdentityService.extUri.dirname(resource)),260// Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134261this.fileService.watch(resource)262))));263264this._folderSettingsModelParser = new ConfigurationModelParser(name, logService);265this._folderSettingsParseOptions = configurationParseOptions;266this._standAloneConfigurations = [];267this._cache = ConfigurationModel.createEmptyModel(this.logService);268269this._register(Event.debounce(270Event.any(271Event.filter(this.fileService.onDidFilesChange, e => this.handleFileChangesEvent(e)),272Event.filter(this.fileService.onDidRunOperation, e => this.handleFileOperationEvent(e))273), () => undefined, 100)(() => this._onDidChange.fire()));274}275276async resolveContents(donotResolveSettings?: boolean): Promise<[string | undefined, [string, string | undefined][]]> {277278const resolveContents = async (resources: URI[]): Promise<(string | undefined)[]> => {279return Promise.all(resources.map(async resource => {280try {281const content = await this.fileService.readFile(resource, { atomic: true });282return content.value.toString();283} catch (error) {284this.logService.trace(`Error while resolving configuration file '${resource.toString()}': ${errors.getErrorMessage(error)}`);285if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND286&& (<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_DIRECTORY) {287this.logService.error(error);288}289}290return '{}';291}));292};293294const [[settingsContent], standAloneConfigurationContents] = await Promise.all([295donotResolveSettings ? Promise.resolve([undefined]) : resolveContents([this.settingsResource]),296resolveContents(this.standAloneConfigurationResources.map(([, resource]) => resource)),297]);298299return [settingsContent, standAloneConfigurationContents.map((content, index) => ([this.standAloneConfigurationResources[index][0], content]))];300}301302async loadConfiguration(settingsConfiguration?: ConfigurationModel): Promise<ConfigurationModel> {303304const [settingsContent, standAloneConfigurationContents] = await this.resolveContents(!!settingsConfiguration);305306// reset307this._standAloneConfigurations = [];308this._folderSettingsModelParser.parse('', this._folderSettingsParseOptions);309310// parse311if (settingsContent !== undefined) {312this._folderSettingsModelParser.parse(settingsContent, this._folderSettingsParseOptions);313}314for (let index = 0; index < standAloneConfigurationContents.length; index++) {315const contents = standAloneConfigurationContents[index][1];316if (contents !== undefined) {317const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0], this.logService);318standAloneConfigurationModelParser.parse(contents);319this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);320}321}322323// Consolidate (support *.json files in the workspace settings folder)324this.consolidate(settingsConfiguration);325326return this._cache;327}328329getRestrictedSettings(): string[] {330return this._folderSettingsModelParser.restrictedConfigurations;331}332333reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {334const oldContents = this._folderSettingsModelParser.configurationModel.contents;335this._folderSettingsParseOptions = configurationParseOptions;336this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions);337if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) {338this.consolidate();339}340return this._cache;341}342343private consolidate(settingsConfiguration?: ConfigurationModel): void {344this._cache = (settingsConfiguration ?? this._folderSettingsModelParser.configurationModel).merge(...this._standAloneConfigurations);345}346347private handleFileChangesEvent(event: FileChangesEvent): boolean {348// One of the resources has changed349if (this.allResources.some(resource => event.contains(resource))) {350return true;351}352// One of the resource's parent got deleted353if (this.allResources.some(resource => event.contains(this.uriIdentityService.extUri.dirname(resource), FileChangeType.DELETED))) {354return true;355}356return false;357}358359private handleFileOperationEvent(event: FileOperationEvent): boolean {360// One of the resources has changed361if ((event.isOperation(FileOperation.CREATE) || event.isOperation(FileOperation.COPY) || event.isOperation(FileOperation.DELETE) || event.isOperation(FileOperation.WRITE))362&& this.allResources.some(resource => this.uriIdentityService.extUri.isEqual(event.resource, resource))) {363return true;364}365// One of the resource's parent got deleted366if (event.isOperation(FileOperation.DELETE) && this.allResources.some(resource => this.uriIdentityService.extUri.isEqual(event.resource, this.uriIdentityService.extUri.dirname(resource)))) {367return true;368}369return false;370}371372}373374export class RemoteUserConfiguration extends Disposable {375376private readonly _cachedConfiguration: CachedRemoteUserConfiguration;377private readonly _fileService: IFileService;378private _userConfiguration: FileServiceBasedRemoteUserConfiguration | CachedRemoteUserConfiguration;379private _userConfigurationInitializationPromise: Promise<ConfigurationModel> | null = null;380381private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());382public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;383384private readonly _onDidInitialize = this._register(new Emitter<ConfigurationModel>());385public readonly onDidInitialize = this._onDidInitialize.event;386387constructor(388remoteAuthority: string,389configurationCache: IConfigurationCache,390fileService: IFileService,391uriIdentityService: IUriIdentityService,392remoteAgentService: IRemoteAgentService,393logService: ILogService394) {395super();396this._fileService = fileService;397this._userConfiguration = this._cachedConfiguration = new CachedRemoteUserConfiguration(remoteAuthority, configurationCache, { scopes: REMOTE_MACHINE_SCOPES }, logService);398remoteAgentService.getEnvironment().then(async environment => {399if (environment) {400const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, { scopes: REMOTE_MACHINE_SCOPES }, this._fileService, uriIdentityService, logService));401this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));402this._userConfigurationInitializationPromise = userConfiguration.initialize();403const configurationModel = await this._userConfigurationInitializationPromise;404this._userConfiguration.dispose();405this._userConfiguration = userConfiguration;406this.onDidUserConfigurationChange(configurationModel);407this._onDidInitialize.fire(configurationModel);408}409});410}411412async initialize(): Promise<ConfigurationModel> {413if (this._userConfiguration instanceof FileServiceBasedRemoteUserConfiguration) {414return this._userConfiguration.initialize();415}416417// Initialize cached configuration418let configurationModel = await this._userConfiguration.initialize();419if (this._userConfigurationInitializationPromise) {420// Use user configuration421configurationModel = await this._userConfigurationInitializationPromise;422this._userConfigurationInitializationPromise = null;423}424425return configurationModel;426}427428reload(): Promise<ConfigurationModel> {429return this._userConfiguration.reload();430}431432reparse(): ConfigurationModel {433return this._userConfiguration.reparse({ scopes: REMOTE_MACHINE_SCOPES });434}435436getRestrictedSettings(): string[] {437return this._userConfiguration.getRestrictedSettings();438}439440private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void {441this.updateCache();442this._onDidChangeConfiguration.fire(configurationModel);443}444445private async updateCache(): Promise<void> {446if (this._userConfiguration instanceof FileServiceBasedRemoteUserConfiguration) {447let content: string | undefined;448try {449content = await this._userConfiguration.resolveContent();450} catch (error) {451if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {452return;453}454}455await this._cachedConfiguration.updateConfiguration(content);456}457}458459}460461class FileServiceBasedRemoteUserConfiguration extends Disposable {462463private readonly parser: ConfigurationModelParser;464private parseOptions: ConfigurationParseOptions;465private readonly reloadConfigurationScheduler: RunOnceScheduler;466protected readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());467readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;468469private readonly fileWatcherDisposable = this._register(new MutableDisposable());470private readonly directoryWatcherDisposable = this._register(new MutableDisposable());471472constructor(473private readonly configurationResource: URI,474configurationParseOptions: ConfigurationParseOptions,475private readonly fileService: IFileService,476private readonly uriIdentityService: IUriIdentityService,477private readonly logService: ILogService,478) {479super();480481this.parser = new ConfigurationModelParser(this.configurationResource.toString(), logService);482this.parseOptions = configurationParseOptions;483this._register(fileService.onDidFilesChange(e => this.handleFileChangesEvent(e)));484this._register(fileService.onDidRunOperation(e => this.handleFileOperationEvent(e)));485this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));486this._register(toDisposable(() => {487this.stopWatchingResource();488this.stopWatchingDirectory();489}));490}491492private watchResource(): void {493this.fileWatcherDisposable.value = this.fileService.watch(this.configurationResource);494}495496private stopWatchingResource(): void {497this.fileWatcherDisposable.value = undefined;498}499500private watchDirectory(): void {501const directory = this.uriIdentityService.extUri.dirname(this.configurationResource);502this.directoryWatcherDisposable.value = this.fileService.watch(directory);503}504505private stopWatchingDirectory(): void {506this.directoryWatcherDisposable.value = undefined;507}508509async initialize(): Promise<ConfigurationModel> {510const exists = await this.fileService.exists(this.configurationResource);511this.onResourceExists(exists);512return this.reload();513}514515async resolveContent(): Promise<string> {516const content = await this.fileService.readFile(this.configurationResource, { atomic: true });517return content.value.toString();518}519520async reload(): Promise<ConfigurationModel> {521try {522const content = await this.resolveContent();523this.parser.parse(content, this.parseOptions);524return this.parser.configurationModel;525} catch (e) {526return ConfigurationModel.createEmptyModel(this.logService);527}528}529530reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {531this.parseOptions = configurationParseOptions;532this.parser.reparse(this.parseOptions);533return this.parser.configurationModel;534}535536getRestrictedSettings(): string[] {537return this.parser.restrictedConfigurations;538}539540private handleFileChangesEvent(event: FileChangesEvent): void {541542// Find changes that affect the resource543let affectedByChanges = false;544if (event.contains(this.configurationResource, FileChangeType.ADDED)) {545affectedByChanges = true;546this.onResourceExists(true);547} else if (event.contains(this.configurationResource, FileChangeType.DELETED)) {548affectedByChanges = true;549this.onResourceExists(false);550} else if (event.contains(this.configurationResource, FileChangeType.UPDATED)) {551affectedByChanges = true;552}553554if (affectedByChanges) {555this.reloadConfigurationScheduler.schedule();556}557}558559private handleFileOperationEvent(event: FileOperationEvent): void {560if ((event.isOperation(FileOperation.CREATE) || event.isOperation(FileOperation.COPY) || event.isOperation(FileOperation.DELETE) || event.isOperation(FileOperation.WRITE))561&& this.uriIdentityService.extUri.isEqual(event.resource, this.configurationResource)) {562this.reloadConfigurationScheduler.schedule();563}564}565566private onResourceExists(exists: boolean): void {567if (exists) {568this.stopWatchingDirectory();569this.watchResource();570} else {571this.stopWatchingResource();572this.watchDirectory();573}574}575}576577class CachedRemoteUserConfiguration extends Disposable {578579private readonly _onDidChange: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());580readonly onDidChange: Event<ConfigurationModel> = this._onDidChange.event;581582private readonly key: ConfigurationKey;583private readonly parser: ConfigurationModelParser;584private parseOptions: ConfigurationParseOptions;585private configurationModel: ConfigurationModel;586587constructor(588remoteAuthority: string,589private readonly configurationCache: IConfigurationCache,590configurationParseOptions: ConfigurationParseOptions,591logService: ILogService,592) {593super();594this.key = { type: 'user', key: remoteAuthority };595this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration', logService);596this.parseOptions = configurationParseOptions;597this.configurationModel = ConfigurationModel.createEmptyModel(logService);598}599600getConfigurationModel(): ConfigurationModel {601return this.configurationModel;602}603604initialize(): Promise<ConfigurationModel> {605return this.reload();606}607608reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {609this.parseOptions = configurationParseOptions;610this.parser.reparse(this.parseOptions);611this.configurationModel = this.parser.configurationModel;612return this.configurationModel;613}614615getRestrictedSettings(): string[] {616return this.parser.restrictedConfigurations;617}618619async reload(): Promise<ConfigurationModel> {620try {621const content = await this.configurationCache.read(this.key);622const parsed: { content: string } = JSON.parse(content);623if (parsed.content) {624this.parser.parse(parsed.content, this.parseOptions);625this.configurationModel = this.parser.configurationModel;626}627} catch (e) { /* Ignore error */ }628return this.configurationModel;629}630631async updateConfiguration(content: string | undefined): Promise<void> {632if (content) {633return this.configurationCache.write(this.key, JSON.stringify({ content }));634} else {635return this.configurationCache.remove(this.key);636}637}638}639640export class WorkspaceConfiguration extends Disposable {641642private readonly _cachedConfiguration: CachedWorkspaceConfiguration;643private _workspaceConfiguration: CachedWorkspaceConfiguration | FileServiceBasedWorkspaceConfiguration;644private readonly _workspaceConfigurationDisposables = this._register(new DisposableStore());645private _workspaceIdentifier: IWorkspaceIdentifier | null = null;646private _isWorkspaceTrusted: boolean = false;647648private readonly _onDidUpdateConfiguration = this._register(new Emitter<boolean>());649public readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event;650651private _initialized: boolean = false;652get initialized(): boolean { return this._initialized; }653constructor(654private readonly configurationCache: IConfigurationCache,655private readonly fileService: IFileService,656private readonly uriIdentityService: IUriIdentityService,657private readonly logService: ILogService,658) {659super();660this.fileService = fileService;661this._workspaceConfiguration = this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache, logService);662}663664async initialize(workspaceIdentifier: IWorkspaceIdentifier, workspaceTrusted: boolean): Promise<void> {665this._workspaceIdentifier = workspaceIdentifier;666this._isWorkspaceTrusted = workspaceTrusted;667if (!this._initialized) {668if (this.configurationCache.needsCaching(this._workspaceIdentifier.configPath)) {669this._workspaceConfiguration = this._cachedConfiguration;670this.waitAndInitialize(this._workspaceIdentifier);671} else {672this.doInitialize(new FileServiceBasedWorkspaceConfiguration(this.fileService, this.uriIdentityService, this.logService));673}674}675await this.reload();676}677678async reload(): Promise<void> {679if (this._workspaceIdentifier) {680await this._workspaceConfiguration.load(this._workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() });681}682}683684getFolders(): IStoredWorkspaceFolder[] {685return this._workspaceConfiguration.getFolders();686}687688setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: IJSONEditingService): Promise<void> {689if (this._workspaceIdentifier) {690return jsonEditingService.write(this._workspaceIdentifier.configPath, [{ path: ['folders'], value: folders }], true)691.then(() => this.reload());692}693return Promise.resolve();694}695696isTransient(): boolean {697return this._workspaceConfiguration.isTransient();698}699700getConfiguration(): ConfigurationModel {701return this._workspaceConfiguration.getWorkspaceSettings();702}703704updateWorkspaceTrust(trusted: boolean): ConfigurationModel {705this._isWorkspaceTrusted = trusted;706return this.reparseWorkspaceSettings();707}708709reparseWorkspaceSettings(): ConfigurationModel {710this._workspaceConfiguration.reparseWorkspaceSettings({ scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() });711return this.getConfiguration();712}713714getRestrictedSettings(): string[] {715return this._workspaceConfiguration.getRestrictedSettings();716}717718private async waitAndInitialize(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {719await whenProviderRegistered(workspaceIdentifier.configPath, this.fileService);720if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) {721const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this.fileService, this.uriIdentityService, this.logService));722await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() });723this.doInitialize(fileServiceBasedWorkspaceConfiguration);724this.onDidWorkspaceConfigurationChange(false, true);725}726}727728private doInitialize(fileServiceBasedWorkspaceConfiguration: FileServiceBasedWorkspaceConfiguration): void {729this._workspaceConfigurationDisposables.clear();730this._workspaceConfiguration = this._workspaceConfigurationDisposables.add(fileServiceBasedWorkspaceConfiguration);731this._workspaceConfigurationDisposables.add(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true, false)));732this._initialized = true;733}734735private isUntrusted(): boolean {736return !this._isWorkspaceTrusted;737}738739private async onDidWorkspaceConfigurationChange(reload: boolean, fromCache: boolean): Promise<void> {740if (reload) {741await this.reload();742}743this.updateCache();744this._onDidUpdateConfiguration.fire(fromCache);745}746747private async updateCache(): Promise<void> {748if (this._workspaceIdentifier && this.configurationCache.needsCaching(this._workspaceIdentifier.configPath) && this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration) {749const content = await this._workspaceConfiguration.resolveContent(this._workspaceIdentifier);750await this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier, content);751}752}753}754755class FileServiceBasedWorkspaceConfiguration extends Disposable {756757workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;758workspaceSettings: ConfigurationModel;759private _workspaceIdentifier: IWorkspaceIdentifier | null = null;760private workspaceConfigWatcher: IDisposable;761private readonly reloadConfigurationScheduler: RunOnceScheduler;762763protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());764readonly onDidChange: Event<void> = this._onDidChange.event;765766constructor(767private readonly fileService: IFileService,768uriIdentityService: IUriIdentityService,769private readonly logService: ILogService,770) {771super();772773this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser('', logService);774this.workspaceSettings = ConfigurationModel.createEmptyModel(logService);775776this._register(Event.any(777Event.filter(this.fileService.onDidFilesChange, e => !!this._workspaceIdentifier && e.contains(this._workspaceIdentifier.configPath)),778Event.filter(this.fileService.onDidRunOperation, e => !!this._workspaceIdentifier && (e.isOperation(FileOperation.CREATE) || e.isOperation(FileOperation.COPY) || e.isOperation(FileOperation.DELETE) || e.isOperation(FileOperation.WRITE)) && uriIdentityService.extUri.isEqual(e.resource, this._workspaceIdentifier.configPath))779)(() => this.reloadConfigurationScheduler.schedule()));780this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));781this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile());782}783784get workspaceIdentifier(): IWorkspaceIdentifier | null {785return this._workspaceIdentifier;786}787788async resolveContent(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {789const content = await this.fileService.readFile(workspaceIdentifier.configPath, { atomic: true });790return content.value.toString();791}792793async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise<void> {794if (!this._workspaceIdentifier || this._workspaceIdentifier.id !== workspaceIdentifier.id) {795this._workspaceIdentifier = workspaceIdentifier;796this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceIdentifier.id, this.logService);797dispose(this.workspaceConfigWatcher);798this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile());799}800let contents = '';801try {802contents = await this.resolveContent(this._workspaceIdentifier);803} catch (error) {804const exists = await this.fileService.exists(this._workspaceIdentifier.configPath);805if (exists) {806this.logService.error(error);807}808}809this.workspaceConfigurationModelParser.parse(contents, configurationParseOptions);810this.consolidate();811}812813getConfigurationModel(): ConfigurationModel {814return this.workspaceConfigurationModelParser.configurationModel;815}816817getFolders(): IStoredWorkspaceFolder[] {818return this.workspaceConfigurationModelParser.folders;819}820821isTransient(): boolean {822return this.workspaceConfigurationModelParser.transient;823}824825getWorkspaceSettings(): ConfigurationModel {826return this.workspaceSettings;827}828829reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {830this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions);831this.consolidate();832return this.getWorkspaceSettings();833}834835getRestrictedSettings(): string[] {836return this.workspaceConfigurationModelParser.getRestrictedWorkspaceSettings();837}838839private consolidate(): void {840this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel);841}842843private watchWorkspaceConfigurationFile(): IDisposable {844return this._workspaceIdentifier ? this.fileService.watch(this._workspaceIdentifier.configPath) : Disposable.None;845}846847}848849class CachedWorkspaceConfiguration {850851readonly onDidChange: Event<void> = Event.None;852853workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;854workspaceSettings: ConfigurationModel;855856constructor(857private readonly configurationCache: IConfigurationCache,858private readonly logService: ILogService859) {860this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser('', logService);861this.workspaceSettings = ConfigurationModel.createEmptyModel(logService);862}863864async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise<void> {865try {866const key = this.getKey(workspaceIdentifier);867const contents = await this.configurationCache.read(key);868const parsed: { content: string } = JSON.parse(contents);869if (parsed.content) {870this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key, this.logService);871this.workspaceConfigurationModelParser.parse(parsed.content, configurationParseOptions);872this.consolidate();873}874} catch (e) {875}876}877878get workspaceIdentifier(): IWorkspaceIdentifier | null {879return null;880}881882getConfigurationModel(): ConfigurationModel {883return this.workspaceConfigurationModelParser.configurationModel;884}885886getFolders(): IStoredWorkspaceFolder[] {887return this.workspaceConfigurationModelParser.folders;888}889890isTransient(): boolean {891return this.workspaceConfigurationModelParser.transient;892}893894getWorkspaceSettings(): ConfigurationModel {895return this.workspaceSettings;896}897898reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {899this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions);900this.consolidate();901return this.getWorkspaceSettings();902}903904getRestrictedSettings(): string[] {905return this.workspaceConfigurationModelParser.getRestrictedWorkspaceSettings();906}907908private consolidate(): void {909this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel);910}911912async updateWorkspace(workspaceIdentifier: IWorkspaceIdentifier, content: string | undefined): Promise<void> {913try {914const key = this.getKey(workspaceIdentifier);915if (content) {916await this.configurationCache.write(key, JSON.stringify({ content }));917} else {918await this.configurationCache.remove(key);919}920} catch (error) {921}922}923924private getKey(workspaceIdentifier: IWorkspaceIdentifier): ConfigurationKey {925return {926type: 'workspaces',927key: workspaceIdentifier.id928};929}930}931932class CachedFolderConfiguration {933934readonly onDidChange = Event.None;935936private _folderSettingsModelParser: ConfigurationModelParser;937private _folderSettingsParseOptions: ConfigurationParseOptions;938private _standAloneConfigurations: ConfigurationModel[];939private configurationModel: ConfigurationModel;940private readonly key: ConfigurationKey;941942constructor(943folder: URI,944configFolderRelativePath: string,945configurationParseOptions: ConfigurationParseOptions,946private readonly configurationCache: IConfigurationCache,947private readonly logService: ILogService948) {949this.key = { type: 'folder', key: hash(joinPath(folder, configFolderRelativePath).toString()).toString(16) };950this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration', logService);951this._folderSettingsParseOptions = configurationParseOptions;952this._standAloneConfigurations = [];953this.configurationModel = ConfigurationModel.createEmptyModel(logService);954}955956async loadConfiguration(): Promise<ConfigurationModel> {957try {958const contents = await this.configurationCache.read(this.key);959const { content: configurationContents }: { content: IStringDictionary<string> } = JSON.parse(contents.toString());960if (configurationContents) {961for (const key of Object.keys(configurationContents)) {962if (key === FOLDER_SETTINGS_NAME) {963this._folderSettingsModelParser.parse(configurationContents[key], this._folderSettingsParseOptions);964} else {965const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(key, key, this.logService);966standAloneConfigurationModelParser.parse(configurationContents[key]);967this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);968}969}970}971this.consolidate();972} catch (e) {973}974return this.configurationModel;975}976977async updateConfiguration(settingsContent: string | undefined, standAloneConfigurationContents: [string, string | undefined][]): Promise<void> {978const content: IStringDictionary<unknown> = {};979if (settingsContent) {980content[FOLDER_SETTINGS_NAME] = settingsContent;981}982standAloneConfigurationContents.forEach(([key, contents]) => {983if (contents) {984content[key] = contents;985}986});987if (Object.keys(content).length) {988await this.configurationCache.write(this.key, JSON.stringify({ content }));989} else {990await this.configurationCache.remove(this.key);991}992}993994getRestrictedSettings(): string[] {995return this._folderSettingsModelParser.restrictedConfigurations;996}997998reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {999this._folderSettingsParseOptions = configurationParseOptions;1000this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions);1001this.consolidate();1002return this.configurationModel;1003}10041005private consolidate(): void {1006this.configurationModel = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations);1007}10081009getUnsupportedKeys(): string[] {1010return [];1011}1012}10131014export class FolderConfiguration extends Disposable {10151016protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());1017readonly onDidChange: Event<void> = this._onDidChange.event;10181019private folderConfiguration: CachedFolderConfiguration | FileServiceBasedConfiguration;1020private readonly scopes: ConfigurationScope[];1021private readonly configurationFolder: URI;1022private cachedFolderConfiguration: CachedFolderConfiguration;10231024constructor(1025useCache: boolean,1026readonly workspaceFolder: IWorkspaceFolder,1027configFolderRelativePath: string,1028private readonly workbenchState: WorkbenchState,1029private workspaceTrusted: boolean,1030fileService: IFileService,1031uriIdentityService: IUriIdentityService,1032logService: ILogService,1033private readonly configurationCache: IConfigurationCache1034) {1035super();10361037this.scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES;1038this.configurationFolder = uriIdentityService.extUri.joinPath(workspaceFolder.uri, configFolderRelativePath);1039this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, { scopes: this.scopes, skipRestricted: this.isUntrusted() }, configurationCache, logService);1040if (useCache && this.configurationCache.needsCaching(workspaceFolder.uri)) {1041this.folderConfiguration = this.cachedFolderConfiguration;1042whenProviderRegistered(workspaceFolder.uri, fileService)1043.then(() => {1044this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService));1045this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));1046this.onDidFolderConfigurationChange();1047});1048} else {1049this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService));1050this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));1051}1052}10531054loadConfiguration(): Promise<ConfigurationModel> {1055return this.folderConfiguration.loadConfiguration();1056}10571058updateWorkspaceTrust(trusted: boolean): ConfigurationModel {1059this.workspaceTrusted = trusted;1060return this.reparse();1061}10621063reparse(): ConfigurationModel {1064const configurationModel = this.folderConfiguration.reparse({ scopes: this.scopes, skipRestricted: this.isUntrusted() });1065this.updateCache();1066return configurationModel;1067}10681069getRestrictedSettings(): string[] {1070return this.folderConfiguration.getRestrictedSettings();1071}10721073private isUntrusted(): boolean {1074return !this.workspaceTrusted;1075}10761077private onDidFolderConfigurationChange(): void {1078this.updateCache();1079this._onDidChange.fire();1080}10811082private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) {1083const settingsResource = uriIdentityService.extUri.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`);1084const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, MCP_CONFIGURATION_KEY].map(name => ([name, uriIdentityService.extUri.joinPath(this.configurationFolder, `${name}.json`)]));1085return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, { scopes: this.scopes, skipRestricted: this.isUntrusted() }, fileService, uriIdentityService, logService);1086}10871088private async updateCache(): Promise<void> {1089if (this.configurationCache.needsCaching(this.configurationFolder) && this.folderConfiguration instanceof FileServiceBasedConfiguration) {1090const [settingsContent, standAloneConfigurationContents] = await this.folderConfiguration.resolveContents();1091this.cachedFolderConfiguration.updateConfiguration(settingsContent, standAloneConfigurationContents);1092}1093}10941095public addRelated(disposable: IDisposable): void {1096this._register(disposable);1097}1098}109911001101