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<StorageChangeEvent>();
|
|
get onDidChange(): Event<StorageChangeEvent> {
|
|
return this._onDidChange.event;
|
|
}
|
|
|
|
private _onDidChangeSecrets = new EventEmitter<SecretStorageChangeEvent>();
|
|
get onDidChangeSecrets(): Event<SecretStorageChangeEvent> {
|
|
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<T extends GlobalStoragePath>(key: T): GlobalStoragePathValue<T>;
|
|
get<T extends GlobalStoragePath>(
|
|
key: T,
|
|
defaultValue: NonNullable<GlobalStoragePathValue<T>>,
|
|
): NonNullable<GlobalStoragePathValue<T>>;
|
|
@debug({ logThreshold: 50 })
|
|
get<T extends GlobalStoragePath>(
|
|
key: T,
|
|
defaultValue?: GlobalStoragePathValue<T>,
|
|
): GlobalStoragePathValue<T> | undefined {
|
|
return this.context.globalState.get(`gitlens:${key}`, defaultValue);
|
|
}
|
|
|
|
@debug({ logThreshold: 250 })
|
|
async delete<T extends GlobalStoragePath>(key: T): Promise<void> {
|
|
await this.context.globalState.update(`gitlens:${key}`, undefined);
|
|
this._onDidChange.fire({ key: key, workspace: false });
|
|
}
|
|
|
|
@debug({ args: { 1: false }, logThreshold: 250 })
|
|
async store<T extends GlobalStoragePath>(key: T, value: GlobalStoragePathValue<T>): Promise<void> {
|
|
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<string | undefined> {
|
|
return this.context.secrets.get(key);
|
|
}
|
|
|
|
@debug({ args: false, logThreshold: 250 })
|
|
async deleteSecret(key: SecretKeys): Promise<void> {
|
|
return this.context.secrets.delete(key);
|
|
}
|
|
|
|
@debug({ args: false, logThreshold: 250 })
|
|
async storeSecret(key: SecretKeys, value: string): Promise<void> {
|
|
return this.context.secrets.store(key, value);
|
|
}
|
|
|
|
getWorkspace<T extends WorkspaceStoragePath>(key: T): WorkspaceStoragePathValue<T>;
|
|
getWorkspace<T extends WorkspaceStoragePath>(
|
|
key: T,
|
|
defaultValue: NonNullable<WorkspaceStoragePathValue<T>>,
|
|
): NonNullable<WorkspaceStoragePathValue<T>>;
|
|
@debug({ logThreshold: 25 })
|
|
getWorkspace<T extends WorkspaceStoragePath>(
|
|
key: T,
|
|
defaultValue?: WorkspaceStoragePathValue<T>,
|
|
): WorkspaceStoragePathValue<T> | undefined {
|
|
return this.context.workspaceState.get(`gitlens:${key}`, defaultValue);
|
|
}
|
|
|
|
@debug({ logThreshold: 250 })
|
|
async deleteWorkspace<T extends WorkspaceStoragePath>(key: T): Promise<void> {
|
|
await this.context.workspaceState.update(`gitlens:${key}`, undefined);
|
|
this._onDidChange.fire({ key: key, workspace: true });
|
|
}
|
|
|
|
@debug({ args: { 1: false }, logThreshold: 250 })
|
|
async storeWorkspace<T extends WorkspaceStoragePath>(key: T, value: WorkspaceStoragePathValue<T>): Promise<void> {
|
|
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<string, boolean>;
|
|
};
|
|
};
|
|
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<Subscription>;
|
|
};
|
|
synced: {
|
|
version?: string;
|
|
// Keep the pre-release version separate from the released version
|
|
preVersion?: string;
|
|
};
|
|
usages?: Record<TrackedUsageKeys, TrackedUsage>;
|
|
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<string, boolean>;
|
|
gitComandPalette: {
|
|
usage?: RecentUsage;
|
|
};
|
|
gitPath?: string;
|
|
graph: {
|
|
banners: {
|
|
dismissed?: Record<string, boolean>;
|
|
};
|
|
columns?: Record<string, StoredGraphColumn>;
|
|
hiddenRefs?: Record<string, StoredGraphHiddenRef>;
|
|
};
|
|
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<T, SchemaVersion extends number = 1> {
|
|
v: SchemaVersion;
|
|
data: T;
|
|
}
|
|
|
|
export interface StoredAvatar {
|
|
uri: string;
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface StoredBranchComparison {
|
|
ref: string;
|
|
notation: '..' | '...' | undefined;
|
|
type: Exclude<ViewShowBranchComparison, false> | 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<string, StoredPinnedItem>;
|
|
export type StoredStarred = Record<string, boolean>;
|
|
export type RecentUsage = Record<string, number>;
|
|
|
|
interface DeprecatedPinnedComparison {
|
|
path: string;
|
|
ref1: StoredNamedRef;
|
|
ref2: StoredNamedRef;
|
|
notation?: '..' | '...';
|
|
}
|
|
|
|
interface DeprecatedPinnedComparisons {
|
|
[id: string]: DeprecatedPinnedComparison;
|
|
}
|
|
|
|
type SubPath<T, Key extends keyof T> = Key extends string
|
|
? T[Key] extends Record<string, any>
|
|
?
|
|
| `${Key}:${SubPath<T[Key], Exclude<keyof T[Key], keyof any[]>>}`
|
|
| `${Key}:${Exclude<keyof T[Key], keyof any[]> & string}`
|
|
: never
|
|
: never;
|
|
|
|
type Path<T> = SubPath<T, keyof T> | keyof T;
|
|
|
|
type PathValue<T, P extends Path<T>> = P extends `${infer Key}:${infer Rest}`
|
|
? Key extends keyof T
|
|
? Rest extends Path<T[Key]>
|
|
? PathValue<T[Key], Rest>
|
|
: never
|
|
: never
|
|
: P extends keyof T
|
|
? T[P]
|
|
: never;
|
|
|
|
type GlobalStoragePath = Path<GlobalStorage>;
|
|
type GlobalStoragePathValue<P extends GlobalStoragePath> = PathValue<GlobalStorage, P>;
|
|
|
|
type WorkspaceStoragePath = Path<WorkspaceStorage>;
|
|
type WorkspaceStoragePathValue<P extends WorkspaceStoragePath> = PathValue<WorkspaceStorage, P>;
|