diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index 3e9b2d6c68b0fb7b5da21dcbdae49a4bca8c1187..c80286daf0dc6a56b5082c17a59a4645faab22e8 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -6,7 +6,7 @@ import 'vs/css!./lineNumbers'; import * as platform from 'vs/base/common/platform'; import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; -import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; +import { RenderLineNumbersType, EditorOptionId, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { editorActiveLineNumber, editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry'; import { RenderingContext } from 'vs/editor/common/view/renderingContext'; @@ -45,7 +45,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { this._lineHeight = config.lineHeight; this._renderLineNumbers = config.viewInfo.renderLineNumbers; this._renderCustomLineNumbers = config.viewInfo.renderCustomLineNumbers; - this._renderFinalNewline = config.viewInfo.renderFinalNewline; + this._renderFinalNewline = this._context.configuration.options.get(EditorOptionId.RenderFinalNewline, EditorOption.RenderFinalNewline); this._lineNumbersLeft = config.layoutInfo.lineNumbersLeft; this._lineNumbersWidth = config.layoutInfo.lineNumbersWidth; } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 1fd0db269f430b7a5ae6a52ac2d937e6a63618da..6ae96a3430c3c5ae31232ca4d76058b7b40ac939 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -63,6 +63,58 @@ export interface IEnvConfiguration { const hasOwnProperty = Object.hasOwnProperty; +export class EditorConfiguration2 { + public static readOptions(options: editorOptions.IEditorOptions): editorOptions.RawEditorOptions { + // console.log(`parseOptions`, options); + const result = new editorOptions.RawEditorOptions(); + for (const editorOption of editorOptions.editorOptionsRegistry) { + result._write(editorOption.id, editorOption.read(options)); + } + return result; + } + + public static mixOptions(a: editorOptions.RawEditorOptions, b: editorOptions.IEditorOptions): editorOptions.RawEditorOptions { + // console.log(`mixOptions`, a, b); + const result = new editorOptions.RawEditorOptions(); + for (const editorOption of editorOptions.editorOptionsRegistry) { + result._write(editorOption.id, editorOption.mix(a._read(editorOption.id), editorOption.read(b))); + } + return result; + } + + public static validateOptions(options: editorOptions.RawEditorOptions): editorOptions.ValidatedEditorOptions { + // console.log(`validateOptions`, options); + const result = new editorOptions.ValidatedEditorOptions(); + for (const editorOption of editorOptions.editorOptionsRegistry) { + result._write(editorOption.id, editorOption.validate(options._read(editorOption.id))); + } + return result; + } + + public static computeOptions(options: editorOptions.ValidatedEditorOptions, env: editorOptions.IEnvironmentalOptions): editorOptions.ComputedEditorOptions { + // console.log(`computeOptions`, options, env); + const result = new editorOptions.ComputedEditorOptions(); + for (const editorOption of editorOptions.editorOptionsRegistry) { + result._write(editorOption.id, editorOption.compute(options._read(editorOption.id))); + } + return result; + } + + public static checkEquals(a: editorOptions.ComputedEditorOptions, b: editorOptions.ComputedEditorOptions): editorOptions.ChangedEditorOptions | null { + // console.log(`equals`, a, b); + const result = new editorOptions.ChangedEditorOptions(); + let somethingChanged = false; + for (const editorOption of editorOptions.editorOptionsRegistry) { + const equals = editorOption.equals(a._read(editorOption.id), b._read(editorOption.id)); + result._write(editorOption.id, equals); + if (!equals) { + somethingChanged = true; + } + } + return (somethingChanged ? result : null); + } +} + export abstract class CommonEditorConfiguration extends Disposable implements editorCommon.IConfiguration { public readonly isSimpleWidget: boolean; @@ -75,6 +127,10 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; + private _rawOptions2: editorOptions.RawEditorOptions; + private _validatedOptions2: editorOptions.ValidatedEditorOptions; + public options!: editorOptions.ComputedEditorOptions; + constructor(isSimpleWidget: boolean, options: editorOptions.IEditorOptions) { super(); @@ -89,6 +145,10 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed this._rawOptions.parameterHints = objects.mixin({}, this._rawOptions.parameterHints || {}); this._validatedOptions = editorOptions.EditorOptionsValidator.validate(this._rawOptions, EDITOR_DEFAULTS); + + this._rawOptions2 = EditorConfiguration2.readOptions(options); + this._validatedOptions2 = EditorConfiguration2.validateOptions(this._rawOptions2); + this._isDominatedByLongLines = false; this._lineNumbersDigitCount = 1; @@ -105,16 +165,20 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed protected _recomputeOptions(): void { const oldOptions = this.editor; - const newOptions = this._computeInternalOptions(); + const oldOptions2 = this.options; + const [newOptions, newOptions2] = this._computeInternalOptions(); + + const changeEvent = (oldOptions2 ? EditorConfiguration2.checkEquals(oldOptions2, newOptions2) : null); - if (oldOptions && oldOptions.equals(newOptions)) { + if (oldOptions && oldOptions.equals(newOptions) && oldOptions2 && changeEvent === null) { return; } this.editor = newOptions; + this.options = newOptions2; if (oldOptions) { - this._onDidChange.fire(oldOptions.createChangeEvent(newOptions)); + this._onDidChange.fire(oldOptions.createChangeEvent(newOptions, changeEvent)); } } @@ -122,7 +186,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed return this._rawOptions; } - private _computeInternalOptions(): editorOptions.InternalEditorOptions { + private _computeInternalOptions(): [editorOptions.InternalEditorOptions, editorOptions.ComputedEditorOptions] { const opts = this._validatedOptions; const partialEnv = this._getEnvConfiguration(); const bareFontInfo = BareFontInfo.createFromRawSettings(this._rawOptions, partialEnv.zoomLevel, this.isSimpleWidget); @@ -138,7 +202,9 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed tabFocusMode: TabFocus.getTabFocusMode(), accessibilitySupport: partialEnv.accessibilitySupport }; - return editorOptions.InternalEditorOptionsFactory.createInternalEditorOptions(env, opts); + const r = editorOptions.InternalEditorOptionsFactory.createInternalEditorOptions(env, opts); + const r2 = EditorConfiguration2.computeOptions(this._validatedOptions2, env); + return [r, r2]; } private static _primitiveArrayEquals(a: any[], b: any[]): boolean { @@ -189,7 +255,11 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed return; } this._rawOptions = objects.mixin(this._rawOptions, newOptions || {}); + this._rawOptions2 = EditorConfiguration2.mixOptions(this._rawOptions2, newOptions); + this._validatedOptions = editorOptions.EditorOptionsValidator.validate(this._rawOptions, EDITOR_DEFAULTS); + this._validatedOptions2 = EditorConfiguration2.validateOptions(this._rawOptions2); + this._recomputeOptions(); } @@ -275,7 +345,7 @@ const editorConfiguration: IConfigurationNode = { }, 'editor.renderFinalNewline': { 'type': 'boolean', - 'default': EDITOR_DEFAULTS.viewInfo.renderFinalNewline, + 'default': editorOptions.EditorOption.RenderFinalNewline.defaultValue, 'description': nls.localize('renderFinalNewline', "Render last line number when the file ends with a newline.") }, 'editor.rulers': { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 43c568a191f498f99fb6187b81b8037219d57b88..6c0c01f4524c16ed6c4ad3cd4b2e02bd7560b18c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -997,7 +997,6 @@ export interface InternalEditorViewOptions { readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; readonly cursorSurroundingLines: number; - readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; readonly revealHorizontalRightPadding: number; @@ -1230,8 +1229,14 @@ export class InternalEditorOptions { /** * @internal */ - public createChangeEvent(newOpts: InternalEditorOptions): IConfigurationChangedEvent { + public createChangeEvent(newOpts: InternalEditorOptions, changeEvent: ChangedEditorOptions | null): IConfigurationChangedEvent { return { + hasChanged: (id: EditorOptionId) => { + if (!changeEvent) { + return false; + } + return changeEvent.get(id); + }, canUseLayerHinting: (this.canUseLayerHinting !== newOpts.canUseLayerHinting), pixelRatio: (this.pixelRatio !== newOpts.pixelRatio), editorClassName: (this.editorClassName !== newOpts.editorClassName), @@ -1312,7 +1317,6 @@ export class InternalEditorOptions { && a.renderLineNumbers === b.renderLineNumbers && a.renderCustomLineNumbers === b.renderCustomLineNumbers && a.cursorSurroundingLines === b.cursorSurroundingLines - && a.renderFinalNewline === b.renderFinalNewline && a.selectOnLineNumbers === b.selectOnLineNumbers && a.glyphMargin === b.glyphMargin && a.revealHorizontalRightPadding === b.revealHorizontalRightPadding @@ -1638,6 +1642,7 @@ export interface EditorLayoutInfo { * An event describing that the configuration of the editor has changed. */ export interface IConfigurationChangedEvent { + hasChanged(id: EditorOptionId): boolean; readonly canUseLayerHinting: boolean; readonly pixelRatio: boolean; readonly editorClassName: boolean; @@ -2076,7 +2081,6 @@ export class EditorOptionsValidator { cursorSurroundingLines: _clampedInt(opts.cursorSurroundingLines, defaults.cursorWidth, 0, Number.MAX_VALUE), renderLineNumbers: renderLineNumbers, renderCustomLineNumbers: renderCustomLineNumbers, - renderFinalNewline: _boolean(opts.renderFinalNewline, defaults.renderFinalNewline), selectOnLineNumbers: _boolean(opts.selectOnLineNumbers, defaults.selectOnLineNumbers), glyphMargin: _boolean(opts.glyphMargin, defaults.glyphMargin), revealHorizontalRightPadding: _clampedInt(opts.revealHorizontalRightPadding, defaults.revealHorizontalRightPadding, 0, 1000), @@ -2198,7 +2202,6 @@ export class InternalEditorOptionsFactory { renderLineNumbers: opts.viewInfo.renderLineNumbers, renderCustomLineNumbers: opts.viewInfo.renderCustomLineNumbers, cursorSurroundingLines: opts.viewInfo.cursorSurroundingLines, - renderFinalNewline: opts.viewInfo.renderFinalNewline, selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers, glyphMargin: opts.viewInfo.glyphMargin, revealHorizontalRightPadding: opts.viewInfo.revealHorizontalRightPadding, @@ -2665,7 +2668,6 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { renderLineNumbers: RenderLineNumbersType.On, renderCustomLineNumbers: null, cursorSurroundingLines: 0, - renderFinalNewline: true, selectOnLineNumbers: true, glyphMargin: true, revealHorizontalRightPadding: 30, @@ -2770,3 +2772,127 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { codeActionsOnSaveTimeout: 750 }, }; + +export interface IRawEditorOptionsBag { + [key: string]: any; +} + +/** + * @internal + */ +export class RawEditorOptions { + private readonly _values: any[] = []; + public _read(id: EditorOptionId): T | undefined { + return this._values[id]; + } + public _write(id: EditorOptionId, value: T | undefined): void { + this._values[id] = value; + } +} + +/** + * @internal + */ +export class ValidatedEditorOptions { + private readonly _values: any[] = []; + public _read(option: EditorOptionId): T { + return this._values[option]; + } + public _write(option: EditorOptionId, value: T): void { + this._values[option] = value; + } +} + +export interface IComputedEditorOptions { + get(id: EditorOptionId, option: IEditorOption): T3; +} + +/** + * @internal + */ +export class ComputedEditorOptions { + private readonly _values: any[] = []; + public _read(id: EditorOptionId): T { + return this._values[id]; + } + public get(id: EditorOptionId, option: IEditorOption): T3 { + return this._values[id]; + } + public _write(id: EditorOptionId, value: T): void { + this._values[id] = value; + } +} + +/** + * @internal + */ +export class ChangedEditorOptions { + private readonly _values: boolean[] = []; + public get(id: EditorOptionId): boolean { + return this._values[id]; + } + public _write(id: EditorOptionId, value: boolean): void { + this._values[id] = value; + } +} + +interface IEditorOption { + readonly id: EditorOptionId; + readonly name: string; + readonly defaultValue: T1; + read(options: IRawEditorOptionsBag): T1 | undefined; + mix(a: T1 | undefined, b: T1 | undefined): T1 | undefined; + validate(input: T1 | undefined): T2; + compute(value: T2): T3; + equals(a: T3, b: T3): boolean; +} + +class BooleanEditorOption implements IEditorOption { + public readonly id: EditorOptionId; + public readonly name: string; + public readonly defaultValue: boolean; + + constructor(id: EditorOptionId, name: string, defaultValue: boolean) { + this.id = id; + this.name = name; + this.defaultValue = defaultValue; + } + + public read(options: IRawEditorOptionsBag): boolean | undefined { + return options[this.name]; + } + + public mix(a: boolean | undefined, b: boolean | undefined): boolean | undefined { + return (typeof b !== 'undefined' ? b : a); + } + + public validate(input: boolean | undefined): boolean { + return _boolean(input, this.defaultValue); + } + + public compute(value: boolean): boolean { + return value; + } + + public equals(a: boolean, b: boolean): boolean { + return (a === b); + } +} + +/** + * @internal + */ +export const editorOptionsRegistry: IEditorOption[] = []; + +function registerEditorOption(option: IEditorOption): IEditorOption { + editorOptionsRegistry[option.id] = option; + return option; +} + +export const enum EditorOptionId { + RenderFinalNewline, +} + +export const EditorOption = { + RenderFinalNewline: registerEditorOption(new BooleanEditorOption(EditorOptionId.RenderFinalNewline, 'renderFinalNewline', true)) +}; diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index bde26ab98021735cf5c0a3fb2db2988a58d68ad7..6c9541592342f83015e12a7a13c0fd2ef413b78f 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -152,6 +152,7 @@ export interface IConfiguration extends IDisposable { onDidChange(listener: (e: editorOptions.IConfigurationChangedEvent) => void): IDisposable; readonly editor: editorOptions.InternalEditorOptions; + readonly options: editorOptions.IComputedEditorOptions; setMaxLineNumber(maxLineNumber: number): void; updateOptions(newOptions: editorOptions.IEditorOptions): void; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index a705c132478b76f0796322cfd305467a462fbaa2..ef807a300680ba7421bd1b987ae0a3c223a7823d 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -430,6 +430,10 @@ export enum RenderLineNumbersType { Custom = 4 } +export enum EditorOptionId { + RenderFinalNewline = 0 +} + /** * A positioning preference for rendering content widgets. */ diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 280e00cbdf7123f4e5c47a0ff2faaed9b2d0707a..c3936c2a1b7dd7f5fbe64b847f133d7f28c06da0 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -369,6 +369,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { RenderMinimap: standaloneEnums.RenderMinimap, ScrollType: standaloneEnums.ScrollType, RenderLineNumbersType: standaloneEnums.RenderLineNumbersType, + EditorOptionId: standaloneEnums.EditorOptionId, // classes InternalEditorOptions: editorOptions.InternalEditorOptions, @@ -378,7 +379,8 @@ export function createMonacoEditorAPI(): typeof monaco.editor { FindMatch: FindMatch, // vars - EditorType: editorCommon.EditorType + EditorType: editorCommon.EditorType, + EditorOption: editorOptions.EditorOption, }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6e0b1286ef04bde6c8e9fe739b9bb0bca216a1b1..970d6f079d6015d263e468e65b1bba9f98c74bfa 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3312,7 +3312,6 @@ declare namespace monaco.editor { readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; readonly cursorSurroundingLines: number; - readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; readonly revealHorizontalRightPadding: number; @@ -3525,6 +3524,7 @@ declare namespace monaco.editor { * An event describing that the configuration of the editor has changed. */ export interface IConfigurationChangedEvent { + hasChanged(id: EditorOptionId): boolean; readonly canUseLayerHinting: boolean; readonly pixelRatio: boolean; readonly editorClassName: boolean; @@ -3551,6 +3551,33 @@ declare namespace monaco.editor { readonly contribInfo: boolean; } + export interface IRawEditorOptionsBag { + [key: string]: any; + } + + export interface IComputedEditorOptions { + get(id: EditorOptionId, option: IEditorOption): T3; + } + + interface IEditorOption { + readonly id: EditorOptionId; + readonly name: string; + readonly defaultValue: T1; + read(options: IRawEditorOptionsBag): T1 | undefined; + mix(a: T1 | undefined, b: T1 | undefined): T1 | undefined; + validate(input: T1 | undefined): T2; + compute(value: T2): T3; + equals(a: T3, b: T3): boolean; + } + + export enum EditorOptionId { + RenderFinalNewline = 0 + } + + export const EditorOption: { + RenderFinalNewline: IEditorOption; + }; + /** * A view zone is a full horizontal rectangle that 'pushes' text down. * The editor reserves space for view zones when rendering.