Skip to content

Commit 39d5332

Browse files
authored
Merge pull request microsoft#138042 from microsoft/hediet/allowedCharactersRecord
allowed characters: string -> record
2 parents cca3e81 + a78af4e commit 39d5332

File tree

5 files changed

+143
-60
lines changed

5 files changed

+143
-60
lines changed

src/vs/editor/common/config/commonEditorConfig.ts

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,15 @@ class EditorConfiguration2 {
112112
}
113113

114114
private static _deepEquals<T>(a: T, b: T): boolean {
115-
if (typeof a !== 'object' || typeof b !== 'object') {
116-
return (a === b);
115+
if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
116+
return a === b;
117117
}
118118
if (Array.isArray(a) || Array.isArray(b)) {
119119
return (Array.isArray(a) && Array.isArray(b) ? arrays.equals(a, b) : false);
120120
}
121+
if (Object.keys(a).length !== Object.keys(b).length) {
122+
return false;
123+
}
121124
for (let key in a) {
122125
if (!EditorConfiguration2._deepEquals(a[key], b[key])) {
123126
return false;
@@ -138,6 +141,22 @@ class EditorConfiguration2 {
138141
}
139142
return (somethingChanged ? new ConfigurationChangedEvent(result) : null);
140143
}
144+
145+
/**
146+
* Returns true if something changed.
147+
* Modifies `options`.
148+
*/
149+
public static applyUpdate(options: IEditorOptions, update: Readonly<IEditorOptions>): boolean {
150+
let changed = false;
151+
for (const editorOption of editorOptionsRegistry) {
152+
if (update.hasOwnProperty(editorOption.name)) {
153+
const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]);
154+
(options as any)[editorOption.name] = result.newValue;
155+
changed = changed || result.didChange;
156+
}
157+
}
158+
return changed;
159+
}
141160
}
142161

143162
/**
@@ -382,43 +401,17 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
382401
return EditorConfiguration2.computeOptions(this._validatedOptions, env);
383402
}
384403

385-
private static _subsetEquals(base: { [key: string]: any }, subset: { [key: string]: any }): boolean {
386-
for (const key in subset) {
387-
if (hasOwnProperty.call(subset, key)) {
388-
const subsetValue = subset[key];
389-
const baseValue = base[key];
390-
391-
if (baseValue === subsetValue) {
392-
continue;
393-
}
394-
if (Array.isArray(baseValue) && Array.isArray(subsetValue)) {
395-
if (!arrays.equals(baseValue, subsetValue)) {
396-
return false;
397-
}
398-
continue;
399-
}
400-
if (baseValue && typeof baseValue === 'object' && subsetValue && typeof subsetValue === 'object') {
401-
if (!this._subsetEquals(baseValue, subsetValue)) {
402-
return false;
403-
}
404-
continue;
405-
}
406-
407-
return false;
408-
}
409-
}
410-
return true;
411-
}
412-
413404
public updateOptions(_newOptions: Readonly<IEditorOptions>): void {
414405
if (typeof _newOptions === 'undefined') {
415406
return;
416407
}
417408
const newOptions = deepCloneAndMigrateOptions(_newOptions);
418-
if (CommonEditorConfiguration._subsetEquals(this._rawOptions, newOptions)) {
409+
410+
const didChange = EditorConfiguration2.applyUpdate(this._rawOptions, newOptions);
411+
if (!didChange) {
419412
return;
420413
}
421-
this._rawOptions = objects.mixin(this._rawOptions, newOptions || {});
414+
422415
this._readOptions = EditorConfiguration2.readOptions(this._rawOptions);
423416
this._validatedOptions = EditorConfiguration2.validateOptions(this._readOptions);
424417

src/vs/editor/common/config/editorOptions.ts

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper';
1212
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
1313
import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
1414
import { IJSONSchema } from 'vs/base/common/jsonSchema';
15+
import * as arrays from 'vs/base/common/arrays';
16+
import * as objects from 'vs/base/common/objects';
1517

1618
//#region typed options
1719

@@ -806,6 +808,11 @@ export interface IEditorOption<K1 extends EditorOption, V> {
806808
* @internal
807809
*/
808810
compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V;
811+
812+
/**
813+
* Might modify `value`.
814+
*/
815+
applyUpdate(value: V, update: V): ApplyUpdateResult<V>;
809816
}
810817

