Skip to content

Commit be036f3

Browse files
committed
fix #194427
1 parent 901d3e5 commit be036f3

File tree

4 files changed

+74
-21
lines changed

4 files changed

+74
-21
lines changed

src/vs/platform/configuration/common/configurationModels.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ export class Configuration {
912912
return this._workspaceConfiguration;
913913
}
914914

915-
protected get folderConfigurations(): ResourceMap<ConfigurationModel> {
915+
get folderConfigurations(): ResourceMap<ConfigurationModel> {
916916
return this._folderConfigurations;
917917
}
918918

src/vs/workbench/common/configuration.ts

+41-18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
1313
import { Emitter } from 'vs/base/common/event';
1414
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
1515
import { OperatingSystem, isWindows } from 'vs/base/common/platform';
16+
import { URI } from 'vs/base/common/uri';
17+
import { equals } from 'vs/base/common/objects';
1618

1719
export const applicationConfigurationNodeBase = Object.freeze<IConfigurationNode>({
1820
'id': 'application',
@@ -89,35 +91,56 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl
8991
}
9092

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

95-
private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, overrides: IConfigurationOverrides): Promise<void> {
96-
const data = this.configurationService.inspect(migration.key, overrides);
97+
private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, resource?: URI): Promise<void> {
98+
const inspectData = this.configurationService.inspect(migration.key, { resource });
99+
100+
const targetPairs: [keyof IConfigurationValue<any>, ConfigurationTarget][] = [
101+
['userValue', ConfigurationTarget.USER],
102+
['userLocalValue', ConfigurationTarget.USER_LOCAL],
103+
['userRemoteValue', ConfigurationTarget.USER_REMOTE],
104+
['workspaceValue', ConfigurationTarget.WORKSPACE],
105+
['workspaceFolderValue', ConfigurationTarget.WORKSPACE_FOLDER],
106+
];
107+
for (const [dataKey, target] of targetPairs) {
108+
const migrationValues: [[string, ConfigurationValue], string[]][] = [];
109+
110+
// Collect migrations for language overrides
111+
for (const overrideIdentifier of inspectData.overrideIdentifiers ?? []) {
112+
const keyValuePairs = await this.runMigration(migration, { resource, overrideIdentifier }, dataKey);
113+
for (const keyValuePair of keyValuePairs ?? []) {
114+
let keyValueAndOverridesPair = migrationValues.find(([[k, v]]) => k === keyValuePair[0] && equals(v.value, keyValuePair[1].value));
115+
if (!keyValueAndOverridesPair) {
116+
migrationValues.push(keyValueAndOverridesPair = [keyValuePair, []]);
117+
}
118+
keyValueAndOverridesPair[1].push(overrideIdentifier);
119+
}
120+
}
97121

98-
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'userValue', ConfigurationTarget.USER);
99-
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'userLocalValue', ConfigurationTarget.USER_LOCAL);
100-
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'userRemoteValue', ConfigurationTarget.USER_REMOTE);
101-
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'workspaceFolderValue', ConfigurationTarget.WORKSPACE_FOLDER);
102-
await this.migrateConfigurationForFolderOverrideAndTarget(migration, overrides, data, 'workspaceValue', ConfigurationTarget.WORKSPACE);
122+
// Collect migrations
123+
const keyValuePairs = await this.runMigration(migration, { resource }, dataKey, inspectData);
124+
for (const keyValuePair of keyValuePairs ?? []) {
125+
migrationValues.push([keyValuePair, []]);
126+
}
103127

104-
if (typeof overrides.overrideIdentifier === 'undefined' && typeof data.overrideIdentifiers !== 'undefined') {
105-
for (const overrideIdentifier of data.overrideIdentifiers) {
106-
await this.migrateConfigurationsForFolderAndOverride(migration, { resource: overrides.resource, overrideIdentifier });
128+
if (migrationValues.length) {
129+
// apply migrations
130+
await Promise.allSettled(migrationValues.map(async ([[key, value], overrideIdentifiers]) =>
131+
this.configurationService.updateValue(key, value.value, { resource, overrideIdentifiers }, target)));
107132
}
108133
}
109134
}
110135

111-
private async migrateConfigurationForFolderOverrideAndTarget(migration: ConfigurationMigration, overrides: IConfigurationOverrides, data: IConfigurationValue<any>, dataKey: keyof IConfigurationValue<any>, target: ConfigurationTarget): Promise<void> {
112-
const value = data[dataKey];
113-
if (typeof value === 'undefined') {
114-
return;
136+
private async runMigration(migration: ConfigurationMigration, overrides: IConfigurationOverrides, dataKey: keyof IConfigurationValue<any>, data?: IConfigurationValue<any>): Promise<ConfigurationKeyValuePairs | undefined> {
137+
const value = (data ?? this.configurationService.inspect(migration.key, overrides))[dataKey];
138+
if (value === undefined) {
139+
return undefined;
115140
}
116-
117141
const valueAccessor = (key: string) => this.configurationService.inspect(key, overrides)[dataKey];
118142
const result = await migration.migrateFn(value, valueAccessor);
119-
const keyValuePairs: ConfigurationKeyValuePairs = Array.isArray(result) ? result : [[migration.key, result]];
120-
await Promise.allSettled(keyValuePairs.map(async ([key, value]) => this.configurationService.updateValue(key, value.value, overrides, target)));
143+
return Array.isArray(result) ? result : [[migration.key, result]];
121144
}
122145
}
123146

src/vs/workbench/services/configuration/browser/configurationService.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { ILogService } from 'vs/platform/log/common/log';
3333
import { toErrorMessage } from 'vs/base/common/errorMessage';
3434
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
3535
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
36-
import { delta, distinct } from 'vs/base/common/arrays';
36+
import { delta, distinct, equals as arrayEquals } from 'vs/base/common/arrays';
3737
import { IStringDictionary } from 'vs/base/common/collections';
3838
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
3939
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
@@ -1017,6 +1017,17 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
10171017
throw new Error('Invalid configuration target');
10181018
}
10191019

