diff --git a/src/browser/MouseZoneManager.ts b/src/browser/MouseZoneManager.ts index b6740157ce..71ffe7c5e3 100644 --- a/src/browser/MouseZoneManager.ts +++ b/src/browser/MouseZoneManager.ts @@ -150,7 +150,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } // Restart the tooltip timeout - this._tooltipTimeout = window.setTimeout(() => this._onTooltip(e), this._optionsService.options.linkTooltipHoverDuration); + this._tooltipTimeout = window.setTimeout(() => this._onTooltip(e), this._optionsService.rawOptions.linkTooltipHoverDuration); } private _onTooltip(e: MouseEvent): void { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 68291f6497..1302e051da 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -260,7 +260,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.viewport?.syncScrollArea(); break; case 'screenReaderMode': - if (this.optionsService.options.screenReaderMode) { + if (this.optionsService.rawOptions.screenReaderMode) { if (!this._accessibilityManager && this._renderService) { this._accessibilityManager = new AccessibilityManager(this, this._renderService); } @@ -271,7 +271,7 @@ export class Terminal extends CoreTerminal implements ITerminal { break; case 'tabStopWidth': this.buffers.setupTabStops(); break; case 'theme': - this._setTheme(this.optionsService.options.theme); + this._setTheme(this.optionsService.rawOptions.theme); break; } } @@ -715,7 +715,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this.coreMouseService.onProtocolChange(events => { // apply global changes on events if (events) { - if (this.optionsService.options.logLevel === 'debug') { + if (this.optionsService.rawOptions.logLevel === 'debug') { this._logService.debug('Binding to mouse events:', this.coreMouseService.explainEvents(events)); } this.element!.classList.add('enable-mouse-events'); @@ -1087,7 +1087,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // other listeners. When screen reader mode is enabled, this could cause issues if the event // is handled at a higher level, this is a compromise in order to echo keys to the screen // reader. - if (!this.optionsService.options.screenReaderMode) { + if (!this.optionsService.rawOptions.screenReaderMode) { return this.cancel(event, true); } @@ -1182,7 +1182,7 @@ export class Terminal extends CoreTerminal implements ITerminal { protected _inputEvent(ev: InputEvent): boolean { // Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to // support reading out character input which can doubling up input characters - if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.options.screenReaderMode) { + if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.rawOptions.screenReaderMode) { if (this._keyPressHandled) { return false; } diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 1dfc9e3e48..14fab897ce 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -110,7 +110,7 @@ export class Viewport extends Disposable implements IViewport { } // Update scroll bar width - if (this._optionsService.options.scrollback === 0) { + if (this._optionsService.rawOptions.scrollback === 0) { this.scrollBarWidth = 0; } else { this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; @@ -153,7 +153,7 @@ export class Viewport extends Disposable implements IViewport { } // If the scroll bar visibility changed - if (this._lastHadScrollBar !== (this._optionsService.options.scrollback > 0)) { + if (this._lastHadScrollBar !== (this._optionsService.rawOptions.scrollback > 0)) { this._refresh(immediate); } } @@ -259,15 +259,15 @@ export class Viewport extends Disposable implements IViewport { } private _applyScrollModifier(amount: number, ev: WheelEvent): number { - const modifier = this._optionsService.options.fastScrollModifier; + const modifier = this._optionsService.rawOptions.fastScrollModifier; // Multiply the scroll speed when the modifier is down if ((modifier === 'alt' && ev.altKey) || (modifier === 'ctrl' && ev.ctrlKey) || (modifier === 'shift' && ev.shiftKey)) { - return amount * this._optionsService.options.fastScrollSensitivity * this._optionsService.options.scrollSensitivity; + return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity; } - return amount * this._optionsService.options.scrollSensitivity; + return amount * this._optionsService.rawOptions.scrollSensitivity; } /** diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index 4e17672553..61051b5891 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -217,8 +217,8 @@ export class CompositionHelper { this._compositionView.style.top = cursorTop + 'px'; this._compositionView.style.height = cellHeight + 'px'; this._compositionView.style.lineHeight = cellHeight + 'px'; - this._compositionView.style.fontFamily = this._optionsService.options.fontFamily; - this._compositionView.style.fontSize = this._optionsService.options.fontSize + 'px'; + this._compositionView.style.fontFamily = this._optionsService.rawOptions.fontFamily; + this._compositionView.style.fontSize = this._optionsService.rawOptions.fontSize + 'px'; // Sync the textarea to the exact position of the composition view so the IME knows where the // text is. const compositionViewBounds = this._compositionView.getBoundingClientRect(); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index f65d48c17b..117805f929 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -30,17 +30,21 @@ export class Terminal implements ITerminalApi { this._core = new TerminalCore(options); this._addonManager = new AddonManager(); - this._publicOptions = {}; + this._publicOptions = { ... this._core.options }; + const getter = (propName: string): any => { + return this._core.options[propName]; + }; + const setter = (propName: string, value: any): void => { + this._checkReadonlyOptions(propName); + this._core.options[propName] = value; + }; + for (const propName in this._core.options) { - Object.defineProperty(this._publicOptions, propName, { - get: () => { - return this._core.options[propName]; - }, - set: (value: any) => { - this._checkReadonlyOptions(propName); - this._core.options[propName] = value; - } - }); + const desc = { + get: getter.bind(this, propName), + set: setter.bind(this, propName) + }; + Object.defineProperty(this._publicOptions, propName, desc); } } @@ -54,7 +58,7 @@ export class Terminal implements ITerminalApi { } private _checkProposedApi(): void { - if (!this._core.optionsService.options.allowProposedApi) { + if (!this._core.optionsService.rawOptions.allowProposedApi) { throw new Error('You must set the allowProposedApi option to true to use proposed API'); } } diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 343ebfe20a..629e94364a 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -112,7 +112,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(this._optionsService.options, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas = acquireCharAtlas(this._optionsService.rawOptions, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight); this._charAtlas.warmUp(); } @@ -267,7 +267,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // Draw custom characters if applicable let drawSuccess = false; - if (this._optionsService.options.customGlyphs !== false) { + if (this._optionsService.rawOptions.customGlyphs !== false) { drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight); } @@ -315,7 +315,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); } - const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8; + const drawInBrightColor = this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8; fg += drawInBrightColor ? 8 : 0; this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; @@ -356,7 +356,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { let bg = cell.getBgColor(); - if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { + if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { bg += 8; } this._ctx.fillStyle = this._colors.ansi[bg].css; @@ -370,7 +370,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else { let fg = cell.getFgColor(); - if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { fg += 8; } this._ctx.fillStyle = this._colors.ansi[fg].css; @@ -386,7 +386,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // Draw custom characters if applicable let drawSuccess = false; - if (this._optionsService.options.customGlyphs !== false) { + if (this._optionsService.rawOptions.customGlyphs !== false) { drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight); } @@ -421,14 +421,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param isBold If we should use the bold fontWeight. */ protected _getFont(isBold: boolean, isItalic: boolean): string { - const fontWeight = isBold ? this._optionsService.options.fontWeightBold : this._optionsService.options.fontWeight; + const fontWeight = isBold ? this._optionsService.rawOptions.fontWeightBold : this._optionsService.rawOptions.fontWeight; const fontStyle = isItalic ? 'italic' : ''; - return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`; + return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`; } private _getContrastColor(cell: CellData): IColor | undefined { - if (this._optionsService.options.minimumContrastRatio === 1) { + if (this._optionsService.rawOptions.minimumContrastRatio === 1) { return undefined; } @@ -455,7 +455,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse); const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); - const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio); + const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio); if (!result) { this._colors.contrastCache.setColor(cell.bg, cell.fg, null); @@ -495,7 +495,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - if (this._optionsService.options.drawBoldTextInBrightColors && bold && fgColor < 8) { + if (this._optionsService.rawOptions.drawBoldTextInBrightColors && bold && fgColor < 8) { fgColor += 8; } return this._colors.ansi[fgColor].rgba; diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index c267f476cb..ea419cb27e 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -94,7 +94,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } public onOptionsChanged(): void { - if (this._optionsService.options.cursorBlink) { + if (this._optionsService.rawOptions.cursorBlink) { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => { this._render(true); @@ -148,7 +148,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._clearCursor(); this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - const cursorStyle = this._optionsService.options.cursorStyle; + const cursorStyle = this._optionsService.rawOptions.cursorStyle; if (cursorStyle && cursorStyle !== 'block') { this._cursorRenderers[cursorStyle](cursorX, viewportRelativeCursorY, this._cell); } else { @@ -174,7 +174,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._state.x === cursorX && this._state.y === viewportRelativeCursorY && this._state.isFocused === this._coreBrowserService.isFocused && - this._state.style === this._optionsService.options.cursorStyle && + this._state.style === this._optionsService.rawOptions.cursorStyle && this._state.width === this._cell.getWidth()) { return; } @@ -182,13 +182,13 @@ export class CursorRenderLayer extends BaseRenderLayer { } this._ctx.save(); - this._cursorRenderers[this._optionsService.options.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell); + this._cursorRenderers[this._optionsService.rawOptions.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell); this._ctx.restore(); this._state.x = cursorX; this._state.y = viewportRelativeCursorY; this._state.isFocused = false; - this._state.style = this._optionsService.options.cursorStyle; + this._state.style = this._optionsService.rawOptions.cursorStyle; this._state.width = this._cell.getWidth(); } @@ -213,7 +213,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._fillLeftLineAtCell(x, y, this._optionsService.options.cursorWidth); + this._fillLeftLineAtCell(x, y, this._optionsService.rawOptions.cursorWidth); this._ctx.restore(); } diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 162a7ed3b1..7a64257da7 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -39,7 +39,7 @@ export class Renderer extends Disposable implements IRenderer { @IOptionsService private readonly _optionsService: IOptionsService ) { super(); - const allowTransparency = this._optionsService.options.allowTransparency; + const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id), instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id), @@ -178,18 +178,18 @@ export class Renderer extends Disposable implements IRenderer { // will be floored because since lineHeight can never be lower then 1, there // is a guarentee that the scaled line height will always be larger than // scaled char height. - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); // Calculate the y coordinate within a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharTop = this._optionsService.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCharTop = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); // Calculate the x coordinate with a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharLeft = Math.floor(this._optionsService.options.letterSpacing / 2); + this.dimensions.scaledCharLeft = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 59fbb7b11b..33d942ff7c 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -225,7 +225,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { let bg = cell.getBgColor(); - if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { + if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { bg += 8; } this._ctx.fillStyle = this._colors.ansi[bg].css; @@ -237,7 +237,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else { let fg = cell.getFgColor(); - if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { fg += 8; } this._ctx.fillStyle = this._colors.ansi[fg].css; @@ -271,7 +271,7 @@ export class TextRenderLayer extends BaseRenderLayer { } public onOptionsChanged(): void { - this._setTransparency(this._optionsService.options.allowTransparency); + this._setTransparency(this._optionsService.rawOptions.allowTransparency); } /** diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index d08cf98789..ee28339946 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -107,8 +107,8 @@ export class DomRenderer extends Disposable implements IRenderer { private _updateDimensions(): void { this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio; this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing); - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); this.dimensions.scaledCharLeft = 0; this.dimensions.scaledCharTop = 0; this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols; @@ -161,16 +161,16 @@ export class DomRenderer extends Disposable implements IRenderer { let styles = `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + ` color: ${this._colors.foreground.css};` + - ` font-family: ${this._optionsService.options.fontFamily};` + - ` font-size: ${this._optionsService.options.fontSize}px;` + + ` font-family: ${this._optionsService.rawOptions.fontFamily};` + + ` font-size: ${this._optionsService.rawOptions.fontSize}px;` + `}`; // Text styles styles += `${this._terminalSelector} span:not(.${BOLD_CLASS}) {` + - ` font-weight: ${this._optionsService.options.fontWeight};` + + ` font-weight: ${this._optionsService.rawOptions.fontWeight};` + `}` + `${this._terminalSelector} span.${BOLD_CLASS} {` + - ` font-weight: ${this._optionsService.options.fontWeightBold};` + + ` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` + `}` + `${this._terminalSelector} span.${ITALIC_CLASS} {` + ` font-style: italic;` + @@ -210,7 +210,7 @@ export class DomRenderer extends Disposable implements IRenderer { ` color: ${this._colors.cursorAccent.css};` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` + - ` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` + + ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` + ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` + @@ -356,7 +356,7 @@ export class DomRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1); - const cursorBlink = this._optionsService.options.cursorBlink; + const cursorBlink = this._optionsService.rawOptions.cursorBlink; for (let y = start; y <= end; y++) { const rowElement = this._rowElements[y]; @@ -364,7 +364,7 @@ export class DomRenderer extends Disposable implements IRenderer { const row = y + this._bufferService.buffer.ydisp; const lineData = this._bufferService.buffer.lines.get(row); - const cursorStyle = this._optionsService.options.cursorStyle; + const cursorStyle = this._optionsService.rawOptions.cursorStyle; rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); } } diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index a24f3e4636..fda800aed5 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -175,7 +175,7 @@ export class DomRendererRowFactory { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - if (cell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) { + if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) { fg += 8; } if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) { @@ -225,7 +225,7 @@ export class DomRendererRowFactory { } private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean { - if (this._optionsService.options.minimumContrastRatio === 1) { + if (this._optionsService.rawOptions.minimumContrastRatio === 1) { return false; } @@ -234,7 +234,7 @@ export class DomRendererRowFactory { // Calculate and store in cache if (adjustedColor === undefined) { - adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); + adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.rawOptions.minimumContrastRatio); this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null); } diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 79696a2818..b04e157f62 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -69,8 +69,8 @@ class DomMeasureStrategy implements IMeasureStrategy { } public measure(): IReadonlyMeasureResult { - this._measureElement.style.fontFamily = this._optionsService.options.fontFamily; - this._measureElement.style.fontSize = `${this._optionsService.options.fontSize}px`; + this._measureElement.style.fontFamily = this._optionsService.rawOptions.fontFamily; + this._measureElement.style.fontSize = `${this._optionsService.rawOptions.fontSize}px`; // Note that this triggers a synchronous layout const geometry = this._measureElement.getBoundingClientRect(); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index fce53ecdb7..1ea2395d8e 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -414,7 +414,7 @@ export class SelectionService extends Disposable implements ISelectionService { */ public shouldForceSelection(event: MouseEvent): boolean { if (Browser.isMac) { - return event.altKey && this._optionsService.options.macOptionClickForcesSelection; + return event.altKey && this._optionsService.rawOptions.macOptionClickForcesSelection; } return event.shiftKey; @@ -567,7 +567,7 @@ export class SelectionService extends Disposable implements ISelectionService { * @param event the mouse or keyboard event */ public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean { - return event.altKey && !(Browser.isMac && this._optionsService.options.macOptionClickForcesSelection); + return event.altKey && !(Browser.isMac && this._optionsService.rawOptions.macOptionClickForcesSelection); } /** @@ -993,7 +993,7 @@ export class SelectionService extends Disposable implements ISelectionService { if (cell.getWidth() === 0) { return false; } - return this._optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0; + return this._optionsService.rawOptions.wordSeparator.indexOf(cell.getChars()) >= 0; } /** diff --git a/src/browser/services/SoundService.ts b/src/browser/services/SoundService.ts index 3880b42d36..a3b6800d42 100644 --- a/src/browser/services/SoundService.ts +++ b/src/browser/services/SoundService.ts @@ -34,7 +34,7 @@ export class SoundService implements ISoundService { return; } const bellAudioSource = ctx.createBufferSource(); - ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.options.bellSound)), (buffer) => { + ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.rawOptions.bellSound)), (buffer) => { bellAudioSource.buffer = buffer; bellAudioSource.connect(ctx.destination); bellAudioSource.start(0); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index d537824746..12b374c848 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -247,7 +247,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { } protected _setup(): void { - if (this.optionsService.options.windowsMode) { + if (this.optionsService.rawOptions.windowsMode) { this._enableWindowsMode(); } } @@ -267,7 +267,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.buffers.resize(this.cols, this.rows); break; case 'windowsMode': - if (this.optionsService.options.windowsMode) { + if (this.optionsService.rawOptions.windowsMode) { this._enableWindowsMode(); } else { this._windowsMode?.dispose(); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 870ecf0153..f62d62e134 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -176,8 +176,8 @@ class DECRQSS implements IDcsHandler { break; case ' q': // DECSCUSR const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 }; - let style = STYLES[this._optionsService.options.cursorStyle]; - style -= this._optionsService.options.cursorBlink ? 1 : 0; + let style = STYLES[this._optionsService.rawOptions.cursorStyle]; + style -= this._optionsService.rawOptions.cursorBlink ? 1 : 0; this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); break; default: @@ -589,7 +589,7 @@ export class InputHandler extends Disposable implements IInputHandler { let code: number; let chWidth: number; const charset = this._charsetService.charset; - const screenReaderMode = this._optionsService.options.screenReaderMode; + const screenReaderMode = this._optionsService.rawOptions.screenReaderMode; const cols = this._bufferService.cols; const wraparoundMode = this._coreService.decPrivateModes.wraparound; const insertMode = this._coreService.modes.insertMode; @@ -731,7 +731,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed return this._parser.registerCsiHandler(id, params => { - if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { + if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) { return true; } return callback(params); @@ -786,7 +786,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public lineFeed(): boolean { this._dirtyRowService.markDirty(this._activeBuffer.y); - if (this._optionsService.options.convertEol) { + if (this._optionsService.rawOptions.convertEol) { this._activeBuffer.x = 0; } this._activeBuffer.y++; @@ -890,7 +890,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const originalX = this._activeBuffer.x; this._activeBuffer.x = this._activeBuffer.nextStop(); - if (this._optionsService.options.screenReaderMode) { + if (this._optionsService.rawOptions.screenReaderMode) { this._onA11yTab.fire(this._activeBuffer.x - originalX); } return true; @@ -1749,7 +1749,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @param term The terminal name to evaluate */ private _is(term: string): boolean { - return (this._optionsService.options.termName + '').indexOf(term) === 0; + return (this._optionsService.rawOptions.termName + '').indexOf(term) === 0; } /** @@ -1915,7 +1915,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is enabled * through `options.windowsOptions`. */ - if (this._optionsService.options.windowOptions.setWinLines) { + if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(132, this._bufferService.rows); this._onRequestReset.fire(); } @@ -2149,7 +2149,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is enabled * through `options.windowsOptions`. */ - if (this._optionsService.options.windowOptions.setWinLines) { + if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(80, this._bufferService.rows); this._onRequestReset.fire(); } @@ -2726,7 +2726,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps >= 24 not implemented */ public windowOptions(params: IParams): boolean { - if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { + if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) { return true; } const second = (params.length > 1) ? params.params[1] : 0; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 67488be0cd..58f6c70920 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -120,12 +120,13 @@ export class MockLogService implements ILogService { export class MockOptionsService implements IOptionsService { public serviceBrand: any; - public options: ITerminalOptions = clone(DEFAULT_OPTIONS); + public readonly rawOptions: ITerminalOptions = clone(DEFAULT_OPTIONS); + public options: ITerminalOptions = this.rawOptions; public onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { - this.options[key] = testOptions[key]; + this.rawOptions[key] = testOptions[key]; } } } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index c788bf361a..8c2b154b88 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -107,7 +107,7 @@ export class Buffer implements IBuffer { return rows; } - const correctBufferLength = rows + this._optionsService.options.scrollback; + const correctBufferLength = rows + this._optionsService.rawOptions.scrollback; return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength; } @@ -172,7 +172,7 @@ export class Buffer implements IBuffer { if (this._rows < newRows) { for (let y = this._rows; y < newRows; y++) { if (this.lines.length < newRows + this.ybase) { - if (this._optionsService.options.windowsMode) { + if (this._optionsService.rawOptions.windowsMode) { // Just add the new missing rows on Windows as conpty reprints the screen with it's // view of the world. Once a line enters scrollback for conpty it remains there this.lines.push(new BufferLine(newCols, nullCell)); @@ -252,7 +252,7 @@ export class Buffer implements IBuffer { } private get _isReflowEnabled(): boolean { - return this._hasScrollback && !this._optionsService.options.windowsMode; + return this._hasScrollback && !this._optionsService.rawOptions.windowsMode; } private _reflow(newCols: number, newRows: number): void { @@ -550,7 +550,7 @@ export class Buffer implements IBuffer { i = 0; } - for (; i < this._cols; i += this._optionsService.options.tabStopWidth) { + for (; i < this._cols; i += this._optionsService.rawOptions.tabStopWidth) { this.tabs[i] = true; } } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index c8c5f27313..bba60dd8a3 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -36,8 +36,8 @@ export class BufferService extends Disposable implements IBufferService { @IOptionsService private _optionsService: IOptionsService ) { super(); - this.cols = Math.max(_optionsService.options.cols || 0, MINIMUM_COLS); - this.rows = Math.max(_optionsService.options.rows || 0, MINIMUM_ROWS); + this.cols = Math.max(_optionsService.rawOptions.cols || 0, MINIMUM_COLS); + this.rows = Math.max(_optionsService.rawOptions.rows || 0, MINIMUM_ROWS); this.buffers = new BufferSet(_optionsService, this); } diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 43b89a9cc9..20a3460318 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -62,7 +62,7 @@ export class CoreService extends Disposable implements ICoreService { public triggerDataEvent(data: string, wasUserInput: boolean = false): void { // Prevents all events to pty process if stdin is disabled - if (this._optionsService.options.disableStdin) { + if (this._optionsService.rawOptions.disableStdin) { return; } @@ -83,7 +83,7 @@ export class CoreService extends Disposable implements ICoreService { } public triggerBinaryEvent(data: string): void { - if (this._optionsService.options.disableStdin) { + if (this._optionsService.rawOptions.disableStdin) { return; } this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); diff --git a/src/common/services/LogService.ts b/src/common/services/LogService.ts index de4e18f841..d3566567e1 100644 --- a/src/common/services/LogService.ts +++ b/src/common/services/LogService.ts @@ -46,7 +46,7 @@ export class LogService implements ILogService { } private _updateLogLevel(): void { - this.logLevel = optionsKeyToLogLevel[this._optionsService.options.logLevel]; + this.logLevel = optionsKeyToLogLevel[this._optionsService.rawOptions.logLevel]; } private _evalLazyOptionalParams(optionalParams: any[]): void { diff --git a/src/common/services/OptionsService.test.ts b/src/common/services/OptionsService.test.ts index 8675b6b99f..ae9e77ec06 100644 --- a/src/common/services/OptionsService.test.ts +++ b/src/common/services/OptionsService.test.ts @@ -10,7 +10,7 @@ describe('OptionsService', () => { describe('constructor', () => { const originalError = console.error; beforeEach(() => { - console.error = () => {}; + console.error = () => { }; }); afterEach(() => { console.error = originalError; @@ -26,7 +26,11 @@ describe('OptionsService', () => { assert.equal(optionsService.getOption('cols'), 80); }); it('uses default value if invalid constructor option value passed', () => { - assert.equal(new OptionsService({tabStopWidth: 0}).getOption('tabStopWidth'), DEFAULT_OPTIONS.tabStopWidth); + assert.equal(new OptionsService({ tabStopWidth: 0 }).getOption('tabStopWidth'), DEFAULT_OPTIONS.tabStopWidth); + }); + it('object.keys return the correct number of options', () => { + const optionsService = new OptionsService({ cols: 80, rows: 25 }); + assert.notEqual(Object.keys(optionsService.options).length, 0); }); }); describe('setOption', () => { diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 65b0703c77..43fe998121 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -60,7 +60,7 @@ const FONT_WEIGHT_OPTIONS: Extract[] = ['normal', 'bold', '1 export class OptionsService implements IOptionsService { public serviceBrand: any; - private _options: ITerminalOptions; + public readonly rawOptions: ITerminalOptions; public options: ITerminalOptions; private _onOptionChange = new EventEmitter(); @@ -68,12 +68,12 @@ export class OptionsService implements IOptionsService { constructor(options: Partial) { // set the default value of each option - this._options = { ...DEFAULT_OPTIONS }; + const defaultOptions = { ...DEFAULT_OPTIONS }; for (const key in options) { - if (key in this._options) { + if (key in defaultOptions) { try { const newValue = options[key]; - this._options[key] = this._sanitizeAndValidateOption(key, newValue); + defaultOptions[key] = this._sanitizeAndValidateOption(key, newValue); } catch (e) { console.error(e); } @@ -81,34 +81,39 @@ export class OptionsService implements IOptionsService { } // set up getters and setters for each option - this.options = this._setupOptions(this._options); + this.rawOptions = defaultOptions; + this.options = { ... defaultOptions }; + this._setupOptions(); } - private _setupOptions(options: ITerminalOptions): ITerminalOptions { - const copiedOptions = { ... options }; - for (const propName in copiedOptions) { - Object.defineProperty(copiedOptions, propName, { - get: () => { - if (!(propName in DEFAULT_OPTIONS)) { - throw new Error(`No option with key "${propName}"`); - } - return this._options[propName]; - }, - set: (value: any) => { - if (!(propName in DEFAULT_OPTIONS)) { - throw new Error(`No option with key "${propName}"`); - } - - value = this._sanitizeAndValidateOption(propName, value); - // Don't fire an option change event if they didn't change - if (this._options[propName] !== value) { - this._options[propName] = value; - this._onOptionChange.fire(propName); - } - } - }); + private _setupOptions(): void { + const getter = (propName: string): any => { + if (!(propName in DEFAULT_OPTIONS)) { + throw new Error(`No option with key "${propName}"`); + } + return this.rawOptions[propName]; + }; + + const setter = (propName: string, value: any): void => { + if (!(propName in DEFAULT_OPTIONS)) { + throw new Error(`No option with key "${propName}"`); + } + + value = this._sanitizeAndValidateOption(propName, value); + // Don't fire an option change event if they didn't change + if (this.rawOptions[propName] !== value) { + this.rawOptions[propName] = value; + this._onOptionChange.fire(propName); + } + }; + + for (const propName in this.rawOptions) { + const desc = { + get: getter.bind(this, propName), + set: setter.bind(this, propName) + }; + Object.defineProperty(this.options, propName, desc); } - return copiedOptions; } public setOption(key: string, value: any): void { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 537ac6db6b..90dca988c7 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -188,6 +188,12 @@ export const IOptionsService = createDecorator('OptionsService' export interface IOptionsService { serviceBrand: undefined; + /** + * Read only access to the raw options object, this is an internal-only fast path for accessing + * single options without any validation as we trust TypeScript to enforce correct usage + * internally. + */ + readonly rawOptions: Readonly; readonly options: ITerminalOptions; readonly onOptionChange: IEvent; diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 7243e6171d..2b0c00d470 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -189,6 +189,10 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.options.fontSize`), 30); assert.equal(await page.evaluate(`window.term.options.fontFamily`), 'Arial'); }); + it('object.keys return the correct number of options', async () => { + await openTerminal(page); + assert.notEqual(await page.evaluate(`Object.keys(window.term.options).length`), 0); + }); }); describe('renderer', () => {