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.

335 lines
11 KiB

пре 2 година
пре 2 година
пре 3 година
пре 3 година
пре 3 година
пре 3 година
пре 3 година
пре 3 година
пре 3 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 4 година
пре 3 година
пре 3 година
пре 2 година
пре 3 година
пре 3 година
  1. export * from './config';
  2. import type { ConfigurationChangeEvent, ConfigurationScope, Event, ExtensionContext } from 'vscode';
  3. import { ConfigurationTarget, EventEmitter, workspace } from 'vscode';
  4. import type { Config } from './config';
  5. import { areEqual } from './system/object';
  6. const configPrefix = 'gitlens';
  7. interface ConfigurationOverrides {
  8. get<T extends ConfigPath>(section: T, value: ConfigPathValue<T>): ConfigPathValue<T>;
  9. getAll(config: Config): Config;
  10. onChange(e: ConfigurationChangeEvent): ConfigurationChangeEvent;
  11. }
  12. export class Configuration {
  13. static configure(context: ExtensionContext): void {
  14. context.subscriptions.push(
  15. workspace.onDidChangeConfiguration(configuration.onConfigurationChanged, configuration),
  16. );
  17. }
  18. private _onDidChange = new EventEmitter<ConfigurationChangeEvent>();
  19. get onDidChange(): Event<ConfigurationChangeEvent> {
  20. return this._onDidChange.event;
  21. }
  22. private _onDidChangeAny = new EventEmitter<ConfigurationChangeEvent>();
  23. get onDidChangeAny(): Event<ConfigurationChangeEvent> {
  24. return this._onDidChangeAny.event;
  25. }
  26. private _onWillChange = new EventEmitter<ConfigurationChangeEvent>();
  27. get onWillChange(): Event<ConfigurationChangeEvent> {
  28. return this._onWillChange.event;
  29. }
  30. private onConfigurationChanged(e: ConfigurationChangeEvent) {
  31. if (!e.affectsConfiguration(configPrefix)) {
  32. this._onDidChangeAny.fire(e);
  33. return;
  34. }
  35. this._onWillChange.fire(e);
  36. if (this._overrides?.onChange != null) {
  37. e = this._overrides.onChange(e);
  38. }
  39. this._onDidChangeAny.fire(e);
  40. this._onDidChange.fire(e);
  41. }
  42. private _overrides: Partial<ConfigurationOverrides> | undefined;
  43. applyOverrides(overrides: ConfigurationOverrides): void {
  44. this._overrides = overrides;
  45. }
  46. clearOverrides(): void {
  47. if (this._overrides == null) return;
  48. // Don't clear the "onChange" override as we need to keep it until the stack unwinds (so the the event propagates with the override)
  49. this._overrides.get = undefined;
  50. this._overrides.getAll = undefined;
  51. queueMicrotask(() => (this._overrides = undefined));
  52. }
  53. get<T extends ConfigPath>(section: T, scope?: ConfigurationScope | null): ConfigPathValue<T>;
  54. get<T extends ConfigPath>(
  55. section: T,
  56. scope: ConfigurationScope | null | undefined,
  57. defaultValue: NonNullable<ConfigPathValue<T>>,
  58. ): NonNullable<ConfigPathValue<T>>;
  59. get<T extends ConfigPath>(
  60. section: T,
  61. scope?: ConfigurationScope | null,
  62. defaultValue?: NonNullable<ConfigPathValue<T>>,
  63. ): ConfigPathValue<T> {
  64. const value =
  65. defaultValue === undefined
  66. ? workspace.getConfiguration(configPrefix, scope).get<ConfigPathValue<T>>(section)!
  67. : workspace.getConfiguration(configPrefix, scope).get<ConfigPathValue<T>>(section, defaultValue)!;
  68. return this._overrides?.get == null ? value : this._overrides.get<T>(section, value);
  69. }
  70. getAll(skipOverrides?: boolean): Config {
  71. const config = workspace.getConfiguration().get<Config>(configPrefix)!;
  72. return skipOverrides || this._overrides?.getAll == null ? config : this._overrides.getAll(config);
  73. }
  74. getAny<T>(section: string, scope?: ConfigurationScope | null): T | undefined;
  75. getAny<T>(section: string, scope: ConfigurationScope | null | undefined, defaultValue: T): T;
  76. getAny<T>(section: string, scope?: ConfigurationScope | null, defaultValue?: T): T | undefined {
  77. return defaultValue === undefined
  78. ? workspace.getConfiguration(undefined, scope).get<T>(section)
  79. : workspace.getConfiguration(undefined, scope).get<T>(section, defaultValue);
  80. }
  81. changed<T extends ConfigPath>(
  82. e: ConfigurationChangeEvent | undefined,
  83. section: T | T[],
  84. scope?: ConfigurationScope | null | undefined,
  85. ): boolean {
  86. if (e == null) return true;
  87. return Array.isArray(section)
  88. ? section.some(s => e.affectsConfiguration(`${configPrefix}.${s}`, scope!))
  89. : e.affectsConfiguration(`${configPrefix}.${section}`, scope!);
  90. }
  91. inspect<T extends ConfigPath, V extends ConfigPathValue<T>>(section: T, scope?: ConfigurationScope | null) {
  92. return workspace
  93. .getConfiguration(configPrefix, scope)
  94. .inspect<V>(section === undefined ? configPrefix : section);
  95. }
  96. inspectAny<T>(section: string, scope?: ConfigurationScope | null) {
  97. return workspace.getConfiguration(undefined, scope).inspect<T>(section);
  98. }
  99. async migrate<T extends ConfigPath>(
  100. from: string,
  101. to: T,
  102. options: { fallbackValue?: ConfigPathValue<T>; migrationFn?(value: any): ConfigPathValue<T> },
  103. ): Promise<boolean> {
  104. const inspection = configuration.inspect(from as any);
  105. if (inspection === undefined) return false;
  106. let migrated = false;
  107. if (inspection.globalValue !== undefined) {
  108. await this.update(
  109. to,
  110. options.migrationFn != null ? options.migrationFn(inspection.globalValue) : inspection.globalValue,
  111. ConfigurationTarget.Global,
  112. );
  113. migrated = true;
  114. // 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`
  115. // if (from !== to) {
  116. // try {
  117. // await this.update(from, undefined, ConfigurationTarget.Global);
  118. // }
  119. // catch { }
  120. // }
  121. }
  122. if (inspection.workspaceValue !== undefined) {
  123. await this.update(
  124. to,
  125. options.migrationFn != null
  126. ? options.migrationFn(inspection.workspaceValue)
  127. : inspection.workspaceValue,
  128. ConfigurationTarget.Workspace,
  129. );
  130. migrated = true;
  131. // 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`
  132. // if (from !== to) {
  133. // try {
  134. // await this.update(from, undefined, ConfigurationTarget.Workspace);
  135. // }
  136. // catch { }
  137. // }
  138. }
  139. if (inspection.workspaceFolderValue !== undefined) {
  140. await this.update(
  141. to,
  142. options.migrationFn != null
  143. ? options.migrationFn(inspection.workspaceFolderValue)
  144. : inspection.workspaceFolderValue,
  145. ConfigurationTarget.WorkspaceFolder,
  146. );
  147. migrated = true;
  148. // 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`
  149. // if (from !== to) {
  150. // try {
  151. // await this.update(from, undefined, ConfigurationTarget.WorkspaceFolder);
  152. // }
  153. // catch { }
  154. // }
  155. }
  156. if (!migrated && options.fallbackValue !== undefined) {
  157. await this.update(to, options.fallbackValue, ConfigurationTarget.Global);
  158. migrated = true;
  159. }
  160. return migrated;
  161. }
  162. async migrateIfMissing<T extends ConfigPath>(
  163. from: string,
  164. to: T,
  165. options: { migrationFn?(value: any): ConfigPathValue<T> },
  166. ): Promise<void> {
  167. const fromInspection = configuration.inspect(from as any);
  168. if (fromInspection === undefined) return;
  169. const toInspection = configuration.inspect(to);
  170. if (fromInspection.globalValue !== undefined) {
  171. if (toInspection === undefined || toInspection.globalValue === undefined) {
  172. await this.update(
  173. to,
  174. options.migrationFn != null
  175. ? options.migrationFn(fromInspection.globalValue)
  176. : fromInspection.globalValue,
  177. ConfigurationTarget.Global,
  178. );
  179. // 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`
  180. // if (from !== to) {
  181. // try {
  182. // await this.update(from, undefined, ConfigurationTarget.Global);
  183. // }
  184. // catch { }
  185. // }
  186. }
  187. }
  188. if (fromInspection.workspaceValue !== undefined) {
  189. if (toInspection === undefined || toInspection.workspaceValue === undefined) {
  190. await this.update(
  191. to,
  192. options.migrationFn != null
  193. ? options.migrationFn(fromInspection.workspaceValue)
  194. : fromInspection.workspaceValue,
  195. ConfigurationTarget.Workspace,
  196. );
  197. // 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`
  198. // if (from !== to) {
  199. // try {
  200. // await this.update(from, undefined, ConfigurationTarget.Workspace);
  201. // }
  202. // catch { }
  203. // }
  204. }
  205. }
  206. if (fromInspection.workspaceFolderValue !== undefined) {
  207. if (toInspection === undefined || toInspection.workspaceFolderValue === undefined) {
  208. await this.update(
  209. to,
  210. options.migrationFn != null
  211. ? options.migrationFn(fromInspection.workspaceFolderValue)
  212. : fromInspection.workspaceFolderValue,
  213. ConfigurationTarget.WorkspaceFolder,
  214. );
  215. // 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`
  216. // if (from !== to) {
  217. // try {
  218. // await this.update(from, undefined, ConfigurationTarget.WorkspaceFolder);
  219. // }
  220. // catch { }
  221. // }
  222. }
  223. }
  224. }
  225. matches<T extends ConfigPath>(match: T, section: ConfigPath, value: unknown): value is ConfigPathValue<T> {
  226. return match === section;
  227. }
  228. name<T extends ConfigPath>(section: T): string {
  229. return section;
  230. }
  231. update<T extends ConfigPath>(
  232. section: T,
  233. value: ConfigPathValue<T> | undefined,
  234. target: ConfigurationTarget,
  235. ): Thenable<void> {
  236. return workspace.getConfiguration(configPrefix).update(section, value, target);
  237. }
  238. updateAny(
  239. section: string,
  240. value: any,
  241. target: ConfigurationTarget,
  242. scope?: ConfigurationScope | null,
  243. ): Thenable<void> {
  244. return workspace
  245. .getConfiguration(undefined, target === ConfigurationTarget.Global ? undefined : scope!)
  246. .update(section, value, target);
  247. }
  248. updateEffective<T extends ConfigPath>(section: T, value: ConfigPathValue<T> | undefined): Thenable<void> {
  249. const inspect = configuration.inspect(section)!;
  250. if (inspect.workspaceFolderValue !== undefined) {
  251. if (value === inspect.workspaceFolderValue) return Promise.resolve(undefined);
  252. return configuration.update(section, value, ConfigurationTarget.WorkspaceFolder);
  253. }
  254. if (inspect.workspaceValue !== undefined) {
  255. if (value === inspect.workspaceValue) return Promise.resolve(undefined);
  256. return configuration.update(section, value, ConfigurationTarget.Workspace);
  257. }
  258. if (inspect.globalValue === value || (inspect.globalValue === undefined && value === inspect.defaultValue)) {
  259. return Promise.resolve(undefined);
  260. }
  261. return configuration.update(
  262. section,
  263. areEqual(value, inspect.defaultValue) ? undefined : value,
  264. ConfigurationTarget.Global,
  265. );
  266. }
  267. }
  268. export const configuration = new Configuration();
  269. type SubPath<T, Key extends keyof T> = Key extends string
  270. ? T[Key] extends Record<string, any>
  271. ?
  272. | `${Key}.${SubPath<T[Key], Exclude<keyof T[Key], keyof any[]>> & string}`
  273. | `${Key}.${Exclude<keyof T[Key], keyof any[]> & string}`
  274. : never
  275. : never;
  276. type Path<T> = SubPath<T, keyof T> | keyof T;
  277. type PathValue<T, P extends Path<T>> = P extends `${infer Key}.${infer Rest}`
  278. ? Key extends keyof T
  279. ? Rest extends Path<T[Key]>
  280. ? PathValue<T[Key], Rest>
  281. : never
  282. : never
  283. : P extends keyof T
  284. ? T[P]
  285. : never;
  286. type ConfigPath = Path<Config>;
  287. type ConfigPathValue<P extends ConfigPath> = PathValue<Config, P>;