1020+
if (overrides?.overrideIdentifiers?.length && overrides.overrideIdentifiers.length > 1) {
1021+
const configurationModel = this.getConfigurationModel(editableConfigurationTarget, overrides.resource);
1022+
if (configurationModel) {
1023+
const overrideIdentifiers = overrides.overrideIdentifiers.sort();
1024+
const existingOverrides = configurationModel.overrides.find(override => arrayEquals([...override.identifiers].sort(), overrideIdentifiers));
1025+
if (existingOverrides) {
1026+
overrides.overrideIdentifiers = existingOverrides.identifiers;
1027+
}
1028+
}
1029+
}
1030+
10201031
// Use same instance of ConfigurationEditing to make sure all writes go through the same queue
10211032
this.configurationEditing = this.configurationEditing ?? this.instantiationService.createInstance(ConfigurationEditing, (await this.remoteAgentService.getEnvironment())?.settingsPath ?? null);
10221033
await this.configurationEditing.writeConfiguration(editableConfigurationTarget, { key, value }, { scopes: overrides, ...options });
@@ -1041,6 +1052,15 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
10411052
}
10421053
}
10431054

1055+
private getConfigurationModel(target: EditableConfigurationTarget, resource?: URI | null): ConfigurationModel | undefined {
1056+
switch (target) {
1057+
case EditableConfigurationTarget.USER_LOCAL: return this._configuration.localUserConfiguration;
1058+
case EditableConfigurationTarget.USER_REMOTE: return this._configuration.remoteUserConfiguration;
1059+
case EditableConfigurationTarget.WORKSPACE: return this._configuration.workspaceConfiguration;
1060+
case EditableConfigurationTarget.WORKSPACE_FOLDER: return resource ? this._configuration.folderConfigurations.get(resource) : undefined;
1061+
}
1062+
}
1063+
10441064
private deriveConfigurationTargets(key: string, value: any, inspect: IConfigurationValue<any>): ConfigurationTarget[] {
10451065
if (equals(value, inspect.value)) {
10461066
return [];

src/vs/workbench/services/configuration/test/browser/configurationService.test.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1295,12 +1295,22 @@ suite('WorkspaceConfigurationService - Folder', () => {
12951295
}));
12961296

12971297
test('update language configuration for multiple languages', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
1298-
await testObject.updateValue('configurationService.folder.languageSetting', 'multiLangValue', { overrideIdentifiers: ['deflang', 'xyzlang'] }, ConfigurationTarget.USER);
1298+
await testObject.updateValue('configurationService.folder.languageSetting', 'multiLangValue', { overrideIdentifiers: ['xyzlang', 'deflang'] }, ConfigurationTarget.USER);
12991299
assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'deflang' }), 'multiLangValue');
13001300
assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'xyzlang' }), 'multiLangValue');
13011301
assert.deepStrictEqual(testObject.getValue(keyFromOverrideIdentifiers(['deflang', 'xyzlang'])), { 'configurationService.folder.languageSetting': 'multiLangValue' });
13021302
}));
13031303

1304+
test('update language configuration for multiple languages when already set', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
1305+
await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "[deflang][xyzlang]": { "configurationService.folder.languageSetting": "userValue" }}'));
1306+
await testObject.updateValue('configurationService.folder.languageSetting', 'multiLangValue', { overrideIdentifiers: ['xyzlang', 'deflang'] }, ConfigurationTarget.USER);
1307+
assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'deflang' }), 'multiLangValue');
1308+
assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'xyzlang' }), 'multiLangValue');
1309+
assert.deepStrictEqual(testObject.getValue(keyFromOverrideIdentifiers(['deflang', 'xyzlang'])), { 'configurationService.folder.languageSetting': 'multiLangValue' });
1310+
const actualContent = (await fileService.readFile(userDataProfileService.currentProfile.settingsResource)).value.toString();
1311+
assert.deepStrictEqual(JSON.parse(actualContent), { '[deflang][xyzlang]': { 'configurationService.folder.languageSetting': 'multiLangValue' } });
1312+
}));
1313+
13041314
test('update resource language configuration', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
13051315
await testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER);
13061316
assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting'), 'value');

0 commit comments

Comments
 (0)