Skip to content

Commit

Permalink
Merge pull request #148732 from microsoft/sandy081/configurationMigra…
Browse files Browse the repository at this point in the history
…tion

Introduce configuration migration utility
  • Loading branch information
sandy081 committed May 5, 2022
2 parents c08941b + ba2569e commit 63076cf
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 87 deletions.
1 change: 1 addition & 0 deletions src/vs/workbench/browser/workbench.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { isStandalone } from 'vs/base/browser/browser';
import 'vs/workbench/common/configurationMigration';

const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);

Expand Down
99 changes: 99 additions & 0 deletions src/vs/workbench/common/configurationMigration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';

export const Extensions = {
ConfigurationMigration: 'base.contributions.configuration.migration'
};

export type ConfigurationValue = { value: any | undefined /* Remove */ };
export type ConfigurationKeyValuePairs = [string, ConfigurationValue][];
export type ConfigurationMigrationFn = (value: any, valueAccessor: (key: string) => any) => ConfigurationValue | ConfigurationKeyValuePairs | Promise<ConfigurationValue | ConfigurationKeyValuePairs>;
export type ConfigurationMigration = { key: string; migrateFn: ConfigurationMigrationFn };

export interface IConfigurationMigrationRegistry {
registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void;
}

class ConfigurationMigrationRegistry implements IConfigurationMigrationRegistry {

readonly migrations: ConfigurationMigration[] = [];

private readonly _onDidRegisterConfigurationMigrations = new Emitter<ConfigurationMigration[]>();
readonly onDidRegisterConfigurationMigration = this._onDidRegisterConfigurationMigrations.event;

registerConfigurationMigrations(configurationMigrations: ConfigurationMigration[]): void {
this.migrations.push(...configurationMigrations);
}

}

const configurationMigrationRegistry = new ConfigurationMigrationRegistry();
Registry.add(Extensions.ConfigurationMigration, configurationMigrationRegistry);

class ConfigurationMigrationWorkbenchContribution extends Disposable implements IWorkbenchContribution {

constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
) {
super();
this._register(this.workspaceService.onDidChangeWorkspaceFolders(async (e) => {
for (const folder of e.added) {
await this.migrateConfigurationsForFolder(folder, configurationMigrationRegistry.migrations);
}
}));
this.migrateConfigurations(configurationMigrationRegistry.migrations);
this._register(configurationMigrationRegistry.onDidRegisterConfigurationMigration(migration => this.migrateConfigurations(migration)));
}

private async migrateConfigurations(migrations: ConfigurationMigration[]): Promise<void> {
await this.migrateConfigurationsForFolder(undefined, migrations);
for (const folder of this.workspaceService.getWorkspace().folders) {
await this.migrateConfigurationsForFolder(folder, migrations);
}
}

private async migrateConfigurationsForFolder(folder: IWorkspaceFolder | undefined, migrations: ConfigurationMigration[]): Promise<void> {
await Promise.all(migrations.map(migration => this.migrateConfigurationsForFolderAndOverride(migration, { resource: folder?.uri })));
}

private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, overrides: IConfigurationOverrides): Promise<void> {
const data = this.configurationService.inspect(migration.key, overrides);

await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'userValue', ConfigurationTarget.USER);
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'userLocalValue', ConfigurationTarget.USER_LOCAL);
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'userRemoteValue', ConfigurationTarget.USER_REMOTE);
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'workspaceFolderValue', ConfigurationTarget.WORKSPACE_FOLDER);
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'workspaceValue', ConfigurationTarget.WORKSPACE);

if (typeof overrides.overrideIdentifier === 'undefined' && typeof data.overrideIdentifiers !== 'undefined') {
for (const overrideIdentifier of data.overrideIdentifiers) {
await this.migrateConfigurationsForFolderAndOverride(migration, { resource: overrides.resource, overrideIdentifier });
}
}
}

private async migrateConfigurationForFolderOverrideAndTarget(migration: ConfigurationMigration, overrides: IConfigurationOverrides, data: IConfigurationValue<any>, dataKey: keyof IConfigurationValue<any>, target: ConfigurationTarget): Promise<void> {
const value = data[dataKey];
if (typeof value === 'undefined') {
return;
}

const valueAccessor = (key: string) => this.configurationService.inspect(key, overrides)[dataKey];
const result = await migration.migrateFn(value, valueAccessor);
const keyValuePairs: ConfigurationKeyValuePairs = Array.isArray(result) ? result : [[migration.key, result]];
await Promise.allSettled(keyValuePairs.map(async ([key, value]) => this.configurationService.updateValue(key, value.value, overrides, target)));
}
}

Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ConfigurationMigrationWorkbenchContribution, LifecyclePhase.Eventually);
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration';
import { EditorSettingMigration, ISettingsReader, ISettingsWriter } from 'vs/editor/browser/config/migrateOptions';
import { Disposable } from 'vs/base/common/lifecycle';

