Path: blob/main/src/vs/editor/browser/config/editorConfiguration.ts
5221 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as browser from '../../../base/browser/browser.js';6import * as arrays from '../../../base/common/arrays.js';7import { Emitter, Event } from '../../../base/common/event.js';8import { Disposable } from '../../../base/common/lifecycle.js';9import * as objects from '../../../base/common/objects.js';10import * as platform from '../../../base/common/platform.js';11import { ElementSizeObserver } from './elementSizeObserver.js';12import { FontMeasurements } from './fontMeasurements.js';13import { migrateOptions } from './migrateOptions.js';14import { TabFocus } from './tabFocus.js';15import { ComputeOptionsMemory, ConfigurationChangedEvent, EditorOption, editorOptionsRegistry, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, IEnvironmentalOptions } from '../../common/config/editorOptions.js';16import { EditorZoom } from '../../common/config/editorZoom.js';17import { BareFontInfo, FontInfo, IValidatedEditorOptions } from '../../common/config/fontInfo.js';18import { createBareFontInfoFromValidatedSettings } from '../../common/config/fontInfoFromSettings.js';19import { IDimension } from '../../common/core/2d/dimension.js';20import { IEditorConfiguration } from '../../common/config/editorConfiguration.js';21import { AccessibilitySupport, IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js';22import { getWindow, getWindowById } from '../../../base/browser/dom.js';23import { PixelRatio } from '../../../base/browser/pixelRatio.js';24import { MenuId } from '../../../platform/actions/common/actions.js';25import { InputMode } from '../../common/inputMode.js';2627export interface IEditorConstructionOptions extends IEditorOptions {28/**29* The initial editor dimension (to avoid measuring the container).30*/31dimension?: IDimension;32/**33* Place overflow widgets inside an external DOM node.34* Defaults to an internal DOM node.35*/36overflowWidgetsDomNode?: HTMLElement;37}3839export class EditorConfiguration extends Disposable implements IEditorConfiguration {4041private _onDidChange = this._register(new Emitter<ConfigurationChangedEvent>());42public readonly onDidChange: Event<ConfigurationChangedEvent> = this._onDidChange.event;4344private _onDidChangeFast = this._register(new Emitter<ConfigurationChangedEvent>());45public readonly onDidChangeFast: Event<ConfigurationChangedEvent> = this._onDidChangeFast.event;4647public readonly isSimpleWidget: boolean;48public readonly contextMenuId: MenuId;49private readonly _containerObserver: ElementSizeObserver;5051private _isDominatedByLongLines: boolean = false;52private _viewLineCount: number = 1;53private _lineNumbersDigitCount: number = 1;54private _reservedHeight: number = 0;55private _glyphMarginDecorationLaneCount: number = 1;56private _targetWindowId: number;5758private readonly _computeOptionsMemory: ComputeOptionsMemory = new ComputeOptionsMemory();59/**60* Raw options as they were passed in and merged with all calls to `updateOptions`.61*/62private readonly _rawOptions: IEditorOptions;63/**64* Validated version of `_rawOptions`.65*/66private _validatedOptions: ValidatedEditorOptions;67/**68* Complete options which are a combination of passed in options and env values.69*/70public options: ComputedEditorOptions;7172constructor(73isSimpleWidget: boolean,74contextMenuId: MenuId,75options: Readonly<IEditorConstructionOptions>,76container: HTMLElement | null,77@IAccessibilityService private readonly _accessibilityService: IAccessibilityService78) {79super();80this.isSimpleWidget = isSimpleWidget;81this.contextMenuId = contextMenuId;82this._containerObserver = this._register(new ElementSizeObserver(container, options.dimension));83this._targetWindowId = getWindow(container).vscodeWindowId;8485this._rawOptions = deepCloneAndMigrateOptions(options);86this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);87this.options = this._computeOptions();8889if (this.options.get(EditorOption.automaticLayout)) {90this._containerObserver.startObserving();91}9293this._register(EditorZoom.onDidChangeZoomLevel(() => this._recomputeOptions()));94this._register(TabFocus.onDidChangeTabFocus(() => this._recomputeOptions()));95this._register(this._containerObserver.onDidChange(() => this._recomputeOptions()));96this._register(FontMeasurements.onDidChange(() => this._recomputeOptions()));97this._register(PixelRatio.getInstance(getWindow(container)).onDidChange(() => this._recomputeOptions()));98this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions()));99this._register(InputMode.onDidChangeInputMode(() => this._recomputeOptions()));100}101102private _recomputeOptions(): void {103const newOptions = this._computeOptions();104const changeEvent = EditorOptionsUtil.checkEquals(this.options, newOptions);105if (changeEvent === null) {106// nothing changed!107return;108}109110this.options = newOptions;111this._onDidChangeFast.fire(changeEvent);112this._onDidChange.fire(changeEvent);113}114115private _computeOptions(): ComputedEditorOptions {116const partialEnv = this._readEnvConfiguration();117const bareFontInfo = createBareFontInfoFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget);118const fontInfo = this._readFontInfo(bareFontInfo);119const env: IEnvironmentalOptions = {120memory: this._computeOptionsMemory,121outerWidth: partialEnv.outerWidth,122outerHeight: partialEnv.outerHeight - this._reservedHeight,123fontInfo: fontInfo,124extraEditorClassName: partialEnv.extraEditorClassName,125isDominatedByLongLines: this._isDominatedByLongLines,126viewLineCount: this._viewLineCount,127lineNumbersDigitCount: this._lineNumbersDigitCount,128emptySelectionClipboard: partialEnv.emptySelectionClipboard,129pixelRatio: partialEnv.pixelRatio,130tabFocusMode: this._validatedOptions.get(EditorOption.tabFocusMode) || TabFocus.getTabFocusMode(),131inputMode: InputMode.getInputMode(),132accessibilitySupport: partialEnv.accessibilitySupport,133glyphMarginDecorationLaneCount: this._glyphMarginDecorationLaneCount,134editContextSupported: partialEnv.editContextSupported135};136return EditorOptionsUtil.computeOptions(this._validatedOptions, env);137}138139protected _readEnvConfiguration(): IEnvConfiguration {140return {141extraEditorClassName: getExtraEditorClassName(),142outerWidth: this._containerObserver.getWidth(),143outerHeight: this._containerObserver.getHeight(),144emptySelectionClipboard: browser.isWebKit || browser.isFirefox,145pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value,146// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any147editContextSupported: typeof (globalThis as any).EditContext === 'function',148accessibilitySupport: (149this._accessibilityService.isScreenReaderOptimized()150? AccessibilitySupport.Enabled151: this._accessibilityService.getAccessibilitySupport()152)153};154}155156protected _readFontInfo(bareFontInfo: BareFontInfo): FontInfo {157return FontMeasurements.readFontInfo(getWindowById(this._targetWindowId, true).window, bareFontInfo);158}159160public getRawOptions(): IEditorOptions {161return this._rawOptions;162}163164public updateOptions(_newOptions: Readonly<IEditorOptions>): void {165const newOptions = deepCloneAndMigrateOptions(_newOptions);166167const didChange = EditorOptionsUtil.applyUpdate(this._rawOptions, newOptions);168if (!didChange) {169return;170}171172this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);173this._recomputeOptions();174}175176public observeContainer(dimension?: IDimension): void {177this._containerObserver.observe(dimension);178}179180public setIsDominatedByLongLines(isDominatedByLongLines: boolean): void {181if (this._isDominatedByLongLines === isDominatedByLongLines) {182return;183}184this._isDominatedByLongLines = isDominatedByLongLines;185this._recomputeOptions();186}187188public setModelLineCount(modelLineCount: number): void {189const lineNumbersDigitCount = digitCount(modelLineCount);190if (this._lineNumbersDigitCount === lineNumbersDigitCount) {191return;192}193this._lineNumbersDigitCount = lineNumbersDigitCount;194this._recomputeOptions();195}196197public setViewLineCount(viewLineCount: number): void {198if (this._viewLineCount === viewLineCount) {199return;200}201this._viewLineCount = viewLineCount;202this._recomputeOptions();203}204205public setReservedHeight(reservedHeight: number) {206if (this._reservedHeight === reservedHeight) {207return;208}209this._reservedHeight = reservedHeight;210this._recomputeOptions();211}212213public setGlyphMarginDecorationLaneCount(decorationLaneCount: number): void {214if (this._glyphMarginDecorationLaneCount === decorationLaneCount) {215return;216}217this._glyphMarginDecorationLaneCount = decorationLaneCount;218this._recomputeOptions();219}220}221222function digitCount(n: number): number {223let r = 0;224while (n) {225n = Math.floor(n / 10);226r++;227}228return r ? r : 1;229}230231function getExtraEditorClassName(): string {232let extra = '';233if (browser.isSafari || browser.isWebkitWebView) {234// See https://github.com/microsoft/vscode/issues/108822235extra += 'no-minimap-shadow ';236extra += 'enable-user-select ';237} else {238// Use user-select: none in all browsers except Safari and native macOS WebView239extra += 'no-user-select ';240}241if (platform.isMacintosh) {242extra += 'mac ';243}244return extra;245}246247export interface IEnvConfiguration {248extraEditorClassName: string;249outerWidth: number;250outerHeight: number;251emptySelectionClipboard: boolean;252pixelRatio: number;253accessibilitySupport: AccessibilitySupport;254editContextSupported: boolean;255}256257class ValidatedEditorOptions implements IValidatedEditorOptions {258private readonly _values: unknown[] = [];259public _read<T>(option: EditorOption): T {260return this._values[option] as T;261}262public get<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T> {263return this._values[id] as FindComputedEditorOptionValueById<T>;264}265public _write<T>(option: EditorOption, value: T): void {266this._values[option] = value;267}268}269270export class ComputedEditorOptions implements IComputedEditorOptions {271private readonly _values: unknown[] = [];272public _read<T>(id: EditorOption): T {273if (id >= this._values.length) {274throw new Error('Cannot read uninitialized value');275}276return this._values[id] as T;277}278public get<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T> {279return this._read(id);280}281public _write<T>(id: EditorOption, value: T): void {282this._values[id] = value;283}284}285286class EditorOptionsUtil {287288public static validateOptions(options: IEditorOptions): ValidatedEditorOptions {289const result = new ValidatedEditorOptions();290for (const editorOption of editorOptionsRegistry) {291const value = (editorOption.name === '_never_' ? undefined : (options as Record<string, unknown>)[editorOption.name]);292result._write(editorOption.id, editorOption.validate(value));293}294return result;295}296297public static computeOptions(options: ValidatedEditorOptions, env: IEnvironmentalOptions): ComputedEditorOptions {298const result = new ComputedEditorOptions();299for (const editorOption of editorOptionsRegistry) {300result._write(editorOption.id, editorOption.compute(env, result, options._read(editorOption.id)));301}302return result;303}304305private static _deepEquals<T>(a: T, b: T): boolean {306if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {307return a === b;308}309if (Array.isArray(a) || Array.isArray(b)) {310return (Array.isArray(a) && Array.isArray(b) ? arrays.equals(a, b) : false);311}312if (Object.keys(a as unknown as object).length !== Object.keys(b as unknown as object).length) {313return false;314}315for (const key in a) {316if (!EditorOptionsUtil._deepEquals(a[key], b[key])) {317return false;318}319}320return true;321}322323public static checkEquals(a: ComputedEditorOptions, b: ComputedEditorOptions): ConfigurationChangedEvent | null {324const result: boolean[] = [];325let somethingChanged = false;326for (const editorOption of editorOptionsRegistry) {327const changed = !EditorOptionsUtil._deepEquals(a._read(editorOption.id), b._read(editorOption.id));328result[editorOption.id] = changed;329if (changed) {330somethingChanged = true;331}332}333return (somethingChanged ? new ConfigurationChangedEvent(result) : null);334}335336/**337* Returns true if something changed.338* Modifies `options`.339*/340public static applyUpdate(options: IEditorOptions, update: Readonly<IEditorOptions>): boolean {341let changed = false;342for (const editorOption of editorOptionsRegistry) {343if (update.hasOwnProperty(editorOption.name)) {344const result = editorOption.applyUpdate((options as Record<string, unknown>)[editorOption.name], (update as Record<string, unknown>)[editorOption.name]);345(options as Record<string, unknown>)[editorOption.name] = result.newValue;346changed = changed || result.didChange;347}348}349return changed;350}351}352353function deepCloneAndMigrateOptions(_options: Readonly<IEditorOptions>): IEditorOptions {354const options = objects.deepClone(_options);355migrateOptions(options);356return options;357}358359360