Path: blob/main/src/vs/workbench/common/configuration.ts
5220 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};5960type ConfigurationValue = { value: unknown | undefined /* Remove */ };61export type ConfigurationKeyValuePairs = [string, ConfigurationValue][];62// eslint-disable-next-line @typescript-eslint/no-explicit-any63export type ConfigurationMigrationFn = (value: any, valueAccessor: (key: string) => any) => ConfigurationValue | ConfigurationKeyValuePairs | Promise<ConfigurationValue | ConfigurationKeyValuePairs>;64export type ConfigurationMigration = { key: string; migrateFn: ConfigurationMigrationFn };6566export interface IConfigurationMigrationRegistry {67registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void;68}6970class ConfigurationMigrationRegistry implements IConfigurationMigrationRegistry {7172readonly migrations: ConfigurationMigration[] = [];7374private readonly _onDidRegisterConfigurationMigrations = new Emitter<ConfigurationMigration[]>();75readonly onDidRegisterConfigurationMigration = this._onDidRegisterConfigurationMigrations.event;7677registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void {78this.migrations.push(...configurationMigrations);79}8081}8283const configurationMigrationRegistry = new ConfigurationMigrationRegistry();84Registry.add(Extensions.ConfigurationMigration, configurationMigrationRegistry);8586export class ConfigurationMigrationWorkbenchContribution extends Disposable implements IWorkbenchContribution {8788static readonly ID = 'workbench.contrib.configurationMigration';8990constructor(91@IConfigurationService private readonly configurationService: IConfigurationService,92@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,93) {94super();95this._register(this.workspaceService.onDidChangeWorkspaceFolders(async (e) => {96for (const folder of e.added) {97await this.migrateConfigurationsForFolder(folder, configurationMigrationRegistry.migrations);98}99}));100this.migrateConfigurations(configurationMigrationRegistry.migrations);101this._register(configurationMigrationRegistry.onDidRegisterConfigurationMigration(migration => this.migrateConfigurations(migration)));102}103104private async migrateConfigurations(migrations: ConfigurationMigration[]): Promise<void> {105await this.migrateConfigurationsForFolder(undefined, migrations);106for (const folder of this.workspaceService.getWorkspace().folders) {107await this.migrateConfigurationsForFolder(folder, migrations);108}109}110111private async migrateConfigurationsForFolder(folder: IWorkspaceFolder | undefined, migrations: ConfigurationMigration[]): Promise<void> {112await Promise.all([migrations.map(migration => this.migrateConfigurationsForFolderAndOverride(migration, folder?.uri))]);113}114115private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, resource?: URI): Promise<void> {116const inspectData = this.configurationService.inspect(migration.key, { resource });117118const targetPairs: [keyof IConfigurationValue<unknown>, 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];130for (const [dataKey, target] of targetPairs) {131const inspectValue = inspectData[dataKey] as IInspectValue<unknown> | undefined;132if (!inspectValue) {133continue;134}135136const migrationValues: [[string, ConfigurationValue], string[]][] = [];137138if (inspectValue.value !== undefined) {139const keyValuePairs = await this.runMigration(migration, dataKey, inspectValue.value, resource, undefined);140for (const keyValuePair of keyValuePairs ?? []) {141migrationValues.push([keyValuePair, []]);142}143}144145for (const { identifiers, value } of inspectValue.overrides ?? []) {146if (value !== undefined) {147const keyValuePairs = await this.runMigration(migration, dataKey, value, resource, identifiers);148for (const keyValuePair of keyValuePairs ?? []) {149migrationValues.push([keyValuePair, identifiers]);150}151}152}153154if (migrationValues.length) {155// apply migrations156await Promise.allSettled(migrationValues.map(async ([[key, value], overrideIdentifiers]) =>157this.configurationService.updateValue(key, value.value, { resource, overrideIdentifiers }, target)));158}159}160}161162private async runMigration(migration: ConfigurationMigration, dataKey: keyof IConfigurationValue<unknown>, value: unknown, resource: URI | undefined, overrideIdentifiers: string[] | undefined): Promise<ConfigurationKeyValuePairs | undefined> {163const valueAccessor = (key: string) => {164const inspectData = this.configurationService.inspect(key, { resource });165const inspectValue = inspectData[dataKey] as IInspectValue<unknown> | undefined;166if (!inspectValue) {167return undefined;168}169if (!overrideIdentifiers) {170return inspectValue.value;171}172return inspectValue.overrides?.find(({ identifiers }) => equals(identifiers, overrideIdentifiers))?.value;173};174const result = await migration.migrateFn(value, valueAccessor);175return Array.isArray(result) ? result : [[migration.key, result]];176}177}178179export class DynamicWorkbenchSecurityConfiguration extends Disposable implements IWorkbenchContribution {180181static readonly ID = 'workbench.contrib.dynamicWorkbenchSecurityConfiguration';182183private readonly _ready = new DeferredPromise<void>();184readonly ready = this._ready.p;185186constructor(187@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService188) {189super();190191this.create();192}193194private async create(): Promise<void> {195try {196await this.doCreate();197} finally {198this._ready.complete();199}200}201202private async doCreate(): Promise<void> {203if (!isWindows) {204const remoteEnvironment = await this.remoteAgentService.getEnvironment();205if (remoteEnvironment?.os !== OperatingSystem.Windows) {206return;207}208}209210// Windows: UNC allow list security configuration211const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);212registry.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_MACHINE225},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_MACHINE231}232}233});234}235}236237export const CONFIG_NEW_WINDOW_PROFILE = 'window.newWindowProfile';238239export class DynamicWindowConfiguration extends Disposable implements IWorkbenchContribution {240241static readonly ID = 'workbench.contrib.dynamicWindowConfiguration';242243private configurationNode: IConfigurationNode | undefined;244private newWindowProfile: IUserDataProfile | undefined;245246constructor(247@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,248@IConfigurationService private readonly configurationService: IConfigurationService,249) {250super();251this.registerNewWindowProfileConfiguration();252this._register(this.userDataProfilesService.onDidChangeProfiles((e) => this.registerNewWindowProfileConfiguration()));253254this.setNewWindowProfile();255this.checkAndResetNewWindowProfileConfig();256257this._register(configurationService.onDidChangeConfiguration(e => {258if (e.source !== ConfigurationTarget.DEFAULT && e.affectsConfiguration(CONFIG_NEW_WINDOW_PROFILE)) {259this.setNewWindowProfile();260}261}));262this._register(this.userDataProfilesService.onDidChangeProfiles(() => this.checkAndResetNewWindowProfileConfig()));263}264265private registerNewWindowProfileConfiguration(): void {266const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);267const 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(() => ''), 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};280if (this.configurationNode) {281registry.updateConfigurations({ add: [configurationNode], remove: [this.configurationNode] });282} else {283registry.registerConfiguration(configurationNode);284}285this.configurationNode = configurationNode;286}287288private setNewWindowProfile(): void {289const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);290this.newWindowProfile = newWindowProfileName ? this.userDataProfilesService.profiles.find(profile => profile.name === newWindowProfileName) : undefined;291}292293private checkAndResetNewWindowProfileConfig(): void {294const newWindowProfileName = this.configurationService.getValue(CONFIG_NEW_WINDOW_PROFILE);295if (!newWindowProfileName) {296return;297}298const profile = this.newWindowProfile ? this.userDataProfilesService.profiles.find(profile => profile.id === this.newWindowProfile!.id) : undefined;299if (newWindowProfileName === profile?.name) {300return;301}302this.configurationService.updateValue(CONFIG_NEW_WINDOW_PROFILE, profile?.name);303}304}305306307