class EditorSettingsMigration extends Disposable implements IWorkbenchContribution {

constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
) {
super();
this._register(this._workspaceService.onDidChangeWorkspaceFolders(async (e) => {
for (const folder of e.added) {
await this._migrateEditorSettingsForFolder(folder);
}
}));
this._migrateEditorSettings();
}

private async _migrateEditorSettings(): Promise<void> {
await this._migrateEditorSettingsForFolder(undefined);
for (const folder of this._workspaceService.getWorkspace().folders) {
await this._migrateEditorSettingsForFolder(folder);
import { EditorSettingMigration, ISettingsWriter } from 'vs/editor/browser/config/migrateOptions';
import { ConfigurationKeyValuePairs, Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configurationMigration';

Registry.as<IConfigurationMigrationRegistry>(Extensions.ConfigurationMigration)
.registerConfigurationMigrations(EditorSettingMigration.items.map(item => ({
key: `editor.${item.key}`,
migrateFn: (value, accessor) => {
const configurationKeyValuePairs: ConfigurationKeyValuePairs = [];
const writer: ISettingsWriter = (key, value) => configurationKeyValuePairs.push([`editor.${key}`, { value }]);
item.migrate(value, key => accessor(`editor.${key}`), writer);
return configurationKeyValuePairs;
}
}

private async _migrateEditorSettingsForFolder(folder: IWorkspaceFolder | undefined): Promise<void> {
await Promise.all(EditorSettingMigration.items.map(migration => this._migrateEditorSettingForFolderAndOverride(migration, { resource: folder?.uri })));
}

private async _migrateEditorSettingForFolderAndOverride(migration: EditorSettingMigration, overrides: IConfigurationOverrides): Promise<void> {
const data = this._configurationService.inspect(`editor.${migration.key}`, overrides);

await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'userValue', ConfigurationTarget.USER);
await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'userLocalValue', ConfigurationTarget.USER_LOCAL);
await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'userRemoteValue', ConfigurationTarget.USER_REMOTE);
await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'workspaceFolderValue', ConfigurationTarget.WORKSPACE_FOLDER);
await this._migrateEditorSettingForFolderOverrideAndTarget(migration, overrides, data, 'workspaceValue', ConfigurationTarget.WORKSPACE);

if (typeof overrides.overrideIdentifier === 'undefined' && typeof data.overrideIdentifiers !== 'undefined') {
for (const overrideIdentifier of data.overrideIdentifiers) {
await this._migrateEditorSettingForFolderAndOverride(migration, { resource: overrides.resource, overrideIdentifier });
}
}
}

private async _migrateEditorSettingForFolderOverrideAndTarget(migration: EditorSettingMigration, overrides: IConfigurationOverrides, data: IConfigurationValue<any>, dataKey: keyof IConfigurationValue<any>, target: ConfigurationTarget): Promise<void> {
const value = data[dataKey];
if (typeof value === 'undefined') {
return;
}

const writeCalls: [string, any][] = [];
const read: ISettingsReader = (key: string) => this._configurationService.inspect(`editor.${key}`, overrides)[dataKey];
const write: ISettingsWriter = (key: string, value: any) => writeCalls.push([key, value]);
migration.migrate(value, read, write);
for (const [wKey, wValue] of writeCalls) {
await this._configurationService.updateValue(`editor.${wKey}`, wValue, overrides, target);
}
}
}

Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorSettingsMigration, LifecyclePhase.Eventually);
})));
26 changes: 4 additions & 22 deletions src/vs/workbench/contrib/search/browser/search.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as nls from 'vs/nls';
import { Action2, MenuId, MenuRegistry, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ICommandAction } from 'vs/platform/action/common/action';
import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IFileService } from 'vs/platform/files/common/files';
Expand Down Expand Up @@ -55,6 +55,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { ISearchConfiguration, SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search';
import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configurationMigration';

registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true);
registerSingleton(ISearchHistoryService, SearchHistoryService, true);
Expand Down Expand Up @@ -666,30 +667,11 @@ class RegisterSearchViewContribution implements IWorkbenchContribution {
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
) {
const data = configurationService.inspect('search.location');

if (data.value === 'panel') {
viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.Panel);
}

if (data.userValue) {
configurationService.updateValue('search.location', undefined, ConfigurationTarget.USER);
}

if (data.userLocalValue) {
configurationService.updateValue('search.location', undefined, ConfigurationTarget.USER_LOCAL);
}

if (data.userRemoteValue) {
configurationService.updateValue('search.location', undefined, ConfigurationTarget.USER_REMOTE);
}

if (data.workspaceFolderValue) {
configurationService.updateValue('search.location', undefined, ConfigurationTarget.WORKSPACE_FOLDER);
}

if (data.workspaceValue) {
configurationService.updateValue('search.location', undefined, ConfigurationTarget.WORKSPACE);
}
Registry.as<IConfigurationMigrationRegistry>(Extensions.ConfigurationMigration)
.registerConfigurationMigrations([{ key: 'search.location', migrateFn: (value: any) => ({ value: undefined }) }]);
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterSearchViewContribution, LifecyclePhase.Starting);
Expand Down

0 comments on commit 63076cf

Please sign in to comment.