Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/common/configuration.ts
3291 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
export type ConfigurationValue = { value: any | undefined /* Remove */ };
62
export type ConfigurationKeyValuePairs = [string, ConfigurationValue][];
63
export type ConfigurationMigrationFn = (value: any, valueAccessor: (key: string) => any) => ConfigurationValue | ConfigurationKeyValuePairs | Promise<ConfigurationValue | ConfigurationKeyValuePairs>;
64
export type ConfigurationMigration = { key: string; migrateFn: ConfigurationMigrationFn };
65
66
export interface IConfigurationMigrationRegistry {
67
registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void;
68
}
69
70
class ConfigurationMigrationRegistry implements IConfigurationMigrationRegistry {
71
72
readonly migrations: ConfigurationMigration[] = [];
73
74
private readonly _onDidRegisterConfigurationMigrations = new Emitter<ConfigurationMigration[]>();
75
readonly onDidRegisterConfigurationMigration = this._onDidRegisterConfigurationMigrations.event;
76
77
registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void {
78
this.migrations.push(...configurationMigrations);
79
}
80
81
}
82
83
const configurationMigrationRegistry = new ConfigurationMigrationRegistry();
84
Registry.add(Extensions.ConfigurationMigration, configurationMigrationRegistry);
85
86
export class ConfigurationMigrationWorkbenchContribution extends Disposable implements IWorkbenchContribution {
87
88
static readonly ID = 'workbench.contrib.configurationMigration';
89
90
constructor(
91
@IConfigurationService private readonly configurationService: IConfigurationService,
92
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
93
) {
94
super();
95
this._register(this.workspaceService.onDidChangeWorkspaceFolders(async (e) => {
96
for (const folder of e.added) {
97
await this.migrateConfigurationsForFolder(folder, configurationMigrationRegistry.migrations);
98
}
99
}));
100
this.migrateConfigurations(configurationMigrationRegistry.migrations);
101
this._register(configurationMigrationRegistry.onDidRegisterConfigurationMigration(migration => this.migrateConfigurations(migration)));
102
}
103
104
private async migrateConfigurations(migrations: ConfigurationMigration[]): Promise<void> {
105
await this.migrateConfigurationsForFolder(undefined, migrations);
106
for (const folder of this.workspaceService.getWorkspace().folders) {
107
await this.migrateConfigurationsForFolder(folder, migrations);
108
}
109
}
110
111
private async migrateConfigurationsForFolder(folder: IWorkspaceFolder | undefined, migrations: ConfigurationMigration[]): Promise<void> {
112
await Promise.all([migrations.map(migration => this.migrateConfigurationsForFolderAndOverride(migration, folder?.uri))]);
113
}
114
115
private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, resource?: URI): Promise<void> {
116
const inspectData = this.configurationService.inspect(migration.key, { resource });
117
118
const targetPairs: [keyof IConfigurationValue<any>, ConfigurationTarget][] = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? [
119
['user', ConfigurationTarget.USER],
120
['userLocal', ConfigurationTarget.USER_LOCAL],
121
['userRemote', ConfigurationTarget.USER_REMOTE],
122
['workspace', ConfigurationTarget.WORKSPACE],
123
['workspaceFolder', ConfigurationTarget.WORKSPACE_FOLDER],
124
] : [
125
['user', ConfigurationTarget.USER],
126
['userLocal', ConfigurationTarget.USER_LOCAL],
127
['userRemote', ConfigurationTarget.USER_REMOTE],
128
['workspace', ConfigurationTarget.WORKSPACE],
129
];
130
for (const [dataKey, target] of targetPairs) {
131
const inspectValue = inspectData[dataKey] as IInspectValue<any> | undefined;
132
if (!inspectValue) {
133
continue;
134
}
135
136
const migrationValues: [[string, ConfigurationValue], string[]][] = [];
137
138
if (inspectValue.value !== undefined) {
139
const keyValuePairs = await this.runMigration(migration, dataKey, inspectValue.value, resource, undefined);
140
for (const keyValuePair of keyValuePairs ?? []) {
141
migrationValues.push([keyValuePair, []]);
142
}
143
}
144
145
for (const { identifiers, value } of inspectValue.overrides ?? []) {
146
if (value !== undefined) {
147
const keyValuePairs = await this.runMigration(migration, dataKey, value, resource, identifiers);
148
for (const keyValuePair of keyValuePairs ?? []) {
149
migrationValues.push([keyValuePair, identifiers]);
150
}
151
}
152
}
153
154
if (migrationValues.length) {
155
// apply migrations
156
await Promise.allSettled(migrationValues.map(async ([[key, value], overrideIdentifiers]) =>
157
this.configurationService.updateValue(key, value.value, { resource, overrideIdentifiers }, target)));
158
}
159
}
160
}
161
162
private async runMigration(migration: ConfigurationMigration, dataKey: keyof IConfigurationValue<any>, value: any, resource: URI | undefined, overrideIdentifiers: string[] | undefined): Promise<ConfigurationKeyValuePairs | undefined> {
163
const valueAccessor = (key: string) => {
164
const inspectData = this.configurationService.inspect(key, { resource });
165
const inspectValue = inspectData[dataKey] as IInspectValue<any> | undefined;
166
if (!inspectValue) {
167
return undefined;
168
}
169
if (!overrideIdentifiers) {
170
return inspectValue.value;
171
}
172
return inspectValue.overrides?.find(({ identifiers }) => equals(identifiers, overrideIdentifiers))?.value;
173
};
174
const result = await migration.migrateFn(value, valueAccessor);
175
return Array.isArray(result) ? result : [[migration.key, result]];
176
}
177
}
178
179
export class DynamicWorkbenchSecurityConfiguration extends Disposable implements IWorkbenchContribution {
180
181
static readonly ID = 'workbench.contrib.dynamicWorkbenchSecurityConfiguration';
182
183
private readonly _ready = new DeferredPromise<void>();
184
readonly ready = this._ready.p;
185
186
constructor(
187
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
188
) {
189
super();
190
191
this.create();
192
}
193
194
private async create(): Promise<void> {
195
try {
196
await this.doCreate();
197
} finally {
198
this._ready.complete();
199
}
200
}
201
202
private async doCreate(): Promise<void> {
203
if (!isWindows) {
204
const remoteEnvironment = await this.remoteAgentService.getEnvironment();
205
if (remoteEnvironment?.os !== OperatingSystem.Windows) {
206
return;
207
}
208
}
209
210
// Windows: UNC allow list security configuration
211
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
212
registry.registerConfiguration({
213
...securityConfigurationNodeBase,
214
'properties': {
215
'security.allowedUNCHosts': {
216
'type': 'array',
217
'items': {
218
'type': 'string',
219
'pattern': '^[^\\\\]+$',
220
'patternErrorMessage': localize('security.allowedUNCHosts.patternErrorMessage', 'UNC host names must not contain backslashes.')
221
},
222
'default': [],
223
'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.'),
224
'scope': ConfigurationScope.APPLICATION_MACHINE
225
},
226
'security.restrictUNCAccess': {
227
'type': 'boolean',
228
'default': true,
229
'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.'),
230
'scope': ConfigurationScope.APPLICATION_MACHINE
231
}
232
}
233
});
234
}
235
}
236
237
export const CONFIG_NEW_WINDOW_PROFILE = 'window.newWindowProfile';
238
239
export class DynamicWindowConfiguration extends Disposable implements IWorkbenchContribution {
240
241
static readonly ID = 'workbench.contrib.dynamicWindowConfiguration';
242
243
private configurationNode: IConfigurationNode | undefined;
244
private newWindowProfile: IUserDataProfile | undefined;
245
246
constructor(
247
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
248
@IConfigurationService private readonly configurationService: IConfigurationService,
249
) {
250
super();
251
this.registerNewWindowProfileConfiguration();
252
this._register(this.userDataProfilesService.onDidChangeProfiles((e) => this.registerNewWindowProfileConfiguration()));
253
254
this.setNewWindowProfile();
255
this.checkAndResetNewWindowProfileConfig();
256
257
this._register(configurationService.onDidChangeConfiguration(e => {
258
if (e.source !== ConfigurationTarget.DEFAULT && e.affectsConfiguration(CONFIG_NEW_WINDOW_PROFILE)) {
259
this.setNewWindowProfile();
260
}
261
}));
262
this._register(this.userDataProfilesService.onDidChangeProfiles(() => this.checkAndResetNewWindowProfileConfig()));
263
}
264
265
private registerNewWindowProfileConfiguration(): void {
266
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
267
const configurationNode: IConfigurationNode = {
268
...windowConfigurationNodeBase,
269
'properties': {
270
[CONFIG_NEW_WINDOW_PROFILE]: {
271
'type': ['string', 'null'],
272
'default': null,
273
'enum': [...this.userDataProfilesService.profiles.map(profile => profile.name), null],
274
'enumItemLabels': [...this.userDataProfilesService.profiles.map(p => ''), localize('active window', "Active Window")],
275
'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."),
276
'scope': ConfigurationScope.APPLICATION,
277
}
278
}
279
};
280
if (this.configurationNode) {
281
registry.updateConfigurations({ add: [configurationNode], remove: [this.configurationNode] });
282
} else {
283
registry.registerConfiguration(configurationNode);
284
}
285
this.configurationNode = configurationNode;
286
}
287
288
private setNewWindowProfile(): void {
289
const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);
290
this.newWindowProfile = newWindowProfileName ? this.userDataProfilesService.profiles.find(profile => profile.name === newWindowProfileName) : undefined;
291
}
292
293
private checkAndResetNewWindowProfileConfig(): void {
294
const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);
295
if (!newWindowProfileName) {
296
return;
297
}
298
const profile = this.newWindowProfile ? this.userDataProfilesService.profiles.find(profile => profile.id === this.newWindowProfile!.id) : undefined;
299
if (newWindowProfileName === profile?.name) {
300
return;
301
}
302
this.configurationService.updateValue(CONFIG_NEW_WINDOW_PROFILE, profile?.name);
303
}
304
}
305
306