From 093aa3c433320e9aaa32ca55fe8fc54e6fbe8336 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 2 Dec 2021 17:02:21 +0100 Subject: [PATCH] Allows editor options to override how new config updates are applied. --- .../common/config/commonEditorConfig.ts | 57 ++++++++----------- src/vs/editor/common/config/editorOptions.ts | 47 +++++++++++++++ .../standalone/browser/standaloneEditor.ts | 3 +- src/vs/monaco.d.ts | 10 ++++ 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index cbb3314229ee..45b3bd7bcb52 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -112,12 +112,15 @@ class EditorConfiguration2 { } private static _deepEquals(a: T, b: T): boolean { - if (typeof a !== 'object' || typeof b !== 'object') { - return (a === b); + if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) { + return a === b; } if (Array.isArray(a) || Array.isArray(b)) { return (Array.isArray(a) && Array.isArray(b) ? arrays.equals(a, b) : false); } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } for (let key in a) { if (!EditorConfiguration2._deepEquals(a[key], b[key])) { return false; @@ -138,6 +141,22 @@ class EditorConfiguration2 { } return (somethingChanged ? new ConfigurationChangedEvent(result) : null); } + + /** + * Returns true if something changed. + * Modifies `options`. + */ + public static applyUpdate(options: IEditorOptions, update: Readonly): boolean { + let changed = false; + for (const editorOption of editorOptionsRegistry) { + if (update.hasOwnProperty(editorOption.name)) { + const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]); + (options as any)[editorOption.name] = result.newValue; + changed = changed || result.didChange; + } + } + return changed; + } } /** @@ -382,43 +401,17 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC return EditorConfiguration2.computeOptions(this._validatedOptions, env); } - private static _subsetEquals(base: { [key: string]: any }, subset: { [key: string]: any }): boolean { - for (const key in subset) { - if (hasOwnProperty.call(subset, key)) { - const subsetValue = subset[key]; - const baseValue = base[key]; - - if (baseValue === subsetValue) { - continue; - } - if (Array.isArray(baseValue) && Array.isArray(subsetValue)) { - if (!arrays.equals(baseValue, subsetValue)) { - return false; - } - continue; - } - if (baseValue && typeof baseValue === 'object' && subsetValue && typeof subsetValue === 'object') { - if (!this._subsetEquals(baseValue, subsetValue)) { - return false; - } - continue; - } - - return false; - } - } - return true; - } - public updateOptions(_newOptions: Readonly): void { if (typeof _newOptions === 'undefined') { return; } const newOptions = deepCloneAndMigrateOptions(_newOptions); - if (CommonEditorConfiguration._subsetEquals(this._rawOptions, newOptions)) { + + const didChange = EditorConfiguration2.applyUpdate(this._rawOptions, newOptions); + if (!didChange) { return; } - this._rawOptions = objects.mixin(this._rawOptions, newOptions || {}); + this._readOptions = EditorConfiguration2.readOptions(this._rawOptions); this._validatedOptions = EditorConfiguration2.validateOptions(this._readOptions); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 98617176a0c0..78a887e21027 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -12,6 +12,8 @@ import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import * as arrays from 'vs/base/common/arrays'; +import * as objects from 'vs/base/common/objects'; //#region typed options @@ -806,6 +808,11 @@ export interface IEditorOption { * @internal */ compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V; + + /** + * Might modify `value`. + */ + applyUpdate(value: V, update: V): ApplyUpdateResult; } type PossibleKeyName0 = { [K in keyof IEditorOptions]: IEditorOptions[K] extends V | undefined ? K : never }[keyof IEditorOptions]; @@ -828,6 +835,10 @@ abstract class BaseEditorOption implements IEditorOp this.schema = schema; } + public applyUpdate(value: V, update: V): ApplyUpdateResult { + return applyUpdate(value, update); + } + public abstract validate(input: any): V; public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V { @@ -835,6 +846,34 @@ abstract class BaseEditorOption implements IEditorOp } } +export class ApplyUpdateResult { + constructor( + public readonly newValue: T, + public readonly didChange: boolean + ) { } +} + +function applyUpdate(value: T, update: T): ApplyUpdateResult { + if (typeof value !== 'object' || typeof update !== 'object') { + return new ApplyUpdateResult(update, value === update); + } + if (Array.isArray(value) || Array.isArray(update)) { + const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update); + return new ApplyUpdateResult(update, arrayEquals); + } + let didChange = false; + for (let key in update) { + if ((value as T & object).hasOwnProperty(key)) { + const result = applyUpdate(value[key], update[key]); + if (result.didChange) { + value[key] = result.newValue; + didChange = true; + } + } + } + return new ApplyUpdateResult(value, didChange); +} + /** * @internal */ @@ -853,6 +892,10 @@ abstract class ComputedEditorOption implements IEdit this.deps = deps; } + public applyUpdate(value: V, update: V): ApplyUpdateResult { + return applyUpdate(value, update); + } + public validate(input: any): V { return this.defaultValue; } @@ -874,6 +917,10 @@ class SimpleEditorOption implements IEditorOption { + return applyUpdate(value, update); + } + public validate(input: any): V { if (typeof input === 'undefined') { return this.defaultValue; diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index a1c35fd98a9d..438761bc36cd 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { DiffNavigator, IDiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; -import { EditorOptions, ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; +import { EditorOptions, ConfigurationChangedEvent, ApplyUpdateResult } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Token } from 'vs/editor/common/core/token'; import { IEditor, EditorType } from 'vs/editor/common/editorCommon'; @@ -393,6 +393,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { FontInfo: FontInfo, TextModelResolvedOptions: TextModelResolvedOptions, FindMatch: FindMatch, + ApplyUpdateResult: ApplyUpdateResult, // vars EditorType: EditorType, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 38f9d640e7e9..5d075b98e6e3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3378,6 +3378,16 @@ declare namespace monaco.editor { readonly id: K1; readonly name: string; defaultValue: V; + /** + * Might modify `value`. + */ + applyUpdate(value: V, update: V): ApplyUpdateResult; + } + + export class ApplyUpdateResult { + readonly newValue: T; + readonly didChange: boolean; + constructor(newValue: T, didChange: boolean); } /**