configurationModels.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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. import * as arrays from '../../../base/common/arrays.js';
  6. import { ResourceMap } from '../../../base/common/map.js';
  7. import * as objects from '../../../base/common/objects.js';
  8. import * as types from '../../../base/common/types.js';
  9. import { URI } from '../../../base/common/uri.js';
  10. import { addToValueTree, getConfigurationValue, removeFromValueTree, toValuesTree } from './configuration.js';
  11. import { Extensions, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from './configurationRegistry.js';
  12. import { Registry } from '../../registry/common/platform.js';
  13. export class ConfigurationModel {
  14. constructor(_contents = {}, _keys = [], _overrides = []) {
  15. this._contents = _contents;
  16. this._keys = _keys;
  17. this._overrides = _overrides;
  18. this.isFrozen = false;
  19. this.overrideConfigurations = new Map();
  20. }
  21. get contents() {
  22. return this.checkAndFreeze(this._contents);
  23. }
  24. get overrides() {
  25. return this.checkAndFreeze(this._overrides);
  26. }
  27. get keys() {
  28. return this.checkAndFreeze(this._keys);
  29. }
  30. isEmpty() {
  31. return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0;
  32. }
  33. getValue(section) {
  34. return section ? getConfigurationValue(this.contents, section) : this.contents;
  35. }
  36. override(identifier) {
  37. let overrideConfigurationModel = this.overrideConfigurations.get(identifier);
  38. if (!overrideConfigurationModel) {
  39. overrideConfigurationModel = this.createOverrideConfigurationModel(identifier);
  40. this.overrideConfigurations.set(identifier, overrideConfigurationModel);
  41. }
  42. return overrideConfigurationModel;
  43. }
  44. merge(...others) {
  45. const contents = objects.deepClone(this.contents);
  46. const overrides = objects.deepClone(this.overrides);
  47. const keys = [...this.keys];
  48. for (const other of others) {
  49. this.mergeContents(contents, other.contents);
  50. for (const otherOverride of other.overrides) {
  51. const [override] = overrides.filter(o => arrays.equals(o.identifiers, otherOverride.identifiers));
  52. if (override) {
  53. this.mergeContents(override.contents, otherOverride.contents);
  54. override.keys.push(...otherOverride.keys);
  55. override.keys = arrays.distinct(override.keys);
  56. }
  57. else {
  58. overrides.push(objects.deepClone(otherOverride));
  59. }
  60. }
  61. for (const key of other.keys) {
  62. if (keys.indexOf(key) === -1) {
  63. keys.push(key);
  64. }
  65. }
  66. }
  67. return new ConfigurationModel(contents, keys, overrides);
  68. }
  69. freeze() {
  70. this.isFrozen = true;
  71. return this;
  72. }
  73. createOverrideConfigurationModel(identifier) {
  74. const overrideContents = this.getContentsForOverrideIdentifer(identifier);
  75. if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) {
  76. // If there are no valid overrides, return self
  77. return this;
  78. }
  79. let contents = {};
  80. for (const key of arrays.distinct([...Object.keys(this.contents), ...Object.keys(overrideContents)])) {
  81. let contentsForKey = this.contents[key];
  82. let overrideContentsForKey = overrideContents[key];
  83. // If there are override contents for the key, clone and merge otherwise use base contents
  84. if (overrideContentsForKey) {
  85. // Clone and merge only if base contents and override contents are of type object otherwise just override
  86. if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') {
  87. contentsForKey = objects.deepClone(contentsForKey);
  88. this.mergeContents(contentsForKey, overrideContentsForKey);
  89. }
  90. else {
  91. contentsForKey = overrideContentsForKey;
  92. }
  93. }
  94. contents[key] = contentsForKey;
  95. }
  96. return new ConfigurationModel(contents, this.keys, this.overrides);
  97. }
  98. mergeContents(source, target) {
  99. for (const key of Object.keys(target)) {
  100. if (key in source) {
  101. if (types.isObject(source[key]) && types.isObject(target[key])) {
  102. this.mergeContents(source[key], target[key]);
  103. continue;
  104. }
  105. }
  106. source[key] = objects.deepClone(target[key]);
  107. }
  108. }
  109. checkAndFreeze(data) {
  110. if (this.isFrozen && !Object.isFrozen(data)) {
  111. return objects.deepFreeze(data);
  112. }
  113. return data;
  114. }
  115. getContentsForOverrideIdentifer(identifier) {
  116. let contentsForIdentifierOnly = null;
  117. let contents = null;
  118. const mergeContents = (contentsToMerge) => {
  119. if (contentsToMerge) {
  120. if (contents) {
  121. this.mergeContents(contents, contentsToMerge);
  122. }
  123. else {
  124. contents = objects.deepClone(contentsToMerge);
  125. }
  126. }
  127. };
  128. for (const override of this.overrides) {
  129. if (arrays.equals(override.identifiers, [identifier])) {
  130. contentsForIdentifierOnly = override.contents;
  131. }
  132. else if (override.identifiers.includes(identifier)) {
  133. mergeContents(override.contents);
  134. }
  135. }
  136. // Merge contents of the identifier only at the end to take precedence.
  137. mergeContents(contentsForIdentifierOnly);
  138. return contents;
  139. }
  140. toJSON() {
  141. return {
  142. contents: this.contents,
  143. overrides: this.overrides,
  144. keys: this.keys
  145. };
  146. }
  147. // Update methods
  148. setValue(key, value) {
  149. this.addKey(key);
  150. addToValueTree(this.contents, key, value, e => { throw new Error(e); });
  151. }
  152. removeValue(key) {
  153. if (this.removeKey(key)) {
  154. removeFromValueTree(this.contents, key);
  155. }
  156. }
  157. addKey(key) {
  158. let index = this.keys.length;
  159. for (let i = 0; i < index; i++) {
  160. if (key.indexOf(this.keys[i]) === 0) {
  161. index = i;
  162. }
  163. }
  164. this.keys.splice(index, 1, key);
  165. }
  166. removeKey(key) {
  167. let index = this.keys.indexOf(key);
  168. if (index !== -1) {
  169. this.keys.splice(index, 1);
  170. return true;
  171. }
  172. return false;
  173. }
  174. }
  175. export class DefaultConfigurationModel extends ConfigurationModel {
  176. constructor(configurationDefaultsOverrides = {}) {
  177. const properties = Registry.as(Extensions.Configuration).getConfigurationProperties();
  178. const keys = Object.keys(properties);
  179. const contents = Object.create(null);
  180. const overrides = [];
  181. for (const key in properties) {
  182. const defaultOverrideValue = configurationDefaultsOverrides[key];
  183. const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default;
  184. addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`));
  185. }
  186. for (const key of Object.keys(contents)) {
  187. if (OVERRIDE_PROPERTY_REGEX.test(key)) {
  188. overrides.push({
  189. identifiers: overrideIdentifiersFromKey(key),
  190. keys: Object.keys(contents[key]),
  191. contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)),
  192. });
  193. }
  194. }
  195. super(contents, keys, overrides);
  196. }
  197. }
  198. export class Configuration {
  199. constructor(_defaultConfiguration, _localUserConfiguration, _remoteUserConfiguration = new ConfigurationModel(), _workspaceConfiguration = new ConfigurationModel(), _folderConfigurations = new ResourceMap(), _memoryConfiguration = new ConfigurationModel(), _memoryConfigurationByResource = new ResourceMap(), _freeze = true) {
  200. this._defaultConfiguration = _defaultConfiguration;
  201. this._localUserConfiguration = _localUserConfiguration;
  202. this._remoteUserConfiguration = _remoteUserConfiguration;
  203. this._workspaceConfiguration = _workspaceConfiguration;
  204. this._folderConfigurations = _folderConfigurations;
  205. this._memoryConfiguration = _memoryConfiguration;
  206. this._memoryConfigurationByResource = _memoryConfigurationByResource;
  207. this._freeze = _freeze;
  208. this._workspaceConsolidatedConfiguration = null;
  209. this._foldersConsolidatedConfigurations = new ResourceMap();
  210. this._userConfiguration = null;
  211. }
  212. getValue(section, overrides, workspace) {
  213. const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace);
  214. return consolidateConfigurationModel.getValue(section);
  215. }
  216. updateValue(key, value, overrides = {}) {
  217. let memoryConfiguration;
  218. if (overrides.resource) {
  219. memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource);
  220. if (!memoryConfiguration) {
  221. memoryConfiguration = new ConfigurationModel();
  222. this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration);
  223. }
  224. }
  225. else {
  226. memoryConfiguration = this._memoryConfiguration;
  227. }
  228. if (value === undefined) {
  229. memoryConfiguration.removeValue(key);
  230. }
  231. else {
  232. memoryConfiguration.setValue(key, value);
  233. }
  234. if (!overrides.resource) {
  235. this._workspaceConsolidatedConfiguration = null;
  236. }
  237. }
  238. get userConfiguration() {
  239. if (!this._userConfiguration) {
  240. this._userConfiguration = this._remoteUserConfiguration.isEmpty() ? this._localUserConfiguration : this._localUserConfiguration.merge(this._remoteUserConfiguration);
  241. if (this._freeze) {
  242. this._userConfiguration.freeze();
  243. }
  244. }
  245. return this._userConfiguration;
  246. }
  247. getConsolidateConfigurationModel(overrides, workspace) {
  248. let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace);
  249. return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel;
  250. }
  251. getConsolidatedConfigurationModelForResource({ resource }, workspace) {
  252. let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
  253. if (workspace && resource) {
  254. const root = workspace.getFolder(resource);
  255. if (root) {
  256. consolidateConfiguration = this.getFolderConsolidatedConfiguration(root.uri) || consolidateConfiguration;
  257. }
  258. const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource);
  259. if (memoryConfigurationForResource) {
  260. consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource);
  261. }
  262. }
  263. return consolidateConfiguration;
  264. }
  265. getWorkspaceConsolidatedConfiguration() {
  266. if (!this._workspaceConsolidatedConfiguration) {
  267. this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this.userConfiguration, this._workspaceConfiguration, this._memoryConfiguration);
  268. if (this._freeze) {
  269. this._workspaceConfiguration = this._workspaceConfiguration.freeze();
  270. }
  271. }
  272. return this._workspaceConsolidatedConfiguration;
  273. }
  274. getFolderConsolidatedConfiguration(folder) {
  275. let folderConsolidatedConfiguration = this._foldersConsolidatedConfigurations.get(folder);
  276. if (!folderConsolidatedConfiguration) {
  277. const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
  278. const folderConfiguration = this._folderConfigurations.get(folder);
  279. if (folderConfiguration) {
  280. folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration);
  281. if (this._freeze) {
  282. folderConsolidatedConfiguration = folderConsolidatedConfiguration.freeze();
  283. }
  284. this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration);
  285. }
  286. else {
  287. folderConsolidatedConfiguration = workspaceConsolidateConfiguration;
  288. }
  289. }
  290. return folderConsolidatedConfiguration;
  291. }
  292. toData() {
  293. return {
  294. defaults: {
  295. contents: this._defaultConfiguration.contents,
  296. overrides: this._defaultConfiguration.overrides,
  297. keys: this._defaultConfiguration.keys
  298. },
  299. user: {
  300. contents: this.userConfiguration.contents,
  301. overrides: this.userConfiguration.overrides,
  302. keys: this.userConfiguration.keys
  303. },
  304. workspace: {
  305. contents: this._workspaceConfiguration.contents,
  306. overrides: this._workspaceConfiguration.overrides,
  307. keys: this._workspaceConfiguration.keys
  308. },
  309. folders: [...this._folderConfigurations.keys()].reduce((result, folder) => {
  310. const { contents, overrides, keys } = this._folderConfigurations.get(folder);
  311. result.push([folder, { contents, overrides, keys }]);
  312. return result;
  313. }, [])
  314. };
  315. }
  316. static parse(data) {
  317. const defaultConfiguration = this.parseConfigurationModel(data.defaults);
  318. const userConfiguration = this.parseConfigurationModel(data.user);
  319. const workspaceConfiguration = this.parseConfigurationModel(data.workspace);
  320. const folders = data.folders.reduce((result, value) => {
  321. result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1]));
  322. return result;
  323. }, new ResourceMap());
  324. return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false);
  325. }
  326. static parseConfigurationModel(model) {
  327. return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze();
  328. }
  329. }
  330. export class ConfigurationChangeEvent {
  331. constructor(change, previous, currentConfiguraiton, currentWorkspace) {
  332. this.change = change;
  333. this.previous = previous;
  334. this.currentConfiguraiton = currentConfiguraiton;
  335. this.currentWorkspace = currentWorkspace;
  336. this._previousConfiguration = undefined;
  337. const keysSet = new Set();
  338. change.keys.forEach(key => keysSet.add(key));
  339. change.overrides.forEach(([, keys]) => keys.forEach(key => keysSet.add(key)));
  340. this.affectedKeys = [...keysSet.values()];
  341. const configurationModel = new ConfigurationModel();
  342. this.affectedKeys.forEach(key => configurationModel.setValue(key, {}));
  343. this.affectedKeysTree = configurationModel.contents;
  344. }
  345. get previousConfiguration() {
  346. if (!this._previousConfiguration && this.previous) {
  347. this._previousConfiguration = Configuration.parse(this.previous.data);
  348. }
  349. return this._previousConfiguration;
  350. }
  351. affectsConfiguration(section, overrides) {
  352. var _a;
  353. if (this.doesAffectedKeysTreeContains(this.affectedKeysTree, section)) {
  354. if (overrides) {
  355. const value1 = this.previousConfiguration ? this.previousConfiguration.getValue(section, overrides, (_a = this.previous) === null || _a === void 0 ? void 0 : _a.workspace) : undefined;
  356. const value2 = this.currentConfiguraiton.getValue(section, overrides, this.currentWorkspace);
  357. return !objects.equals(value1, value2);
  358. }
  359. return true;
  360. }
  361. return false;
  362. }
  363. doesAffectedKeysTreeContains(affectedKeysTree, section) {
  364. let requestedTree = toValuesTree({ [section]: true }, () => { });
  365. let key;
  366. while (typeof requestedTree === 'object' && (key = Object.keys(requestedTree)[0])) { // Only one key should present, since we added only one property
  367. affectedKeysTree = affectedKeysTree[key];
  368. if (!affectedKeysTree) {
  369. return false; // Requested tree is not found
  370. }
  371. requestedTree = requestedTree[key];
  372. }
  373. return true;
  374. }
  375. }