import type { Disposable, Event, ExtensionContext, SecretStorageChangeEvent } from 'vscode'; import { EventEmitter } from 'vscode'; import type { ViewShowBranchComparison } from './config'; import type { StoredSearchQuery } from './git/search'; import type { Subscription } from './subscription'; import { debug } from './system/decorators/log'; import type { TrackedUsage, TrackedUsageKeys } from './usageTracker'; import type { CompletedActions } from './webviews/home/protocol'; export type StorageChangeEvent = | { /** * The key of the stored value that has changed. */ readonly key: GlobalStoragePath; readonly workspace: false; } | { /** * The key of the stored value that has changed. */ readonly key: WorkspaceStoragePath; readonly workspace: true; }; export class Storage implements Disposable { private _onDidChange = new EventEmitter(); get onDidChange(): Event { return this._onDidChange.event; } private _onDidChangeSecrets = new EventEmitter(); get onDidChangeSecrets(): Event { return this._onDidChangeSecrets.event; } private readonly _disposable: Disposable; constructor(private readonly context: ExtensionContext) { this._disposable = this.context.secrets.onDidChange(e => this._onDidChangeSecrets.fire(e)); } dispose(): void { this._disposable.dispose(); } get(key: T): GlobalStoragePathValue; get( key: T, defaultValue: NonNullable>, ): NonNullable>; @debug({ logThreshold: 50 }) get( key: T, defaultValue?: GlobalStoragePathValue, ): GlobalStoragePathValue | undefined { return this.context.globalState.get(`gitlens:${key}`, defaultValue); } @debug({ logThreshold: 250 }) async delete(key: T): Promise { await this.context.globalState.update(`gitlens:${key}`, undefined); this._onDidChange.fire({ key: key, workspace: false }); } @debug({ args: { 1: false }, logThreshold: 250 }) async store(key: T, value: GlobalStoragePathValue): Promise { await this.context.globalState.update(`gitlens:${key}`, value); this._onDidChange.fire({ key: key, workspace: false }); } @debug({ args: false, logThreshold: 250 }) async getSecret(key: SecretKeys): Promise { return this.context.secrets.get(key); } @debug({ args: false, logThreshold: 250 }) async deleteSecret(key: SecretKeys): Promise { return this.context.secrets.delete(key); } @debug({ args: false, logThreshold: 250 }) async storeSecret(key: SecretKeys, value: string): Promise { return this.context.secrets.store(key, value); } getWorkspace(key: T): WorkspaceStoragePathValue; getWorkspace( key: T, defaultValue: NonNullable>, ): NonNullable>; @debug({ logThreshold: 25 }) getWorkspace( key: T, defaultValue?: WorkspaceStoragePathValue, ): WorkspaceStoragePathValue | undefined { return this.context.workspaceState.get(`gitlens:${key}`, defaultValue); } @debug({ logThreshold: 250 }) async deleteWorkspace(key: T): Promise { await this.context.workspaceState.update(`gitlens:${key}`, undefined); this._onDidChange.fire({ key: key, workspace: true }); } @debug({ args: { 1: false }, logThreshold: 250 }) async storeWorkspace(key: T, value: WorkspaceStoragePathValue): Promise { await this.context.workspaceState.update(`gitlens:${key}`, value); this._onDidChange.fire({ key: key, workspace: true }); } } export type SecretKeys = string; export const enum DeprecatedStorageKeys { /** @deprecated */ DisallowConnectionPrefix = 'gitlens:disallow:connection:', } export const enum SyncedStorageKeys { Version = 'gitlens:synced:version', PreReleaseVersion = 'gitlens:synced:preVersion', HomeViewWelcomeVisible = 'gitlens:views:welcome:visible', } export interface GlobalStorage { avatars?: [string, StoredAvatar][]; provider: { authentication: { skip: Record; }; }; home: { actions: { completed?: CompletedActions[]; }; steps: { completed?: string[]; }; sections: { dismissed?: string[]; }; }; pendingWelcomeOnFocus?: boolean; pendingWhatsNewOnFocus?: boolean; plus: { migratedAuthentication?: boolean; discountNotificationShown?: boolean; }; // Don't change this key name ('premium`) as its the stored subscription premium: { subscription?: Stored; }; synced: { version?: string; // Keep the pre-release version separate from the released version preVersion?: string; }; usages?: Record; version?: string; // Keep the pre-release version separate from the released version preVersion?: string; views: { welcome: { visible?: boolean; }; }; } export interface WorkspaceStorage { assumeRepositoriesOnStartup?: boolean; branch: { comparisons?: StoredBranchComparisons; }; connected: Record; gitComandPalette: { usage?: RecentUsage; }; gitPath?: string; graph: { banners: { dismissed?: Record; }; columns?: Record; hiddenRefs?: Record; }; remote: { default?: string; }; starred: { branches?: StoredStarred; repositories?: StoredStarred; }; views: { repositories: { autoRefresh?: boolean; }; searchAndCompare: { keepResults?: boolean; pinned?: StoredPinnedItems; }; commitDetails: { autolinksExpanded?: boolean; dismissed?: string[]; }; }; pinned: { /** @deprecated use `gitlens:views:searchAndCompare:pinned` */ comparisons?: DeprecatedPinnedComparisons; }; } export interface Stored { v: SchemaVersion; data: T; } export interface StoredAvatar { uri: string; timestamp: number; } export interface StoredBranchComparison { ref: string; notation: '..' | '...' | undefined; type: Exclude | undefined; } export interface StoredBranchComparisons { [id: string]: string | StoredBranchComparison; } export interface StoredGraphColumn { isHidden?: boolean; width?: number; } export type StoredGraphRefType = 'head' | 'remote' | 'tag'; export interface StoredGraphHiddenRef { id: string; type: StoredGraphRefType; name: string; owner?: string; } export interface StoredNamedRef { label?: string; ref: string; } export interface StoredPinnedComparison { type: 'comparison'; timestamp: number; path: string; ref1: StoredNamedRef; ref2: StoredNamedRef; notation?: '..' | '...'; } export interface StoredPinnedSearch { type: 'search'; timestamp: number; path: string; labels: { label: string; queryLabel: | string | { label: string; resultsType?: { singular: string; plural: string }; }; }; search: StoredSearchQuery; } export type StoredPinnedItem = StoredPinnedComparison | StoredPinnedSearch; export type StoredPinnedItems = Record; export type StoredStarred = Record; export type RecentUsage = Record; interface DeprecatedPinnedComparison { path: string; ref1: StoredNamedRef; ref2: StoredNamedRef; notation?: '..' | '...'; } interface DeprecatedPinnedComparisons { [id: string]: DeprecatedPinnedComparison; } type SubPath = Key extends string ? T[Key] extends Record ? | `${Key}:${SubPath>}` | `${Key}:${Exclude & string}` : never : never; type Path = SubPath | keyof T; type PathValue> = P extends `${infer Key}:${infer Rest}` ? Key extends keyof T ? Rest extends Path ? PathValue : never : never : P extends keyof T ? T[P] : never; type GlobalStoragePath = Path; type GlobalStoragePathValue

= PathValue; type WorkspaceStoragePath = Path; type WorkspaceStoragePathValue

= PathValue;