Path: blob/main/src/vs/workbench/services/configuration/browser/configuration.ts
3296 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 { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js';26import { isEmptyObject, isObject } from '../../../../base/common/types.js';27import { DefaultConfiguration as BaseDefaultConfiguration } from '../../../../platform/configuration/common/configurations.js';28import { IJSONEditingService } from '../common/jsonEditing.js';29import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.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<any> = {};37private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' };3839private updateCache: boolean = false;4041constructor(42private readonly configurationCache: IConfigurationCache,43environmentService: IBrowserWorkbenchEnvironmentService,44logService: ILogService,45) {46super(logService);47if (environmentService.options?.configurationDefaults) {48this.configurationRegistry.registerDefaultConfigurations([{ overrides: environmentService.options.configurationDefaults }]);49}50}5152protected override getConfigurationDefaultOverrides(): IStringDictionary<any> {53return this.cachedConfigurationDefaultsOverrides;54}5556override async initialize(): Promise<ConfigurationModel> {57await this.initializeCachedConfigurationDefaultsOverrides();58return super.initialize();59}6061override reload(): ConfigurationModel {62this.updateCache = true;63this.cachedConfigurationDefaultsOverrides = {};64this.updateCachedConfigurationDefaultsOverrides();65return super.reload();66}6768hasCachedConfigurationDefaultsOverrides(): boolean {69return !isEmptyObject(this.cachedConfigurationDefaultsOverrides);70}7172private initiaizeCachedConfigurationDefaultsOverridesPromise: Promise<void> | undefined;73private initializeCachedConfigurationDefaultsOverrides(): Promise<void> {74if (!this.initiaizeCachedConfigurationDefaultsOverridesPromise) {75this.initiaizeCachedConfigurationDefaultsOverridesPromise = (async () => {76try {77// Read only when the cache exists78if (localStorage.getItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY)) {79const content = await this.configurationCache.read(this.cacheKey);80if (content) {81this.cachedConfigurationDefaultsOverrides = JSON.parse(content);82}83}84} catch (error) { /* ignore */ }85this.cachedConfigurationDefaultsOverrides = isObject(this.cachedConfigurationDefaultsOverrides) ? this.cachedConfigurationDefaultsOverrides : {};86})();87}88return this.initiaizeCachedConfigurationDefaultsOverridesPromise;89}9091protected override onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void {92super.onDidUpdateConfiguration(properties, defaultsOverrides);93if (defaultsOverrides) {94this.updateCachedConfigurationDefaultsOverrides();95}96}9798private async updateCachedConfigurationDefaultsOverrides(): Promise<void> {99if (!this.updateCache) {100return;101}102const cachedConfigurationDefaultsOverrides: IStringDictionary<any> = {};103const configurationDefaultsOverrides = this.configurationRegistry.getConfigurationDefaultsOverrides();104for (const [key, value] of configurationDefaultsOverrides) {105if (!OVERRIDE_PROPERTY_REGEX.test(key) && value.value !== undefined) {106cachedConfigurationDefaultsOverrides[key] = value.value;107}108}109try {110if (Object.keys(cachedConfigurationDefaultsOverrides).length) {111localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes');112await this.configurationCache.write(this.cacheKey, JSON.stringify(cachedConfigurationDefaultsOverrides));113} else {114localStorage.removeItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY);115await this.configurationCache.remove(this.cacheKey);116}117} catch (error) {/* Ignore error */ }118}119120}121122export class ApplicationConfiguration extends UserSettings {123124private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());125readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;126127private readonly reloadConfigurationScheduler: RunOnceScheduler;128129constructor(130userDataProfilesService: IUserDataProfilesService,131fileService: IFileService,132uriIdentityService: IUriIdentityService,133logService: ILogService,134) {135super(userDataProfilesService.defaultProfile.settingsResource, { scopes: APPLICATION_SCOPES, skipUnregistered: true }, uriIdentityService.extUri, fileService, logService);136this._register(this.onDidChange(() => this.reloadConfigurationScheduler.schedule()));137this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));138}139140async initialize(): Promise<ConfigurationModel> {141return this.loadConfiguration();142}143144override async loadConfiguration(): Promise<ConfigurationModel> {145const model = await super.loadConfiguration();146const value = model.getValue<string[]>(APPLY_ALL_PROFILES_SETTING);147const allProfilesSettings = Array.isArray(value) ? value : [];148return this.parseOptions.include || allProfilesSettings.length149? this.reparse({ ...this.parseOptions, include: allProfilesSettings })150: model;151}152}153154export class UserConfiguration extends Disposable {155156private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());157readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;158159private readonly userConfiguration = this._register(new MutableDisposable<UserSettings | FileServiceBasedConfiguration>());160private readonly userConfigurationChangeDisposable = this._register(new MutableDisposable<IDisposable>());161private readonly reloadConfigurationScheduler: RunOnceScheduler;162163get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; }164165constructor(166private settingsResource: URI,167private tasksResource: URI | undefined,168private mcpResource: URI | undefined,169private configurationParseOptions: ConfigurationParseOptions,170private readonly fileService: IFileService,171private readonly uriIdentityService: IUriIdentityService,172private readonly logService: ILogService,173) {174super();175this.userConfiguration.value = new UserSettings(settingsResource, this.configurationParseOptions, uriIdentityService.extUri, this.fileService, logService);176this.userConfigurationChangeDisposable.value = this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule());177this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.userConfiguration.value!.loadConfiguration().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));178}179180async reset(settingsResource: URI, tasksResource: URI | undefined, mcpResource: URI | undefined, configurationParseOptions: ConfigurationParseOptions): Promise<ConfigurationModel> {181this.settingsResource = settingsResource;182this.tasksResource = tasksResource;183this.mcpResource = mcpResource;184this.configurationParseOptions = configurationParseOptions;185return this.doReset();186}187188private async doReset(settingsConfiguration?: ConfigurationModel): Promise<ConfigurationModel> {189const folder = this.uriIdentityService.extUri.dirname(this.settingsResource);190const standAloneConfigurationResources: [string, URI][] = [];191if (this.tasksResource) {192standAloneConfigurationResources.push([TASKS_CONFIGURATION_KEY, this.tasksResource]);193}194if (this.mcpResource) {195standAloneConfigurationResources.push([MCP_CONFIGURATION_KEY, this.mcpResource]);196}197const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.settingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService);198const configurationModel = await fileServiceBasedConfiguration.loadConfiguration(settingsConfiguration);199this.userConfiguration.value = fileServiceBasedConfiguration;200201// Check for value because userConfiguration might have been disposed.202if (this.userConfigurationChangeDisposable.value) {203this.userConfigurationChangeDisposable.value = this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule());204}205206return configurationModel;207}208209async initialize(): Promise<ConfigurationModel> {210return this.userConfiguration.value!.loadConfiguration();211}212213async reload(settingsConfiguration?: ConfigurationModel): Promise<ConfigurationModel> {214if (this.hasTasksLoaded) {215return this.userConfiguration.value!.loadConfiguration();216}217return this.doReset(settingsConfiguration);218}219220reparse(parseOptions?: Partial<ConfigurationParseOptions>): ConfigurationModel {221this.configurationParseOptions = { ...this.configurationParseOptions, ...parseOptions };222return this.userConfiguration.value!.reparse(this.configurationParseOptions);223}224225getRestrictedSettings(): string[] {226return this.userConfiguration.value!.getRestrictedSettings();227}228}229230class FileServiceBasedConfiguration extends Disposable {231232private readonly allResources: URI[];233private _folderSettingsModelParser: ConfigurationModelParser;234private _folderSettingsParseOptions: ConfigurationParseOptions;235private _standAloneConfigurations: ConfigurationModel[];236private _cache: ConfigurationModel;237238private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());239readonly onDidChange: Event<void> = this._onDidChange.event;240241constructor(242name: string,243private readonly settingsResource: URI,244private readonly standAloneConfigurationResources: [string, URI][],245configurationParseOptions: ConfigurationParseOptions,246private readonly fileService: IFileService,247private readonly uriIdentityService: IUriIdentityService,248private readonly logService: ILogService,249) {250super();251this.allResources = [this.settingsResource, ...this.standAloneConfigurationResources.map(([, resource]) => resource)];252this._register(combinedDisposable(...this.allResources.map(resource => combinedDisposable(253this.fileService.watch(uriIdentityService.extUri.dirname(resource)),254// Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134255this.fileService.watch(resource)256))));257258this._folderSettingsModelParser = new ConfigurationModelParser(name, logService);259this._folderSettingsParseOptions = configurationParseOptions;260this._standAloneConfigurations = [];261this._cache = ConfigurationModel.createEmptyModel(this.logService);262263this._register(Event.debounce(264Event.any(265Event.filter(this.fileService.onDidFilesChange, e => this.handleFileChangesEvent(e)),266Event.filter(this.fileService.onDidRunOperation, e => this.handleFileOperationEvent(e))267), () => undefined, 100)(() => this._onDidChange.fire()));268}269270async resolveContents(donotResolveSettings?: boolean): Promise<[string | undefined, [string, string | undefined][]]> {271272const resolveContents = async (resources: URI[]): Promise<(string | undefined)[]> => {273return Promise.all(resources.map(async resource => {274try {275const content = await this.fileService.readFile(resource, { atomic: true });276return content.value.toString();277} catch (error) {278this.logService.trace(`Error while resolving configuration file '${resource.toString()}': ${errors.getErrorMessage(error)}`);279if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND280&& (<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_DIRECTORY) {281this.logService.error(error);282}283}284return '{}';285}));286};287288const [[settingsContent], standAloneConfigurationContents] = await Promise.all([289donotResolveSettings ? Promise.resolve([undefined]) : resolveContents([this.settingsResource]),290resolveContents(this.standAloneConfigurationResources.map(([, resource]) => resource)),291]);292293return [settingsContent, standAloneConfigurationContents.map((content, index) => ([this.standAloneConfigurationResources[index][0], content]))];294}295296async loadConfiguration(settingsConfiguration?: ConfigurationModel): Promise<ConfigurationModel> {297298const [settingsContent, standAloneConfigurationContents] = await this.resolveContents(!!settingsConfiguration);299300// reset301this._standAloneConfigurations = [];302this._folderSettingsModelParser.parse('', this._folderSettingsParseOptions);303304// parse305if (settingsContent !== undefined) {306this._folderSettingsModelParser.parse(settingsContent, this._folderSettingsParseOptions);307}308for (let index = 0; index < standAloneConfigurationContents.length; index++) {309const contents = standAloneConfigurationContents[index][1];310if (contents !== undefined) {311const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0], this.logService);312standAloneConfigurationModelParser.parse(contents);313this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);314}315}316317// Consolidate (support *.json files in the workspace settings folder)318this.consolidate(settingsConfiguration);319320return this._cache;321}322323getRestrictedSettings(): string[] {324return this._folderSettingsModelParser.restrictedConfigurations;325}326327reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {328const oldContents = this._folderSettingsModelParser.configurationModel.contents;329this._folderSettingsParseOptions = configurationParseOptions;330this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions);331if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) {332this.consolidate();333}334return this._cache;335}336337private consolidate(settingsConfiguration?: ConfigurationModel): void {338this._cache = (settingsConfiguration ?? this._folderSettingsModelParser.configurationModel).merge(...this._standAloneConfigurations);339}340341private handleFileChangesEvent(event: FileChangesEvent): boolean {342// One of the resources has changed343if (this.allResources.some(resource => event.contains(resource))) {344return true;345}346// One of the resource's parent got deleted347if (this.allResources.some(resource => event.contains(this.uriIdentityService.extUri.dirname(resource), FileChangeType.DELETED))) {348return true;349}350return false;351}352353private handleFileOperationEvent(event: FileOperationEvent): boolean {354// One of the resources has changed355if ((event.isOperation(FileOperation.CREATE) || event.isOperation(FileOperation.COPY) || event.isOperation(FileOperation.DELETE) || event.isOperation(FileOperation.WRITE))356&& this.allResources.some(resource => this.uriIdentityService.extUri.isEqual(event.resource, resource))) {357return true;358}359// One of the resource's parent got deleted360if (event.isOperation(FileOperation.DELETE) && this.allResources.some(resource => this.uriIdentityService.extUri.isEqual(event.resource, this.uriIdentityService.extUri.dirname(resource)))) {361return true;362}363return false;364}365366}367368export class RemoteUserConfiguration extends Disposable {369370private readonly _cachedConfiguration: CachedRemoteUserConfiguration;371private readonly _fileService: IFileService;372private _userConfiguration: FileServiceBasedRemoteUserConfiguration | CachedRemoteUserConfiguration;373private _userConfigurationInitializationPromise: Promise<ConfigurationModel> | null = null;374375private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());376public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;377378private readonly _onDidInitialize = this._register(new Emitter<ConfigurationModel>());379public readonly onDidInitialize = this._onDidInitialize.event;380381constructor(382remoteAuthority: string,383configurationCache: IConfigurationCache,384fileService: IFileService,385uriIdentityService: IUriIdentityService,386remoteAgentService: IRemoteAgentService,387logService: ILogService388) {389super();390this._fileService = fileService;391this._userConfiguration = this._cachedConfiguration = new CachedRemoteUserConfiguration(remoteAuthority, configurationCache, { scopes: REMOTE_MACHINE_SCOPES }, logService);392remoteAgentService.getEnvironment().then(async environment => {393if (environment) {394const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, { scopes: REMOTE_MACHINE_SCOPES }, this._fileService, uriIdentityService, logService));395this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));396this._userConfigurationInitializationPromise = userConfiguration.initialize();397const configurationModel = await this._userConfigurationInitializationPromise;398this._userConfiguration.dispose();399this._userConfiguration = userConfiguration;400this.onDidUserConfigurationChange(configurationModel);401this._onDidInitialize.fire(configurationModel);402}403});404}405406async initialize(): Promise<ConfigurationModel> {407if (this._userConfiguration instanceof FileServiceBasedRemoteUserConfiguration) {408return this._userConfiguration.initialize();409}410411// Initialize cached configuration412let configurationModel = await this._userConfiguration.initialize();413if (this._userConfigurationInitializationPromise) {414// Use user configuration415configurationModel = await this._userConfigurationInitializationPromise;416this._userConfigurationInitializationPromise = null;417}418419return configurationModel;420}421422reload(): Promise<ConfigurationModel> {423return this._userConfiguration.reload();424}425426reparse(): ConfigurationModel {427return this._userConfiguration.reparse({ scopes: REMOTE_MACHINE_SCOPES });428}429430getRestrictedSettings(): string[] {431return this._userConfiguration.getRestrictedSettings();432}433434private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void {435this.updateCache();436this._onDidChangeConfiguration.fire(configurationModel);437}438439private async updateCache(): Promise<void> {440if (this._userConfiguration instanceof FileServiceBasedRemoteUserConfiguration) {441let content: string | undefined;442try {443content = await this._userConfiguration.resolveContent();444} catch (error) {445if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {446return;447}448}449await this._cachedConfiguration.updateConfiguration(content);450}451}452453}454455class FileServiceBasedRemoteUserConfiguration extends Disposable {456457private readonly parser: ConfigurationModelParser;458private parseOptions: ConfigurationParseOptions;459private readonly reloadConfigurationScheduler: RunOnceScheduler;460protected readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());461readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;462463private readonly fileWatcherDisposable = this._register(new MutableDisposable());464private readonly directoryWatcherDisposable = this._register(new MutableDisposable());465466constructor(467private readonly configurationResource: URI,468configurationParseOptions: ConfigurationParseOptions,469private readonly fileService: IFileService,470private readonly uriIdentityService: IUriIdentityService,471private readonly logService: ILogService,472) {473super();474475this.parser = new ConfigurationModelParser(this.configurationResource.toString(), logService);476this.parseOptions = configurationParseOptions;477this._register(fileService.onDidFilesChange(e => this.handleFileChangesEvent(e)));478this._register(fileService.onDidRunOperation(e => this.handleFileOperationEvent(e)));479this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));480this._register(toDisposable(() => {481this.stopWatchingResource();482this.stopWatchingDirectory();483}));484}485486private watchResource(): void {487this.fileWatcherDisposable.value = this.fileService.watch(this.configurationResource);488}489490private stopWatchingResource(): void {491this.fileWatcherDisposable.value = undefined;492}493494private watchDirectory(): void {495const directory = this.uriIdentityService.extUri.dirname(this.configurationResource);496this.directoryWatcherDisposable.value = this.fileService.watch(directory);497}498499private stopWatchingDirectory(): void {500this.directoryWatcherDisposable.value = undefined;501}502503async initialize(): Promise<ConfigurationModel> {504const exists = await this.fileService.exists(this.configurationResource);505this.onResourceExists(exists);506return this.reload();507}508509async resolveContent(): Promise<string> {510const content = await this.fileService.readFile(this.configurationResource, { atomic: true });511return content.value.toString();512}513514async reload(): Promise<ConfigurationModel> {515try {516const content = await this.resolveContent();517this.parser.parse(content, this.parseOptions);518return this.parser.configurationModel;519} catch (e) {520return ConfigurationModel.createEmptyModel(this.logService);521}522}523524reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {525this.parseOptions = configurationParseOptions;526this.parser.reparse(this.parseOptions);527return this.parser.configurationModel;528}529530getRestrictedSettings(): string[] {531return this.parser.restrictedConfigurations;532}533534private handleFileChangesEvent(event: FileChangesEvent): void {535536// Find changes that affect the resource537let affectedByChanges = false;538if (event.contains(this.configurationResource, FileChangeType.ADDED)) {539affectedByChanges = true;540this.onResourceExists(true);541} else if (event.contains(this.configurationResource, FileChangeType.DELETED)) {542affectedByChanges = true;543this.onResourceExists(false);544} else if (event.contains(this.configurationResource, FileChangeType.UPDATED)) {545affectedByChanges = true;546}547548if (affectedByChanges) {549this.reloadConfigurationScheduler.schedule();550}551}552553private handleFileOperationEvent(event: FileOperationEvent): void {554if ((event.isOperation(FileOperation.CREATE) || event.isOperation(FileOperation.COPY) || event.isOperation(FileOperation.DELETE) || event.isOperation(FileOperation.WRITE))555&& this.uriIdentityService.extUri.isEqual(event.resource, this.configurationResource)) {556this.reloadConfigurationScheduler.schedule();557}558}559560private onResourceExists(exists: boolean): void {561if (exists) {562this.stopWatchingDirectory();563this.watchResource();564} else {565this.stopWatchingResource();566this.watchDirectory();567}568}569}570571class CachedRemoteUserConfiguration extends Disposable {572573private readonly _onDidChange: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());574readonly onDidChange: Event<ConfigurationModel> = this._onDidChange.event;575576private readonly key: ConfigurationKey;577private readonly parser: ConfigurationModelParser;578private parseOptions: ConfigurationParseOptions;579private configurationModel: ConfigurationModel;580581constructor(582remoteAuthority: string,583private readonly configurationCache: IConfigurationCache,584configurationParseOptions: ConfigurationParseOptions,585logService: ILogService,586) {587super();588this.key = { type: 'user', key: remoteAuthority };589this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration', logService);590this.parseOptions = configurationParseOptions;591this.configurationModel = ConfigurationModel.createEmptyModel(logService);592}593594getConfigurationModel(): ConfigurationModel {595return this.configurationModel;596}597598initialize(): Promise<ConfigurationModel> {599return this.reload();600}601602reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {603this.parseOptions = configurationParseOptions;604this.parser.reparse(this.parseOptions);605this.configurationModel = this.parser.configurationModel;606return this.configurationModel;607}608609getRestrictedSettings(): string[] {610return this.parser.restrictedConfigurations;611}612613async reload(): Promise<ConfigurationModel> {614try {615const content = await this.configurationCache.read(this.key);616const parsed: { content: string } = JSON.parse(content);617if (parsed.content) {618this.parser.parse(parsed.content, this.parseOptions);619this.configurationModel = this.parser.configurationModel;620}621} catch (e) { /* Ignore error */ }622return this.configurationModel;623}624625async updateConfiguration(content: string | undefined): Promise<void> {626if (content) {627return this.configurationCache.write(this.key, JSON.stringify({ content }));628} else {629return this.configurationCache.remove(this.key);630}631}632}633634export class WorkspaceConfiguration extends Disposable {635636private readonly _cachedConfiguration: CachedWorkspaceConfiguration;637private _workspaceConfiguration: CachedWorkspaceConfiguration | FileServiceBasedWorkspaceConfiguration;638private readonly _workspaceConfigurationDisposables = this._register(new DisposableStore());639private _workspaceIdentifier: IWorkspaceIdentifier | null = null;640private _isWorkspaceTrusted: boolean = false;641642private readonly _onDidUpdateConfiguration = this._register(new Emitter<boolean>());643public readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event;644645private _initialized: boolean = false;646get initialized(): boolean { return this._initialized; }647constructor(648private readonly configurationCache: IConfigurationCache,649private readonly fileService: IFileService,650private readonly uriIdentityService: IUriIdentityService,651private readonly logService: ILogService,652) {653super();654this.fileService = fileService;655this._workspaceConfiguration = this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache, logService);656}657658async initialize(workspaceIdentifier: IWorkspaceIdentifier, workspaceTrusted: boolean): Promise<void> {659this._workspaceIdentifier = workspaceIdentifier;660this._isWorkspaceTrusted = workspaceTrusted;661if (!this._initialized) {662if (this.configurationCache.needsCaching(this._workspaceIdentifier.configPath)) {663this._workspaceConfiguration = this._cachedConfiguration;664this.waitAndInitialize(this._workspaceIdentifier);665} else {666this.doInitialize(new FileServiceBasedWorkspaceConfiguration(this.fileService, this.uriIdentityService, this.logService));667}668}669await this.reload();670}671672async reload(): Promise<void> {673if (this._workspaceIdentifier) {674await this._workspaceConfiguration.load(this._workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() });675}676}677678getFolders(): IStoredWorkspaceFolder[] {679return this._workspaceConfiguration.getFolders();680}681682setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: IJSONEditingService): Promise<void> {683if (this._workspaceIdentifier) {684return jsonEditingService.write(this._workspaceIdentifier.configPath, [{ path: ['folders'], value: folders }], true)685.then(() => this.reload());686}687return Promise.resolve();688}689690isTransient(): boolean {691return this._workspaceConfiguration.isTransient();692}693694getConfiguration(): ConfigurationModel {695return this._workspaceConfiguration.getWorkspaceSettings();696}697698updateWorkspaceTrust(trusted: boolean): ConfigurationModel {699this._isWorkspaceTrusted = trusted;700return this.reparseWorkspaceSettings();701}702703reparseWorkspaceSettings(): ConfigurationModel {704this._workspaceConfiguration.reparseWorkspaceSettings({ scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() });705return this.getConfiguration();706}707708getRestrictedSettings(): string[] {709return this._workspaceConfiguration.getRestrictedSettings();710}711712private async waitAndInitialize(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {713await whenProviderRegistered(workspaceIdentifier.configPath, this.fileService);714if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) {715const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this.fileService, this.uriIdentityService, this.logService));716await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() });717this.doInitialize(fileServiceBasedWorkspaceConfiguration);718this.onDidWorkspaceConfigurationChange(false, true);719}720}721722private doInitialize(fileServiceBasedWorkspaceConfiguration: FileServiceBasedWorkspaceConfiguration): void {723this._workspaceConfigurationDisposables.clear();724this._workspaceConfiguration = this._workspaceConfigurationDisposables.add(fileServiceBasedWorkspaceConfiguration);725this._workspaceConfigurationDisposables.add(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true, false)));726this._initialized = true;727}728729private isUntrusted(): boolean {730return !this._isWorkspaceTrusted;731}732733private async onDidWorkspaceConfigurationChange(reload: boolean, fromCache: boolean): Promise<void> {734if (reload) {735await this.reload();736}737this.updateCache();738this._onDidUpdateConfiguration.fire(fromCache);739}740741private async updateCache(): Promise<void> {742if (this._workspaceIdentifier && this.configurationCache.needsCaching(this._workspaceIdentifier.configPath) && this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration) {743const content = await this._workspaceConfiguration.resolveContent(this._workspaceIdentifier);744await this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier, content);745}746}747}748749class FileServiceBasedWorkspaceConfiguration extends Disposable {750751workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;752workspaceSettings: ConfigurationModel;753private _workspaceIdentifier: IWorkspaceIdentifier | null = null;754private workspaceConfigWatcher: IDisposable;755private readonly reloadConfigurationScheduler: RunOnceScheduler;756757protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());758readonly onDidChange: Event<void> = this._onDidChange.event;759760constructor(761private readonly fileService: IFileService,762uriIdentityService: IUriIdentityService,763private readonly logService: ILogService,764) {765super();766767this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser('', logService);768this.workspaceSettings = ConfigurationModel.createEmptyModel(logService);769770this._register(Event.any(771Event.filter(this.fileService.onDidFilesChange, e => !!this._workspaceIdentifier && e.contains(this._workspaceIdentifier.configPath)),772Event.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))773)(() => this.reloadConfigurationScheduler.schedule()));774this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));775this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile());776}777778get workspaceIdentifier(): IWorkspaceIdentifier | null {779return this._workspaceIdentifier;780}781782async resolveContent(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> {783const content = await this.fileService.readFile(workspaceIdentifier.configPath, { atomic: true });784return content.value.toString();785}786787async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise<void> {788if (!this._workspaceIdentifier || this._workspaceIdentifier.id !== workspaceIdentifier.id) {789this._workspaceIdentifier = workspaceIdentifier;790this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceIdentifier.id, this.logService);791dispose(this.workspaceConfigWatcher);792this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile());793}794let contents = '';795try {796contents = await this.resolveContent(this._workspaceIdentifier);797} catch (error) {798const exists = await this.fileService.exists(this._workspaceIdentifier.configPath);799if (exists) {800this.logService.error(error);801}802}803this.workspaceConfigurationModelParser.parse(contents, configurationParseOptions);804this.consolidate();805}806807getConfigurationModel(): ConfigurationModel {808return this.workspaceConfigurationModelParser.configurationModel;809}810811getFolders(): IStoredWorkspaceFolder[] {812return this.workspaceConfigurationModelParser.folders;813}814815isTransient(): boolean {816return this.workspaceConfigurationModelParser.transient;817}818819getWorkspaceSettings(): ConfigurationModel {820return this.workspaceSettings;821}822823reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {824this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions);825this.consolidate();826return this.getWorkspaceSettings();827}828829getRestrictedSettings(): string[] {830return this.workspaceConfigurationModelParser.getRestrictedWorkspaceSettings();831}832833private consolidate(): void {834this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel);835}836837private watchWorkspaceConfigurationFile(): IDisposable {838return this._workspaceIdentifier ? this.fileService.watch(this._workspaceIdentifier.configPath) : Disposable.None;839}840841}842843class CachedWorkspaceConfiguration {844845readonly onDidChange: Event<void> = Event.None;846847workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;848workspaceSettings: ConfigurationModel;849850constructor(851private readonly configurationCache: IConfigurationCache,852private readonly logService: ILogService853) {854this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser('', logService);855this.workspaceSettings = ConfigurationModel.createEmptyModel(logService);856}857858async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise<void> {859try {860const key = this.getKey(workspaceIdentifier);861const contents = await this.configurationCache.read(key);862const parsed: { content: string } = JSON.parse(contents);863if (parsed.content) {864this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key, this.logService);865this.workspaceConfigurationModelParser.parse(parsed.content, configurationParseOptions);866this.consolidate();867}868} catch (e) {869}870}871872get workspaceIdentifier(): IWorkspaceIdentifier | null {873return null;874}875876getConfigurationModel(): ConfigurationModel {877return this.workspaceConfigurationModelParser.configurationModel;878}879880getFolders(): IStoredWorkspaceFolder[] {881return this.workspaceConfigurationModelParser.folders;882}883884isTransient(): boolean {885return this.workspaceConfigurationModelParser.transient;886}887888getWorkspaceSettings(): ConfigurationModel {889return this.workspaceSettings;890}891892reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {893this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions);894this.consolidate();895return this.getWorkspaceSettings();896}897898getRestrictedSettings(): string[] {899return this.workspaceConfigurationModelParser.getRestrictedWorkspaceSettings();900}901902private consolidate(): void {903this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel);904}905906async updateWorkspace(workspaceIdentifier: IWorkspaceIdentifier, content: string | undefined): Promise<void> {907try {908const key = this.getKey(workspaceIdentifier);909if (content) {910await this.configurationCache.write(key, JSON.stringify({ content }));911} else {912await this.configurationCache.remove(key);913}914} catch (error) {915}916}917918private getKey(workspaceIdentifier: IWorkspaceIdentifier): ConfigurationKey {919return {920type: 'workspaces',921key: workspaceIdentifier.id922};923}924}925926class CachedFolderConfiguration {927928readonly onDidChange = Event.None;929930private _folderSettingsModelParser: ConfigurationModelParser;931private _folderSettingsParseOptions: ConfigurationParseOptions;932private _standAloneConfigurations: ConfigurationModel[];933private configurationModel: ConfigurationModel;934private readonly key: ConfigurationKey;935936constructor(937folder: URI,938configFolderRelativePath: string,939configurationParseOptions: ConfigurationParseOptions,940private readonly configurationCache: IConfigurationCache,941private readonly logService: ILogService942) {943this.key = { type: 'folder', key: hash(joinPath(folder, configFolderRelativePath).toString()).toString(16) };944this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration', logService);945this._folderSettingsParseOptions = configurationParseOptions;946this._standAloneConfigurations = [];947this.configurationModel = ConfigurationModel.createEmptyModel(logService);948}949950async loadConfiguration(): Promise<ConfigurationModel> {951try {952const contents = await this.configurationCache.read(this.key);953const { content: configurationContents }: { content: IStringDictionary<string> } = JSON.parse(contents.toString());954if (configurationContents) {955for (const key of Object.keys(configurationContents)) {956if (key === FOLDER_SETTINGS_NAME) {957this._folderSettingsModelParser.parse(configurationContents[key], this._folderSettingsParseOptions);958} else {959const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(key, key, this.logService);960standAloneConfigurationModelParser.parse(configurationContents[key]);961this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);962}963}964}965this.consolidate();966} catch (e) {967}968return this.configurationModel;969}970971async updateConfiguration(settingsContent: string | undefined, standAloneConfigurationContents: [string, string | undefined][]): Promise<void> {972const content: any = {};973if (settingsContent) {974content[FOLDER_SETTINGS_NAME] = settingsContent;975}976standAloneConfigurationContents.forEach(([key, contents]) => {977if (contents) {978content[key] = contents;979}980});981if (Object.keys(content).length) {982await this.configurationCache.write(this.key, JSON.stringify({ content }));983} else {984await this.configurationCache.remove(this.key);985}986}987988getRestrictedSettings(): string[] {989return this._folderSettingsModelParser.restrictedConfigurations;990}991992reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {993this._folderSettingsParseOptions = configurationParseOptions;994this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions);995this.consolidate();996return this.configurationModel;997}998999private consolidate(): void {1000this.configurationModel = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations);1001}10021003getUnsupportedKeys(): string[] {1004return [];1005}1006}10071008export class FolderConfiguration extends Disposable {10091010protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());1011readonly onDidChange: Event<void> = this._onDidChange.event;10121013private folderConfiguration: CachedFolderConfiguration | FileServiceBasedConfiguration;1014private readonly scopes: ConfigurationScope[];1015private readonly configurationFolder: URI;1016private cachedFolderConfiguration: CachedFolderConfiguration;10171018constructor(1019useCache: boolean,1020readonly workspaceFolder: IWorkspaceFolder,1021configFolderRelativePath: string,1022private readonly workbenchState: WorkbenchState,1023private workspaceTrusted: boolean,1024fileService: IFileService,1025uriIdentityService: IUriIdentityService,1026logService: ILogService,1027private readonly configurationCache: IConfigurationCache1028) {1029super();10301031this.scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES;1032this.configurationFolder = uriIdentityService.extUri.joinPath(workspaceFolder.uri, configFolderRelativePath);1033this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, { scopes: this.scopes, skipRestricted: this.isUntrusted() }, configurationCache, logService);1034if (useCache && this.configurationCache.needsCaching(workspaceFolder.uri)) {1035this.folderConfiguration = this.cachedFolderConfiguration;1036whenProviderRegistered(workspaceFolder.uri, fileService)1037.then(() => {1038this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService));1039this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));1040this.onDidFolderConfigurationChange();1041});1042} else {1043this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService));1044this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));1045}1046}10471048loadConfiguration(): Promise<ConfigurationModel> {1049return this.folderConfiguration.loadConfiguration();1050}10511052updateWorkspaceTrust(trusted: boolean): ConfigurationModel {1053this.workspaceTrusted = trusted;1054return this.reparse();1055}10561057reparse(): ConfigurationModel {1058const configurationModel = this.folderConfiguration.reparse({ scopes: this.scopes, skipRestricted: this.isUntrusted() });1059this.updateCache();1060return configurationModel;1061}10621063getRestrictedSettings(): string[] {1064return this.folderConfiguration.getRestrictedSettings();1065}10661067private isUntrusted(): boolean {1068return !this.workspaceTrusted;1069}10701071private onDidFolderConfigurationChange(): void {1072this.updateCache();1073this._onDidChange.fire();1074}10751076private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) {1077const settingsResource = uriIdentityService.extUri.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`);1078const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, MCP_CONFIGURATION_KEY].map(name => ([name, uriIdentityService.extUri.joinPath(this.configurationFolder, `${name}.json`)]));1079return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, { scopes: this.scopes, skipRestricted: this.isUntrusted() }, fileService, uriIdentityService, logService);1080}10811082private async updateCache(): Promise<void> {1083if (this.configurationCache.needsCaching(this.configurationFolder) && this.folderConfiguration instanceof FileServiceBasedConfiguration) {1084const [settingsContent, standAloneConfigurationContents] = await this.folderConfiguration.resolveContents();1085this.cachedFolderConfiguration.updateConfiguration(settingsContent, standAloneConfigurationContents);1086}1087}1088}108910901091