Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/services/configuration/browser/configurationService.ts
13401 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 { onUnexpectedError } from '../../../../base/common/errors.js';
7
import { Emitter, Event } from '../../../../base/common/event.js';
8
import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js';
9
import { ResourceMap } from '../../../../base/common/map.js';
10
import { URI } from '../../../../base/common/uri.js';
11
import { Queue } from '../../../../base/common/async.js';
12
import { VSBuffer } from '../../../../base/common/buffer.js';
13
import { JSONPath, ParseError, parse } from '../../../../base/common/json.js';
14
import { applyEdits, setProperty } from '../../../../base/common/jsonEdit.js';
15
import { Edit, FormattingOptions } from '../../../../base/common/jsonFormatter.js';
16
import { equals } from '../../../../base/common/objects.js';
17
import { distinct, equals as arrayEquals } from '../../../../base/common/arrays.js';
18
import { OS, OperatingSystem } from '../../../../base/common/platform.js';
19
import { IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationUpdateOptions, IConfigurationUpdateOverrides, IConfigurationValue, ConfigurationTarget, isConfigurationOverrides, isConfigurationUpdateOverrides } from '../../../../platform/configuration/common/configuration.js';
20
import { ConfigurationChangeEvent, ConfigurationModel } from '../../../../platform/configuration/common/configurationModels.js';
21
import { DefaultConfiguration, IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from '../../../../platform/configuration/common/configurations.js';
22
import { Extensions, IConfigurationRegistry, keyFromOverrideIdentifiers } from '../../../../platform/configuration/common/configurationRegistry.js';
23
import { IFileService, FileOperationError, FileOperationResult } from '../../../../platform/files/common/files.js';
24
import { ILogService } from '../../../../platform/log/common/log.js';
25
import { IPolicyService, NullPolicyService } from '../../../../platform/policy/common/policy.js';
26
import { Registry } from '../../../../platform/registry/common/platform.js';
27
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
28
import { IWorkspaceContextService, IWorkspaceFoldersChangeEvent, IWorkspaceFolder, WorkbenchState, Workspace } from '../../../../platform/workspace/common/workspace.js';
29
import { FolderConfiguration, UserConfiguration } from '../../../../workbench/services/configuration/browser/configuration.js';
30
import { APPLICATION_SCOPES, APPLY_ALL_PROFILES_SETTING, FOLDER_CONFIG_FOLDER_NAME, FOLDER_SETTINGS_PATH, IWorkbenchConfigurationService, RestrictedSettings } from '../../../../workbench/services/configuration/common/configuration.js';
31
import { Configuration } from '../../../../workbench/services/configuration/common/configurationModels.js';
32
import { IUserDataProfileService } from '../../../../workbench/services/userDataProfile/common/userDataProfile.js';
33
34
// Import to register configuration contributions
35
import '../../../../workbench/services/configuration/browser/configurationService.js';
36
37
export class ConfigurationService extends Disposable implements IWorkbenchConfigurationService {
38
39
declare readonly _serviceBrand: undefined;
40
41
private _configuration: Configuration;
42
private readonly defaultConfiguration: DefaultConfiguration;
43
private readonly policyConfiguration: IPolicyConfiguration;
44
private readonly userConfiguration: UserConfiguration;
45
private readonly cachedFolderConfigs = this._register(new DisposableMap<URI, FolderConfiguration>(new ResourceMap()));
46
47
private readonly _onDidChangeConfiguration = this._register(new Emitter<IConfigurationChangeEvent>());
48
readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;
49
50
readonly onDidChangeRestrictedSettings = Event.None;
51
readonly restrictedSettings: RestrictedSettings = { default: [] };
52
53
private readonly configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
54
55
private readonly settingsResource: URI;
56
private readonly configurationEditing: ConfigurationEditing;
57
58
constructor(
59
userDataProfileService: IUserDataProfileService,
60
private readonly workspaceService: IWorkspaceContextService,
61
private readonly uriIdentityService: IUriIdentityService,
62
private readonly fileService: IFileService,
63
policyService: IPolicyService,
64
private readonly logService: ILogService,
65
) {
66
super();
67
68
this.settingsResource = userDataProfileService.currentProfile.settingsResource;
69
this.defaultConfiguration = this._register(new DefaultConfiguration(logService));
70
this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService));
71
this.userConfiguration = this._register(new UserConfiguration(userDataProfileService.currentProfile.settingsResource, userDataProfileService.currentProfile.tasksResource, userDataProfileService.currentProfile.mcpResource, {}, fileService, uriIdentityService, logService));
72
this.configurationEditing = new ConfigurationEditing(fileService, this);
73
74
this._configuration = new Configuration(
75
ConfigurationModel.createEmptyModel(logService),
76
ConfigurationModel.createEmptyModel(logService),
77
ConfigurationModel.createEmptyModel(logService),
78
ConfigurationModel.createEmptyModel(logService),
79
ConfigurationModel.createEmptyModel(logService),
80
ConfigurationModel.createEmptyModel(logService),
81
new ResourceMap(),
82
ConfigurationModel.createEmptyModel(logService),
83
new ResourceMap<ConfigurationModel>(),
84
this.workspaceService.getWorkspace() as Workspace,
85
this.logService
86
);
87
88
this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDefaultConfigurationChanged(defaults, properties)));
89
this._register(this.policyConfiguration.onDidChangeConfiguration(configurationModel => this.onPolicyConfigurationChanged(configurationModel)));
90
this._register(this.userConfiguration.onDidChangeConfiguration(userConfiguration => this.onUserConfigurationChanged(userConfiguration)));
91
this._register(this.workspaceService.onWillChangeWorkspaceFolders(e => e.join(this.loadFolderConfigurations(e.changes.added))));
92
this._register(this.workspaceService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e)));
93
}
94
95
async initialize(): Promise<void> {
96
const [defaultModel, policyModel, userModel] = await Promise.all([
97
this.defaultConfiguration.initialize(),
98
this.policyConfiguration.initialize(),
99
this.userConfiguration.initialize()
100
]);
101
const workspace = this.workspaceService.getWorkspace() as Workspace;
102
this._configuration = new Configuration(
103
defaultModel,
104
policyModel,
105
ConfigurationModel.createEmptyModel(this.logService),
106
userModel,
107
ConfigurationModel.createEmptyModel(this.logService),
108
ConfigurationModel.createEmptyModel(this.logService),
109
new ResourceMap(),
110
ConfigurationModel.createEmptyModel(this.logService),
111
new ResourceMap<ConfigurationModel>(),
112
workspace,
113
this.logService
114
);
115
await this.loadFolderConfigurations(workspace.folders);
116
}
117
118
// #region IWorkbenchConfigurationService
119
120
getConfigurationData(): IConfigurationData {
121
return this._configuration.toData();
122
}
123
124
getValue<T>(): T;
125
getValue<T>(section: string): T;
126
getValue<T>(overrides: IConfigurationOverrides): T;
127
getValue<T>(section: string, overrides: IConfigurationOverrides): T;
128
getValue(arg1?: unknown, arg2?: unknown): unknown {
129
const section = typeof arg1 === 'string' ? arg1 : undefined;
130
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : undefined;
131
return this._configuration.getValue(section, overrides);
132
}
133
134
updateValue(key: string, value: unknown): Promise<void>;
135
updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise<void>;
136
updateValue(key: string, value: unknown, target: ConfigurationTarget): Promise<void>;
137
updateValue(key: string, value: unknown, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, options?: IConfigurationUpdateOptions): Promise<void>;
138
async updateValue(key: string, value: unknown, arg3?: unknown, arg4?: unknown, _options?: IConfigurationUpdateOptions): Promise<void> {
139
const overrides: IConfigurationUpdateOverrides | undefined = isConfigurationUpdateOverrides(arg3) ? arg3
140
: isConfigurationOverrides(arg3) ? { resource: arg3.resource, overrideIdentifiers: arg3.overrideIdentifier ? [arg3.overrideIdentifier] : undefined } : undefined;
141
const target: ConfigurationTarget | undefined = (overrides ? arg4 : arg3) as ConfigurationTarget | undefined;
142
143
if (overrides?.overrideIdentifiers) {
144
overrides.overrideIdentifiers = distinct(overrides.overrideIdentifiers);
145
overrides.overrideIdentifiers = overrides.overrideIdentifiers.length ? overrides.overrideIdentifiers : undefined;
146
}
147
148
const inspect = this.inspect(key, { resource: overrides?.resource, overrideIdentifier: overrides?.overrideIdentifiers ? overrides.overrideIdentifiers[0] : undefined });
149
if (inspect.policyValue !== undefined) {
150
throw new Error(`Unable to write ${key} because it is configured in system policy.`);
151
}
152
153
// Remove the setting, if the value is same as default value
154
if (equals(value, inspect.defaultValue)) {
155
value = undefined;
156
}
157
158
if (overrides?.overrideIdentifiers?.length && overrides.overrideIdentifiers.length > 1) {
159
const overrideIdentifiers = overrides.overrideIdentifiers.sort();
160
const existingOverrides = this._configuration.localUserConfiguration.overrides.find(override => arrayEquals([...override.identifiers].sort(), overrideIdentifiers));
161
if (existingOverrides) {
162
overrides.overrideIdentifiers = existingOverrides.identifiers;
163
}
164
}
165
166
const path = overrides?.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key];
167
168
const settingsResource = this.getSettingsResource(target, overrides?.resource ?? undefined);
169
await this.configurationEditing.write(settingsResource, path, value);
170
await this.reloadConfiguration();
171
}
172
173
private getSettingsResource(target: ConfigurationTarget | undefined, resource: URI | undefined): URI {
174
if (target === ConfigurationTarget.WORKSPACE_FOLDER || target === ConfigurationTarget.WORKSPACE) {
175
if (resource) {
176
const folder = this.workspaceService.getWorkspaceFolder(resource);
177
if (folder) {
178
return this.uriIdentityService.extUri.joinPath(folder.uri, FOLDER_SETTINGS_PATH);
179
}
180
}
181
}
182
return this.settingsResource;
183
}
184
185
inspect<T>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<T> {
186
return this._configuration.inspect<T>(key, overrides);
187
}
188
189
keys(): { default: string[]; policy: string[]; user: string[]; workspace: string[]; workspaceFolder: string[] } {
190
return this._configuration.keys();
191
}
192
193
async reloadConfiguration(_target?: ConfigurationTarget | IWorkspaceFolder): Promise<void> {
194
const userModel = await this.userConfiguration.initialize();
195
const previousData = this._configuration.toData();
196
const change = this._configuration.compareAndUpdateLocalUserConfiguration(userModel);
197
198
// Reload folder configurations
199
for (const folder of this.workspaceService.getWorkspace().folders) {
200
const folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
201
if (folderConfiguration) {
202
const folderModel = await folderConfiguration.loadConfiguration();
203
const folderChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderModel);
204
change.keys.push(...folderChange.keys);
205
change.overrides.push(...folderChange.overrides);
206
}
207
}
208
209
this.triggerConfigurationChange(change, previousData, ConfigurationTarget.USER);
210
}
211
212
hasCachedConfigurationDefaultsOverrides(): boolean {
213
return false;
214
}
215
216
async whenRemoteConfigurationLoaded(): Promise<void> { }
217
218
isSettingAppliedForAllProfiles(key: string): boolean {
219
const scope = this.configurationRegistry.getConfigurationProperties()[key]?.scope;
220
if (scope && APPLICATION_SCOPES.includes(scope)) {
221
return true;
222
}
223
const allProfilesSettings = this.getValue<string[]>(APPLY_ALL_PROFILES_SETTING) ?? [];
224
return Array.isArray(allProfilesSettings) && allProfilesSettings.includes(key);
225
}
226
227
// #endregion
228
229
// #region Configuration change handlers
230
231
private onDefaultConfigurationChanged(defaults: ConfigurationModel, properties?: string[]): void {
232
const previousData = this._configuration.toData();
233
const change = this._configuration.compareAndUpdateDefaultConfiguration(defaults, properties);
234
this._configuration.updateLocalUserConfiguration(this.userConfiguration.reparse());
235
for (const folder of this.workspaceService.getWorkspace().folders) {
236
const folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
237
if (folderConfiguration) {
238
this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration.reparse());
239
}
240
}
241
this.triggerConfigurationChange(change, previousData, ConfigurationTarget.DEFAULT);
242
}
243
244
private onPolicyConfigurationChanged(policyConfiguration: ConfigurationModel): void {
245
const previousData = this._configuration.toData();
246
const change = this._configuration.compareAndUpdatePolicyConfiguration(policyConfiguration);
247
this.triggerConfigurationChange(change, previousData, ConfigurationTarget.DEFAULT);
248
}
249
250
private onUserConfigurationChanged(userConfiguration: ConfigurationModel): void {
251
const previousData = this._configuration.toData();
252
const change = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration);
253
this.triggerConfigurationChange(change, previousData, ConfigurationTarget.USER);
254
}
255
256
private onWorkspaceFoldersChanged(e: IWorkspaceFoldersChangeEvent): void {
257
// Remove configurations for removed folders
258
const previousData = this._configuration.toData();
259
const keys: string[] = [];
260
const overrides: [string, string[]][] = [];
261
for (const folder of e.removed) {
262
const change = this._configuration.compareAndDeleteFolderConfiguration(folder.uri);
263
keys.push(...change.keys);
264
overrides.push(...change.overrides);
265
this.cachedFolderConfigs.deleteAndDispose(folder.uri);
266
}
267
if (keys.length || overrides.length) {
268
this.triggerConfigurationChange({ keys, overrides }, previousData, ConfigurationTarget.WORKSPACE_FOLDER);
269
}
270
}
271
272
private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): void {
273
const folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
274
if (folderConfiguration) {
275
folderConfiguration.loadConfiguration().then(configurationModel => {
276
const previousData = this._configuration.toData();
277
const change = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, configurationModel);
278
this.triggerConfigurationChange(change, previousData, ConfigurationTarget.WORKSPACE_FOLDER);
279
}, onUnexpectedError);
280
}
281
}
282
283
private async loadFolderConfigurations(folders: readonly IWorkspaceFolder[]): Promise<void> {
284
for (const folder of folders) {
285
let folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
286
if (!folderConfiguration) {
287
folderConfiguration = new FolderConfiguration(false, folder, FOLDER_CONFIG_FOLDER_NAME, WorkbenchState.WORKSPACE, true, this.fileService, this.uriIdentityService, this.logService, { needsCaching: () => false, read: async () => '', write: async () => { }, remove: async () => { } });
288
folderConfiguration.addRelated(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder)));
289
this.cachedFolderConfigs.set(folder.uri, folderConfiguration);
290
}
291
const configurationModel = await folderConfiguration.loadConfiguration();
292
this._configuration.updateFolderConfiguration(folder.uri, configurationModel);
293
}
294
}
295
296
private triggerConfigurationChange(change: IConfigurationChange, previousData: IConfigurationData, target: ConfigurationTarget): void {
297
if (change.keys.length) {
298
const workspace = this.workspaceService.getWorkspace() as Workspace;
299
const event = new ConfigurationChangeEvent(change, { data: previousData, workspace }, this._configuration, workspace, this.logService);
300
event.source = target;
301
this._onDidChangeConfiguration.fire(event);
302
}
303
}
304
305
// #endregion
306
}
307
308
class ConfigurationEditing {
309
310
private readonly queue = new Queue<void>();
311
312
constructor(
313
private readonly fileService: IFileService,
314
private readonly configurationService: ConfigurationService,
315
) { }
316
317
write(settingsResource: URI, path: JSONPath, value: unknown): Promise<void> {
318
return this.queue.queue(() => this.doWriteConfiguration(settingsResource, path, value));
319
}
320
321
private async doWriteConfiguration(settingsResource: URI, path: JSONPath, value: unknown): Promise<void> {
322
let content: string;
323
try {
324
const fileContent = await this.fileService.readFile(settingsResource);
325
content = fileContent.value.toString();
326
} catch (error) {
327
if ((error as FileOperationError).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
328
content = '{}';
329
} else {
330
throw error;
331
}
332
}
333
334
const parseErrors: ParseError[] = [];
335
parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true });
336
if (parseErrors.length > 0) {
337
throw new Error('Unable to write into the settings file. Please open the file to correct errors/warnings in the file and try again.');
338
}
339
340
const edits = this.getEdits(content, path, value);
341
content = applyEdits(content, edits);
342
343
await this.fileService.writeFile(settingsResource, VSBuffer.fromString(content));
344
}
345
346
private getEdits(content: string, path: JSONPath, value: unknown): Edit[] {
347
const { tabSize, insertSpaces, eol } = this.formattingOptions;
348
349
if (!path.length) {
350
const newContent = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t');
351
return [{
352
content: newContent,
353
length: content.length,
354
offset: 0
355
}];
356
}
357
358
return setProperty(content, path, value, { tabSize, insertSpaces, eol });
359
}
360
361
private _formattingOptions: Required<FormattingOptions> | undefined;
362
private get formattingOptions(): Required<FormattingOptions> {
363
if (!this._formattingOptions) {
364
let eol = OS === OperatingSystem.Linux || OS === OperatingSystem.Macintosh ? '\n' : '\r\n';
365
const configuredEol = this.configurationService.getValue<string>('files.eol', { overrideIdentifier: 'jsonc' });
366
if (configuredEol && typeof configuredEol === 'string' && configuredEol !== 'auto') {
367
eol = configuredEol;
368
}
369
this._formattingOptions = {
370
eol,
371
insertSpaces: !!this.configurationService.getValue('editor.insertSpaces', { overrideIdentifier: 'jsonc' }),
372
tabSize: this.configurationService.getValue('editor.tabSize', { overrideIdentifier: 'jsonc' })
373
};
374
}
375
return this._formattingOptions;
376
}
377
}
378
379