Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/config/editorConfiguration.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as browser from '../../../base/browser/browser.js';
7
import * as arrays from '../../../base/common/arrays.js';
8
import { Emitter, Event } from '../../../base/common/event.js';
9
import { Disposable } from '../../../base/common/lifecycle.js';
10
import * as objects from '../../../base/common/objects.js';
11
import * as platform from '../../../base/common/platform.js';
12
import { ElementSizeObserver } from './elementSizeObserver.js';
13
import { FontMeasurements } from './fontMeasurements.js';
14
import { migrateOptions } from './migrateOptions.js';
15
import { TabFocus } from './tabFocus.js';
16
import { ComputeOptionsMemory, ConfigurationChangedEvent, EditorOption, editorOptionsRegistry, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, IEnvironmentalOptions } from '../../common/config/editorOptions.js';
17
import { EditorZoom } from '../../common/config/editorZoom.js';
18
import { BareFontInfo, FontInfo, IValidatedEditorOptions } from '../../common/config/fontInfo.js';
19
import { IDimension } from '../../common/core/2d/dimension.js';
20
import { IEditorConfiguration } from '../../common/config/editorConfiguration.js';
21
import { AccessibilitySupport, IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js';
22
import { getWindow, getWindowById } from '../../../base/browser/dom.js';
23
import { PixelRatio } from '../../../base/browser/pixelRatio.js';
24
import { MenuId } from '../../../platform/actions/common/actions.js';
25
import { InputMode } from '../../common/inputMode.js';
26
27
export interface IEditorConstructionOptions extends IEditorOptions {
28
/**
29
* The initial editor dimension (to avoid measuring the container).
30
*/
31
dimension?: IDimension;
32
/**
33
* Place overflow widgets inside an external DOM node.
34
* Defaults to an internal DOM node.
35
*/
36
overflowWidgetsDomNode?: HTMLElement;
37
}
38
39
export class EditorConfiguration extends Disposable implements IEditorConfiguration {
40
41
private _onDidChange = this._register(new Emitter<ConfigurationChangedEvent>());
42
public readonly onDidChange: Event<ConfigurationChangedEvent> = this._onDidChange.event;
43
44
private _onDidChangeFast = this._register(new Emitter<ConfigurationChangedEvent>());
45
public readonly onDidChangeFast: Event<ConfigurationChangedEvent> = this._onDidChangeFast.event;
46
47
public readonly isSimpleWidget: boolean;
48
public readonly contextMenuId: MenuId;
49
private readonly _containerObserver: ElementSizeObserver;
50
51
private _isDominatedByLongLines: boolean = false;
52
private _viewLineCount: number = 1;
53
private _lineNumbersDigitCount: number = 1;
54
private _reservedHeight: number = 0;
55
private _glyphMarginDecorationLaneCount: number = 1;
56
private _targetWindowId: number;
57
58
private readonly _computeOptionsMemory: ComputeOptionsMemory = new ComputeOptionsMemory();
59
/**
60
* Raw options as they were passed in and merged with all calls to `updateOptions`.
61
*/
62
private readonly _rawOptions: IEditorOptions;
63
/**
64
* Validated version of `_rawOptions`.
65
*/
66
private _validatedOptions: ValidatedEditorOptions;
67
/**
68
* Complete options which are a combination of passed in options and env values.
69
*/
70
public options: ComputedEditorOptions;
71
72
constructor(
73
isSimpleWidget: boolean,
74
contextMenuId: MenuId,
75
options: Readonly<IEditorConstructionOptions>,
76
container: HTMLElement | null,
77
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
78
) {
79
super();
80
this.isSimpleWidget = isSimpleWidget;
81
this.contextMenuId = contextMenuId;
82
this._containerObserver = this._register(new ElementSizeObserver(container, options.dimension));
83
this._targetWindowId = getWindow(container).vscodeWindowId;
84
85
this._rawOptions = deepCloneAndMigrateOptions(options);
86
this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);
87
this.options = this._computeOptions();
88
89
if (this.options.get(EditorOption.automaticLayout)) {
90
this._containerObserver.startObserving();
91
}
92
93
this._register(EditorZoom.onDidChangeZoomLevel(() => this._recomputeOptions()));
94
this._register(TabFocus.onDidChangeTabFocus(() => this._recomputeOptions()));
95
this._register(this._containerObserver.onDidChange(() => this._recomputeOptions()));
96
this._register(FontMeasurements.onDidChange(() => this._recomputeOptions()));
97
this._register(PixelRatio.getInstance(getWindow(container)).onDidChange(() => this._recomputeOptions()));
98
this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions()));
99
this._register(InputMode.onDidChangeInputMode(() => this._recomputeOptions()));
100
}
101
102
private _recomputeOptions(): void {
103
const newOptions = this._computeOptions();
104
const changeEvent = EditorOptionsUtil.checkEquals(this.options, newOptions);
105
if (changeEvent === null) {
106
// nothing changed!
107
return;
108
}
109
110
this.options = newOptions;
111
this._onDidChangeFast.fire(changeEvent);
112
this._onDidChange.fire(changeEvent);
113
}
114
115
private _computeOptions(): ComputedEditorOptions {
116
const partialEnv = this._readEnvConfiguration();
117
const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget);
118
const fontInfo = this._readFontInfo(bareFontInfo);
119
const env: IEnvironmentalOptions = {
120
memory: this._computeOptionsMemory,
121
outerWidth: partialEnv.outerWidth,
122
outerHeight: partialEnv.outerHeight - this._reservedHeight,
123
fontInfo: fontInfo,
124
extraEditorClassName: partialEnv.extraEditorClassName,
125
isDominatedByLongLines: this._isDominatedByLongLines,
126
viewLineCount: this._viewLineCount,
127
lineNumbersDigitCount: this._lineNumbersDigitCount,
128
emptySelectionClipboard: partialEnv.emptySelectionClipboard,
129
pixelRatio: partialEnv.pixelRatio,
130
tabFocusMode: this._validatedOptions.get(EditorOption.tabFocusMode) || TabFocus.getTabFocusMode(),
131
inputMode: InputMode.getInputMode(),
132
accessibilitySupport: partialEnv.accessibilitySupport,
133
glyphMarginDecorationLaneCount: this._glyphMarginDecorationLaneCount,
134
editContextSupported: partialEnv.editContextSupported
135
};
136
return EditorOptionsUtil.computeOptions(this._validatedOptions, env);
137
}
138
139
protected _readEnvConfiguration(): IEnvConfiguration {
140
return {
141
extraEditorClassName: getExtraEditorClassName(),
142
outerWidth: this._containerObserver.getWidth(),
143
outerHeight: this._containerObserver.getHeight(),
144
emptySelectionClipboard: browser.isWebKit || browser.isFirefox,
145
pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value,
146
editContextSupported: typeof (globalThis as any).EditContext === 'function',
147
accessibilitySupport: (
148
this._accessibilityService.isScreenReaderOptimized()
149
? AccessibilitySupport.Enabled
150
: this._accessibilityService.getAccessibilitySupport()
151
)
152
};
153
}
154
155
protected _readFontInfo(bareFontInfo: BareFontInfo): FontInfo {
156
return FontMeasurements.readFontInfo(getWindowById(this._targetWindowId, true).window, bareFontInfo);
157
}
158
159
public getRawOptions(): IEditorOptions {
160
return this._rawOptions;
161
}
162
163
public updateOptions(_newOptions: Readonly<IEditorOptions>): void {
164
const newOptions = deepCloneAndMigrateOptions(_newOptions);
165
166
const didChange = EditorOptionsUtil.applyUpdate(this._rawOptions, newOptions);
167
if (!didChange) {
168
return;
169
}
170
171
this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);
172
this._recomputeOptions();
173
}
174
175
public observeContainer(dimension?: IDimension): void {
176
this._containerObserver.observe(dimension);
177
}
178
179
public setIsDominatedByLongLines(isDominatedByLongLines: boolean): void {
180
if (this._isDominatedByLongLines === isDominatedByLongLines) {
181
return;
182
}
183
this._isDominatedByLongLines = isDominatedByLongLines;
184
this._recomputeOptions();
185
}
186
187
public setModelLineCount(modelLineCount: number): void {
188
const lineNumbersDigitCount = digitCount(modelLineCount);
189
if (this._lineNumbersDigitCount === lineNumbersDigitCount) {
190
return;
191
}
192
this._lineNumbersDigitCount = lineNumbersDigitCount;
193
this._recomputeOptions();
194
}
195
196
public setViewLineCount(viewLineCount: number): void {
197
if (this._viewLineCount === viewLineCount) {
198
return;
199
}
200
this._viewLineCount = viewLineCount;
201
this._recomputeOptions();
202
}
203
204
public setReservedHeight(reservedHeight: number) {
205
if (this._reservedHeight === reservedHeight) {
206
return;
207
}
208
this._reservedHeight = reservedHeight;
209
this._recomputeOptions();
210
}
211
212
public setGlyphMarginDecorationLaneCount(decorationLaneCount: number): void {
213
if (this._glyphMarginDecorationLaneCount === decorationLaneCount) {
214
return;
215
}
216
this._glyphMarginDecorationLaneCount = decorationLaneCount;
217
this._recomputeOptions();
218
}
219
}
220
221
function digitCount(n: number): number {
222
let r = 0;
223
while (n) {
224
n = Math.floor(n / 10);
225
r++;
226
}
227
return r ? r : 1;
228
}
229
230
function getExtraEditorClassName(): string {
231
let extra = '';
232
if (browser.isSafari || browser.isWebkitWebView) {
233
// See https://github.com/microsoft/vscode/issues/108822
234
extra += 'no-minimap-shadow ';
235
extra += 'enable-user-select ';
236
} else {
237
// Use user-select: none in all browsers except Safari and native macOS WebView
238
extra += 'no-user-select ';
239
}
240
if (platform.isMacintosh) {
241
extra += 'mac ';
242
}
243
return extra;
244
}
245
246
export interface IEnvConfiguration {
247
extraEditorClassName: string;
248
outerWidth: number;
249
outerHeight: number;
250
emptySelectionClipboard: boolean;
251
pixelRatio: number;
252
accessibilitySupport: AccessibilitySupport;
253
editContextSupported: boolean;
254
}
255
256
class ValidatedEditorOptions implements IValidatedEditorOptions {
257
private readonly _values: any[] = [];
258
public _read<T>(option: EditorOption): T {
259
return this._values[option];
260
}
261
public get<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T> {
262
return this._values[id];
263
}
264
public _write<T>(option: EditorOption, value: T): void {
265
this._values[option] = value;
266
}
267
}
268
269
export class ComputedEditorOptions implements IComputedEditorOptions {
270
private readonly _values: any[] = [];
271
public _read<T>(id: EditorOption): T {
272
if (id >= this._values.length) {
273
throw new Error('Cannot read uninitialized value');
274
}
275
return this._values[id];
276
}
277
public get<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T> {
278
return this._read(id);
279
}
280
public _write<T>(id: EditorOption, value: T): void {
281
this._values[id] = value;
282
}
283
}
284
285
class EditorOptionsUtil {
286
287
public static validateOptions(options: IEditorOptions): ValidatedEditorOptions {
288
const result = new ValidatedEditorOptions();
289
for (const editorOption of editorOptionsRegistry) {
290
const value = (editorOption.name === '_never_' ? undefined : (options as any)[editorOption.name]);
291
result._write(editorOption.id, editorOption.validate(value));
292
}
293
return result;
294
}
295
296
public static computeOptions(options: ValidatedEditorOptions, env: IEnvironmentalOptions): ComputedEditorOptions {
297
const result = new ComputedEditorOptions();
298
for (const editorOption of editorOptionsRegistry) {
299
result._write(editorOption.id, editorOption.compute(env, result, options._read(editorOption.id)));
300
}
301
return result;
302
}
303
304
private static _deepEquals<T>(a: T, b: T): boolean {
305
if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
306
return a === b;
307
}
308
if (Array.isArray(a) || Array.isArray(b)) {
309
return (Array.isArray(a) && Array.isArray(b) ? arrays.equals(a, b) : false);
310
}
311
if (Object.keys(a as unknown as object).length !== Object.keys(b as unknown as object).length) {
312
return false;
313
}
314
for (const key in a) {
315
if (!EditorOptionsUtil._deepEquals(a[key], b[key])) {
316
return false;
317
}
318
}
319
return true;
320
}
321
322
public static checkEquals(a: ComputedEditorOptions, b: ComputedEditorOptions): ConfigurationChangedEvent | null {
323
const result: boolean[] = [];
324
let somethingChanged = false;
325
for (const editorOption of editorOptionsRegistry) {
326
const changed = !EditorOptionsUtil._deepEquals(a._read(editorOption.id), b._read(editorOption.id));
327
result[editorOption.id] = changed;
328
if (changed) {
329
somethingChanged = true;
330
}
331
}
332
return (somethingChanged ? new ConfigurationChangedEvent(result) : null);
333
}
334
335
/**
336
* Returns true if something changed.
337
* Modifies `options`.
338
*/
339
public static applyUpdate(options: IEditorOptions, update: Readonly<IEditorOptions>): boolean {
340
let changed = false;
341
for (const editorOption of editorOptionsRegistry) {
342
if (update.hasOwnProperty(editorOption.name)) {
343
const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]);
344
(options as any)[editorOption.name] = result.newValue;
345
changed = changed || result.didChange;
346
}
347
}
348
return changed;
349
}
350
}
351
352
function deepCloneAndMigrateOptions(_options: Readonly<IEditorOptions>): IEditorOptions {
353
const options = objects.deepClone(_options);
354
migrateOptions(options);
355
return options;
356
}
357
358