Path: blob/main/src/vs/editor/browser/config/editorConfiguration.ts
3294 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 { IDimension } from '../../common/core/2d/dimension.js';19import { IEditorConfiguration } from '../../common/config/editorConfiguration.js';20import { AccessibilitySupport, IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js';21import { getWindow, getWindowById } from '../../../base/browser/dom.js';22import { PixelRatio } from '../../../base/browser/pixelRatio.js';23import { MenuId } from '../../../platform/actions/common/actions.js';24import { InputMode } from '../../common/inputMode.js';2526export interface IEditorConstructionOptions extends IEditorOptions {27/**28* The initial editor dimension (to avoid measuring the container).29*/30dimension?: IDimension;31/**32* Place overflow widgets inside an external DOM node.33* Defaults to an internal DOM node.34*/35overflowWidgetsDomNode?: HTMLElement;36}3738export class EditorConfiguration extends Disposable implements IEditorConfiguration {3940private _onDidChange = this._register(new Emitter<ConfigurationChangedEvent>());41public readonly onDidChange: Event<ConfigurationChangedEvent> = this._onDidChange.event;4243private _onDidChangeFast = this._register(new Emitter<ConfigurationChangedEvent>());44public readonly onDidChangeFast: Event<ConfigurationChangedEvent> = this._onDidChangeFast.event;4546public readonly isSimpleWidget: boolean;47public readonly contextMenuId: MenuId;48private readonly _containerObserver: ElementSizeObserver;4950private _isDominatedByLongLines: boolean = false;51private _viewLineCount: number = 1;52private _lineNumbersDigitCount: number = 1;53private _reservedHeight: number = 0;54private _glyphMarginDecorationLaneCount: number = 1;55private _targetWindowId: number;5657private readonly _computeOptionsMemory: ComputeOptionsMemory = new ComputeOptionsMemory();58/**59* Raw options as they were passed in and merged with all calls to `updateOptions`.60*/61private readonly _rawOptions: IEditorOptions;62/**63* Validated version of `_rawOptions`.64*/65private _validatedOptions: ValidatedEditorOptions;66/**67* Complete options which are a combination of passed in options and env values.68*/69public options: ComputedEditorOptions;7071constructor(72isSimpleWidget: boolean,73contextMenuId: MenuId,74options: Readonly<IEditorConstructionOptions>,75container: HTMLElement | null,76@IAccessibilityService private readonly _accessibilityService: IAccessibilityService77) {78super();79this.isSimpleWidget = isSimpleWidget;80this.contextMenuId = contextMenuId;81this._containerObserver = this._register(new ElementSizeObserver(container, options.dimension));82this._targetWindowId = getWindow(container).vscodeWindowId;8384this._rawOptions = deepCloneAndMigrateOptions(options);85this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);86this.options = this._computeOptions();8788if (this.options.get(EditorOption.automaticLayout)) {89this._containerObserver.startObserving();90}9192this._register(EditorZoom.onDidChangeZoomLevel(() => this._recomputeOptions()));93this._register(TabFocus.onDidChangeTabFocus(() => this._recomputeOptions()));94this._register(this._containerObserver.onDidChange(() => this._recomputeOptions()));95this._register(FontMeasurements.onDidChange(() => this._recomputeOptions()));96this._register(PixelRatio.getInstance(getWindow(container)).onDidChange(() => this._recomputeOptions()));97this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions()));98this._register(InputMode.onDidChangeInputMode(() => this._recomputeOptions()));99}100101private _recomputeOptions(): void {102const newOptions = this._computeOptions();103const changeEvent = EditorOptionsUtil.checkEquals(this.options, newOptions);104if (changeEvent === null) {105// nothing changed!106return;107}108109this.options = newOptions;110this._onDidChangeFast.fire(changeEvent);111this._onDidChange.fire(changeEvent);112}113114private _computeOptions(): ComputedEditorOptions {115const partialEnv = this._readEnvConfiguration();116const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget);117const fontInfo = this._readFontInfo(bareFontInfo);118const env: IEnvironmentalOptions = {119memory: this._computeOptionsMemory,120outerWidth: partialEnv.outerWidth,121outerHeight: partialEnv.outerHeight - this._reservedHeight,122fontInfo: fontInfo,123extraEditorClassName: partialEnv.extraEditorClassName,124isDominatedByLongLines: this._isDominatedByLongLines,125viewLineCount: this._viewLineCount,126lineNumbersDigitCount: this._lineNumbersDigitCount,127emptySelectionClipboard: partialEnv.emptySelectionClipboard,128pixelRatio: partialEnv.pixelRatio,129tabFocusMode: this._validatedOptions.get(EditorOption.tabFocusMode) || TabFocus.getTabFocusMode(),130inputMode: InputMode.getInputMode(),131accessibilitySupport: partialEnv.accessibilitySupport,132glyphMarginDecorationLaneCount: this._glyphMarginDecorationLaneCount,133editContextSupported: partialEnv.editContextSupported134};135return EditorOptionsUtil.computeOptions(this._validatedOptions, env);136}137138protected _readEnvConfiguration(): IEnvConfiguration {139return {140extraEditorClassName: getExtraEditorClassName(),141outerWidth: this._containerObserver.getWidth(),142outerHeight: this._containerObserver.getHeight(),143emptySelectionClipboard: browser.isWebKit || browser.isFirefox,144pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value,145editContextSupported: typeof (globalThis as any).EditContext === 'function',146accessibilitySupport: (147this._accessibilityService.isScreenReaderOptimized()148? AccessibilitySupport.Enabled149: this._accessibilityService.getAccessibilitySupport()150)151};152}153154protected _readFontInfo(bareFontInfo: BareFontInfo): FontInfo {155return FontMeasurements.readFontInfo(getWindowById(this._targetWindowId, true).window, bareFontInfo);156}157158public getRawOptions(): IEditorOptions {159return this._rawOptions;160}161162public updateOptions(_newOptions: Readonly<IEditorOptions>): void {163const newOptions = deepCloneAndMigrateOptions(_newOptions);164165const didChange = EditorOptionsUtil.applyUpdate(this._rawOptions, newOptions);166if (!didChange) {167return;168}169170this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);171this._recomputeOptions();172}173174public observeContainer(dimension?: IDimension): void {175this._containerObserver.observe(dimension);176}177178public setIsDominatedByLongLines(isDominatedByLongLines: boolean): void {179if (this._isDominatedByLongLines === isDominatedByLongLines) {180return;181}182this._isDominatedByLongLines = isDominatedByLongLines;183this._recomputeOptions();184}185186public setModelLineCount(modelLineCount: number): void {187const lineNumbersDigitCount = digitCount(modelLineCount);188if (this._lineNumbersDigitCount === lineNumbersDigitCount) {189return;190}191this._lineNumbersDigitCount = lineNumbersDigitCount;192this._recomputeOptions();193}194195public setViewLineCount(viewLineCount: number): void {196if (this._viewLineCount === viewLineCount) {197return;198}199this._viewLineCount = viewLineCount;200this._recomputeOptions();201}202203public setReservedHeight(reservedHeight: number) {204if (this._reservedHeight === reservedHeight) {205return;206}207this._reservedHeight = reservedHeight;208this._recomputeOptions();209}210211public setGlyphMarginDecorationLaneCount(decorationLaneCount: number): void {212if (this._glyphMarginDecorationLaneCount === decorationLaneCount) {213return;214}215this._glyphMarginDecorationLaneCount = decorationLaneCount;216this._recomputeOptions();217}218}219220function digitCount(n: number): number {221let r = 0;222while (n) {223n = Math.floor(n / 10);224r++;225}226return r ? r : 1;227}228229function getExtraEditorClassName(): string {230let extra = '';231if (browser.isSafari || browser.isWebkitWebView) {232// See https://github.com/microsoft/vscode/issues/108822233extra += 'no-minimap-shadow ';234extra += 'enable-user-select ';235} else {236// Use user-select: none in all browsers except Safari and native macOS WebView237extra += 'no-user-select ';238}239if (platform.isMacintosh) {240extra += 'mac ';241}242return extra;243}244245export interface IEnvConfiguration {246extraEditorClassName: string;247outerWidth: number;248outerHeight: number;249emptySelectionClipboard: boolean;250pixelRatio: number;251accessibilitySupport: AccessibilitySupport;252editContextSupported: boolean;253}254255class ValidatedEditorOptions implements IValidatedEditorOptions {256private readonly _values: any[] = [];257public _read<T>(option: EditorOption): T {258return this._values[option];259}260public get<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T> {261return this._values[id];262}263public _write<T>(option: EditorOption, value: T): void {264this._values[option] = value;265}266}267268export class ComputedEditorOptions implements IComputedEditorOptions {269private readonly _values: any[] = [];270public _read<T>(id: EditorOption): T {271if (id >= this._values.length) {272throw new Error('Cannot read uninitialized value');273}274return this._values[id];275}276public get<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T> {277return this._read(id);278}279public _write<T>(id: EditorOption, value: T): void {280this._values[id] = value;281}282}283284class EditorOptionsUtil {285286public static validateOptions(options: IEditorOptions): ValidatedEditorOptions {287const result = new ValidatedEditorOptions();288for (const editorOption of editorOptionsRegistry) {289const value = (editorOption.name === '_never_' ? undefined : (options as any)[editorOption.name]);290result._write(editorOption.id, editorOption.validate(value));291}292return result;293}294295public static computeOptions(options: ValidatedEditorOptions, env: IEnvironmentalOptions): ComputedEditorOptions {296const result = new ComputedEditorOptions();297for (const editorOption of editorOptionsRegistry) {298result._write(editorOption.id, editorOption.compute(env, result, options._read(editorOption.id)));299}300return result;301}302303private static _deepEquals<T>(a: T, b: T): boolean {304if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {305return a === b;306}307if (Array.isArray(a) || Array.isArray(b)) {308return (Array.isArray(a) && Array.isArray(b) ? arrays.equals(a, b) : false);309}310if (Object.keys(a as unknown as object).length !== Object.keys(b as unknown as object).length) {311return false;312}313for (const key in a) {314if (!EditorOptionsUtil._deepEquals(a[key], b[key])) {315return false;316}317}318return true;319}320321public static checkEquals(a: ComputedEditorOptions, b: ComputedEditorOptions): ConfigurationChangedEvent | null {322const result: boolean[] = [];323let somethingChanged = false;324for (const editorOption of editorOptionsRegistry) {325const changed = !EditorOptionsUtil._deepEquals(a._read(editorOption.id), b._read(editorOption.id));326result[editorOption.id] = changed;327if (changed) {328somethingChanged = true;329}330}331return (somethingChanged ? new ConfigurationChangedEvent(result) : null);332}333334/**335* Returns true if something changed.336* Modifies `options`.337*/338public static applyUpdate(options: IEditorOptions, update: Readonly<IEditorOptions>): boolean {339let changed = false;340for (const editorOption of editorOptionsRegistry) {341if (update.hasOwnProperty(editorOption.name)) {342const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]);343(options as any)[editorOption.name] = result.newValue;344changed = changed || result.didChange;345}346}347return changed;348}349}350351function deepCloneAndMigrateOptions(_options: Readonly<IEditorOptions>): IEditorOptions {352const options = objects.deepClone(_options);353migrateOptions(options);354return options;355}356357358