811818
type PossibleKeyName0<V> = { [K in keyof IEditorOptions]: IEditorOptions[K] extends V | undefined ? K : never }[keyof IEditorOptions];
@@ -828,13 +835,45 @@ abstract class BaseEditorOption<K1 extends EditorOption, V> implements IEditorOp
828835
this.schema = schema;
829836
}
830837

838+
public applyUpdate(value: V, update: V): ApplyUpdateResult<V> {
839+
return applyUpdate(value, update);
840+
}
841+
831842
public abstract validate(input: any): V;
832843

833844
public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V {
834845
return value;
835846
}
836847
}
837848

849+
export class ApplyUpdateResult<T> {
850+
constructor(
851+
public readonly newValue: T,
852+
public readonly didChange: boolean
853+
) { }
854+
}
855+
856+
function applyUpdate<T>(value: T, update: T): ApplyUpdateResult<T> {
857+
if (typeof value !== 'object' || typeof update !== 'object' || !value || !update) {
858+
return new ApplyUpdateResult(update, value !== update);
859+
}
860+
if (Array.isArray(value) || Array.isArray(update)) {
861+
const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update);
862+
return new ApplyUpdateResult(update, arrayEquals);
863+
}
864+
let didChange = false;
865+
for (let key in update) {
866+
if ((update as T & object).hasOwnProperty(key)) {
867+
const result = applyUpdate(value[key], update[key]);
868+
if (result.didChange) {
869+
value[key] = result.newValue;
870+
didChange = true;
871+
}
872+
}
873+
}
874+
return new ApplyUpdateResult(value, didChange);
875+
}
876+
838877
/**
839878
* @internal
840879
*/
@@ -853,6 +892,10 @@ abstract class ComputedEditorOption<K1 extends EditorOption, V> implements IEdit
853892
this.deps = deps;
854893
}
855894

895+
public applyUpdate(value: V, update: V): ApplyUpdateResult<V> {
896+
return applyUpdate(value, update);
897+
}
898+
856899
public validate(input: any): V {
857900
return this.defaultValue;
858901
}
@@ -874,6 +917,10 @@ class SimpleEditorOption<K1 extends EditorOption, V> implements IEditorOption<K1
874917
this.schema = schema;
875918
}
876919

