'use strict'; export * from './config'; import { ConfigurationChangeEvent, ConfigurationTarget, Event, EventEmitter, ExtensionContext, Uri, workspace } from 'vscode'; import { Config } from './config'; import { extensionId } from './constants'; import { Objects } from './system'; const emptyConfig: Config = new Proxy({} as Config, { get: function() { return emptyConfig; } }); type ConfigInspection = { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T; workspaceFolderValue?: T; }; export interface ConfigurationWillChangeEvent { change: ConfigurationChangeEvent; transform?(e: ConfigurationChangeEvent): ConfigurationChangeEvent; } export class Configuration { static configure(context: ExtensionContext) { context.subscriptions.push( workspace.onDidChangeConfiguration(configuration.onConfigurationChanged, configuration) ); } private _onDidChange = new EventEmitter(); get onDidChange(): Event { return this._onDidChange.event; } private _onDidChangeAny = new EventEmitter(); get onDidChangeAny(): Event { return this._onDidChange.event; } private _onWillChange = new EventEmitter(); get onWillChange(): Event { return this._onWillChange.event; } private onConfigurationChanged(e: ConfigurationChangeEvent) { if (!e.affectsConfiguration(extensionId, null!)) { this._onDidChangeAny.fire(e); return; } const evt: ConfigurationWillChangeEvent = { change: e }; this._onWillChange.fire(evt); if (evt.transform !== undefined) { e = evt.transform(e); } this._onDidChange.fire(e); } readonly initializingChangeEvent: ConfigurationChangeEvent = { affectsConfiguration: (section: string, resource?: Uri) => true }; get(): Config; get(s1: S1, resource?: Uri | null, defaultValue?: Config[S1]): Config[S1]; get( s1: S1, s2: S2, resource?: Uri | null, defaultValue?: Config[S1][S2] ): Config[S1][S2]; get( s1: S1, s2: S2, s3: S3, resource?: Uri | null, defaultValue?: Config[S1][S2][S3] ): Config[S1][S2][S3]; get< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >( s1: S1, s2: S2, s3: S3, s4: S4, resource?: Uri | null, defaultValue?: Config[S1][S2][S3][S4] ): Config[S1][S2][S3][S4]; get(...args: any[]): T { let section: string | undefined; let resource: Uri | null | undefined; let defaultValue: T | undefined; if (args.length > 0) { section = args[0]; if (typeof args[1] === 'string') { section += `.${args[1]}`; if (typeof args[2] === 'string') { section += `.${args[2]}`; if (typeof args[2] === 'string') { section += `.${args[3]}`; resource = args[4]; defaultValue = args[5]; } else { resource = args[3]; defaultValue = args[4]; } } else { resource = args[2]; defaultValue = args[3]; } } else { resource = args[1]; defaultValue = args[2]; } } return defaultValue === undefined ? workspace .getConfiguration(section === undefined ? undefined : extensionId, resource) .get(section === undefined ? extensionId : section)! : workspace .getConfiguration(section === undefined ? undefined : extensionId, resource) .get(section === undefined ? extensionId : section, defaultValue)!; } getAny(section: string, resource?: Uri | null, defaultValue?: T) { return defaultValue === undefined ? workspace.getConfiguration(undefined, resource).get(section)! : workspace.getConfiguration(undefined, resource).get(section, defaultValue)!; } changed(e: ConfigurationChangeEvent, s1: S1, resource?: Uri | null): boolean; changed( e: ConfigurationChangeEvent, s1: S1, s2: S2, resource?: Uri | null ): boolean; changed( e: ConfigurationChangeEvent, s1: S1, s2: S2, s3: S3, resource?: Uri | null ): boolean; changed< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >(e: ConfigurationChangeEvent, s1: S1, s2: S2, s3: S3, s4: S4, resource?: Uri | null): boolean; changed(e: ConfigurationChangeEvent, ...args: any[]) { let section: string = args[0]; let resource: Uri | null | undefined; if (typeof args[1] === 'string') { section += `.${args[1]}`; if (typeof args[2] === 'string') { section += `.${args[2]}`; if (typeof args[3] === 'string') { section += args[3]; resource = args[4]; } else { resource = args[3]; } } else { resource = args[2]; } } else { resource = args[1]; } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return e.affectsConfiguration(`${extensionId}.${section}`, resource!); } initializing(e: ConfigurationChangeEvent) { return e === this.initializingChangeEvent; } inspect(s1: S1, resource?: Uri | null): ConfigInspection | undefined; inspect( s1: S1, s2: S2, resource?: Uri | null ): ConfigInspection | undefined; inspect( s1: S1, s2: S2, s3: S3, resource?: Uri | null ): ConfigInspection | undefined; inspect< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >(s1: S1, s2: S2, s3: S3, s4: S4, resource?: Uri | null): ConfigInspection | undefined; inspect(...args: any[]) { let section: string = args[0]; let resource: Uri | null | undefined; if (typeof args[1] === 'string') { section += `.${args[1]}`; if (typeof args[2] === 'string') { section += `.${args[2]}`; if (typeof args[3] === 'string') { section += args[3]; resource = args[4]; } else { resource = args[3]; } } else { resource = args[2]; } } else { resource = args[1]; } return workspace .getConfiguration(section === undefined ? undefined : extensionId, resource) .inspect(section === undefined ? extensionId : section); } inspectAny(section: string, resource?: Uri | null) { return workspace.getConfiguration(undefined, resource).inspect(section); } migrate( from: string, to1: S1, options: { fallbackValue?: Config[S1]; migrationFn?(value: any): Config[S1] } ): Promise; migrate( from: string, to1: S1, to2: S2, options: { fallbackValue?: Config[S1][S2]; migrationFn?(value: any): Config[S1][S2] } ): Promise; migrate( from: string, to1: S1, to2: S2, to3: S3, options: { fallbackValue?: Config[S1][S2][S3]; migrationFn?(value: any): Config[S1][S2][S3] } ): Promise; migrate< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >( from: string, to1: S1, to2: S2, to3: S3, to4: S4, options: { fallbackValue?: Config[S1][S2][S3][S4]; migrationFn?(value: any): Config[S1][S2][S3][S4] } ): Promise; async migrate(from: string, ...args: any[]): Promise { let to: string = args[0]; let options: { fallbackValue?: any; migrationFn?(value: any): any } | undefined; if (typeof args[1] === 'string' && args.length > 3) { to += `.${args[1]}`; if (typeof args[2] === 'string' && args.length > 4) { to += `.${args[2]}`; if (typeof args[3] === 'string' && args.length > 5) { to += `.${args[3]}`; options = args[4]; } else { options = args[3]; } } else { options = args[2]; } } else { options = args[1]; } if (options === undefined) { options = {}; } const inspection = configuration.inspect(from as any); if (inspection === undefined) return false; let migrated = false; if (inspection.globalValue !== undefined) { await this.update( to as any, options.migrationFn ? options.migrationFn(inspection.globalValue) : inspection.globalValue, ConfigurationTarget.Global ); migrated = true; // Can't delete the old setting currently because it errors with `Unable to write to User Settings because is not a registered configuration` // if (from !== to) { // try { // await this.update(from, undefined, ConfigurationTarget.Global); // } // catch { } // } } if (inspection.workspaceValue !== undefined) { await this.update( to as any, options.migrationFn ? options.migrationFn(inspection.workspaceValue) : inspection.workspaceValue, ConfigurationTarget.Workspace ); migrated = true; // Can't delete the old setting currently because it errors with `Unable to write to User Settings because is not a registered configuration` // if (from !== to) { // try { // await this.update(from, undefined, ConfigurationTarget.Workspace); // } // catch { } // } } if (inspection.workspaceFolderValue !== undefined) { await this.update( to as any, options.migrationFn ? options.migrationFn(inspection.workspaceFolderValue) : inspection.workspaceFolderValue, ConfigurationTarget.WorkspaceFolder ); migrated = true; // Can't delete the old setting currently because it errors with `Unable to write to User Settings because is not a registered configuration` // if (from !== to) { // try { // await this.update(from, undefined, ConfigurationTarget.WorkspaceFolder); // } // catch { } // } } if (!migrated && options.fallbackValue !== undefined) { await this.update(to as any, options.fallbackValue, ConfigurationTarget.Global); migrated = true; } return migrated; } migrateIfMissing( from: string, to1: S1, options: { migrationFn?(value: any): Config[S1] } ): Promise; migrateIfMissing( from: string, to1: S1, to2: S2, options: { migrationFn?(value: any): Config[S1][S2] } ): Promise; migrateIfMissing( from: string, to1: S1, to2: S2, to3: S3, options: { migrationFn?(value: any): Config[S1][S2][S3] } ): Promise; migrateIfMissing< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >( from: string, to1: S1, to2: S2, to3: S3, to4: S4, options: { migrationFn?(value: any): Config[S1][S2][S3][S4] } ): Promise; async migrateIfMissing(from: string, ...args: any[]): Promise { let to: string = args[0]; let options: { migrationFn?(value: any): any } | undefined; if (typeof args[1] === 'string' && args.length > 3) { to += `.${args[1]}`; if (typeof args[2] === 'string' && args.length > 4) { to += `.${args[2]}`; if (typeof args[3] === 'string' && args.length > 5) { to += `.${args[3]}`; options = args[4]; } else { options = args[3]; } } else { options = args[2]; } } else { options = args[1]; } if (options === undefined) { options = {}; } // async migrateIfMissing(from: string, to: string, options: { migrationFn?(value: TFrom): TTo } = {}) { const fromInspection = configuration.inspect(from as any); if (fromInspection === undefined) return; const toInspection = configuration.inspect(to as any); if (fromInspection.globalValue !== undefined) { if (toInspection === undefined || toInspection.globalValue === undefined) { await this.update( to as any, options.migrationFn ? options.migrationFn(fromInspection.globalValue) : fromInspection.globalValue, ConfigurationTarget.Global ); // Can't delete the old setting currently because it errors with `Unable to write to User Settings because is not a registered configuration` // if (from !== to) { // try { // await this.update(from, undefined, ConfigurationTarget.Global); // } // catch { } // } } } if (fromInspection.workspaceValue !== undefined) { if (toInspection === undefined || toInspection.workspaceValue === undefined) { await this.update( to as any, options.migrationFn ? options.migrationFn(fromInspection.workspaceValue) : fromInspection.workspaceValue, ConfigurationTarget.Workspace ); // Can't delete the old setting currently because it errors with `Unable to write to User Settings because is not a registered configuration` // if (from !== to) { // try { // await this.update(from, undefined, ConfigurationTarget.Workspace); // } // catch { } // } } } if (fromInspection.workspaceFolderValue !== undefined) { if (toInspection === undefined || toInspection.workspaceFolderValue === undefined) { await this.update( to as any, options.migrationFn ? options.migrationFn(fromInspection.workspaceFolderValue) : fromInspection.workspaceFolderValue, ConfigurationTarget.WorkspaceFolder ); // Can't delete the old setting currently because it errors with `Unable to write to User Settings because is not a registered configuration` // if (from !== to) { // try { // await this.update(from, undefined, ConfigurationTarget.WorkspaceFolder); // } // catch { } // } } } } name(s1: S1): string; name(s1: S1, s2: S2): string; name( s1: S1, s2: S2, s3: S3 ): string; name< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >(s1: S1, s2: S2, s3: S3, s4: S4): string; name(...args: string[]) { return args.join('.'); } update(s1: S1, value: Config[S1] | undefined, target: ConfigurationTarget): Thenable; update( s1: S1, s2: S2, value: Config[S1][S2] | undefined, target: ConfigurationTarget ): Thenable; update( s1: S1, s2: S2, s3: S3, value: Config[S1][S2][S3] | undefined, target: ConfigurationTarget ): Thenable; update< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >( s1: S1, s2: S2, s3: S3, s4: S4, value: Config[S1][S2][S3][S4] | undefined, target: ConfigurationTarget ): Thenable; update(...args: any[]) { let section: string = args[0]; let value; let target: ConfigurationTarget; if (typeof args[1] === 'string' && args.length > 3) { section += `.${args[1]}`; if (typeof args[2] === 'string' && args.length > 4) { section += `.${args[2]}`; if (typeof args[3] === 'string' && args.length > 5) { section += `.${args[3]}`; value = args[4]; target = args[5]; } else { value = args[3]; target = args[4]; } } else { value = args[2]; target = args[3]; } } else { value = args[1]; target = args[2]; } return workspace.getConfiguration(extensionId).update(section, value, target); } updateAny(section: string, value: any, target: ConfigurationTarget, resource?: Uri | null) { return workspace .getConfiguration(undefined, target === ConfigurationTarget.Global ? undefined : resource!) .update(section, value, target); } updateEffective(s1: S1, value: Config[S1]): Thenable; updateEffective( s1: S1, s2: S2, value: Config[S1][S2] ): Thenable; updateEffective( s1: S1, s2: S2, s3: S3, value: Config[S1][S2][S3] ): Thenable; updateEffective< S1 extends keyof Config, S2 extends keyof Config[S1], S3 extends keyof Config[S1][S2], S4 extends keyof Config[S1][S2][S3] >(s1: S1, s2: S2, s3: S3, s4: S4, value: Config[S1][S2][S3][S4]): Thenable; updateEffective(...args: any[]) { let section: string = args[0]; let value; if (typeof args[1] === 'string' && args.length > 2) { section += `.${args[1]}`; if (typeof args[2] === 'string' && args.length > 3) { section += `.${args[2]}`; if (typeof args[3] === 'string' && args.length > 4) { section += `.${args[3]}`; value = args[4]; } else { value = args[3]; } } else { value = args[2]; } } else { value = args[1]; } const inspect = configuration.inspect(section as any)!; if (inspect.workspaceFolderValue !== undefined) { if (value === inspect.workspaceFolderValue) return Promise.resolve(undefined); return configuration.update(section as any, value, ConfigurationTarget.WorkspaceFolder); } if (inspect.workspaceValue !== undefined) { if (value === inspect.workspaceValue) return Promise.resolve(undefined); return configuration.update(section as any, value, ConfigurationTarget.Workspace); } if (inspect.globalValue === value || (inspect.globalValue === undefined && value === inspect.defaultValue)) { return Promise.resolve(undefined); } return configuration.update( section as any, Objects.areEquivalent(value, inspect.defaultValue) ? undefined : value, ConfigurationTarget.Global ); } } export const configuration = new Configuration();