Browse Source

Reworks how modes apply to configuration

- Adds override support to configuration
 - Splits configuration.get into get & getAll
 - Allows passing array to configuration.changed
Avoids getting the entire configuration on startup
main
Eric Amodio 2 years ago
parent
commit
41bc88c1b1
7 changed files with 171 additions and 120 deletions
  1. +4
    -4
      src/commands/switchMode.ts
  2. +46
    -28
      src/configuration.ts
  3. +104
    -73
      src/container.ts
  4. +10
    -8
      src/extension.ts
  5. +5
    -5
      src/quickpicks/modePicker.ts
  6. +1
    -1
      src/webviews/settings/settingsWebview.ts
  7. +1
    -1
      src/webviews/webviewWithConfigBase.ts

+ 4
- 4
src/commands/switchMode.ts View File

@ -25,15 +25,15 @@ export class SwitchModeCommand extends Command {
cc.exitDetails = ` \u2014 mode=${pick.key ?? ''}`;
}
const active = this.container.config.mode.active;
const active = configuration.get('mode.active');
if (active === pick.key) return;
// Check if we have applied any annotations and clear them if we won't be applying them again
if (active != null && active.length !== 0) {
const activeAnnotations = this.container.config.modes?.[active].annotations;
const modes = configuration.get('modes');
const activeAnnotations = modes?.[active].annotations;
if (activeAnnotations != null) {
const newAnnotations =
pick.key != null ? this.container.config.modes?.[pick.key].annotations : undefined;
const newAnnotations = pick.key != null ? modes?.[pick.key].annotations : undefined;
if (activeAnnotations !== newAnnotations) {
await this.container.fileAnnotations.clearAll();
}

+ 46
- 28
src/configuration.ts View File

@ -14,9 +14,10 @@ import { areEqual } from './system/object';
const configPrefix = 'gitlens';
export interface ConfigurationWillChangeEvent {
change: ConfigurationChangeEvent;
transform?(e: ConfigurationChangeEvent): ConfigurationChangeEvent;
interface ConfigurationOverrides {
get<T extends ConfigPath>(section: T, value: ConfigPathValue<T>): ConfigPathValue<T>;
getAll(config: Config): Config;
onChange(e: ConfigurationChangeEvent): ConfigurationChangeEvent;
}
export class Configuration {
@ -36,8 +37,8 @@ export class Configuration {
return this._onDidChangeAny.event;
}
private _onWillChange = new EventEmitter<ConfigurationWillChangeEvent>();
get onWillChange(): Event<ConfigurationWillChangeEvent> {
private _onWillChange = new EventEmitter<ConfigurationChangeEvent>();
get onWillChange(): Event<ConfigurationChangeEvent> {
return this._onWillChange.event;
}
@ -48,37 +49,46 @@ export class Configuration {
return;
}
const evt: ConfigurationWillChangeEvent = {
change: e,
};
this._onWillChange.fire(evt);
this._onWillChange.fire(e);
if (evt.transform !== undefined) {
e = evt.transform(e);
if (this._overrides?.onChange != null) {
e = this._overrides.onChange(e);
}
this._onDidChangeAny.fire(e);
this._onDidChange.fire(e);
}
get(): Config;
private _overrides: Partial<ConfigurationOverrides> | undefined;
applyOverrides(overrides: ConfigurationOverrides): void {
this._overrides = overrides;
}
clearOverrides(): void {
if (this._overrides == null) return;
// Don't clear the "onChange" override as we need to keep it until the stack unwinds (so the the event propagates with the override)
this._overrides.get = undefined;
this._overrides.getAll = undefined;
queueMicrotask(() => (this._overrides = undefined));
}
get<T extends ConfigPath>(
section: T,
scope?: ConfigurationScope | null,
defaultValue?: ConfigPathValue<T>,
): ConfigPathValue<T>;
get<T extends ConfigPath>(
section?: T,
scope?: ConfigurationScope | null,
defaultValue?: ConfigPathValue<T>,
): Config | ConfigPathValue<T> {
return defaultValue === undefined
? workspace
.getConfiguration(section === undefined ? undefined : configPrefix, scope)
.get<ConfigPathValue<T>>(section === undefined ? configPrefix : section)!
: workspace
.getConfiguration(section === undefined ? undefined : configPrefix, scope)
.get<ConfigPathValue<T>>(section === undefined ? configPrefix : section, defaultValue)!;
): ConfigPathValue<T> {
const value =
defaultValue === undefined
? workspace.getConfiguration(configPrefix, scope).get<ConfigPathValue<T>>(section)!
: workspace.getConfiguration(configPrefix, scope).get<ConfigPathValue<T>>(section, defaultValue)!;
return this._overrides?.get == null ? value : this._overrides.get<T>(section, value);
}
getAll(skipOverrides?: boolean): Config {
const config = workspace.getConfiguration().get<Config>(configPrefix)!;
return skipOverrides || this._overrides?.getAll == null ? config : this._overrides.getAll(config);
}
getAny<T>(section: string, scope?: ConfigurationScope | null): T | undefined;
@ -91,15 +101,19 @@ export class Configuration {
changed<T extends ConfigPath>(
e: ConfigurationChangeEvent | undefined,
section: T,
section: T | T[],
scope?: ConfigurationScope | null | undefined,
): boolean {
return e?.affectsConfiguration(`${configPrefix}.${section}`, scope!) ?? true;
if (e == null) return true;
return Array.isArray(section)
? section.some(s => e.affectsConfiguration(`${configPrefix}.${s}`, scope!))
: e.affectsConfiguration(`${configPrefix}.${section}`, scope!);
}
inspect<T extends ConfigPath, V extends ConfigPathValue<T>>(section: T, scope?: ConfigurationScope | null) {
return workspace
.getConfiguration(section === undefined ? undefined : configPrefix, scope)
.getConfiguration(configPrefix, scope)
.inspect<V>(section === undefined ? configPrefix : section);
}
@ -243,6 +257,10 @@ export class Configuration {
}
}
matches<T extends ConfigPath>(match: T, section: ConfigPath, value: unknown): value is ConfigPathValue<T> {
return match === section;
}
name<T extends ConfigPath>(section: T): string {
return section;
}

+ 104
- 73
src/container.ts View File

@ -1,11 +1,4 @@
import {
ConfigurationChangeEvent,
ConfigurationScope,
Event,
EventEmitter,
ExtensionContext,
ExtensionMode,
} from 'vscode';
import { ConfigurationChangeEvent, Event, EventEmitter, ExtensionContext, ExtensionMode } from 'vscode';
import { getSupportedGitProviders } from '@env/providers';
import { Autolinks } from './annotations/autolinks';
import { FileAnnotationController } from './annotations/fileAnnotationController';
@ -18,10 +11,10 @@ import {
AnnotationsToggleMode,
Config,
configuration,
ConfigurationWillChangeEvent,
DateSource,
DateStyle,
FileAnnotationType,
ModeConfig,
} from './configuration';
import { Commands } from './constants';
import { GitFileSystemProvider } from './git/fsProvider';
@ -71,17 +64,17 @@ export class Container {
if (Container.#instance != null) return (Container.#instance as any)[prop];
// Allow access to config before we are initialized
if (prop === 'config') return configuration.get();
if (prop === 'config') return configuration.getAll();
// debugger;
throw new Error('Container is not initialized');
},
});
static create(context: ExtensionContext, cfg: Config, insiders: boolean) {
static create(context: ExtensionContext, insiders: boolean) {
if (Container.#instance != null) throw new Error('Container is already initialized');
Container.#instance = new Container(context, cfg, insiders);
Container.#instance = new Container(context, insiders);
return Container.#instance;
}
@ -145,17 +138,13 @@ export class Container {
},
};
private _configsAffectedByMode: string[] | undefined;
private _applyModeConfigurationTransformBound:
| ((e: ConfigurationChangeEvent) => ConfigurationChangeEvent)
| undefined;
private _configAffectedByModeRegex: RegExp | undefined;
private _terminalLinks: GitTerminalLinkProvider | undefined;
private constructor(context: ExtensionContext, config: Config, insiders: boolean) {
private constructor(context: ExtensionContext, insiders: boolean) {
this._context = context;
this._config = this.applyMode(config);
this._insiders = insiders;
this.ensureModeApplied();
context.subscriptions.push((this._storage = new Storage(this._context)));
@ -205,7 +194,7 @@ export class Container {
context.subscriptions.push((this._homeView = new HomeWebviewView(this)));
context.subscriptions.push((this._timelineView = new TimelineWebviewView(this)));
if (config.terminalLinks.enabled) {
if (configuration.get('terminalLinks.enabled')) {
context.subscriptions.push((this._terminalLinks = new GitTerminalLinkProvider(this)));
}
@ -214,7 +203,7 @@ export class Container {
if (!configuration.changed(e, 'terminalLinks.enabled')) return;
this._terminalLinks?.dispose();
if (this.config.terminalLinks.enabled) {
if (configuration.get('terminalLinks.enabled')) {
context.subscriptions.push((this._terminalLinks = new GitTerminalLinkProvider(this)));
}
}),
@ -241,22 +230,20 @@ export class Container {
this._git.registrationComplete();
}
private onConfigurationChanging(e: ConfigurationWillChangeEvent) {
private onConfigurationChanging(e: ConfigurationChangeEvent) {
this._config = undefined;
this._mode = undefined;
if (configuration.changed(e.change, 'outputLevel')) {
if (configuration.changed(e, 'outputLevel')) {
Logger.logLevel = configuration.get('outputLevel');
}
if (configuration.changed(e.change, 'defaultGravatarsStyle')) {
if (configuration.changed(e, 'defaultGravatarsStyle')) {
resetAvatarCache('fallback');
}
if (configuration.changed(e.change, 'mode') || configuration.changed(e.change, 'modes')) {
if (this._applyModeConfigurationTransformBound == null) {
this._applyModeConfigurationTransformBound = this.applyModeConfigurationTransform.bind(this);
}
e.transform = this._applyModeConfigurationTransformBound;
if (configuration.changed(e, 'mode')) {
this.ensureModeApplied();
}
}
@ -304,7 +291,7 @@ export class Container {
private _config: Config | undefined;
get config() {
if (this._config == null) {
this._config = this.applyMode(configuration.get());
this._config = configuration.getAll();
}
return this._config;
}
@ -580,25 +567,32 @@ export class Container {
return this._worktreesView;
}
private applyMode(config: Config) {
if (!config.mode.active) return config;
private _mode: ModeConfig | undefined;
get mode() {
if (this._mode == null) {
this._mode = configuration.get('modes')?.[configuration.get('mode.active')];
}
return this._mode;
}
private ensureModeApplied() {
const mode = this.mode;
if (mode == null) {
configuration.clearOverrides();
const mode = config.modes?.[config.mode.active];
if (mode == null) return config;
return;
}
if (mode.annotations != null) {
let command: Commands | undefined;
switch (mode.annotations) {
case 'blame':
config.blame.toggleMode = AnnotationsToggleMode.Window;
command = Commands.ToggleFileBlame;
break;
case 'changes':
config.changes.toggleMode = AnnotationsToggleMode.Window;
command = Commands.ToggleFileChanges;
break;
case 'heatmap':
config.heatmap.toggleMode = AnnotationsToggleMode.Window;
command = Commands.ToggleFileHeatmap;
break;
}
@ -608,50 +602,87 @@ export class Container {
type: mode.annotations as FileAnnotationType,
on: true,
};
// Make sure to delay the execution by a bit so that the configuration changes get propegated first
// Make sure to delay the execution by a bit so that the configuration changes get propagated first
setTimeout(() => executeCommand(command!, commandArgs), 50);
}
}
if (mode.codeLens != null) {
config.codeLens.enabled = mode.codeLens;
}
// Apply any required configuration overrides
configuration.applyOverrides({
get: (section, value) => {
if (mode.annotations != null) {
if (configuration.matches(`${mode.annotations}.toggleMode`, section, value)) {
value = AnnotationsToggleMode.Window as typeof value;
return value;
}
if (mode.currentLine != null) {
config.currentLine.enabled = mode.currentLine;
}
if (configuration.matches(mode.annotations, section, value)) {
value.toggleMode = AnnotationsToggleMode.Window;
return value;
}
}
if (mode.hovers != null) {
config.hovers.enabled = mode.hovers;
}
for (const key of ['codeLens', 'currentLine', 'hovers', 'statusBar'] as const) {
if (mode[key] != null) {
if (configuration.matches(`${key}.enabled`, section, value)) {
value = mode[key] as NonNullable<typeof value>;
return value;
} else if (configuration.matches(key, section, value)) {
value.enabled = mode[key]!;
return value;
}
}
}
if (mode.statusBar != null) {
config.statusBar.enabled = mode.statusBar;
}
return value;
},
getAll: cfg => {
if (mode.annotations != null) {
cfg[mode.annotations].toggleMode = AnnotationsToggleMode.Window;
}
return config;
}
private applyModeConfigurationTransform(e: ConfigurationChangeEvent): ConfigurationChangeEvent {
if (this._configsAffectedByMode == null) {
this._configsAffectedByMode = [
`gitlens.${configuration.name('mode')}`,
`gitlens.${configuration.name('modes')}`,
`gitlens.${configuration.name('blame.toggleMode')}`,
`gitlens.${configuration.name('changes.toggleMode')}`,
`gitlens.${configuration.name('codeLens')}`,
`gitlens.${configuration.name('currentLine')}`,
`gitlens.${configuration.name('heatmap.toggleMode')}`,
`gitlens.${configuration.name('hovers')}`,
`gitlens.${configuration.name('statusBar')}`,
];
}
if (mode.codeLens != null) {
cfg.codeLens.enabled = mode.codeLens;
}
if (mode.currentLine != null) {
cfg.currentLine.enabled = mode.currentLine;
}
const original = e.affectsConfiguration;
return {
...e,
affectsConfiguration: (section: string, scope?: ConfigurationScope) =>
this._configsAffectedByMode?.some(n => section.startsWith(n)) ? true : original(section, scope),
};
if (mode.hovers != null) {
cfg.hovers.enabled = mode.hovers;
}
if (mode.statusBar != null) {
cfg.statusBar.enabled = mode.statusBar;
}
return cfg;
},
onChange: e => {
// When the mode or modes change, we will simulate that all the affected configuration also changed
if (configuration.changed(e, ['mode', 'modes'])) {
if (this._configAffectedByModeRegex == null) {
this._configAffectedByModeRegex = new RegExp(
`^gitlens\\.(?:${configuration.name('mode')}|${configuration.name(
'modes',
)}|${configuration.name('blame')}|${configuration.name('changes')}|${configuration.name(
'heatmap',
)}|${configuration.name('codeLens')}|${configuration.name(
'currentLine',
)}|${configuration.name('hovers')}|${configuration.name('statusBar')})\\b`,
);
}
const original = e.affectsConfiguration;
e = {
...e,
affectsConfiguration: (section, scope) =>
this._configAffectedByModeRegex!.test(section) ? true : original(section, scope),
};
}
return e;
},
});
}
}

+ 10
- 8
src/extension.ts View File

@ -24,6 +24,7 @@ export async function activate(context: ExtensionContext): Promise
const gitlensVersion = context.extension.packageJSON.version;
const insiders = context.extension.id === 'eamodio.gitlens-insiders' || satisfies(gitlensVersion, '> 2020.0.0');
const outputLevel = configuration.get('outputLevel');
Logger.configure(context, configuration.get('outputLevel'), o => {
if (GitUri.is(o)) {
return `GitUri(${o.toString(true)}${o.repoPath ? ` repoPath=${o.repoPath}` : ''}${
@ -95,19 +96,19 @@ export async function activate(context: ExtensionContext): Promise
}
Configuration.configure(context);
const cfg = configuration.get();
setDefaultDateLocales(cfg.defaultDateLocale ?? env.language);
setDefaultDateLocales(configuration.get('defaultDateLocale') ?? env.language);
context.subscriptions.push(
configuration.onDidChange(e => {
if (!e.affectsConfiguration('gitlens.defaultDateLocale')) return;
setDefaultDateLocales(configuration.get('defaultDateLocale', undefined, env.language));
if (configuration.changed(e, 'defaultDateLocale')) {
setDefaultDateLocales(configuration.get('defaultDateLocale', undefined, env.language));
}
}),
);
// await migrateSettings(context, previousVersion);
const container = Container.create(context, cfg, insiders);
const container = Container.create(context, insiders);
once(container.onReady)(() => {
context.subscriptions.push(...registerCommands(container));
registerBuiltInActionRunners(container);
@ -122,9 +123,9 @@ export async function activate(context: ExtensionContext): Promise
void context.globalState.update(SyncedStorageKeys.Version, gitlensVersion);
}
if (cfg.outputLevel === OutputLevel.Debug) {
if (outputLevel === OutputLevel.Debug) {
setTimeout(async () => {
if (cfg.outputLevel !== OutputLevel.Debug) return;
if (configuration.get('outputLevel') !== OutputLevel.Debug) return;
if (!container.insiders) {
if (await Messages.showDebugLoggingWarningMessage()) {
@ -143,9 +144,10 @@ export async function activate(context: ExtensionContext): Promise
void setContext(ContextKeys.Debugging, true);
}
const mode = container.mode;
sw.stop({
message: ` activated${exitMessage != null ? `, ${exitMessage}` : ''}${
cfg.mode.active ? `, mode: ${cfg.mode.active}` : ''
mode != null ? `, mode: ${mode.name}` : ''
}`,
});

+ 5
- 5
src/quickpicks/modePicker.ts View File

@ -1,6 +1,6 @@
import { QuickPickItem, window } from 'vscode';
import { configuration } from '../configuration';
import { GlyphChars } from '../constants';
import { Container } from '../container';
export interface ModesQuickPickItem extends QuickPickItem {
key: string | undefined;
@ -8,13 +8,13 @@ export interface ModesQuickPickItem extends QuickPickItem {
export namespace ModePicker {
export async function show(): Promise<ModesQuickPickItem | undefined> {
if (Container.instance.config.modes == null) return undefined;
const modes = configuration.get('modes');
if (modes == null) return undefined;
const modes = Container.instance.config.modes;
const modeKeys = Object.keys(modes);
if (modeKeys.length === 0) return undefined;
const mode = Container.instance.config.mode.active;
const mode = configuration.get('mode.active');
const items = modeKeys.map(key => {
const modeCfg = modes[key];
@ -28,7 +28,7 @@ export namespace ModePicker {
return item;
});
if (mode) {
if (mode && modes[mode] != null) {
items.splice(0, 0, {
label: `Exit ${modes[mode].name} mode`,
key: undefined,

+ 1
- 1
src/webviews/settings/settingsWebview.ts View File

@ -54,7 +54,7 @@ export class SettingsWebview extends WebviewWithConfigBase {
return {
// Make sure to get the raw config, not from the container which has the modes mixed in
config: configuration.get(),
config: configuration.getAll(true),
customSettings: this.getCustomSettings(),
scope: 'user',
scopes: scopes,

+ 1
- 1
src/webviews/webviewWithConfigBase.ts View File

@ -241,7 +241,7 @@ export abstract class WebviewWithConfigBase extends WebviewBase {
private notifyDidChangeConfiguration() {
// Make sure to get the raw config, not from the container which has the modes mixed in
return this.notify(DidChangeConfigurationNotificationType, {
config: configuration.get(),
config: configuration.getAll(true),
customSettings: this.getCustomSettings(),
});
}

Loading…
Cancel
Save