920+
public applyUpdate(value: V, update: V): ApplyUpdateResult<V> {
921+
return applyUpdate(value, update);
922+
}
923+
877924
public validate(input: any): V {
878925
if (typeof input === 'undefined') {
879926
return this.defaultValue;
@@ -3265,9 +3312,9 @@ export interface IUnicodeHighlightOptions {
32653312
ambiguousCharacters?: boolean;
32663313
includeComments?: boolean | InUntrustedWorkspace;
32673314
/**
3268-
* A list of allowed code points in a single string.
3315+
* A map of allowed characters (true: allowed).
32693316
*/
3270-
allowedCharacters?: string;
3317+
allowedCharacters?: Record<string, true>;
32713318
}
32723319

32733320
/**
@@ -3293,7 +3340,7 @@ class UnicodeHighlight extends BaseEditorOption<EditorOption.unicodeHighlighting
32933340
invisibleCharacters: true,
32943341
ambiguousCharacters: true,
32953342
includeComments: inUntrustedWorkspace,
3296-
allowedCharacters: '',
3343+
allowedCharacters: {},
32973344
};
32983345

32993346
super(
@@ -3327,14 +3374,34 @@ class UnicodeHighlight extends BaseEditorOption<EditorOption.unicodeHighlighting
33273374
},
33283375
[unicodeHighlightConfigKeys.allowedCharacters]: {
33293376
restricted: true,
3330-
type: 'string',
3377+
type: 'object',
33313378
default: defaults.allowedCharacters,
3332-
description: nls.localize('unicodeHighlight.allowedCharacters', "Defines allowed characters that are not being highlighted.")
3379+
description: nls.localize('unicodeHighlight.allowedCharacters', "Defines allowed characters that are not being highlighted."),
3380+
additionalProperties: {
3381+
type: 'boolean'
3382+
}
33333383
},
33343384
}
33353385
);
33363386
}
33373387

3388+
public override applyUpdate(value: Required<Readonly<IUnicodeHighlightOptions>>, update: Required<Readonly<IUnicodeHighlightOptions>>): ApplyUpdateResult<Required<Readonly<IUnicodeHighlightOptions>>> {
3389+
let didChange = false;
3390+
if (update.allowedCharacters) {
3391+
// Treat allowedCharacters atomically
3392+
if (!objects.equals(value.allowedCharacters, update.allowedCharacters)) {
3393+
value = { ...value, allowedCharacters: update.allowedCharacters };
3394+
didChange = true;
3395+
}
3396+
}
3397+
3398+
const result = super.applyUpdate(value, update);
3399+
if (didChange) {
3400+
return new ApplyUpdateResult(result.newValue, true);
3401+
}
3402+
return result;
3403+
}
3404+
33383405
public validate(_input: any): InternalUnicodeHighlightOptions {
33393406
if (!_input || typeof _input !== 'object') {
33403407
return this.defaultValue;
@@ -3345,16 +3412,22 @@ class UnicodeHighlight extends BaseEditorOption<EditorOption.unicodeHighlighting
33453412
invisibleCharacters: boolean(input.invisibleCharacters, this.defaultValue.invisibleCharacters),
33463413
ambiguousCharacters: boolean(input.ambiguousCharacters, this.defaultValue.ambiguousCharacters),
33473414
includeComments: primitiveSet<boolean | InUntrustedWorkspace>(input.includeComments, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]),
3348-
allowedCharacters: string(input.allowedCharacters, ''),
3415+
allowedCharacters: this.validateAllowedCharacters(_input.allowedCharacters, this.defaultValue.allowedCharacters),
33493416
};
33503417
}
3351-
}
33523418

3353-
function string(value: unknown, defaultValue: string): string {
3354-
if (typeof value !== 'string') {
3355-
return defaultValue;
3419+
private validateAllowedCharacters(map: unknown, defaultValue: Record<string, true>): Record<string, true> {
3420+
if ((typeof map !== 'object') || !map) {
3421+
return defaultValue;
3422+
}
3423+
const result: Record<string, true> = {};
3424+
for (const [key, value] of Object.entries(map)) {
3425+
if (value === true) {
3426+
result[key] = true;
3427+
}
3428+
}
3429+
return result;
33563430
}
3357-
return value;
33583431
}
33593432

33603433
//#endregion

src/vs/editor/contrib/unicodeHighlighter/unicodeHighlighter.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio
160160
ambiguousCharacters: options.ambiguousCharacters,
161161
invisibleCharacters: options.invisibleCharacters,
162162
includeComments: options.includeComments,
163-
allowedCodePoints: Array.from(options.allowedCharacters).map(c => c.codePointAt(0)!),
163+
allowedCodePoints: Object.keys(options.allowedCharacters).map(c => c.codePointAt(0)!),
164164
};
165165

166166
if (this._editorWorkerService.canComputeUnicodeHighlights(this._editor.getModel().uri)) {
@@ -191,7 +191,7 @@ function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptio
191191
ambiguousCharacters: options.ambiguousCharacters,
192192
invisibleCharacters: options.invisibleCharacters,
193193
includeComments: options.includeComments === inUntrustedWorkspace ? !trusted : options.includeComments,
194-
allowedCharacters: options.allowedCharacters ?? [],
194+
allowedCharacters: options.allowedCharacters ?? {},
195195
};
196196
}
197197

@@ -640,18 +640,7 @@ export class ShowExcludeOptions extends EditorAction {
640640
const options: ExtendedOptions[] = [
641641
{
642642
label: getExcludeCharFromBeingHighlightedLabel(codePoint),
643-
run: async () => {
644-
const existingValue = configurationService.getValue(unicodeHighlightConfigKeys.allowedCharacters);
645-
let value: string;
646-
if (typeof existingValue === 'string') {
647-
value = existingValue;
648-
} else {
649-
value = '';
650-
}
651-
652-
value += char;
653-
await configurationService.updateValue(unicodeHighlightConfigKeys.allowedCharacters, value, ConfigurationTarget.USER);
654-
}
643+
run: () => excludeCharFromBeingHighlighted(configurationService, [codePoint])
655644
},
656645
];
657646

@@ -681,6 +670,23 @@ export class ShowExcludeOptions extends EditorAction {
681670
}
682671
}
683672

673+
async function excludeCharFromBeingHighlighted(configurationService: IConfigurationService, charCodes: number[]) {
674+
const existingValue = configurationService.getValue(unicodeHighlightConfigKeys.allowedCharacters);
675+
676+
let value: Record<string, boolean>;
677+
if ((typeof existingValue === 'object') && existingValue) {
678+
value = existingValue as any;
679+
} else {
680+
value = {};
681+
}
682+
683+
for (const charCode of charCodes) {
684+
value[String.fromCodePoint(charCode)] = true;
685+
}
686+
687+
await configurationService.updateValue(unicodeHighlightConfigKeys.allowedCharacters, value, ConfigurationTarget.USER);
688+
}
689+
684690
function expectNever(value: never) {
685691
throw new Error(`Unexpected value: ${value}`);
686692
}

src/vs/editor/standalone/browser/standaloneEditor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1010
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
1111
import { OpenerService } from 'vs/editor/browser/services/openerService';
1212
import { DiffNavigator, IDiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
13-
import { EditorOptions, ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
13+
import { EditorOptions, ConfigurationChangedEvent, ApplyUpdateResult } from 'vs/editor/common/config/editorOptions';
1414
import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo';
1515
import { Token } from 'vs/editor/common/core/token';
1616
import { IEditor, EditorType } from 'vs/editor/common/editorCommon';
@@ -393,6 +393,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor {
393393
FontInfo: <any>FontInfo,
394394
TextModelResolvedOptions: <any>TextModelResolvedOptions,
395395
FindMatch: <any>FindMatch,
396+
ApplyUpdateResult: <any>ApplyUpdateResult,
396397

397398
// vars
398399
EditorType: EditorType,

src/vs/monaco.d.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3378,6 +3378,16 @@ declare namespace monaco.editor {
33783378
readonly id: K1;
33793379
readonly name: string;
33803380
defaultValue: V;
3381+
/**
3382+
* Might modify `value`.
3383+
*/
3384+
applyUpdate(value: V, update: V): ApplyUpdateResult<V>;
3385+
}
3386+
3387+
export class ApplyUpdateResult<T> {
3388+
readonly newValue: T;
3389+
readonly didChange: boolean;
3390+
constructor(newValue: T, didChange: boolean);
33813391
}
33823392

33833393
/**
@@ -3874,9 +3884,9 @@ declare namespace monaco.editor {
38743884
ambiguousCharacters?: boolean;
38753885
includeComments?: boolean | InUntrustedWorkspace;
38763886
/**
3877-
* A list of allowed code points in a single string.
3887+
* A map of allowed characters (true: allowed).
38783888
*/
3879-
allowedCharacters?: string;
3889+
allowedCharacters?: Record<string, true>;
38803890
}
38813891

38823892
export interface IInlineSuggestOptions {

0 commit comments

Comments
 (0)