Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/common/configuration.ts
5220 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 { localize } from '../../nls.js';
7
import { ConfigurationScope, IConfigurationNode, IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../platform/configuration/common/configurationRegistry.js';
8
import { Registry } from '../../platform/registry/common/platform.js';
9
import { IWorkbenchContribution } from './contributions.js';
10
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from '../../platform/workspace/common/workspace.js';
11
import { ConfigurationTarget, IConfigurationService, IConfigurationValue, IInspectValue } from '../../platform/configuration/common/configuration.js';
12
import { Disposable } from '../../base/common/lifecycle.js';
13
import { Emitter } from '../../base/common/event.js';
14
import { IRemoteAgentService } from '../services/remote/common/remoteAgentService.js';
15
import { OperatingSystem, isWindows } from '../../base/common/platform.js';
16
import { URI } from '../../base/common/uri.js';
17
import { equals } from '../../base/common/objects.js';
18
import { DeferredPromise } from '../../base/common/async.js';
19
import { IUserDataProfile, IUserDataProfilesService } from '../../platform/userDataProfile/common/userDataProfile.js';
20
21
export const applicationConfigurationNodeBase = Object.freeze<IConfigurationNode>({
22
'id': 'application',
23
'order': 100,
24
'title': localize('applicationConfigurationTitle', "Application"),
25
'type': 'object'
26
});
27
28
export const workbenchConfigurationNodeBase = Object.freeze<IConfigurationNode>({
29
'id': 'workbench',
30
'order': 7,
31
'title': localize('workbenchConfigurationTitle', "Workbench"),
32
'type': 'object',
33
});
34
35
export const securityConfigurationNodeBase = Object.freeze<IConfigurationNode>({
36
'id': 'security',
37
'scope': ConfigurationScope.APPLICATION,
38
'title': localize('securityConfigurationTitle', "Security"),
39
'type': 'object',
40
'order': 7
41
});
42
43
export const problemsConfigurationNodeBase = Object.freeze<IConfigurationNode>({
44
'id': 'problems',
45
'title': localize('problemsConfigurationTitle', "Problems"),
46
'type': 'object',
47
'order': 101
48
});
49
50
export const windowConfigurationNodeBase = Object.freeze<IConfigurationNode>({
51
'id': 'window',
52
'order': 8,
53
'title': localize('windowConfigurationTitle', "Window"),
54
'type': 'object',
55
});
56
57
export const Extensions = {
58
ConfigurationMigration: 'base.contributions.configuration.migration'
59
};
60
61
type ConfigurationValue = { value: unknown | undefined /* Remove */ };
62
export type ConfigurationKeyValuePairs = [string, ConfigurationValue][];
63
// eslint-disable-next-line @typescript-eslint/no-explicit-any
64
export type ConfigurationMigrationFn = (value: any, valueAccessor: (key: string) => any) => ConfigurationValue | ConfigurationKeyValuePairs | Promise<ConfigurationValue | ConfigurationKeyValuePairs>;
65
export type ConfigurationMigration = { key: string; migrateFn: ConfigurationMigrationFn };
66
67
export interface IConfigurationMigrationRegistry {
68
registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void;
69
}
70
71
class ConfigurationMigrationRegistry implements IConfigurationMigrationRegistry {
72
73
readonly migrations: ConfigurationMigration[] = [];
74
75
private readonly _onDidRegisterConfigurationMigrations = new Emitter<ConfigurationMigration[]>();
76
readonly onDidRegisterConfigurationMigration = this._onDidRegisterConfigurationMigrations.event;
77
78
registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void {
79
this.migrations.push(...configurationMigrations);
80
}
81
82
}
83
84
const configurationMigrationRegistry = new ConfigurationMigrationRegistry();
85
Registry.add(Extensions.ConfigurationMigration, configurationMigrationRegistry);
86
87
export class ConfigurationMigrationWorkbenchContribution extends Disposable implements IWorkbenchContribution {
88
89
static readonly ID = 'workbench.contrib.configurationMigration';
90
91
constructor(
92
@IConfigurationService private readonly configurationService: IConfigurationService,
93
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
94
) {
95
super();
96
this._register(this.workspaceService.onDidChangeWorkspaceFolders(async (e) => {
97
for (const folder of e.added) {
98
await this.migrateConfigurationsForFolder(folder, configurationMigrationRegistry.migrations);
99
}
100
}));
101
this.migrateConfigurations(configurationMigrationRegistry.migrations);
102
this._register(configurationMigrationRegistry.onDidRegisterConfigurationMigration(migration => this.migrateConfigurations(migration)));
103
}
104
105
private async migrateConfigurations(migrations: ConfigurationMigration[]): Promise<void> {
106
await this.migrateConfigurationsForFolder(undefined, migrations);
107
for (const folder of this.workspaceService.getWorkspace().folders) {
108
await this.migrateConfigurationsForFolder(folder, migrations);
109
}
110
}
111
112
private async migrateConfigurationsForFolder(folder: IWorkspaceFolder | undefined, migrations: ConfigurationMigration[]): Promise<void> {
113
await Promise.all([migrations.map(migration => this.migrateConfigurationsForFolderAndOverride(migration, folder?.uri))]);
114
}
115
116
private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, resource?: URI): Promise<void> {
117
const inspectData = this.configurationService.inspect(migration.key, { resource });
118
119
const targetPairs: [keyof IConfigurationValue<unknown>, ConfigurationTarget][] = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? [
120
['user', ConfigurationTarget.USER],
121
['userLocal', ConfigurationTarget.USER_LOCAL],
122
['userRemote', ConfigurationTarget.USER_REMOTE],
123
['workspace', ConfigurationTarget.WORKSPACE],
124
['workspaceFolder', ConfigurationTarget.WORKSPACE_FOLDER],
125
] : [
126
['user', ConfigurationTarget.USER],
127
['userLocal', ConfigurationTarget.USER_LOCAL],
128
['userRemote', ConfigurationTarget.USER_REMOTE],
129
['workspace', ConfigurationTarget.WORKSPACE],
130
];
131
for (const [dataKey, target] of targetPairs) {
132
const inspectValue = inspectData[dataKey] as IInspectValue<unknown> | undefined;
133
if (!inspectValue) {
134
continue;
135
}
136
137
const migrationValues: [[string, ConfigurationValue], string[]][] = [];
138
139
if (inspectValue.value !== undefined) {
140
const keyValuePairs = await this.runMigration(migration, dataKey, inspectValue.value, resource, undefined);
141
for (const keyValuePair of keyValuePairs ?? []) {
142
migrationValues.push([keyValuePair, []]);
143
}
144
}
145
146
for (const { identifiers, value } of inspectValue.overrides ?? []) {
147
if (value !== undefined) {
148
const keyValuePairs = await this.runMigration(migration, dataKey, value, resource, identifiers);
149
for (const keyValuePair of keyValuePairs ?? []) {
150
migrationValues.push([keyValuePair, identifiers]);
151
}
152
}
153
}
154
155
if (migrationValues.length) {
156
// apply migrations
157
await Promise.allSettled(migrationValues.map(async ([[key, value], overrideIdentifiers]) =>
158
this.configurationService.updateValue(key, value.value, { resource, overrideIdentifiers }, target)));
159
}
160
}
161
}
162
163
private async runMigration(migration: ConfigurationMigration, dataKey: keyof IConfigurationValue<unknown>, value: unknown, resource: URI | undefined, overrideIdentifiers: string[] | undefined): Promise<ConfigurationKeyValuePairs | undefined> {
164
const valueAccessor = (key: string) => {
165
const inspectData = this.configurationService.inspect(key, { resource });
166
const inspectValue = inspectData[dataKey] as IInspectValue<unknown> | undefined;
167
if (!inspectValue) {
168
return undefined;
169
}
170
if (!overrideIdentifiers) {
171
return inspectValue.value;
172
}
173
return inspectValue.overrides?.find(({ identifiers }) => equals(identifiers, overrideIdentifiers))?.value;
174
};
175
const result = await migration.migrateFn(value, valueAccessor);
176
return Array.isArray(result) ? result : [[migration.key, result]];
177
}
178
}
179
180
export class DynamicWorkbenchSecurityConfiguration extends Disposable implements IWorkbenchContribution {
181
182
static readonly ID = 'workbench.contrib.dynamicWorkbenchSecurityConfiguration';
183
184
private readonly _ready = new DeferredPromise<void>();
185
readonly ready = this._ready.p;
186
187
constructor(
188
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
189
) {
190
super();
191
192
this.create();
193
}
194
195
private async create(): Promise<void> {
196
try {
197
await this.doCreate();
198
} finally {
199
this._ready.complete();
200
}
201
}
202
203
private async doCreate(): Promise<void> {
204
if (!isWindows) {
205
const remoteEnvironment = await this.remoteAgentService.getEnvironment();
206
if (remoteEnvironment?.os !== OperatingSystem.Windows) {
207
return;
208
}
209
}
210
211
// Windows: UNC allow list security configuration
212
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
213
registry.registerConfiguration({
214
...securityConfigurationNodeBase,
215
'properties': {
216
'security.allowedUNCHosts': {
217
'type': 'array',
218
'items': {
219
'type': 'string',
220
'pattern': '^[^\\\\]+$',
221
'patternErrorMessage': localize('security.allowedUNCHosts.patternErrorMessage', 'UNC host names must not contain backslashes.')
222
},
223
'default': [],
224
'markdownDescription': localize('security.allowedUNCHosts', 'A set of UNC host names (without leading or trailing backslash, for example `192.168.0.1` or `my-server`) to allow without user confirmation. If a UNC host is being accessed that is not allowed via this setting or has not been acknowledged via user confirmation, an error will occur and the operation stopped. A restart is required when changing this setting. Find out more about this setting at https://aka.ms/vscode-windows-unc.'),
225
'scope': ConfigurationScope.APPLICATION_MACHINE
226
},
227
'security.restrictUNCAccess': {
228
'type': 'boolean',
229
'default': true,
230
'markdownDescription': localize('security.restrictUNCAccess', 'If enabled, only allows access to UNC host names that are allowed by the `#security.allowedUNCHosts#` setting or after user confirmation. Find out more about this setting at https://aka.ms/vscode-windows-unc.'),
231
'scope': ConfigurationScope.APPLICATION_MACHINE
232
}
233
}
234
});
235
}
236
}
237
238
export const CONFIG_NEW_WINDOW_PROFILE = 'window.newWindowProfile';
239
240
export class DynamicWindowConfiguration extends Disposable implements IWorkbenchContribution {
241
242
static readonly ID = 'workbench.contrib.dynamicWindowConfiguration';
243
244
private configurationNode: IConfigurationNode | undefined;
245
private newWindowProfile: IUserDataProfile | undefined;
246
247
constructor(
248
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
249
@IConfigurationService private readonly configurationService: IConfigurationService,
250
) {
251
super();
252
this.registerNewWindowProfileConfiguration();
253
this._register(this.userDataProfilesService.onDidChangeProfiles((e) => this.registerNewWindowProfileConfiguration()));
254
255
this.setNewWindowProfile();
256
this.checkAndResetNewWindowProfileConfig();
257
258
this._register(configurationService.onDidChangeConfiguration(e => {
259
if (e.source !== ConfigurationTarget.DEFAULT && e.affectsConfiguration(CONFIG_NEW_WINDOW_PROFILE)) {
260
this.setNewWindowProfile();
261
}
262
}));
263
this._register(this.userDataProfilesService.onDidChangeProfiles(() => this.checkAndResetNewWindowProfileConfig()));
264
}
265
266
private registerNewWindowProfileConfiguration(): void {
267
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
268
const configurationNode: IConfigurationNode = {
269
...windowConfigurationNodeBase,
270
'properties': {
271
[CONFIG_NEW_WINDOW_PROFILE]: {
272
'type': ['string', 'null'],
273
'default': null,
274
'enum': [...this.userDataProfilesService.profiles.map(profile => profile.name), null],
275
'enumItemLabels': [...this.userDataProfilesService.profiles.map(() => ''), localize('active window', "Active Window")],
276
'description': localize('newWindowProfile', "Specifies the profile to use when opening a new window. If a profile name is provided, the new window will use that profile. If no profile name is provided, the new window will use the profile of the active window or the Default profile if no active window exists."),
277
'scope': ConfigurationScope.APPLICATION,
278
}
279
}
280
};
281
if (this.configurationNode) {
282
registry.updateConfigurations({ add: [configurationNode], remove: [this.configurationNode] });
283
} else {
284
registry.registerConfiguration(configurationNode);
285
}
286
this.configurationNode = configurationNode;
287
}
288
289
private setNewWindowProfile(): void {
290
const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);
291
this.newWindowProfile = newWindowProfileName ? this.userDataProfilesService.profiles.find(profile => profile.name === newWindowProfileName) : undefined;
292
}
293
294
private checkAndResetNewWindowProfileConfig(): void {
295
const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);
296
if (!newWindowProfileName) {
297
return;
298
}
299
const profile = this.newWindowProfile ? this.userDataProfilesService.profiles.find(profile => profile.id === this.newWindowProfile!.id) : undefined;
300
if (newWindowProfileName === profile?.name) {
301
return;
302
}
303
this.configurationService.updateValue(CONFIG_NEW_WINDOW_PROFILE, profile?.name);
304
}
305
}
306
307