Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/configuration/common/configurationService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { distinct, equals as arrayEquals } from '../../../base/common/arrays.js';
7
import { Queue, RunOnceScheduler } from '../../../base/common/async.js';
8
import { VSBuffer } from '../../../base/common/buffer.js';
9
import { Emitter, Event } from '../../../base/common/event.js';
10
import { JSONPath, ParseError, parse } from '../../../base/common/json.js';
11
import { applyEdits, setProperty } from '../../../base/common/jsonEdit.js';
12
import { Edit, FormattingOptions } from '../../../base/common/jsonFormatter.js';
13
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
14
import { ResourceMap } from '../../../base/common/map.js';
15
import { equals } from '../../../base/common/objects.js';
16
import { OS, OperatingSystem } from '../../../base/common/platform.js';
17
import { extUriBiasedIgnorePathCase } from '../../../base/common/resources.js';
18
import { URI } from '../../../base/common/uri.js';
19
import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationUpdateOptions, IConfigurationUpdateOverrides, IConfigurationValue, isConfigurationOverrides, isConfigurationUpdateOverrides } from './configuration.js';
20
import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from './configurationModels.js';
21
import { keyFromOverrideIdentifiers } from './configurationRegistry.js';
22
import { DefaultConfiguration, IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from './configurations.js';
23
import { FileOperationError, FileOperationResult, IFileService } from '../../files/common/files.js';
24
import { ILogService } from '../../log/common/log.js';
25
import { IPolicyService, NullPolicyService } from '../../policy/common/policy.js';
26
27
export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable {
28
29
declare readonly _serviceBrand: undefined;
30
31
private configuration: Configuration;
32
private readonly defaultConfiguration: DefaultConfiguration;
33
private readonly policyConfiguration: IPolicyConfiguration;
34
private readonly userConfiguration: UserSettings;
35
private readonly reloadConfigurationScheduler: RunOnceScheduler;
36
37
private readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
38
readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
39
40
private readonly configurationEditing: ConfigurationEditing;
41
42
constructor(
43
private readonly settingsResource: URI,
44
fileService: IFileService,
45
policyService: IPolicyService,
46
private readonly logService: ILogService,
47
) {
48
super();
49
this.defaultConfiguration = this._register(new DefaultConfiguration(logService));
50
this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService));
51
this.userConfiguration = this._register(new UserSettings(this.settingsResource, {}, extUriBiasedIgnorePathCase, fileService, logService));
52
this.configuration = new Configuration(
53
this.defaultConfiguration.configurationModel,
54
this.policyConfiguration.configurationModel,
55
ConfigurationModel.createEmptyModel(logService),
56
ConfigurationModel.createEmptyModel(logService),
57
ConfigurationModel.createEmptyModel(logService),
58
ConfigurationModel.createEmptyModel(logService),
59
new ResourceMap<ConfigurationModel>(),
60
ConfigurationModel.createEmptyModel(logService),
61
new ResourceMap<ConfigurationModel>(),
62
logService
63
);
64
this.configurationEditing = new ConfigurationEditing(settingsResource, fileService, this);
65
66
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50));
67
this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties)));
68
this._register(this.policyConfiguration.onDidChangeConfiguration(model => this.onDidPolicyConfigurationChange(model)));
69
this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
70
}
71
72
async initialize(): Promise<void> {
73
const [defaultModel, policyModel, userModel] = await Promise.all([this.defaultConfiguration.initialize(), this.policyConfiguration.initialize(), this.userConfiguration.loadConfiguration()]);
74
this.configuration = new Configuration(
75
defaultModel,
76
policyModel,
77
ConfigurationModel.createEmptyModel(this.logService),
78
userModel,
79
ConfigurationModel.createEmptyModel(this.logService),
80
ConfigurationModel.createEmptyModel(this.logService),
81
new ResourceMap<ConfigurationModel>(),
82
ConfigurationModel.createEmptyModel(this.logService),
83
new ResourceMap<ConfigurationModel>(),
84
this.logService
85
);
86
}
87
88
getConfigurationData(): IConfigurationData {
89
return this.configuration.toData();
90
}
91
92
getValue<T>(): T;
93
getValue<T>(section: string): T;
94
getValue<T>(overrides: IConfigurationOverrides): T;
95
getValue<T>(section: string, overrides: IConfigurationOverrides): T;
96
getValue(arg1?: any, arg2?: any): any {
97
const section = typeof arg1 === 'string' ? arg1 : undefined;
98
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {};
99
return this.configuration.getValue(section, overrides, undefined);
100
}
101
102
updateValue(key: string, value: any): Promise<void>;
103
updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise<void>;
104
updateValue(key: string, value: any, target: ConfigurationTarget): Promise<void>;
105
updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, options?: IConfigurationUpdateOptions): Promise<void>;
106
async updateValue(key: string, value: any, arg3?: any, arg4?: any, options?: any): Promise<void> {
107
const overrides: IConfigurationUpdateOverrides | undefined = isConfigurationUpdateOverrides(arg3) ? arg3
108
: isConfigurationOverrides(arg3) ? { resource: arg3.resource, overrideIdentifiers: arg3.overrideIdentifier ? [arg3.overrideIdentifier] : undefined } : undefined;
109
110
const target: ConfigurationTarget | undefined = overrides ? arg4 : arg3;
111
if (target !== undefined) {
112
if (target !== ConfigurationTarget.USER_LOCAL && target !== ConfigurationTarget.USER) {
113
throw new Error(`Unable to write ${key} to target ${target}.`);
114
}
115
}
116
117
if (overrides?.overrideIdentifiers) {
118
overrides.overrideIdentifiers = distinct(overrides.overrideIdentifiers);
119
overrides.overrideIdentifiers = overrides.overrideIdentifiers.length ? overrides.overrideIdentifiers : undefined;
120
}
121
122
const inspect = this.inspect(key, { resource: overrides?.resource, overrideIdentifier: overrides?.overrideIdentifiers ? overrides.overrideIdentifiers[0] : undefined });
123
if (inspect.policyValue !== undefined) {
124
throw new Error(`Unable to write ${key} because it is configured in system policy.`);
125
}
126
127
// Remove the setting, if the value is same as default value
128
if (equals(value, inspect.defaultValue)) {
129
value = undefined;
130
}
131
132
if (overrides?.overrideIdentifiers?.length && overrides.overrideIdentifiers.length > 1) {
133
const overrideIdentifiers = overrides.overrideIdentifiers.sort();
134
const existingOverrides = this.configuration.localUserConfiguration.overrides.find(override => arrayEquals([...override.identifiers].sort(), overrideIdentifiers));
135
if (existingOverrides) {
136
overrides.overrideIdentifiers = existingOverrides.identifiers;
137
}
138
}
139
140
const path = overrides?.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key];
141
142
await this.configurationEditing.write(path, value);
143
await this.reloadConfiguration();
144
}
145
146
inspect<T>(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue<T> {
147
return this.configuration.inspect<T>(key, overrides, undefined);
148
}
149
150
keys(): {
151
default: string[];
152
user: string[];
153
workspace: string[];
154
workspaceFolder: string[];
155
} {
156
return this.configuration.keys(undefined);
157
}
158
159
async reloadConfiguration(): Promise<void> {
160
const configurationModel = await this.userConfiguration.loadConfiguration();
161
this.onDidChangeUserConfiguration(configurationModel);
162
}
163
164
private onDidChangeUserConfiguration(userConfigurationModel: ConfigurationModel): void {
165
const previous = this.configuration.toData();
166
const change = this.configuration.compareAndUpdateLocalUserConfiguration(userConfigurationModel);
167
this.trigger(change, previous, ConfigurationTarget.USER);
168
}
169
170
private onDidDefaultConfigurationChange(defaultConfigurationModel: ConfigurationModel, properties: string[]): void {
171
const previous = this.configuration.toData();
172
const change = this.configuration.compareAndUpdateDefaultConfiguration(defaultConfigurationModel, properties);
173
this.trigger(change, previous, ConfigurationTarget.DEFAULT);
174
}
175
176
private onDidPolicyConfigurationChange(policyConfiguration: ConfigurationModel): void {
177
const previous = this.configuration.toData();
178
const change = this.configuration.compareAndUpdatePolicyConfiguration(policyConfiguration);
179
this.trigger(change, previous, ConfigurationTarget.DEFAULT);
180
}
181
182
private trigger(configurationChange: IConfigurationChange, previous: IConfigurationData, source: ConfigurationTarget): void {
183
const event = new ConfigurationChangeEvent(configurationChange, { data: previous }, this.configuration, undefined, this.logService);
184
event.source = source;
185
this._onDidChangeConfiguration.fire(event);
186
}
187
}
188
189
class ConfigurationEditing {
190
191
private readonly queue: Queue<void>;
192
193
constructor(
194
private readonly settingsResource: URI,
195
private readonly fileService: IFileService,
196
private readonly configurationService: IConfigurationService,
197
) {
198
this.queue = new Queue<void>();
199
}
200
201
write(path: JSONPath, value: any): Promise<void> {
202
return this.queue.queue(() => this.doWriteConfiguration(path, value)); // queue up writes to prevent race conditions
203
}
204
205
private async doWriteConfiguration(path: JSONPath, value: any): Promise<void> {
206
let content: string;
207
try {
208
const fileContent = await this.fileService.readFile(this.settingsResource);
209
content = fileContent.value.toString();
210
} catch (error) {
211
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
212
content = '{}';
213
} else {
214
throw error;
215
}
216
}
217
218
const parseErrors: ParseError[] = [];
219
parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true });
220
if (parseErrors.length > 0) {
221
throw new Error('Unable to write into the settings file. Please open the file to correct errors/warnings in the file and try again.');
222
}
223
224
const edits = this.getEdits(content, path, value);
225
content = applyEdits(content, edits);
226
227
await this.fileService.writeFile(this.settingsResource, VSBuffer.fromString(content));
228
}
229
230
private getEdits(content: string, path: JSONPath, value: any): Edit[] {
231
const { tabSize, insertSpaces, eol } = this.formattingOptions;
232
233
// With empty path the entire file is being replaced, so we just use JSON.stringify
234
if (!path.length) {
235
const content = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t');
236
return [{
237
content,
238
length: content.length,
239
offset: 0
240
}];
241
}
242
243
return setProperty(content, path, value, { tabSize, insertSpaces, eol });
244
}
245
246
private _formattingOptions: Required<FormattingOptions> | undefined;
247
private get formattingOptions(): Required<FormattingOptions> {
248
if (!this._formattingOptions) {
249
let eol = OS === OperatingSystem.Linux || OS === OperatingSystem.Macintosh ? '\n' : '\r\n';
250
const configuredEol = this.configurationService.getValue('files.eol', { overrideIdentifier: 'jsonc' });
251
if (configuredEol && typeof configuredEol === 'string' && configuredEol !== 'auto') {
252
eol = configuredEol;
253
}
254
this._formattingOptions = {
255
eol,
256
insertSpaces: !!this.configurationService.getValue('editor.insertSpaces', { overrideIdentifier: 'jsonc' }),
257
tabSize: this.configurationService.getValue('editor.tabSize', { overrideIdentifier: 'jsonc' })
258
};
259
}
260
return this._formattingOptions;
261
}
262
}
263
264