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.

317 lines
8.3 KiB

  1. import type { Disposable, Event, ExtensionContext, SecretStorageChangeEvent } from 'vscode';
  2. import { EventEmitter } from 'vscode';
  3. import type { ViewShowBranchComparison } from './config';
  4. import type { StoredSearchQuery } from './git/search';
  5. import type { Subscription } from './subscription';
  6. import { debug } from './system/decorators/log';
  7. import type { TrackedUsage, TrackedUsageKeys } from './usageTracker';
  8. import type { CompletedActions } from './webviews/home/protocol';
  9. export type StorageChangeEvent =
  10. | {
  11. /**
  12. * The key of the stored value that has changed.
  13. */
  14. readonly key: GlobalStoragePath;
  15. readonly workspace: false;
  16. }
  17. | {
  18. /**
  19. * The key of the stored value that has changed.
  20. */
  21. readonly key: WorkspaceStoragePath;
  22. readonly workspace: true;
  23. };
  24. export class Storage implements Disposable {
  25. private _onDidChange = new EventEmitter<StorageChangeEvent>();
  26. get onDidChange(): Event<StorageChangeEvent> {
  27. return this._onDidChange.event;
  28. }
  29. private _onDidChangeSecrets = new EventEmitter<SecretStorageChangeEvent>();
  30. get onDidChangeSecrets(): Event<SecretStorageChangeEvent> {
  31. return this._onDidChangeSecrets.event;
  32. }
  33. private readonly _disposable: Disposable;
  34. constructor(private readonly context: ExtensionContext) {
  35. this._disposable = this.context.secrets.onDidChange(e => this._onDidChangeSecrets.fire(e));
  36. }
  37. dispose(): void {
  38. this._disposable.dispose();
  39. }
  40. get<T extends GlobalStoragePath>(key: T): GlobalStoragePathValue<T>;
  41. get<T extends GlobalStoragePath>(
  42. key: T,
  43. defaultValue: NonNullable<GlobalStoragePathValue<T>>,
  44. ): NonNullable<GlobalStoragePathValue<T>>;
  45. @debug({ logThreshold: 50 })
  46. get<T extends GlobalStoragePath>(
  47. key: T,
  48. defaultValue?: GlobalStoragePathValue<T>,
  49. ): GlobalStoragePathValue<T> | undefined {
  50. return this.context.globalState.get(`gitlens:${key}`, defaultValue);
  51. }
  52. @debug({ logThreshold: 250 })
  53. async delete<T extends GlobalStoragePath>(key: T): Promise<void> {
  54. await this.context.globalState.update(`gitlens:${key}`, undefined);
  55. this._onDidChange.fire({ key: key, workspace: false });
  56. }
  57. @debug({ args: { 1: false }, logThreshold: 250 })
  58. async store<T extends GlobalStoragePath>(key: T, value: GlobalStoragePathValue<T>): Promise<void> {
  59. await this.context.globalState.update(`gitlens:${key}`, value);
  60. this._onDidChange.fire({ key: key, workspace: false });
  61. }
  62. @debug({ args: false, logThreshold: 250 })
  63. async getSecret(key: SecretKeys): Promise<string | undefined> {
  64. return this.context.secrets.get(key);
  65. }
  66. @debug({ args: false, logThreshold: 250 })
  67. async deleteSecret(key: SecretKeys): Promise<void> {
  68. return this.context.secrets.delete(key);
  69. }
  70. @debug({ args: false, logThreshold: 250 })
  71. async storeSecret(key: SecretKeys, value: string): Promise<void> {
  72. return this.context.secrets.store(key, value);
  73. }
  74. getWorkspace<T extends WorkspaceStoragePath>(key: T): WorkspaceStoragePathValue<T>;
  75. getWorkspace<T extends WorkspaceStoragePath>(
  76. key: T,
  77. defaultValue: NonNullable<WorkspaceStoragePathValue<T>>,
  78. ): NonNullable<WorkspaceStoragePathValue<T>>;
  79. @debug({ logThreshold: 25 })
  80. getWorkspace<T extends WorkspaceStoragePath>(
  81. key: T,
  82. defaultValue?: WorkspaceStoragePathValue<T>,
  83. ): WorkspaceStoragePathValue<T> | undefined {
  84. return this.context.workspaceState.get(`gitlens:${key}`, defaultValue);
  85. }
  86. @debug({ logThreshold: 250 })
  87. async deleteWorkspace<T extends WorkspaceStoragePath>(key: T): Promise<void> {
  88. await this.context.workspaceState.update(`gitlens:${key}`, undefined);
  89. this._onDidChange.fire({ key: key, workspace: true });
  90. }
  91. @debug({ args: { 1: false }, logThreshold: 250 })
  92. async storeWorkspace<T extends WorkspaceStoragePath>(key: T, value: WorkspaceStoragePathValue<T>): Promise<void> {
  93. await this.context.workspaceState.update(`gitlens:${key}`, value);
  94. this._onDidChange.fire({ key: key, workspace: true });
  95. }
  96. }
  97. export type SecretKeys = string;
  98. export const enum DeprecatedStorageKeys {
  99. /** @deprecated */
  100. DisallowConnectionPrefix = 'gitlens:disallow:connection:',
  101. }
  102. export const enum SyncedStorageKeys {
  103. Version = 'gitlens:synced:version',
  104. PreReleaseVersion = 'gitlens:synced:preVersion',
  105. HomeViewWelcomeVisible = 'gitlens:views:welcome:visible',
  106. }
  107. export interface GlobalStorage {
  108. avatars?: [string, StoredAvatar][];
  109. provider: {
  110. authentication: {
  111. skip: Record<string, boolean>;
  112. };
  113. };
  114. home: {
  115. actions: {
  116. completed?: CompletedActions[];
  117. };
  118. steps: {
  119. completed?: string[];
  120. };
  121. sections: {
  122. dismissed?: string[];
  123. };
  124. };
  125. pendingWelcomeOnFocus?: boolean;
  126. pendingWhatsNewOnFocus?: boolean;
  127. plus: {
  128. migratedAuthentication?: boolean;
  129. discountNotificationShown?: boolean;
  130. };
  131. // Don't change this key name ('premium`) as its the stored subscription
  132. premium: {
  133. subscription?: Stored<Subscription>;
  134. };
  135. synced: {
  136. version?: string;
  137. // Keep the pre-release version separate from the released version
  138. preVersion?: string;
  139. };
  140. usages?: Record<TrackedUsageKeys, TrackedUsage>;
  141. version?: string;
  142. // Keep the pre-release version separate from the released version
  143. preVersion?: string;
  144. views: {
  145. welcome: {
  146. visible?: boolean;
  147. };
  148. };
  149. }
  150. export interface WorkspaceStorage {
  151. assumeRepositoriesOnStartup?: boolean;
  152. branch: {
  153. comparisons?: StoredBranchComparisons;
  154. };
  155. connected: Record<string, boolean>;
  156. gitComandPalette: {
  157. usage?: RecentUsage;
  158. };
  159. gitPath?: string;
  160. graph: {
  161. banners: {
  162. dismissed?: Record<string, boolean>;
  163. };
  164. columns?: Record<string, StoredGraphColumn>;
  165. hiddenRefs?: Record<string, StoredGraphHiddenRef>;
  166. };
  167. remote: {
  168. default?: string;
  169. };
  170. starred: {
  171. branches?: StoredStarred;
  172. repositories?: StoredStarred;
  173. };
  174. views: {
  175. repositories: {
  176. autoRefresh?: boolean;
  177. };
  178. searchAndCompare: {
  179. keepResults?: boolean;
  180. pinned?: StoredPinnedItems;
  181. };
  182. commitDetails: {
  183. autolinksExpanded?: boolean;
  184. dismissed?: string[];
  185. };
  186. };
  187. pinned: {
  188. /** @deprecated use `gitlens:views:searchAndCompare:pinned` */
  189. comparisons?: DeprecatedPinnedComparisons;
  190. };
  191. }
  192. export interface Stored<T, SchemaVersion extends number = 1> {
  193. v: SchemaVersion;
  194. data: T;
  195. }
  196. export interface StoredAvatar {
  197. uri: string;
  198. timestamp: number;
  199. }
  200. export interface StoredBranchComparison {
  201. ref: string;
  202. notation: '..' | '...' | undefined;
  203. type: Exclude<ViewShowBranchComparison, false> | undefined;
  204. }
  205. export interface StoredBranchComparisons {
  206. [id: string]: string | StoredBranchComparison;
  207. }
  208. export interface StoredGraphColumn {
  209. isHidden?: boolean;
  210. width?: number;
  211. }
  212. export type StoredGraphRefType = 'head' | 'remote' | 'tag';
  213. export interface StoredGraphHiddenRef {
  214. id: string;
  215. type: StoredGraphRefType;
  216. name: string;
  217. owner?: string;
  218. }
  219. export interface StoredNamedRef {
  220. label?: string;
  221. ref: string;
  222. }
  223. export interface StoredPinnedComparison {
  224. type: 'comparison';
  225. timestamp: number;
  226. path: string;
  227. ref1: StoredNamedRef;
  228. ref2: StoredNamedRef;
  229. notation?: '..' | '...';
  230. }
  231. export interface StoredPinnedSearch {
  232. type: 'search';
  233. timestamp: number;
  234. path: string;
  235. labels: {
  236. label: string;
  237. queryLabel:
  238. | string
  239. | {
  240. label: string;
  241. resultsType?: { singular: string; plural: string };
  242. };
  243. };
  244. search: StoredSearchQuery;
  245. }
  246. export type StoredPinnedItem = StoredPinnedComparison | StoredPinnedSearch;
  247. export type StoredPinnedItems = Record<string, StoredPinnedItem>;
  248. export type StoredStarred = Record<string, boolean>;
  249. export type RecentUsage = Record<string, number>;
  250. interface DeprecatedPinnedComparison {
  251. path: string;
  252. ref1: StoredNamedRef;
  253. ref2: StoredNamedRef;
  254. notation?: '..' | '...';
  255. }
  256. interface DeprecatedPinnedComparisons {
  257. [id: string]: DeprecatedPinnedComparison;
  258. }
  259. type SubPath<T, Key extends keyof T> = Key extends string
  260. ? T[Key] extends Record<string, any>
  261. ?
  262. | `${Key}:${SubPath<T[Key], Exclude<keyof T[Key], keyof any[]>>}`
  263. | `${Key}:${Exclude<keyof T[Key], keyof any[]> & string}`
  264. : never
  265. : never;
  266. type Path<T> = SubPath<T, keyof T> | keyof T;
  267. type PathValue<T, P extends Path<T>> = P extends `${infer Key}:${infer Rest}`
  268. ? Key extends keyof T
  269. ? Rest extends Path<T[Key]>
  270. ? PathValue<T[Key], Rest>
  271. : never
  272. : never
  273. : P extends keyof T
  274. ? T[P]
  275. : never;
  276. type GlobalStoragePath = Path<GlobalStorage>;
  277. type GlobalStoragePathValue<P extends GlobalStoragePath> = PathValue<GlobalStorage, P>;
  278. type WorkspaceStoragePath = Path<WorkspaceStorage>;
  279. type WorkspaceStoragePathValue<P extends WorkspaceStoragePath> = PathValue<WorkspaceStorage, P>;