Path: blob/main/src/vs/platform/configuration/common/configuration.ts
5259 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 { assertNever } from '../../../base/common/assert.js';6import { IStringDictionary } from '../../../base/common/collections.js';7import { Event } from '../../../base/common/event.js';8import * as types from '../../../base/common/types.js';9import { URI, UriComponents } from '../../../base/common/uri.js';10import { createDecorator } from '../../instantiation/common/instantiation.js';11import { IWorkspaceFolder } from '../../workspace/common/workspace.js';1213export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');1415export function isConfigurationOverrides(obj: unknown): obj is IConfigurationOverrides {16const thing = obj as IConfigurationOverrides;17return thing18&& typeof thing === 'object'19&& (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string')20&& (!thing.resource || thing.resource instanceof URI);21}2223export interface IConfigurationOverrides {24overrideIdentifier?: string | null;25resource?: URI | null;26}2728export function isConfigurationUpdateOverrides(obj: unknown): obj is IConfigurationUpdateOverrides {29const thing = obj as IConfigurationUpdateOverrides | IConfigurationOverrides;30return thing31&& typeof thing === 'object'32&& (!(thing as IConfigurationUpdateOverrides).overrideIdentifiers || Array.isArray((thing as IConfigurationUpdateOverrides).overrideIdentifiers))33&& !(thing as IConfigurationOverrides).overrideIdentifier34&& (!thing.resource || thing.resource instanceof URI);35}3637export type IConfigurationUpdateOverrides = Omit<IConfigurationOverrides, 'overrideIdentifier'> & { overrideIdentifiers?: string[] | null };3839export const enum ConfigurationTarget {40APPLICATION = 1,41USER,42USER_LOCAL,43USER_REMOTE,44WORKSPACE,45WORKSPACE_FOLDER,46DEFAULT,47MEMORY48}49export function ConfigurationTargetToString(configurationTarget: ConfigurationTarget) {50switch (configurationTarget) {51case ConfigurationTarget.APPLICATION: return 'APPLICATION';52case ConfigurationTarget.USER: return 'USER';53case ConfigurationTarget.USER_LOCAL: return 'USER_LOCAL';54case ConfigurationTarget.USER_REMOTE: return 'USER_REMOTE';55case ConfigurationTarget.WORKSPACE: return 'WORKSPACE';56case ConfigurationTarget.WORKSPACE_FOLDER: return 'WORKSPACE_FOLDER';57case ConfigurationTarget.DEFAULT: return 'DEFAULT';58case ConfigurationTarget.MEMORY: return 'MEMORY';59}60}6162export interface IConfigurationChange {63keys: string[];64overrides: [string, string[]][];65}6667export interface IConfigurationChangeEvent {6869readonly source: ConfigurationTarget;70readonly affectedKeys: ReadonlySet<string>;71readonly change: IConfigurationChange;7273affectsConfiguration(configuration: string, overrides?: IConfigurationOverrides): boolean;74}7576export interface IInspectValue<T> {77readonly value?: T;78readonly override?: T;79readonly overrides?: { readonly identifiers: string[]; readonly value: T }[];80}8182export interface IConfigurationValue<T> {8384readonly defaultValue?: T;85readonly applicationValue?: T;86readonly userValue?: T;87readonly userLocalValue?: T;88readonly userRemoteValue?: T;89readonly workspaceValue?: T;90readonly workspaceFolderValue?: T;91readonly memoryValue?: T;92readonly policyValue?: T;93readonly value?: T;9495readonly default?: IInspectValue<T>;96readonly application?: IInspectValue<T>;97readonly user?: IInspectValue<T>;98readonly userLocal?: IInspectValue<T>;99readonly userRemote?: IInspectValue<T>;100readonly workspace?: IInspectValue<T>;101readonly workspaceFolder?: IInspectValue<T>;102readonly memory?: IInspectValue<T>;103readonly policy?: { value?: T };104105readonly overrideIdentifiers?: string[];106}107108export function getConfigValueInTarget<T>(configValue: IConfigurationValue<T>, scope: ConfigurationTarget): T | undefined {109switch (scope) {110case ConfigurationTarget.APPLICATION:111return configValue.applicationValue;112case ConfigurationTarget.USER:113return configValue.userValue;114case ConfigurationTarget.USER_LOCAL:115return configValue.userLocalValue;116case ConfigurationTarget.USER_REMOTE:117return configValue.userRemoteValue;118case ConfigurationTarget.WORKSPACE:119return configValue.workspaceValue;120case ConfigurationTarget.WORKSPACE_FOLDER:121return configValue.workspaceFolderValue;122case ConfigurationTarget.DEFAULT:123return configValue.defaultValue;124case ConfigurationTarget.MEMORY:125return configValue.memoryValue;126default:127assertNever(scope);128}129}130131export function isConfigured<T>(configValue: IConfigurationValue<T>): configValue is IConfigurationValue<T> & { value: T } {132return configValue.applicationValue !== undefined ||133configValue.userValue !== undefined ||134configValue.userLocalValue !== undefined ||135configValue.userRemoteValue !== undefined ||136configValue.workspaceValue !== undefined ||137configValue.workspaceFolderValue !== undefined;138}139140export interface IConfigurationUpdateOptions {141/**142* If `true`, do not notifies the error to user by showing the message box. Default is `false`.143*/144donotNotifyError?: boolean;145/**146* How to handle dirty file when updating the configuration.147*/148handleDirtyFile?: 'save' | 'revert';149}150151export interface IConfigurationService {152readonly _serviceBrand: undefined;153154readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent>;155156getConfigurationData(): IConfigurationData | null;157158/**159* Fetches the value of the section for the given overrides.160* Value can be of native type or an object keyed off the section name.161*162* @param section - Section of the configuration. Can be `null` or `undefined`.163* @param overrides - Overrides that has to be applied while fetching164*165*/166getValue<T>(): T;167getValue<T>(section: string): T;168getValue<T>(overrides: IConfigurationOverrides): T;169getValue<T>(section: string, overrides: IConfigurationOverrides): T;170171/**172* Update a configuration value.173*174* Use `target` to update the configuration in a specific `ConfigurationTarget`.175*176* Use `overrides` to update the configuration for a resource or for override identifiers or both.177*178* Passing a resource through overrides will update the configuration in the workspace folder containing that resource.179*180* *Note 1:* Updating configuration to a default value will remove the configuration from the requested target. If not target is passed, it will be removed from all writeable targets.181*182* *Note 2:* Use `undefined` value to remove the configuration from the given target. If not target is passed, it will be removed from all writeable targets.183*184* Use `donotNotifyError` and set it to `true` to surpresss errors.185*186* @param key setting to be updated187* @param value The new value188*/189updateValue(key: string, value: unknown): Promise<void>;190updateValue(key: string, value: unknown, target: ConfigurationTarget): Promise<void>;191updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise<void>;192updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, options?: IConfigurationUpdateOptions): Promise<void>;193194inspect<T>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<Readonly<T>>;195196reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise<void>;197198keys(): {199default: string[];200policy: string[];201user: string[];202workspace: string[];203workspaceFolder: string[];204memory?: string[];205};206}207208export interface IConfigurationModel {209contents: IStringDictionary<unknown>;210keys: string[];211overrides: IOverrides[];212raw?: ReadonlyArray<IStringDictionary<unknown>> | IStringDictionary<unknown>;213}214215export interface IOverrides {216keys: string[];217contents: IStringDictionary<unknown>;218identifiers: string[];219}220221export interface IConfigurationData {222defaults: IConfigurationModel;223policy: IConfigurationModel;224application: IConfigurationModel;225userLocal: IConfigurationModel;226userRemote: IConfigurationModel;227workspace: IConfigurationModel;228folders: [UriComponents, IConfigurationModel][];229}230231export interface IConfigurationCompareResult {232added: string[];233removed: string[];234updated: string[];235overrides: [string, string[]][];236}237238export function toValuesTree(properties: IStringDictionary<unknown>, conflictReporter: (message: string) => void): IStringDictionary<unknown> {239const root = Object.create(null);240241for (const key in properties) {242addToValueTree(root, key, properties[key], conflictReporter);243}244245return root;246}247248export function addToValueTree(settingsTreeRoot: IStringDictionary<unknown>, key: string, value: unknown, conflictReporter: (message: string) => void): void {249const segments = key.split('.');250const last = segments.pop()!;251252let curr: IStringDictionary<unknown> = settingsTreeRoot;253for (let i = 0; i < segments.length; i++) {254const s = segments[i];255let obj = curr[s];256switch (typeof obj) {257case 'undefined':258obj = curr[s] = Object.create(null);259break;260case 'object':261if (obj === null) {262conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is null`);263return;264}265break;266default:267conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`);268return;269}270curr = obj as IStringDictionary<unknown>;271}272273if (typeof curr === 'object' && curr !== null) {274try {275(curr as IStringDictionary<unknown>)[last] = value; // workaround https://github.com/microsoft/vscode/issues/13606276} catch (e) {277conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`);278}279} else {280conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`);281}282}283284export function removeFromValueTree(valueTree: IStringDictionary<unknown>, key: string): void {285const segments = key.split('.');286doRemoveFromValueTree(valueTree, segments);287}288289function doRemoveFromValueTree(valueTree: IStringDictionary<unknown> | unknown, segments: string[]): void {290if (!valueTree) {291return;292}293294const valueTreeRecord = valueTree as IStringDictionary<unknown>;295const first = segments.shift()!;296if (segments.length === 0) {297// Reached last segment298delete valueTreeRecord[first];299return;300}301302if (Object.keys(valueTreeRecord).indexOf(first) !== -1) {303const value = valueTreeRecord[first];304if (typeof value === 'object' && !Array.isArray(value)) {305doRemoveFromValueTree(value, segments);306if (Object.keys(value as object).length === 0) {307delete valueTreeRecord[first];308}309}310}311}312313/**314* A helper function to get the configuration value with a specific settings path (e.g. config.some.setting)315*/316export function getConfigurationValue<T>(config: IStringDictionary<unknown>, settingPath: string): T | undefined;317export function getConfigurationValue<T>(config: IStringDictionary<unknown>, settingPath: string, defaultValue: T): T;318export function getConfigurationValue<T>(config: IStringDictionary<unknown>, settingPath: string, defaultValue?: T): T | undefined {319function accessSetting(config: IStringDictionary<unknown>, path: string[]): unknown {320let current: unknown = config;321for (const component of path) {322if (typeof current !== 'object' || current === null) {323return undefined;324}325current = (current as IStringDictionary<unknown>)[component];326}327return current as T;328}329330const path = settingPath.split('.');331const result = accessSetting(config, path);332333return typeof result === 'undefined' ? defaultValue : result as T;334}335336export function merge(base: IStringDictionary<unknown>, add: IStringDictionary<unknown>, overwrite: boolean): void {337Object.keys(add).forEach(key => {338if (key !== '__proto__') {339if (key in base) {340if (types.isObject(base[key]) && types.isObject(add[key])) {341merge(base[key] as IStringDictionary<unknown>, add[key] as IStringDictionary<unknown>, overwrite);342} else if (overwrite) {343base[key] = add[key];344}345} else {346base[key] = add[key];347}348}349});350}351352export function getLanguageTagSettingPlainKey(settingKey: string) {353return settingKey354.replace(/^\[/, '')355.replace(/]$/g, '')356.replace(/\]\[/g, ', ');357}358359360