You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

622 lines
18 KiB

8 years ago
  1. 'use strict';
  2. export * from './config';
  3. import {
  4. ConfigurationChangeEvent,
  5. ConfigurationTarget,
  6. Event,
  7. EventEmitter,
  8. ExtensionContext,
  9. Uri,
  10. workspace
  11. } from 'vscode';
  12. import { Config } from './config';
  13. import { extensionId } from './constants';
  14. import { Objects } from './system';
  15. const emptyConfig: Config = new Proxy<Config>({} as Config, {
  16. get: function() {
  17. return emptyConfig;
  18. }
  19. });
  20. type ConfigInspection<T> = {
  21. key: string;
  22. defaultValue?: T;
  23. globalValue?: T;
  24. workspaceValue?: T;
  25. workspaceFolderValue?: T;
  26. };
  27. export interface ConfigurationWillChangeEvent {
  28. change: ConfigurationChangeEvent;
  29. transform?(e: ConfigurationChangeEvent): ConfigurationChangeEvent;
  30. }
  31. export class Configuration {
  32. static configure(context: ExtensionContext) {
  33. context.subscriptions.push(
  34. workspace.onDidChangeConfiguration(configuration.onConfigurationChanged, configuration)
  35. );
  36. }
  37. private _onDidChange = new EventEmitter<ConfigurationChangeEvent>();
  38. get onDidChange(): Event<ConfigurationChangeEvent> {
  39. return this._onDidChange.event;
  40. }
  41. private _onDidChangeAny = new EventEmitter<ConfigurationChangeEvent>();
  42. get onDidChangeAny(): Event<ConfigurationChangeEvent> {
  43. return this._onDidChange.event;
  44. }
  45. private _onWillChange = new EventEmitter<ConfigurationWillChangeEvent>();
  46. get onWillChange(): Event<ConfigurationWillChangeEvent> {
  47. return this._onWillChange.event;
  48. }
  49. private onConfigurationChanged(e: ConfigurationChangeEvent) {
  50. if (!e.affectsConfiguration(extensionId, null!)) {
  51. this._onDidChangeAny.fire(e);
  52. return;
  53. }
  54. const evt: ConfigurationWillChangeEvent = {
  55. change: e
  56. };
  57. this._onWillChange.fire(evt);
  58. if (evt.transform !== undefined) {
  59. e = evt.transform(e);
  60. }
  61. this._onDidChange.fire(e);
  62. }
  63. readonly initializingChangeEvent: ConfigurationChangeEvent = {
  64. affectsConfiguration: (section: string, resource?: Uri) => true
  65. };
  66. get(): Config;
  67. get<S1 extends keyof Config>(s1: S1, resource?: Uri | null, defaultValue?: Config[S1]): Config[S1];
  68. get<S1 extends keyof Config, S2 extends keyof Config[S1]>(
  69. s1: S1,
  70. s2: S2,
  71. resource?: Uri | null,
  72. defaultValue?: Config[S1][S2]
  73. ): Config[S1][S2];
  74. get<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  75. s1: S1,
  76. s2: S2,
  77. s3: S3,
  78. resource?: Uri | null,
  79. defaultValue?: Config[S1][S2][S3]
  80. ): Config[S1][S2][S3];
  81. get<
  82. S1 extends keyof Config,
  83. S2 extends keyof Config[S1],
  84. S3 extends keyof Config[S1][S2],
  85. S4 extends keyof Config[S1][S2][S3]
  86. >(
  87. s1: S1,
  88. s2: S2,
  89. s3: S3,
  90. s4: S4,
  91. resource?: Uri | null,
  92. defaultValue?: Config[S1][S2][S3][S4]
  93. ): Config[S1][S2][S3][S4];
  94. get<T>(...args: any[]): T {
  95. let section: string | undefined;
  96. let resource: Uri | null | undefined;
  97. let defaultValue: T | undefined;
  98. if (args.length > 0) {
  99. section = args[0];
  100. if (typeof args[1] === 'string') {
  101. section += `.${args[1]}`;
  102. if (typeof args[2] === 'string') {
  103. section += `.${args[2]}`;
  104. if (typeof args[2] === 'string') {
  105. section += `.${args[3]}`;
  106. resource = args[4];
  107. defaultValue = args[5];
  108. } else {
  109. resource = args[3];
  110. defaultValue = args[4];
  111. }
  112. } else {
  113. resource = args[2];
  114. defaultValue = args[3];
  115. }
  116. } else {
  117. resource = args[1];
  118. defaultValue = args[2];
  119. }
  120. }
  121. return defaultValue === undefined
  122. ? workspace
  123. .getConfiguration(section === undefined ? undefined : extensionId, resource)
  124. .get<T>(section === undefined ? extensionId : section)!
  125. : workspace
  126. .getConfiguration(section === undefined ? undefined : extensionId, resource)
  127. .get<T>(section === undefined ? extensionId : section, defaultValue)!;
  128. }
  129. getAny<T>(section: string, resource?: Uri | null, defaultValue?: T) {
  130. return defaultValue === undefined
  131. ? workspace.getConfiguration(undefined, resource).get<T>(section)!
  132. : workspace.getConfiguration(undefined, resource).get<T>(section, defaultValue)!;
  133. }
  134. changed<S1 extends keyof Config>(e: ConfigurationChangeEvent, s1: S1, resource?: Uri | null): boolean;
  135. changed<S1 extends keyof Config, S2 extends keyof Config[S1]>(
  136. e: ConfigurationChangeEvent,
  137. s1: S1,
  138. s2: S2,
  139. resource?: Uri | null
  140. ): boolean;
  141. changed<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  142. e: ConfigurationChangeEvent,
  143. s1: S1,
  144. s2: S2,
  145. s3: S3,
  146. resource?: Uri | null
  147. ): boolean;
  148. changed<
  149. S1 extends keyof Config,
  150. S2 extends keyof Config[S1],
  151. S3 extends keyof Config[S1][S2],
  152. S4 extends keyof Config[S1][S2][S3]
  153. >(e: ConfigurationChangeEvent, s1: S1, s2: S2, s3: S3, s4: S4, resource?: Uri | null): boolean;
  154. changed(e: ConfigurationChangeEvent, ...args: any[]) {
  155. let section: string = args[0];
  156. let resource: Uri | null | undefined;
  157. if (typeof args[1] === 'string') {
  158. section += `.${args[1]}`;
  159. if (typeof args[2] === 'string') {
  160. section += `.${args[2]}`;
  161. if (typeof args[3] === 'string') {
  162. section += args[3];
  163. resource = args[4];
  164. } else {
  165. resource = args[3];
  166. }
  167. } else {
  168. resource = args[2];
  169. }
  170. } else {
  171. resource = args[1];
  172. }
  173. // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
  174. return e.affectsConfiguration(`${extensionId}.${section}`, resource!);
  175. }
  176. initializing(e: ConfigurationChangeEvent) {
  177. return e === this.initializingChangeEvent;
  178. }
  179. inspect<S1 extends keyof Config>(s1: S1, resource?: Uri | null): ConfigInspection<Config[S1]> | undefined;
  180. inspect<S1 extends keyof Config, S2 extends keyof Config[S1]>(
  181. s1: S1,
  182. s2: S2,
  183. resource?: Uri | null
  184. ): ConfigInspection<Config[S1][S2]> | undefined;
  185. inspect<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  186. s1: S1,
  187. s2: S2,
  188. s3: S3,
  189. resource?: Uri | null
  190. ): ConfigInspection<Config[S1][S2][S3]> | undefined;
  191. inspect<
  192. S1 extends keyof Config,
  193. S2 extends keyof Config[S1],
  194. S3 extends keyof Config[S1][S2],
  195. S4 extends keyof Config[S1][S2][S3]
  196. >(s1: S1, s2: S2, s3: S3, s4: S4, resource?: Uri | null): ConfigInspection<Config[S1][S2][S3][S4]> | undefined;
  197. inspect(...args: any[]) {
  198. let section: string = args[0];
  199. let resource: Uri | null | undefined;
  200. if (typeof args[1] === 'string') {
  201. section += `.${args[1]}`;
  202. if (typeof args[2] === 'string') {
  203. section += `.${args[2]}`;
  204. if (typeof args[3] === 'string') {
  205. section += args[3];
  206. resource = args[4];
  207. } else {
  208. resource = args[3];
  209. }
  210. } else {
  211. resource = args[2];
  212. }
  213. } else {
  214. resource = args[1];
  215. }
  216. return workspace
  217. .getConfiguration(section === undefined ? undefined : extensionId, resource)
  218. .inspect(section === undefined ? extensionId : section);
  219. }
  220. inspectAny(section: string, resource?: Uri | null) {
  221. return workspace.getConfiguration(undefined, resource).inspect(section);
  222. }
  223. migrate<S1 extends keyof Config>(
  224. from: string,
  225. to1: S1,
  226. options: { fallbackValue?: Config[S1]; migrationFn?(value: any): Config[S1] }
  227. ): Promise<boolean>;
  228. migrate<S1 extends keyof Config, S2 extends keyof Config[S1]>(
  229. from: string,
  230. to1: S1,
  231. to2: S2,
  232. options: { fallbackValue?: Config[S1][S2]; migrationFn?(value: any): Config[S1][S2] }
  233. ): Promise<boolean>;
  234. migrate<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  235. from: string,
  236. to1: S1,
  237. to2: S2,
  238. to3: S3,
  239. options: { fallbackValue?: Config[S1][S2][S3]; migrationFn?(value: any): Config[S1][S2][S3] }
  240. ): Promise<boolean>;
  241. migrate<
  242. S1 extends keyof Config,
  243. S2 extends keyof Config[S1],
  244. S3 extends keyof Config[S1][S2],
  245. S4 extends keyof Config[S1][S2][S3]
  246. >(
  247. from: string,
  248. to1: S1,
  249. to2: S2,
  250. to3: S3,
  251. to4: S4,
  252. options: { fallbackValue?: Config[S1][S2][S3][S4]; migrationFn?(value: any): Config[S1][S2][S3][S4] }
  253. ): Promise<boolean>;
  254. async migrate(from: string, ...args: any[]): Promise<boolean> {
  255. let to: string = args[0];
  256. let options: { fallbackValue?: any; migrationFn?(value: any): any } | undefined;
  257. if (typeof args[1] === 'string' && args.length > 3) {
  258. to += `.${args[1]}`;
  259. if (typeof args[2] === 'string' && args.length > 4) {
  260. to += `.${args[2]}`;
  261. if (typeof args[3] === 'string' && args.length > 5) {
  262. to += `.${args[3]}`;
  263. options = args[4];
  264. } else {
  265. options = args[3];
  266. }
  267. } else {
  268. options = args[2];
  269. }
  270. } else {
  271. options = args[1];
  272. }
  273. if (options === undefined) {
  274. options = {};
  275. }
  276. const inspection = configuration.inspect(from as any);
  277. if (inspection === undefined) return false;
  278. let migrated = false;
  279. if (inspection.globalValue !== undefined) {
  280. await this.update(
  281. to as any,
  282. options.migrationFn ? options.migrationFn(inspection.globalValue) : inspection.globalValue,
  283. ConfigurationTarget.Global
  284. );
  285. migrated = true;
  286. // Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
  287. // if (from !== to) {
  288. // try {
  289. // await this.update(from, undefined, ConfigurationTarget.Global);
  290. // }
  291. // catch { }
  292. // }
  293. }
  294. if (inspection.workspaceValue !== undefined) {
  295. await this.update(
  296. to as any,
  297. options.migrationFn ? options.migrationFn(inspection.workspaceValue) : inspection.workspaceValue,
  298. ConfigurationTarget.Workspace
  299. );
  300. migrated = true;
  301. // Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
  302. // if (from !== to) {
  303. // try {
  304. // await this.update(from, undefined, ConfigurationTarget.Workspace);
  305. // }
  306. // catch { }
  307. // }
  308. }
  309. if (inspection.workspaceFolderValue !== undefined) {
  310. await this.update(
  311. to as any,
  312. options.migrationFn
  313. ? options.migrationFn(inspection.workspaceFolderValue)
  314. : inspection.workspaceFolderValue,
  315. ConfigurationTarget.WorkspaceFolder
  316. );
  317. migrated = true;
  318. // Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
  319. // if (from !== to) {
  320. // try {
  321. // await this.update(from, undefined, ConfigurationTarget.WorkspaceFolder);
  322. // }
  323. // catch { }
  324. // }
  325. }
  326. if (!migrated && options.fallbackValue !== undefined) {
  327. await this.update(to as any, options.fallbackValue, ConfigurationTarget.Global);
  328. migrated = true;
  329. }
  330. return migrated;
  331. }
  332. migrateIfMissing<S1 extends keyof Config>(
  333. from: string,
  334. to1: S1,
  335. options: { migrationFn?(value: any): Config[S1] }
  336. ): Promise<void>;
  337. migrateIfMissing<S1 extends keyof Config, S2 extends keyof Config[S1]>(
  338. from: string,
  339. to1: S1,
  340. to2: S2,
  341. options: { migrationFn?(value: any): Config[S1][S2] }
  342. ): Promise<void>;
  343. migrateIfMissing<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  344. from: string,
  345. to1: S1,
  346. to2: S2,
  347. to3: S3,
  348. options: { migrationFn?(value: any): Config[S1][S2][S3] }
  349. ): Promise<void>;
  350. migrateIfMissing<
  351. S1 extends keyof Config,
  352. S2 extends keyof Config[S1],
  353. S3 extends keyof Config[S1][S2],
  354. S4 extends keyof Config[S1][S2][S3]
  355. >(
  356. from: string,
  357. to1: S1,
  358. to2: S2,
  359. to3: S3,
  360. to4: S4,
  361. options: { migrationFn?(value: any): Config[S1][S2][S3][S4] }
  362. ): Promise<void>;
  363. async migrateIfMissing(from: string, ...args: any[]): Promise<void> {
  364. let to: string = args[0];
  365. let options: { migrationFn?(value: any): any } | undefined;
  366. if (typeof args[1] === 'string' && args.length > 3) {
  367. to += `.${args[1]}`;
  368. if (typeof args[2] === 'string' && args.length > 4) {
  369. to += `.${args[2]}`;
  370. if (typeof args[3] === 'string' && args.length > 5) {
  371. to += `.${args[3]}`;
  372. options = args[4];
  373. } else {
  374. options = args[3];
  375. }
  376. } else {
  377. options = args[2];
  378. }
  379. } else {
  380. options = args[1];
  381. }
  382. if (options === undefined) {
  383. options = {};
  384. }
  385. // async migrateIfMissing<TFrom, TTo>(from: string, to: string, options: { migrationFn?(value: TFrom): TTo } = {}) {
  386. const fromInspection = configuration.inspect(from as any);
  387. if (fromInspection === undefined) return;
  388. const toInspection = configuration.inspect(to as any);
  389. if (fromInspection.globalValue !== undefined) {
  390. if (toInspection === undefined || toInspection.globalValue === undefined) {
  391. await this.update(
  392. to as any,
  393. options.migrationFn ? options.migrationFn(fromInspection.globalValue) : fromInspection.globalValue,
  394. ConfigurationTarget.Global
  395. );
  396. // Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
  397. // if (from !== to) {
  398. // try {
  399. // await this.update(from, undefined, ConfigurationTarget.Global);
  400. // }
  401. // catch { }
  402. // }
  403. }
  404. }
  405. if (fromInspection.workspaceValue !== undefined) {
  406. if (toInspection === undefined || toInspection.workspaceValue === undefined) {
  407. await this.update(
  408. to as any,
  409. options.migrationFn
  410. ? options.migrationFn(fromInspection.workspaceValue)
  411. : fromInspection.workspaceValue,
  412. ConfigurationTarget.Workspace
  413. );
  414. // Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
  415. // if (from !== to) {
  416. // try {
  417. // await this.update(from, undefined, ConfigurationTarget.Workspace);
  418. // }
  419. // catch { }
  420. // }
  421. }
  422. }
  423. if (fromInspection.workspaceFolderValue !== undefined) {
  424. if (toInspection === undefined || toInspection.workspaceFolderValue === undefined) {
  425. await this.update(
  426. to as any,
  427. options.migrationFn
  428. ? options.migrationFn(fromInspection.workspaceFolderValue)
  429. : fromInspection.workspaceFolderValue,
  430. ConfigurationTarget.WorkspaceFolder
  431. );
  432. // Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
  433. // if (from !== to) {
  434. // try {
  435. // await this.update(from, undefined, ConfigurationTarget.WorkspaceFolder);
  436. // }
  437. // catch { }
  438. // }
  439. }
  440. }
  441. }
  442. name<S1 extends keyof Config>(s1: S1): string;
  443. name<S1 extends keyof Config, S2 extends keyof Config[S1]>(s1: S1, s2: S2): string;
  444. name<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  445. s1: S1,
  446. s2: S2,
  447. s3: S3
  448. ): string;
  449. name<
  450. S1 extends keyof Config,
  451. S2 extends keyof Config[S1],
  452. S3 extends keyof Config[S1][S2],
  453. S4 extends keyof Config[S1][S2][S3]
  454. >(s1: S1, s2: S2, s3: S3, s4: S4): string;
  455. name(...args: string[]) {
  456. return args.join('.');
  457. }
  458. update<S1 extends keyof Config>(s1: S1, value: Config[S1] | undefined, target: ConfigurationTarget): Thenable<void>;
  459. update<S1 extends keyof Config, S2 extends keyof Config[S1]>(
  460. s1: S1,
  461. s2: S2,
  462. value: Config[S1][S2] | undefined,
  463. target: ConfigurationTarget
  464. ): Thenable<void>;
  465. update<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  466. s1: S1,
  467. s2: S2,
  468. s3: S3,
  469. value: Config[S1][S2][S3] | undefined,
  470. target: ConfigurationTarget
  471. ): Thenable<void>;
  472. update<
  473. S1 extends keyof Config,
  474. S2 extends keyof Config[S1],
  475. S3 extends keyof Config[S1][S2],
  476. S4 extends keyof Config[S1][S2][S3]
  477. >(
  478. s1: S1,
  479. s2: S2,
  480. s3: S3,
  481. s4: S4,
  482. value: Config[S1][S2][S3][S4] | undefined,
  483. target: ConfigurationTarget
  484. ): Thenable<void>;
  485. update(...args: any[]) {
  486. let section: string = args[0];
  487. let value;
  488. let target: ConfigurationTarget;
  489. if (typeof args[1] === 'string' && args.length > 3) {
  490. section += `.${args[1]}`;
  491. if (typeof args[2] === 'string' && args.length > 4) {
  492. section += `.${args[2]}`;
  493. if (typeof args[3] === 'string' && args.length > 5) {
  494. section += `.${args[3]}`;
  495. value = args[4];
  496. target = args[5];
  497. } else {
  498. value = args[3];
  499. target = args[4];
  500. }
  501. } else {
  502. value = args[2];
  503. target = args[3];
  504. }
  505. } else {
  506. value = args[1];
  507. target = args[2];
  508. }
  509. return workspace.getConfiguration(extensionId).update(section, value, target);
  510. }
  511. updateAny(section: string, value: any, target: ConfigurationTarget, resource?: Uri | null) {
  512. return workspace
  513. .getConfiguration(undefined, target === ConfigurationTarget.Global ? undefined : resource!)
  514. .update(section, value, target);
  515. }
  516. updateEffective<S1 extends keyof Config>(s1: S1, value: Config[S1]): Thenable<void>;
  517. updateEffective<S1 extends keyof Config, S2 extends keyof Config[S1]>(
  518. s1: S1,
  519. s2: S2,
  520. value: Config[S1][S2]
  521. ): Thenable<void>;
  522. updateEffective<S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2]>(
  523. s1: S1,
  524. s2: S2,
  525. s3: S3,
  526. value: Config[S1][S2][S3]
  527. ): Thenable<void>;
  528. updateEffective<
  529. S1 extends keyof Config,
  530. S2 extends keyof Config[S1],
  531. S3 extends keyof Config[S1][S2],
  532. S4 extends keyof Config[S1][S2][S3]
  533. >(s1: S1, s2: S2, s3: S3, s4: S4, value: Config[S1][S2][S3][S4]): Thenable<void>;
  534. updateEffective(...args: any[]) {
  535. let section: string = args[0];
  536. let value;
  537. if (typeof args[1] === 'string' && args.length > 2) {
  538. section += `.${args[1]}`;
  539. if (typeof args[2] === 'string' && args.length > 3) {
  540. section += `.${args[2]}`;
  541. if (typeof args[3] === 'string' && args.length > 4) {
  542. section += `.${args[3]}`;
  543. value = args[4];
  544. } else {
  545. value = args[3];
  546. }
  547. } else {
  548. value = args[2];
  549. }
  550. } else {
  551. value = args[1];
  552. }
  553. const inspect = configuration.inspect(section as any)!;
  554. if (inspect.workspaceFolderValue !== undefined) {
  555. if (value === inspect.workspaceFolderValue) return Promise.resolve(undefined);
  556. return configuration.update(section as any, value, ConfigurationTarget.WorkspaceFolder);
  557. }
  558. if (inspect.workspaceValue !== undefined) {
  559. if (value === inspect.workspaceValue) return Promise.resolve(undefined);
  560. return configuration.update(section as any, value, ConfigurationTarget.Workspace);
  561. }
  562. if (inspect.globalValue === value || (inspect.globalValue === undefined && value === inspect.defaultValue)) {
  563. return Promise.resolve(undefined);
  564. }
  565. return configuration.update(
  566. section as any,
  567. Objects.areEquivalent(value, inspect.defaultValue) ? undefined : value,
  568. ConfigurationTarget.Global
  569. );
  570. }
  571. }
  572. export const configuration = new Configuration();