Path: blob/main/src/vs/workbench/common/configuration.ts
3291 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 { localize } from '../../nls.js';6import { ConfigurationScope, IConfigurationNode, IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../platform/configuration/common/configurationRegistry.js';7import { Registry } from '../../platform/registry/common/platform.js';8import { IWorkbenchContribution } from './contributions.js';9import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from '../../platform/workspace/common/workspace.js';10import { ConfigurationTarget, IConfigurationService, IConfigurationValue, IInspectValue } from '../../platform/configuration/common/configuration.js';11import { Disposable } from '../../base/common/lifecycle.js';12import { Emitter } from '../../base/common/event.js';13import { IRemoteAgentService } from '../services/remote/common/remoteAgentService.js';14import { OperatingSystem, isWindows } from '../../base/common/platform.js';15import { URI } from '../../base/common/uri.js';16import { equals } from '../../base/common/objects.js';17import { DeferredPromise } from '../../base/common/async.js';18import { IUserDataProfile, IUserDataProfilesService } from '../../platform/userDataProfile/common/userDataProfile.js';1920export const applicationConfigurationNodeBase = Object.freeze<IConfigurationNode>({21'id': 'application',22'order': 100,23'title': localize('applicationConfigurationTitle', "Application"),24'type': 'object'25});2627export const workbenchConfigurationNodeBase = Object.freeze<IConfigurationNode>({28'id': 'workbench',29'order': 7,30'title': localize('workbenchConfigurationTitle', "Workbench"),31'type': 'object',32});3334export const securityConfigurationNodeBase = Object.freeze<IConfigurationNode>({35'id': 'security',36'scope': ConfigurationScope.APPLICATION,37'title': localize('securityConfigurationTitle', "Security"),38'type': 'object',39'order': 740});4142export const problemsConfigurationNodeBase = Object.freeze<IConfigurationNode>({43'id': 'problems',44'title': localize('problemsConfigurationTitle', "Problems"),45'type': 'object',46'order': 10147});4849export const windowConfigurationNodeBase = Object.freeze<IConfigurationNode>({50'id': 'window',51'order': 8,52'title': localize('windowConfigurationTitle', "Window"),53'type': 'object',54});5556export const Extensions = {57ConfigurationMigration: 'base.contributions.configuration.migration'58};5960export type ConfigurationValue = { value: any | undefined /* Remove */ };61export type ConfigurationKeyValuePairs = [string, ConfigurationValue][];62export type ConfigurationMigrationFn = (value: any, valueAccessor: (key: string) => any) => ConfigurationValue | ConfigurationKeyValuePairs | Promise<ConfigurationValue | ConfigurationKeyValuePairs>;63export type ConfigurationMigration = { key: string; migrateFn: ConfigurationMigrationFn };6465export interface IConfigurationMigrationRegistry {66registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void;67}6869class ConfigurationMigrationRegistry implements IConfigurationMigrationRegistry {7071readonly migrations: ConfigurationMigration[] = [];7273private readonly _onDidRegisterConfigurationMigrations = new Emitter<ConfigurationMigration[]>();74readonly onDidRegisterConfigurationMigration = this._onDidRegisterConfigurationMigrations.event;7576registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void {77this.migrations.push(...configurationMigrations);78}7980}8182const configurationMigrationRegistry = new ConfigurationMigrationRegistry();83Registry.add(Extensions.ConfigurationMigration, configurationMigrationRegistry);8485export class ConfigurationMigrationWorkbenchContribution extends Disposable implements IWorkbenchContribution {8687static readonly ID = 'workbench.contrib.configurationMigration';8889constructor(90@IConfigurationService private readonly configurationService: IConfigurationService,91@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,92) {93super();94this._register(this.workspaceService.onDidChangeWorkspaceFolders(async (e) => {95for (const folder of e.added) {96await this.migrateConfigurationsForFolder(folder, configurationMigrationRegistry.migrations);97}98}));99this.migrateConfigurations(configurationMigrationRegistry.migrations);100this._register(configurationMigrationRegistry.onDidRegisterConfigurationMigration(migration => this.migrateConfigurations(migration)));101}102103private async migrateConfigurations(migrations: ConfigurationMigration[]): Promise<void> {104await this.migrateConfigurationsForFolder(undefined, migrations);105for (const folder of this.workspaceService.getWorkspace().folders) {106await this.migrateConfigurationsForFolder(folder, migrations);107}108}109110private async migrateConfigurationsForFolder(folder: IWorkspaceFolder | undefined, migrations: ConfigurationMigration[]): Promise<void> {111await Promise.all([migrations.map(migration => this.migrateConfigurationsForFolderAndOverride(migration, folder?.uri))]);112}113114private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, resource?: URI): Promise<void> {115const inspectData = this.configurationService.inspect(migration.key, { resource });116117const targetPairs: [keyof IConfigurationValue<any>, ConfigurationTarget][] = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? [118['user', ConfigurationTarget.USER],119['userLocal', ConfigurationTarget.USER_LOCAL],120['userRemote', ConfigurationTarget.USER_REMOTE],121['workspace', ConfigurationTarget.WORKSPACE],122['workspaceFolder', ConfigurationTarget.WORKSPACE_FOLDER],123] : [124['user', ConfigurationTarget.USER],125['userLocal', ConfigurationTarget.USER_LOCAL],126['userRemote', ConfigurationTarget.USER_REMOTE],127['workspace', ConfigurationTarget.WORKSPACE],128];129for (const [dataKey, target] of targetPairs) {130const inspectValue = inspectData[dataKey] as IInspectValue<any> | undefined;131if (!inspectValue) {132continue;133}134135const migrationValues: [[string, ConfigurationValue], string[]][] = [];136137if (inspectValue.value !== undefined) {138const keyValuePairs = await this.runMigration(migration, dataKey, inspectValue.value, resource, undefined);139for (const keyValuePair of keyValuePairs ?? []) {140migrationValues.push([keyValuePair, []]);141}142}143144for (const { identifiers, value } of inspectValue.overrides ?? []) {145if (value !== undefined) {146const keyValuePairs = await this.runMigration(migration, dataKey, value, resource, identifiers);147for (const keyValuePair of keyValuePairs ?? []) {148migrationValues.push([keyValuePair, identifiers]);149}150}151}152153if (migrationValues.length) {154// apply migrations155await Promise.allSettled(migrationValues.map(async ([[key, value], overrideIdentifiers]) =>156this.configurationService.updateValue(key, value.value, { resource, overrideIdentifiers }, target)));157}158}159}160161private async runMigration(migration: ConfigurationMigration, dataKey: keyof IConfigurationValue<any>, value: any, resource: URI | undefined, overrideIdentifiers: string[] | undefined): Promise<ConfigurationKeyValuePairs | undefined> {162const valueAccessor = (key: string) => {163const inspectData = this.configurationService.inspect(key, { resource });164const inspectValue = inspectData[dataKey] as IInspectValue<any> | undefined;165if (!inspectValue) {166return undefined;167}168if (!overrideIdentifiers) {169return inspectValue.value;170}171return inspectValue.overrides?.find(({ identifiers }) => equals(identifiers, overrideIdentifiers))?.value;172};173const result = await migration.migrateFn(value, valueAccessor);174return Array.isArray(result) ? result : [[migration.key, result]];175}176}177178export class DynamicWorkbenchSecurityConfiguration extends Disposable implements IWorkbenchContribution {179180static readonly ID = 'workbench.contrib.dynamicWorkbenchSecurityConfiguration';181182private readonly _ready = new DeferredPromise<void>();183readonly ready = this._ready.p;184185constructor(186@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService187) {188super();189190this.create();191}192193private async create(): Promise<void> {194try {195await this.doCreate();196} finally {197this._ready.complete();198}199}200201private async doCreate(): Promise<void> {202if (!isWindows) {203const remoteEnvironment = await this.remoteAgentService.getEnvironment();204if (remoteEnvironment?.os !== OperatingSystem.Windows) {205return;206}207}208209// Windows: UNC allow list security configuration210const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);211registry.registerConfiguration({212...securityConfigurationNodeBase,213'properties': {214'security.allowedUNCHosts': {215'type': 'array',216'items': {217'type': 'string',218'pattern': '^[^\\\\]+$',219'patternErrorMessage': localize('security.allowedUNCHosts.patternErrorMessage', 'UNC host names must not contain backslashes.')220},221'default': [],222'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.'),223'scope': ConfigurationScope.APPLICATION_MACHINE224},225'security.restrictUNCAccess': {226'type': 'boolean',227'default': true,228'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.'),229'scope': ConfigurationScope.APPLICATION_MACHINE230}231}232});233}234}235236export const CONFIG_NEW_WINDOW_PROFILE = 'window.newWindowProfile';237238export class DynamicWindowConfiguration extends Disposable implements IWorkbenchContribution {239240static readonly ID = 'workbench.contrib.dynamicWindowConfiguration';241242private configurationNode: IConfigurationNode | undefined;243private newWindowProfile: IUserDataProfile | undefined;244245constructor(246@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,247@IConfigurationService private readonly configurationService: IConfigurationService,248) {249super();250this.registerNewWindowProfileConfiguration();251this._register(this.userDataProfilesService.onDidChangeProfiles((e) => this.registerNewWindowProfileConfiguration()));252253this.setNewWindowProfile();254this.checkAndResetNewWindowProfileConfig();255256this._register(configurationService.onDidChangeConfiguration(e => {257if (e.source !== ConfigurationTarget.DEFAULT && e.affectsConfiguration(CONFIG_NEW_WINDOW_PROFILE)) {258this.setNewWindowProfile();259}260}));261this._register(this.userDataProfilesService.onDidChangeProfiles(() => this.checkAndResetNewWindowProfileConfig()));262}263264private registerNewWindowProfileConfiguration(): void {265const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);266const configurationNode: IConfigurationNode = {267...windowConfigurationNodeBase,268'properties': {269[CONFIG_NEW_WINDOW_PROFILE]: {270'type': ['string', 'null'],271'default': null,272'enum': [...this.userDataProfilesService.profiles.map(profile => profile.name), null],273'enumItemLabels': [...this.userDataProfilesService.profiles.map(p => ''), localize('active window', "Active Window")],274'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."),275'scope': ConfigurationScope.APPLICATION,276}277}278};279if (this.configurationNode) {280registry.updateConfigurations({ add: [configurationNode], remove: [this.configurationNode] });281} else {282registry.registerConfiguration(configurationNode);283}284this.configurationNode = configurationNode;285}286287private setNewWindowProfile(): void {288const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);289this.newWindowProfile = newWindowProfileName ? this.userDataProfilesService.profiles.find(profile => profile.name === newWindowProfileName) : undefined;290}291292private checkAndResetNewWindowProfileConfig(): void {293const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);294if (!newWindowProfileName) {295return;296}297const profile = this.newWindowProfile ? this.userDataProfilesService.profiles.find(profile => profile.id === this.newWindowProfile!.id) : undefined;298if (newWindowProfileName === profile?.name) {299return;300}301this.configurationService.updateValue(CONFIG_NEW_WINDOW_PROFILE, profile?.name);302}303}304305306