diff --git a/addons/addon-fit/src/FitAddon.ts b/addons/addon-fit/src/FitAddon.ts index 87b21e99e..3db988acd 100644 --- a/addons/addon-fit/src/FitAddon.ts +++ b/addons/addon-fit/src/FitAddon.ts @@ -19,8 +19,10 @@ interface ITerminalDimensions { cols: number; } -const MINIMUM_COLS = 2; -const MINIMUM_ROWS = 1; +const enum Constants { + MINIMUM_COLS = 2, + MINIMUM_ROWS = 1 +} function getWindow(e: Node): Window { if (e?.ownerDocument?.defaultView) { @@ -86,8 +88,8 @@ export class FitAddon implements ITerminalAddon, IFitApi { const availableHeight = parentElementHeight - elementPaddingVer; const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth; const geometry = { - cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)), - rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height)) + cols: Math.max(Constants.MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)), + rows: Math.max(Constants.MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height)) }; return geometry; } diff --git a/addons/addon-image/src/ImageRenderer.ts b/addons/addon-image/src/ImageRenderer.ts index 79b83a977..b36cdd004 100644 --- a/addons/addon-image/src/ImageRenderer.ts +++ b/addons/addon-image/src/ImageRenderer.ts @@ -8,8 +8,10 @@ import { IDisposable } from '@xterm/xterm'; import { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types'; import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; -const PLACEHOLDER_LENGTH = 4096; -const PLACEHOLDER_HEIGHT = 24; +const enum Constants { + PLACEHOLDER_LENGTH = 4096, + PLACEHOLDER_HEIGHT = 24 +} /** * ImageRenderer - terminal frontend extension: @@ -110,7 +112,7 @@ export class ImageRenderer extends Disposable implements IDisposable { public showPlaceholder(value: boolean): void { if (value) { if (!this._placeholder && this.cellSize.height !== -1) { - this._createPlaceHolder(Math.max(this.cellSize.height + 1, PLACEHOLDER_HEIGHT)); + this._createPlaceHolder(Math.max(this.cellSize.height + 1, Constants.PLACEHOLDER_HEIGHT)); } } else { this._placeholderBitmap?.close(); @@ -249,7 +251,7 @@ export class ImageRenderer extends Disposable implements IDisposable { } if (!this._placeholder) { - this._createPlaceHolder(Math.max(height + 1, PLACEHOLDER_HEIGHT)); + this._createPlaceHolder(Math.max(height + 1, Constants.PLACEHOLDER_HEIGHT)); } else if (height >= this._placeholder!.height) { this._createPlaceHolder(height + 1); } @@ -379,7 +381,7 @@ export class ImageRenderer extends Disposable implements IDisposable { return this._layers.has(layer); } - private _createPlaceHolder(height: number = PLACEHOLDER_HEIGHT): void { + private _createPlaceHolder(height: number = Constants.PLACEHOLDER_HEIGHT): void { this._placeholderBitmap?.close(); this._placeholderBitmap = undefined; @@ -403,7 +405,7 @@ export class ImageRenderer extends Disposable implements IDisposable { ctx.putImageData(imgData, 0, 0); // create placeholder line, width aligned to blueprint width - const width = (screen.width + bWidth - 1) & ~(bWidth - 1) || PLACEHOLDER_LENGTH; + const width = (screen.width + bWidth - 1) & ~(bWidth - 1) || Constants.PLACEHOLDER_LENGTH; this._placeholder = ImageRenderer.createCanvas(this.document, width, height); const ctx2 = this._placeholder.getContext('2d', { alpha: false }); if (!ctx2) { diff --git a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts index 17cd5ed9f..f449f3186 100644 --- a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts +++ b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts @@ -16,24 +16,23 @@ import { IKittyCommand, IPendingTransmission, IKittyImageData, - BYTES_PER_PIXEL_RGB, - BYTES_PER_PIXEL_RGBA, - ALPHA_OPAQUE, + KittyPixelConstants, parseKittyCommand } from './KittyGraphicsTypes'; -// Memory limit for base64 decoder (4MB, same as IIPHandler) -const DECODER_KEEP_DATA = 4194304; -const DECODER_INITIAL_DATA = 4194304; // 4MB +const enum Constants { + // Memory limit for base64 decoder (4MB, same as IIPHandler) + DECODER_KEEP_DATA = 4194304, + DECODER_INITIAL_DATA = 4194304, // 4MB + // Local mirror of const enum (esbuild can't inline const enums from external packages) + DECODER_OK = 0, + // Maximum control data size + MAX_CONTROL_DATA_SIZE = 512, + // Semicolon codepoint + SEMICOLON = 0x3B +} -// Local mirror of const enum (esbuild can't inline const enums from external packages) -const DECODER_OK: DecodeStatus.OK = 0; - -// Maximum control data size -const MAX_CONTROL_DATA_SIZE = 512; - -// Semicolon codepoint -const SEMICOLON = 0x3B; +const DECODER_OK = Constants.DECODER_OK as unknown as DecodeStatus.OK; // Kitty graphics protocol handler with streaming base64 decoding. export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDisposable { @@ -50,7 +49,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos private _inControlData = true; // Buffer for control data. - private _controlData = new Uint32Array(MAX_CONTROL_DATA_SIZE); + private _controlData = new Uint32Array(Constants.MAX_CONTROL_DATA_SIZE); private _controlLength = 0; // Pre-calculated encoded size limit @@ -77,7 +76,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos // Convert decoded size limit -> max encoded bytes. this._maxEncodedBytes = Math.ceil(this._opts.kittySizeLimit * 4 / 3); // ensure we preallocate more than configured limit while using 4mb initial size. - this._initialEncodedBytes = Math.min(DECODER_INITIAL_DATA, this._maxEncodedBytes); + this._initialEncodedBytes = Math.min(Constants.DECODER_INITIAL_DATA, this._maxEncodedBytes); } public reset(): void { @@ -129,7 +128,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos // Scan for semicolon let controlEnd = end; for (let i = start; i < end; i++) { - if (data[i] === SEMICOLON) { + if (data[i] === Constants.SEMICOLON) { this._inControlData = false; controlEnd = i; break; @@ -138,7 +137,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos // Copy control data const copyLength = controlEnd - start; - if (this._controlLength + copyLength > MAX_CONTROL_DATA_SIZE) { + if (this._controlLength + copyLength > Constants.MAX_CONTROL_DATA_SIZE) { this._aborted = true; return; } @@ -201,7 +200,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos this._activeDecoder = pending.decoder; } if (!this._activeDecoder) { - this._activeDecoder = new Base64Decoder(DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes); + this._activeDecoder = new Base64Decoder(Constants.DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes); this._activeDecoder.init(); } @@ -484,7 +483,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos return true; } - const bytesPerPixel = format === KittyFormat.RGBA ? BYTES_PER_PIXEL_RGBA : BYTES_PER_PIXEL_RGB; + const bytesPerPixel = format === KittyFormat.RGBA ? KittyPixelConstants.BYTES_PER_PIXEL_RGBA : KittyPixelConstants.BYTES_PER_PIXEL_RGB; const expectedBytes = width * height * bytesPerPixel; if (bytes.length < expectedBytes) { @@ -723,7 +722,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos throw new Error('Width and height required for raw pixel data'); } - const bytesPerPixel = image.format === KittyFormat.RGBA ? BYTES_PER_PIXEL_RGBA : BYTES_PER_PIXEL_RGB; + const bytesPerPixel = image.format === KittyFormat.RGBA ? KittyPixelConstants.BYTES_PER_PIXEL_RGBA : KittyPixelConstants.BYTES_PER_PIXEL_RGB; const expectedBytes = width * height * bytesPerPixel; if (bytes.length < expectedBytes) { @@ -734,13 +733,13 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos if (image.format === KittyFormat.RGBA) { // RGBA: use bytes directly — no copy needed - return createImageBitmap(new ImageData(new Uint8ClampedArray(bytes.buffer as ArrayBuffer, bytes.byteOffset, pixelCount * BYTES_PER_PIXEL_RGBA), width, height)); + return createImageBitmap(new ImageData(new Uint8ClampedArray(bytes.buffer as ArrayBuffer, bytes.byteOffset, pixelCount * KittyPixelConstants.BYTES_PER_PIXEL_RGBA), width, height)); } // RGB→RGBA: interleave alpha using uint32 block processing (4 pixels per iteration). // 3 uint32 reads + 4 uint32 writes per 4 pixels vs 28 byte reads/writes — ~6x faster. // Assumes little-endian (all modern browsers/Node.js). - const data = new Uint8ClampedArray(pixelCount * BYTES_PER_PIXEL_RGBA); + const data = new Uint8ClampedArray(pixelCount * KittyPixelConstants.BYTES_PER_PIXEL_RGBA); const src32 = new Uint32Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 4)); const dst32 = new Uint32Array(data.buffer); const alignedPixels = pixelCount & ~3; // round down to multiple of 4 @@ -759,15 +758,15 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos } // Handle remaining 1–3 pixels - let srcByte = alignedPixels * BYTES_PER_PIXEL_RGB; - let dstByte = alignedPixels * BYTES_PER_PIXEL_RGBA; + let srcByte = alignedPixels * KittyPixelConstants.BYTES_PER_PIXEL_RGB; + let dstByte = alignedPixels * KittyPixelConstants.BYTES_PER_PIXEL_RGBA; for (let i = alignedPixels; i < pixelCount; i++) { data[dstByte] = bytes[srcByte]; data[dstByte + 1] = bytes[srcByte + 1]; data[dstByte + 2] = bytes[srcByte + 2]; - data[dstByte + 3] = ALPHA_OPAQUE; - srcByte += BYTES_PER_PIXEL_RGB; - dstByte += BYTES_PER_PIXEL_RGBA; + data[dstByte + 3] = KittyPixelConstants.ALPHA_OPAQUE; + srcByte += KittyPixelConstants.BYTES_PER_PIXEL_RGB; + dstByte += KittyPixelConstants.BYTES_PER_PIXEL_RGBA; } return createImageBitmap(new ImageData(data, width, height)); diff --git a/addons/addon-image/src/kitty/KittyGraphicsTypes.ts b/addons/addon-image/src/kitty/KittyGraphicsTypes.ts index 82b2b0a9a..448ebcc05 100644 --- a/addons/addon-image/src/kitty/KittyGraphicsTypes.ts +++ b/addons/addon-image/src/kitty/KittyGraphicsTypes.ts @@ -82,9 +82,11 @@ export const enum KittyKey { } // Pixel format constants -export const BYTES_PER_PIXEL_RGB = 3; -export const BYTES_PER_PIXEL_RGBA = 4; -export const ALPHA_OPAQUE = 255; +export const enum KittyPixelConstants { + BYTES_PER_PIXEL_RGB = 3, + BYTES_PER_PIXEL_RGBA = 4, + ALPHA_OPAQUE = 255 +} // Parsed Kitty graphics command. export interface IKittyCommand { diff --git a/addons/addon-webgl/src/GlyphRenderer.ts b/addons/addon-webgl/src/GlyphRenderer.ts index f5be446f6..be4ebb76b 100644 --- a/addons/addon-webgl/src/GlyphRenderer.ts +++ b/addons/addon-webgl/src/GlyphRenderer.ts @@ -78,9 +78,11 @@ void main() { }`); } -const INDICES_PER_CELL = 11; -const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; -const CELL_POSITION_INDICES = 2; +const enum Constants { + INDICES_PER_CELL = 11, + BYTES_PER_CELL = INDICES_PER_CELL * 4/*Float32Array.BYTES_PER_ELEMENT*/, + CELL_POSITION_INDICES = 2 +} // Work variables to avoid garbage collection let $i = 0; @@ -160,22 +162,22 @@ export class GlyphRenderer extends Disposable { this._register(toDisposable(() => gl.deleteBuffer(this._attributesBuffer))); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); gl.enableVertexAttribArray(VertexAttribLocations.OFFSET); - gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0); + gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 0); gl.vertexAttribDivisor(VertexAttribLocations.OFFSET, 1); gl.enableVertexAttribArray(VertexAttribLocations.SIZE); - gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1); gl.enableVertexAttribArray(VertexAttribLocations.TEXPAGE); - gl.vertexAttribPointer(VertexAttribLocations.TEXPAGE, 1, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.TEXPAGE, 1, gl.FLOAT, false, Constants.BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.TEXPAGE, 1); gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD); - gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 5 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 5 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1); gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE); - gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 7 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 7 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1); gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION); - gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); // Setup static uniforms @@ -222,12 +224,12 @@ export class GlyphRenderer extends Disposable { } private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void { - $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; + $i = (y * this._terminal.cols + x) * Constants.INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have // underline/strikethrough styles if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { - array.fill(0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); + array.fill(0, $i, $i + Constants.INDICES_PER_CELL - 1 - Constants.CELL_POSITION_INDICES); return; } @@ -288,7 +290,7 @@ export class GlyphRenderer extends Disposable { public clear(): void { const terminal = this._terminal; - const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL; + const newCount = terminal.cols * terminal.rows * Constants.INDICES_PER_CELL; // Clear vertices if (this._vertices.count !== newCount) { @@ -310,7 +312,7 @@ export class GlyphRenderer extends Disposable { for (let x = 0; x < terminal.cols; x++) { this._vertices.attributes[i + 9] = x / terminal.cols; this._vertices.attributes[i + 10] = y / terminal.rows; - i += INDICES_PER_CELL; + i += Constants.INDICES_PER_CELL; } } } @@ -346,8 +348,8 @@ export class GlyphRenderer extends Disposable { // - So we don't send vertices for all the line-ending whitespace to the GPU let bufferLength = 0; for (let y = 0; y < renderModel.lineLengths.length; y++) { - const si = y * this._terminal.cols * INDICES_PER_CELL; - const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); + const si = y * this._terminal.cols * Constants.INDICES_PER_CELL; + const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * Constants.INDICES_PER_CELL); activeBuffer.set(sub, bufferLength); bufferLength += sub.length; } @@ -364,7 +366,7 @@ export class GlyphRenderer extends Disposable { } // Draw the viewport - gl.drawElementsInstanced(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL); + gl.drawElementsInstanced(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, bufferLength / Constants.INDICES_PER_CELL); } public setAtlas(atlas: ITextureAtlas): void { diff --git a/addons/addon-webgl/src/RectangleRenderer.ts b/addons/addon-webgl/src/RectangleRenderer.ts index d22068bab..4ad8d66ff 100644 --- a/addons/addon-webgl/src/RectangleRenderer.ts +++ b/addons/addon-webgl/src/RectangleRenderer.ts @@ -10,7 +10,7 @@ import { Attributes, FgFlags } from 'common/buffer/Constants'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IColor } from 'common/Types'; import { Terminal } from '@xterm/xterm'; -import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { RenderModelConstants } from './RenderModel'; import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; @@ -217,9 +217,9 @@ export class RectangleRenderer extends Disposable { currentFg = 0; currentInverse = false; for (x = 0; x < terminal.cols; x++) { - modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; - fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; + modelIndex = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL; + bg = model.cells[modelIndex + RenderModelConstants.BG_OFFSET]; + fg = model.cells[modelIndex + RenderModelConstants.FG_OFFSET]; inverse = !!(fg & FgFlags.INVERSE); if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) { // A rectangle needs to be drawn if going from non-default to another color diff --git a/addons/addon-webgl/src/RenderModel.ts b/addons/addon-webgl/src/RenderModel.ts index a94f317ee..6113a9bdc 100644 --- a/addons/addon-webgl/src/RenderModel.ts +++ b/addons/addon-webgl/src/RenderModel.ts @@ -7,10 +7,12 @@ import { ICursorRenderModel, IRenderModel } from './Types'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; -export const RENDER_MODEL_INDICIES_PER_CELL = 4; -export const RENDER_MODEL_BG_OFFSET = 1; -export const RENDER_MODEL_FG_OFFSET = 2; -export const RENDER_MODEL_EXT_OFFSET = 3; +export const enum RenderModelConstants { + INDICIES_PER_CELL = 4, + BG_OFFSET = 1, + FG_OFFSET = 2, + EXT_OFFSET = 3 +} export const COMBINED_CHAR_BIT_MASK = 0x80000000; @@ -27,7 +29,7 @@ export class RenderModel implements IRenderModel { } public resize(cols: number, rows: number): void { - const indexCount = cols * rows * RENDER_MODEL_INDICIES_PER_CELL; + const indexCount = cols * rows * RenderModelConstants.INDICIES_PER_CELL; if (indexCount !== this.cells.length) { this.cells = new Uint32Array(indexCount); this.lineLengths = new Uint32Array(rows); diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 83f20c9e7..91867ea26 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -19,7 +19,7 @@ import { ICoreService, IDecorationService, IOptionsService } from 'common/servic import { Terminal } from '@xterm/xterm'; import { GlyphRenderer } from './GlyphRenderer'; import { RectangleRenderer } from './RectangleRenderer'; -import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL, RenderModel } from './RenderModel'; +import { COMBINED_CHAR_BIT_MASK, RenderModel, RenderModelConstants } from './RenderModel'; import { IWebGL2RenderingContext, type ITextureAtlas } from './Types'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { IRenderLayer } from './renderLayer/Types'; @@ -493,7 +493,7 @@ export class WebglRenderer extends Disposable implements IRenderer { chars = cell.getChars(); code = cell.getCode(); - i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; + i = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL; if (!rowHasBlinkingCells && cell.isBlink()) { rowHasBlinkingCells = true; @@ -538,9 +538,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg && - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) { + this._model.cells[i + RenderModelConstants.BG_OFFSET] === this._cellColorResolver.result.bg && + this._model.cells[i + RenderModelConstants.FG_OFFSET] === this._cellColorResolver.result.fg && + this._model.cells[i + RenderModelConstants.EXT_OFFSET] === this._cellColorResolver.result.ext) { continue; } @@ -553,9 +553,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; - this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; + this._model.cells[i + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg; + this._model.cells[i + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg; + this._model.cells[i + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext; width = cell.getWidth(); this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, width, lastBg); @@ -566,14 +566,14 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x <= lastCharX; x++) { - j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; + j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL; this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0); this._model.cells[j] = NULL_CELL_CODE; // Don't re-resolve the cell color since multi-colored ligature backgrounds are not // supported - this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; - this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; - this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; + this._model.cells[j + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg; + this._model.cells[j + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg; + this._model.cells[j + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext; } x--; // Go back to the previous update cell for next iteration } diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 1d3c44ae1..6fc421337 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -12,7 +12,9 @@ import { IBuffer } from 'common/buffer/Types'; import { IInstantiationService } from 'common/services/Services'; import { addDisposableListener } from 'browser/Dom'; -const MAX_ROWS_TO_READ = 20; +const enum Constants { + MAX_ROWS_TO_READ = 20 +} const enum BoundaryPosition { TOP, @@ -137,7 +139,7 @@ export class AccessibilityManager extends Disposable { } private _handleChar(char: string): void { - if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { + if (this._liveRegionLineCount < Constants.MAX_ROWS_TO_READ + 1) { if (this._charsToConsume.length > 0) { // Have the screen reader ignore the char if it was just input const shiftedChar = this._charsToConsume.shift(); @@ -150,7 +152,7 @@ export class AccessibilityManager extends Disposable { if (char === '\n') { this._liveRegionLineCount++; - if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) { + if (this._liveRegionLineCount === Constants.MAX_ROWS_TO_READ + 1) { this._liveRegion.textContent = Strings.tooMuchOutput.get(); } } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 08b5093df..6f873af7b 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -19,13 +19,15 @@ import { Emitter } from 'common/Event'; import { addDisposableListener } from 'browser/Dom'; -const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; -const ROW_CONTAINER_CLASS = 'xterm-rows'; -const FG_CLASS_PREFIX = 'xterm-fg-'; -const BG_CLASS_PREFIX = 'xterm-bg-'; -const FOCUS_CLASS = 'xterm-focus'; -const SELECTION_CLASS = 'xterm-selection'; -const CURSOR_BLINK_IDLE_CLASS = 'xterm-cursor-blink-idle'; +const enum Constants { + TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-', + ROW_CONTAINER_CLASS = 'xterm-rows', + FG_CLASS_PREFIX = 'xterm-fg-', + BG_CLASS_PREFIX = 'xterm-bg-', + FOCUS_CLASS = 'xterm-focus', + SELECTION_CLASS = 'xterm-selection', + CURSOR_BLINK_IDLE_CLASS = 'xterm-cursor-blink-idle' +} let nextTerminalId = 1; @@ -76,12 +78,12 @@ export class DomRenderer extends Disposable implements IRenderer { ) { super(); this._rowContainer = this._document.createElement('div'); - this._rowContainer.classList.add(ROW_CONTAINER_CLASS); + this._rowContainer.classList.add(Constants.ROW_CONTAINER_CLASS); this._rowContainer.style.lineHeight = 'normal'; this._rowContainer.setAttribute('aria-hidden', 'true'); this._refreshRowElements(this._bufferService.cols, this._bufferService.rows); this._selectionContainer = this._document.createElement('div'); - this._selectionContainer.classList.add(SELECTION_CLASS); + this._selectionContainer.classList.add(Constants.SELECTION_CLASS); this._selectionContainer.setAttribute('aria-hidden', 'true'); this.dimensions = createRenderDimensions(); @@ -93,7 +95,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document); - this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); + this._element.classList.add(Constants.TERMINAL_CLASS_PREFIX + this._terminalClass); this._screenElement.appendChild(this._rowContainer); this._screenElement.appendChild(this._selectionContainer); @@ -110,7 +112,7 @@ export class DomRenderer extends Disposable implements IRenderer { )); this._register(toDisposable(() => { - this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); + this._element.classList.remove(Constants.TERMINAL_CLASS_PREFIX + this._terminalClass); // Outside influences such as React unmounts may manipulate the DOM before our disposal. // https://github.com/xtermjs/xterm.js/issues/2960 @@ -160,7 +162,7 @@ export class DomRenderer extends Disposable implements IRenderer { } const styles = - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} span {` + ` display: inline-block;` + // TODO: find workaround for inline-block (creates ~20% render penalty) ` height: 100%;` + ` vertical-align: top;` + @@ -181,7 +183,7 @@ export class DomRenderer extends Disposable implements IRenderer { // Base CSS let styles = - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} {` + // Disabling pointer events circumvents a browser behavior that prevents `click` events from // being delivered if the target element is replaced during the click. This happened due to // refresh() being called during the mousedown handler to start a selection. @@ -189,14 +191,14 @@ export class DomRenderer extends Disposable implements IRenderer { ` color: ${colors.foreground.css};` + `}`; styles += - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}, ${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS}, ${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} span {` + ` font-family: ${this._optionsService.rawOptions.fontFamily};` + ` font-size: ${this._optionsService.rawOptions.fontSize}px;` + ` font-kerning: none;` + ` white-space: pre` + `}`; styles += - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .xterm-dim {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} .xterm-dim {` + ` color: ${color.multiplyOpacity(colors.foreground, 0.5).css};` + `}`; // Text styles @@ -242,70 +244,70 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Cursor styles += - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS}.${Constants.FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + ` animation: ${blinkAnimationUnderlineId} 1s step-end infinite;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS}.${Constants.FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + ` animation: ${blinkAnimationBarId} 1s step-end infinite;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS}.${Constants.FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` animation: ${blinkAnimationBlockId} 1s step-end infinite;` + `}` + // Disable cursor blinking when idle - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${CURSOR_BLINK_IDLE_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS}.${Constants.CURSOR_BLINK_IDLE_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS} {` + ` animation: none !important;` + `}` + // !important helps fix an issue where the cursor will not render on top of the selection, // however it's very hard to fix this issue and retain the blink animation without the use of // !important. So this edge case fails when cursor blink is on. - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` background-color: ${colors.cursor.css};` + ` color: ${colors.cursorAccent.css};` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS}:not(.${RowCss.CURSOR_BLINK_CLASS}) {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS}:not(.${RowCss.CURSOR_BLINK_CLASS}) {` + ` background-color: ${colors.cursor.css} !important;` + ` color: ${colors.cursorAccent.css} !important;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + ` outline: 1px solid ${colors.cursor.css};` + ` outline-offset: -1px;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + + `${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + ` border-bottom: 1px ${colors.cursor.css};` + ` border-bottom-style: solid;` + ` height: calc(100% - 1px);` + `}`; // Selection styles += - `${this._terminalSelector} .${SELECTION_CLASS} {` + + `${this._terminalSelector} .${Constants.SELECTION_CLASS} {` + ` position: absolute;` + ` top: 0;` + ` left: 0;` + ` z-index: 1;` + ` pointer-events: none;` + `}` + - `${this._terminalSelector}.focus .${SELECTION_CLASS} div {` + + `${this._terminalSelector}.focus .${Constants.SELECTION_CLASS} div {` + ` position: absolute;` + ` background-color: ${colors.selectionBackgroundOpaque.css};` + `}` + - `${this._terminalSelector} .${SELECTION_CLASS} div {` + + `${this._terminalSelector} .${Constants.SELECTION_CLASS} div {` + ` position: absolute;` + ` background-color: ${colors.selectionInactiveBackgroundOpaque.css};` + `}`; // Colors for (const [i, c] of colors.ansi.entries()) { styles += - `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + - `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` + - `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; + `${this._terminalSelector} .${Constants.FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + + `${this._terminalSelector} .${Constants.FG_CLASS_PREFIX}${i}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` + + `${this._terminalSelector} .${Constants.BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; } styles += - `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` + - `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).css}; }` + - `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`; + `${this._terminalSelector} .${Constants.FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` + + `${this._terminalSelector} .${Constants.FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).css}; }` + + `${this._terminalSelector} .${Constants.BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`; this._themeStyleElement.textContent = styles; } @@ -361,13 +363,13 @@ export class DomRenderer extends Disposable implements IRenderer { } public handleBlur(): void { - this._rowContainer.classList.remove(FOCUS_CLASS); + this._rowContainer.classList.remove(Constants.FOCUS_CLASS); this._cursorBlinkStateManager.pause(); this.renderRows(0, this._bufferService.rows - 1); } public handleFocus(): void { - this._rowContainer.classList.add(FOCUS_CLASS); + this._rowContainer.classList.add(Constants.FOCUS_CLASS); this._cursorBlinkStateManager.resume(); this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y); } @@ -561,7 +563,7 @@ export class DomRenderer extends Disposable implements IRenderer { } private get _terminalSelector(): string { - return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`; + return `.${Constants.TERMINAL_CLASS_PREFIX}${this._terminalClass}`; } private _handleLinkHover(e: ILinkifierEvent): void { @@ -667,7 +669,7 @@ class CursorBlinkStateManager { public restartBlinkAnimation(): void { if (this._isIdlePaused) { - this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS); + this._rowContainer.classList.remove(Constants.CURSOR_BLINK_IDLE_CLASS); } this._resetIdleTimer(); } @@ -679,7 +681,7 @@ class CursorBlinkStateManager { public resume(): void { this._isIdlePaused = false; - this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS); + this._rowContainer.classList.remove(Constants.CURSOR_BLINK_IDLE_CLASS); this._resetIdleTimer(); } @@ -699,7 +701,7 @@ class CursorBlinkStateManager { } private _stopBlinkingDueToIdle(): void { - this._rowContainer.classList.add(CURSOR_BLINK_IDLE_CLASS); + this._rowContainer.classList.add(Constants.CURSOR_BLINK_IDLE_CLASS); this._isIdlePaused = true; this._idleTimeout = undefined; } diff --git a/src/browser/scrollable/scrollableElement.ts b/src/browser/scrollable/scrollableElement.ts index e30b36fc3..4039d0c4e 100644 --- a/src/browser/scrollable/scrollableElement.ts +++ b/src/browser/scrollable/scrollableElement.ts @@ -18,8 +18,10 @@ import * as platform from 'common/Platform'; import { INewScrollDimensions, INewScrollPosition, IScrollDimensions, IScrollPosition, IScrollEvent, Scrollable, ScrollbarVisibility } from './scrollable'; // import 'vs/css!./media/scrollbars'; -const HIDE_TIMEOUT = 500; -const SCROLL_WHEEL_SENSITIVITY = 50; +const enum Constants { + HIDE_TIMEOUT = 500, + SCROLL_WHEEL_SENSITIVITY = 50 +} class MouseWheelClassifierItem { public timestamp: number; @@ -404,12 +406,12 @@ export class SmoothScrollableElement extends Widget { let desiredScrollPosition: INewScrollPosition = {}; if (deltaY) { - const deltaScrollTop = SCROLL_WHEEL_SENSITIVITY * deltaY; + const deltaScrollTop = Constants.SCROLL_WHEEL_SENSITIVITY * deltaY; const desiredScrollTop = futureScrollPosition.scrollTop - (deltaScrollTop < 0 ? Math.floor(deltaScrollTop) : Math.ceil(deltaScrollTop)); this._verticalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollTop); } if (deltaX) { - const deltaScrollLeft = SCROLL_WHEEL_SENSITIVITY * deltaX; + const deltaScrollLeft = Constants.SCROLL_WHEEL_SENSITIVITY * deltaX; const desiredScrollLeft = futureScrollPosition.scrollLeft - (deltaScrollLeft < 0 ? Math.floor(deltaScrollLeft) : Math.ceil(deltaScrollLeft)); this._horizontalScrollbar.writeScrollPosition(desiredScrollPosition, desiredScrollLeft); } @@ -533,7 +535,7 @@ export class SmoothScrollableElement extends Widget { private _scheduleHide(): void { if (!this._mouseIsOver && !this._isDragging) { - this._hideTimeout.cancelAndSet(() => this._hide(), HIDE_TIMEOUT); + this._hideTimeout.cancelAndSet(() => this._hide(), Constants.HIDE_TIMEOUT); } } } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index e1b8510e9..39692a871 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -18,27 +18,26 @@ import { IBuffer } from 'common/buffer/Types'; import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services'; import { Emitter } from 'common/Event'; -/** - * The number of pixels the mouse needs to be above or below the viewport in - * order to scroll at the maximum speed. - */ -const DRAG_SCROLL_MAX_THRESHOLD = 50; - -/** - * The maximum scrolling speed - */ -const DRAG_SCROLL_MAX_SPEED = 15; - -/** - * The number of milliseconds between drag scroll updates. - */ -const DRAG_SCROLL_INTERVAL = 50; - -/** - * The maximum amount of time that can have elapsed for an alt click to move the - * cursor. - */ -const ALT_CLICK_MOVE_CURSOR_TIME = 500; +const enum Constants { + /** + * The number of pixels the mouse needs to be above or below the viewport in + * order to scroll at the maximum speed. + */ + DRAG_SCROLL_MAX_THRESHOLD = 50, + /** + * The maximum scrolling speed + */ + DRAG_SCROLL_MAX_SPEED = 15, + /** + * The number of milliseconds between drag scroll updates. + */ + DRAG_SCROLL_INTERVAL = 50, + /** + * The maximum amount of time that can have elapsed for an alt click to move the + * cursor. + */ + ALT_CLICK_MOVE_CURSOR_TIME = 500 +} const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); const ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); @@ -424,9 +423,9 @@ export class SelectionService extends Disposable implements ISelectionService { offset -= terminalHeight; } - offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD); - offset /= DRAG_SCROLL_MAX_THRESHOLD; - return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1)); + offset = Math.min(Math.max(offset, -Constants.DRAG_SCROLL_MAX_THRESHOLD), Constants.DRAG_SCROLL_MAX_THRESHOLD); + offset /= Constants.DRAG_SCROLL_MAX_THRESHOLD; + return (offset / Math.abs(offset)) + Math.round(offset * (Constants.DRAG_SCROLL_MAX_SPEED - 1)); } /** @@ -500,7 +499,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener); } - this._dragScrollIntervalTimer = this._coreBrowserService.window.setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL); + this._dragScrollIntervalTimer = this._coreBrowserService.window.setInterval(() => this._dragScroll(), Constants.DRAG_SCROLL_INTERVAL); } /** @@ -675,7 +674,7 @@ export class SelectionService extends Disposable implements ISelectionService { } /** - * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the + * The callback that occurs every Constants.DRAG_SCROLL_INTERVAL ms that does the * scrolling of the viewport. */ private _dragScroll(): void { @@ -713,7 +712,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._removeMouseDownListeners(); - if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.rawOptions.altClickMovesCursor) { + if (this.selectionText.length <= 1 && timeElapsed < Constants.ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.rawOptions.altClickMovesCursor) { if (this._bufferService.buffer.ybase === this._bufferService.buffer.ydisp) { const coordinates = this._mouseCoordsService.getCoords( event, diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 0eff38f25..9bd7faaf5 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -24,7 +24,7 @@ import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, IMouseStateService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; -import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; +import { BufferService, BufferServiceConstants } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; @@ -173,8 +173,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { return; } - x = Math.max(x, MINIMUM_COLS); - y = Math.max(y, MINIMUM_ROWS); + x = Math.max(x, BufferServiceConstants.MINIMUM_COLS); + y = Math.max(y, BufferServiceConstants.MINIMUM_ROWS); // Flush pending writes before resize to avoid race conditions where async // writes are processed with incorrect dimensions diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index c4c2a8e2e..2b7142dd0 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -41,12 +41,13 @@ const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': /** * Max length of the UTF32 input buffer. Real memory consumption is 4 times higher. */ -const MAX_PARSEBUFFER_LENGTH = 131072; - -/** - * Limit length of title and icon name stacks. - */ -const STACK_LIMIT = 10; +const enum Constants { + MAX_PARSEBUFFER_LENGTH = 131072, + /** Limit length of title and icon name stacks. */ + STACK_LIMIT = 10, + // create a warning log if an async handler takes longer than the limit (in ms) + SLOW_ASYNC_LIMIT = 5000 +} // map params to window option function paramToWindowOption(n: number, opts: IWindowOptions): boolean { @@ -85,9 +86,6 @@ export enum WindowsOptionsReportType { GET_CELL_SIZE_PIXELS = 1 } -// create a warning log if an async handler takes longer than the limit (in ms) -const SLOW_ASYNC_LIMIT = 5000; - // Work variables to avoid garbage collection let $temp = 0; @@ -393,7 +391,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._logService.logLevel <= LogLevelEnum.WARN) { let slowTimeout: ReturnType | undefined; const slowPromise = new Promise((_res, rej) => { - slowTimeout = setTimeout(() => rej('#SLOW_TIMEOUT'), SLOW_ASYNC_LIMIT); + slowTimeout = setTimeout(() => rej('#SLOW_TIMEOUT'), Constants.SLOW_ASYNC_LIMIT); }); Promise.race([p, slowPromise]) .then(() => { @@ -407,7 +405,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (err !== '#SLOW_TIMEOUT') { throw err; } - console.warn(`async parser handler taking longer than ${SLOW_ASYNC_LIMIT} ms`); + console.warn(`async parser handler taking longer than ${Constants.SLOW_ASYNC_LIMIT} ms`); }); } } @@ -445,8 +443,8 @@ export class InputHandler extends Disposable implements IInputHandler { cursorStartX = this._parseStack.cursorStartX; cursorStartY = this._parseStack.cursorStartY; this._parseStack.paused = false; - if (data.length > MAX_PARSEBUFFER_LENGTH) { - start = this._parseStack.position + MAX_PARSEBUFFER_LENGTH; + if (data.length > Constants.MAX_PARSEBUFFER_LENGTH) { + start = this._parseStack.position + Constants.MAX_PARSEBUFFER_LENGTH; } } @@ -463,8 +461,8 @@ export class InputHandler extends Disposable implements IInputHandler { // resize input buffer if needed if (this._parseBuffer.length < data.length) { - if (this._parseBuffer.length < MAX_PARSEBUFFER_LENGTH) { - this._parseBuffer = new Uint32Array(Math.min(data.length, MAX_PARSEBUFFER_LENGTH)); + if (this._parseBuffer.length < Constants.MAX_PARSEBUFFER_LENGTH) { + this._parseBuffer = new Uint32Array(Math.min(data.length, Constants.MAX_PARSEBUFFER_LENGTH)); } } @@ -475,9 +473,9 @@ export class InputHandler extends Disposable implements IInputHandler { } // process big data in smaller chunks - if (data.length > MAX_PARSEBUFFER_LENGTH) { - for (let i = start; i < data.length; i += MAX_PARSEBUFFER_LENGTH) { - const end = i + MAX_PARSEBUFFER_LENGTH < data.length ? i + MAX_PARSEBUFFER_LENGTH : data.length; + if (data.length > Constants.MAX_PARSEBUFFER_LENGTH) { + for (let i = start; i < data.length; i += Constants.MAX_PARSEBUFFER_LENGTH) { + const end = i + Constants.MAX_PARSEBUFFER_LENGTH < data.length ? i + Constants.MAX_PARSEBUFFER_LENGTH : data.length; const len = (typeof data === 'string') ? this._stringDecoder.decode(data.substring(i, end), this._parseBuffer) : this._utf8Decoder.decode(data.subarray(i, end), this._parseBuffer); @@ -2955,13 +2953,13 @@ export class InputHandler extends Disposable implements IInputHandler { case 22: // PushTitle if (second === 0 || second === 2) { this._windowTitleStack.push(this._windowTitle); - if (this._windowTitleStack.length > STACK_LIMIT) { + if (this._windowTitleStack.length > Constants.STACK_LIMIT) { this._windowTitleStack.shift(); } } if (second === 0 || second === 1) { this._iconNameStack.push(this._iconName); - if (this._iconNameStack.length > STACK_LIMIT) { + if (this._iconNameStack.length > Constants.STACK_LIMIT) { this._iconNameStack.shift(); } } diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index e415851a0..d3f5a523c 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -9,25 +9,20 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants'; import { stringFromCodePoint } from 'common/input/TextDecoder'; -/** - * buffer memory layout: - * - * | uint32_t | uint32_t | uint32_t | - * | `content` | `FG` | `BG` | - * | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) | - */ - - -/** typed array slots taken by one cell */ -const CELL_SIZE = 3; +const enum Constants { + /** The number of 32 bit array indices taken by one cell. */ + CELL_INDICIES = 3, + /** Factor when to cleanup underlying array buffer after shrinking. */ + CLEANUP_THRESHOLD = 2 +} /** * Cell member indices. * * Direct access: - * `content = data[column * CELL_SIZE + Cell.CONTENT];` - * `fg = data[column * CELL_SIZE + Cell.FG];` - * `bg = data[column * CELL_SIZE + Cell.BG];` + * `content = data[column * Constants.CELL_INDICIES + Cell.CONTENT];` + * `fg = data[column * Constants.CELL_INDICIES + Cell.FG];` + * `bg = data[column * Constants.CELL_INDICIES + Cell.BG];` */ const enum Cell { CONTENT = 0, @@ -41,9 +36,6 @@ export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData()); let $startIndex = 0; const $workCell = new CellData(); -/** Factor when to cleanup underlying array buffer after shrinking. */ -const CLEANUP_THRESHOLD = 2; - /** * Typed array based bufferline implementation. * @@ -60,13 +52,21 @@ const CLEANUP_THRESHOLD = 2; * memory allocs / GC pressure can be greatly reduced by reusing the CellData object. */ export class BufferLine implements IBufferLine { + /** + * Buffer memory layout: + * + * | uint32_t | uint32_t | uint32_t | + * |----------------------------------------|---------------------------------|---------------------------------| + * | `content` | `FG` | `BG` | + * | `wcwidth(2)` `comb(1)` `codepoint(21)` | `flags(8)` `R(8)` `G(8)` `B(8)` | `flags(8)` `R(8)` `G(8)` `B(8)` | + */ protected _data: Uint32Array; protected _combined: {[index: number]: string} = {}; protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {}; public length: number; constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) { - this._data = new Uint32Array(cols * CELL_SIZE); + this._data = new Uint32Array(cols * Constants.CELL_INDICIES); const cell = fillCellData ?? CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); for (let i = 0; i < cols; ++i) { this.setCell(i, cell); @@ -79,10 +79,10 @@ export class BufferLine implements IBufferLine { * @deprecated */ public get(index: number): CharData { - const content = this._data[index * CELL_SIZE + Cell.CONTENT]; + const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; return [ - this._data[index * CELL_SIZE + Cell.FG], + this._data[index * Constants.CELL_INDICIES + Cell.FG], (content & Content.IS_COMBINED_MASK) ? this._combined[index] : (cp) ? stringFromCodePoint(cp) : '', @@ -98,12 +98,12 @@ export class BufferLine implements IBufferLine { * @deprecated */ public set(index: number, value: CharData): void { - this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX]; + this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX]; if (value[CHAR_DATA_CHAR_INDEX].length > 1) { this._combined[index] = value[1]; - this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); + this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } else { - this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); + this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } } @@ -112,22 +112,22 @@ export class BufferLine implements IBufferLine { * use these when only one value is needed, otherwise use `loadCell` */ public getWidth(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT; + return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT; } /** Test whether content has width. */ public hasWidth(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK; + return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.WIDTH_MASK; } /** Get FG cell component. */ public getFg(index: number): number { - return this._data[index * CELL_SIZE + Cell.FG]; + return this._data[index * Constants.CELL_INDICIES + Cell.FG]; } /** Get BG cell component. */ public getBg(index: number): number { - return this._data[index * CELL_SIZE + Cell.BG]; + return this._data[index * Constants.CELL_INDICIES + Cell.BG]; } /** @@ -136,7 +136,7 @@ export class BufferLine implements IBufferLine { * from real empty cells. */ public hasContent(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK; + return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK; } /** @@ -145,7 +145,7 @@ export class BufferLine implements IBufferLine { * a single UTF32 codepoint or the last codepoint of a combined string. */ public getCodePoint(index: number): number { - const content = this._data[index * CELL_SIZE + Cell.CONTENT]; + const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT]; if (content & Content.IS_COMBINED_MASK) { return this._combined[index].charCodeAt(this._combined[index].length - 1); } @@ -154,12 +154,12 @@ export class BufferLine implements IBufferLine { /** Test whether the cell contains a combined string. */ public isCombined(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK; + return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.IS_COMBINED_MASK; } /** Returns the string content of the cell. */ public getString(index: number): string { - const content = this._data[index * CELL_SIZE + Cell.CONTENT]; + const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT]; if (content & Content.IS_COMBINED_MASK) { return this._combined[index]; } @@ -172,7 +172,7 @@ export class BufferLine implements IBufferLine { /** Get state of protected flag. */ public isProtected(index: number): number { - return this._data[index * CELL_SIZE + Cell.BG] & BgFlags.PROTECTED; + return this._data[index * Constants.CELL_INDICIES + Cell.BG] & BgFlags.PROTECTED; } /** @@ -180,7 +180,7 @@ export class BufferLine implements IBufferLine { * to GC as it significantly reduced the amount of new objects/references needed. */ public loadCell(index: number, cell: ICellData): ICellData { - $startIndex = index * CELL_SIZE; + $startIndex = index * Constants.CELL_INDICIES; cell.content = this._data[$startIndex + Cell.CONTENT]; cell.fg = this._data[$startIndex + Cell.FG]; cell.bg = this._data[$startIndex + Cell.BG]; @@ -203,9 +203,9 @@ export class BufferLine implements IBufferLine { if (cell.bg & BgFlags.HAS_EXTENDED) { this._extendedAttrs[index] = cell.extended; } - this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content; - this._data[index * CELL_SIZE + Cell.FG] = cell.fg; - this._data[index * CELL_SIZE + Cell.BG] = cell.bg; + this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content; + this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg; + this._data[index * Constants.CELL_INDICIES + Cell.BG] = cell.bg; } /** @@ -217,9 +217,9 @@ export class BufferLine implements IBufferLine { if (attrs.bg & BgFlags.HAS_EXTENDED) { this._extendedAttrs[index] = attrs.extended; } - this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT); - this._data[index * CELL_SIZE + Cell.FG] = attrs.fg; - this._data[index * CELL_SIZE + Cell.BG] = attrs.bg; + this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT); + this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg; + this._data[index * Constants.CELL_INDICIES + Cell.BG] = attrs.bg; } /** @@ -229,7 +229,7 @@ export class BufferLine implements IBufferLine { * by the previous `setDataFromCodePoint` call, we can omit it here. */ public addCodepointToCell(index: number, codePoint: number, width: number): void { - let content = this._data[index * CELL_SIZE + Cell.CONTENT]; + let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT]; if (content & Content.IS_COMBINED_MASK) { // we already have a combined string, simply add this._combined[index] += stringFromCodePoint(codePoint); @@ -251,7 +251,7 @@ export class BufferLine implements IBufferLine { content &= ~Content.WIDTH_MASK; content |= width << Content.WIDTH_SHIFT; } - this._data[index * CELL_SIZE + Cell.CONTENT] = content; + this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = content; } public insertCells(pos: number, n: number, fillCellData: ICellData): void { @@ -344,13 +344,13 @@ export class BufferLine implements IBufferLine { * The underlying array buffer will not change if there is still enough space * to hold the new buffer line data. * Returns a boolean indicating, whether a `cleanupMemory` call would free - * excess memory (true after shrinking > CLEANUP_THRESHOLD). + * excess memory (true after shrinking > Constants.CLEANUP_THRESHOLD). */ public resize(cols: number, fillCellData: ICellData): boolean { if (cols === this.length) { - return this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength; + return this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength; } - const uint32Cells = cols * CELL_SIZE; + const uint32Cells = cols * Constants.CELL_INDICIES; if (cols > this.length) { if (this._data.buffer.byteLength >= uint32Cells * 4) { // optimization: avoid alloc and data copy if buffer has enough room @@ -385,17 +385,17 @@ export class BufferLine implements IBufferLine { } } this.length = cols; - return uint32Cells * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength; + return uint32Cells * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength; } /** * Cleanup underlying array buffer. * A cleanup will be triggered if the array buffer exceeds the actual used - * memory by a factor of CLEANUP_THRESHOLD. + * memory by a factor of Constants.CLEANUP_THRESHOLD. * Returns 0 or 1 indicating whether a cleanup happened. */ public cleanupMemory(): number { - if (this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength) { + if (this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength) { const data = new Uint32Array(this._data.length); data.set(this._data); this._data = data; @@ -459,8 +459,8 @@ export class BufferLine implements IBufferLine { public getTrimmedLength(): number { for (let i = this.length - 1; i >= 0; --i) { - if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) { - return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT); + if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) { + return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT); } } return 0; @@ -468,8 +468,8 @@ export class BufferLine implements IBufferLine { public getNoBgTrimmedLength(): number { for (let i = this.length - 1; i >= 0; --i) { - if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * CELL_SIZE + Cell.BG] & Attributes.CM_MASK)) { - return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT); + if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * Constants.CELL_INDICIES + Cell.BG] & Attributes.CM_MASK)) { + return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT); } } return 0; @@ -479,19 +479,19 @@ export class BufferLine implements IBufferLine { const srcData = src._data; if (applyInReverse) { for (let cell = length - 1; cell >= 0; cell--) { - for (let i = 0; i < CELL_SIZE; i++) { - this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i]; + for (let i = 0; i < Constants.CELL_INDICIES; i++) { + this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i]; } - if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) { + if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) { this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell]; } } } else { for (let cell = 0; cell < length; cell++) { - for (let i = 0; i < CELL_SIZE; i++) { - this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i]; + for (let i = 0; i < Constants.CELL_INDICIES; i++) { + this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i]; } - if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) { + if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) { this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell]; } } @@ -531,7 +531,7 @@ export class BufferLine implements IBufferLine { } let result = ''; while (startCol < endCol) { - const content = this._data[startCol * CELL_SIZE + Cell.CONTENT]; + const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR; result += chars; diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index c9db076d6..b936c9dbf 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -9,30 +9,30 @@ import { Emitter } from 'common/Event'; declare const setTimeout: (handler: () => void, timeout?: number) => void; -/** - * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input. - * Enable flow control to avoid this limit and make sure that your backend correctly - * propagates this to the underlying pty. (see docs for further instructions) - * Since this limit is meant as a safety parachute to prevent browser crashs, - * it is set to a very high number. Typically xterm.js gets unresponsive with - * a 100 times lower number (>500 kB). - */ -const DISCARD_WATERMARK = 50000000; // ~50 MB - -/** - * The max number of ms to spend on writes before allowing the renderer to - * catch up with a 0ms setTimeout. A value of < 33 to keep us close to - * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS - * depends on the time it takes for the renderer to draw the frame. - */ -const WRITE_TIMEOUT_MS = 12; - -/** - * Threshold of max held chunks in the write buffer, that were already processed. - * This is a tradeoff between extensive write buffer shifts (bad runtime) and high - * memory consumption by data thats not used anymore. - */ -const WRITE_BUFFER_LENGTH_THRESHOLD = 50; +const enum Constants { + /** + * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input. + * Enable flow control to avoid this limit and make sure that your backend correctly + * propagates this to the underlying pty. (see docs for further instructions) + * Since this limit is meant as a safety parachute to prevent browser crashs, + * it is set to a very high number. Typically xterm.js gets unresponsive with + * a 100 times lower number (>500 kB). + */ + DISCARD_WATERMARK = 50000000, // ~50 MB + /** + * The max number of ms to spend on writes before allowing the renderer to + * catch up with a 0ms setTimeout. A value of < 33 to keep us close to + * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS + * depends on the time it takes for the renderer to draw the frame. + */ + WRITE_TIMEOUT_MS = 12, + /** + * Threshold of max held chunks in the write buffer, that were already processed. + * This is a tradeoff between extensive write buffer shifts (bad runtime) and high + * memory consumption by data thats not used anymore. + */ + WRITE_BUFFER_LENGTH_THRESHOLD = 50 +} export class WriteBuffer extends Disposable { private _writeBuffer: (string | Uint8Array)[] = []; @@ -133,7 +133,7 @@ export class WriteBuffer extends Disposable { } public write(data: string | Uint8Array, callback?: () => void): void { - if (this._pendingData > DISCARD_WATERMARK) { + if (this._pendingData > Constants.DISCARD_WATERMARK) { throw new Error('write data discarded, use flow control to avoid losing data'); } @@ -169,7 +169,7 @@ export class WriteBuffer extends Disposable { * effectively lowering the redrawing needs, schematically: * * macroTask _innerWrite: - * if (performance.now() - (lastTime | 0) < WRITE_TIMEOUT_MS): + * if (performance.now() - (lastTime | 0) < Constants.WRITE_TIMEOUT_MS): * schedule microTask _innerWrite(lastTime) * else: * schedule macroTask _innerWrite(0) @@ -218,7 +218,7 @@ export class WriteBuffer extends Disposable { * responsibility to slice hard work), but we can at least schedule a screen update as we * gain control. */ - const continuation: (r: boolean) => void = (r: boolean) => performance.now() - startTime >= WRITE_TIMEOUT_MS + const continuation: (r: boolean) => void = (r: boolean) => performance.now() - startTime >= Constants.WRITE_TIMEOUT_MS ? setTimeout(() => this._innerWrite(0, r)) : this._innerWrite(startTime, r); @@ -235,7 +235,7 @@ export class WriteBuffer extends Disposable { * current microtask queue (executed before setTimeout). */ // const continuation: (r: boolean) => void = performance.now() - startTime >= - // WRITE_TIMEOUT_MS + // Constants.WRITE_TIMEOUT_MS // ? r => setTimeout(() => this._innerWrite(0, r)) // : r => this._innerWrite(startTime, r); @@ -255,14 +255,14 @@ export class WriteBuffer extends Disposable { this._bufferOffset++; this._pendingData -= data.length; - if (performance.now() - startTime >= WRITE_TIMEOUT_MS) { + if (performance.now() - startTime >= Constants.WRITE_TIMEOUT_MS) { break; } } if (this._writeBuffer.length > this._bufferOffset) { // Allow renderer to catch up before processing the next batch // trim already processed chunks if we are above threshold - if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) { + if (this._bufferOffset > Constants.WRITE_BUFFER_LENGTH_THRESHOLD) { this._writeBuffer = this._writeBuffer.slice(this._bufferOffset); this._callbacks = this._callbacks.slice(this._bufferOffset); this._bufferOffset = 0; diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 31872a0b2..8a77c78a4 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -4,10 +4,16 @@ */ import { IParams, ParamsArray } from 'common/parser/Types'; -// max value supported for a single param/subparam (clamped to positive int32 range) -const MAX_VALUE = 0x7FFFFFFF; -// max allowed subparams for a single sequence (hardcoded limitation) -const MAX_SUBPARAMS = 256; +const enum Constants { + /** + * Max value supported for a single param/subparam (clamped to positive int32 range) + */ + MAX_VALUE = 0x7FFFFFFF, + /** + * Max allowed subparams for a single sequence (hardcoded limitation) + */ + MAX_SUBPARAMS = 256 +} /** * Params storage class. @@ -70,7 +76,7 @@ export class Params implements IParams { * @param maxSubParamsLength max length of storable sub parameters */ constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) { - if (maxSubParamsLength > MAX_SUBPARAMS) { + if (maxSubParamsLength > Constants.MAX_SUBPARAMS) { throw new Error('maxSubParamsLength must not be greater than 256'); } this.params = new Int32Array(maxLength); @@ -159,7 +165,7 @@ export class Params implements IParams { throw new Error('values lesser than -1 are not allowed'); } this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength; - this.params[this.length++] = value > MAX_VALUE ? MAX_VALUE : value; + this.params[this.length++] = value > Constants.MAX_VALUE ? Constants.MAX_VALUE : value; } /** @@ -181,7 +187,7 @@ export class Params implements IParams { if (value < -1) { throw new Error('values lesser than -1 are not allowed'); } - this._subParams[this._subParamsLength++] = value > MAX_VALUE ? MAX_VALUE : value; + this._subParams[this._subParamsLength++] = value > Constants.MAX_VALUE ? Constants.MAX_VALUE : value; this._subParamsIdx[this.length - 1]++; } @@ -237,6 +243,6 @@ export class Params implements IParams { const store = this._digitIsSub ? this._subParams : this.params; const cur = store[length - 1]; - store[length - 1] = ~cur ? Math.min(cur * 10 + value, MAX_VALUE) : value; + store[length - 1] = ~cur ? Math.min(cur * 10 + value, Constants.MAX_VALUE) : value; } } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 6a7ce2b34..06ac6b10a 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -10,8 +10,10 @@ import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IBufferService, ILogService, IOptionsService, type IBufferResizeEvent } from 'common/services/Services'; import { Emitter } from 'common/Event'; -export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars -export const MINIMUM_ROWS = 1; +export const enum BufferServiceConstants { + MINIMUM_COLS = 2, // Less than 2 can mess with wide chars + MINIMUM_ROWS = 1 +} export class BufferService extends Disposable implements IBufferService { public serviceBrand: any; @@ -37,8 +39,8 @@ export class BufferService extends Disposable implements IBufferService { @ILogService logService: ILogService ) { super(); - this.cols = Math.max(optionsService.rawOptions.cols || 0, MINIMUM_COLS); - this.rows = Math.max(optionsService.rawOptions.rows || 0, MINIMUM_ROWS); + this.cols = Math.max(optionsService.rawOptions.cols || 0, BufferServiceConstants.MINIMUM_COLS); + this.rows = Math.max(optionsService.rawOptions.rows || 0, BufferServiceConstants.MINIMUM_ROWS); this.buffers = this._register(new BufferSet(optionsService, this, logService)); this._register(this.buffers.onBufferActivate(e => { this._onScroll.fire(e.activeBuffer.ydisp); diff --git a/src/common/services/ServiceRegistry.ts b/src/common/services/ServiceRegistry.ts index 7d887bc61..af51d1641 100644 --- a/src/common/services/ServiceRegistry.ts +++ b/src/common/services/ServiceRegistry.ts @@ -11,13 +11,15 @@ import { IServiceIdentifier } from 'common/services/Services'; -const DI_TARGET = 'di$target'; -const DI_DEPENDENCIES = 'di$dependencies'; +const enum Constants { + DI_TARGET = 'di$target', + DI_DEPENDENCIES = 'di$dependencies' +} export const serviceRegistry: Map> = new Map(); export function getServiceDependencies(ctor: any): { id: IServiceIdentifier, index: number, optional: boolean }[] { - return ctor[DI_DEPENDENCIES] || []; + return ctor[Constants.DI_DEPENDENCIES] || []; } export function createDecorator(id: string): IServiceIdentifier { @@ -40,10 +42,10 @@ export function createDecorator(id: string): IServiceIdentifier { } function storeServiceDependency(id: Function, target: Function, index: number): void { - if ((target as any)[DI_TARGET] === target) { - (target as any)[DI_DEPENDENCIES].push({ id, index }); + if ((target as any)[Constants.DI_TARGET] === target) { + (target as any)[Constants.DI_DEPENDENCIES].push({ id, index }); } else { - (target as any)[DI_DEPENDENCIES] = [{ id, index }]; - (target as any)[DI_TARGET] = target; + (target as any)[Constants.DI_DEPENDENCIES] = [{ id, index }]; + (target as any)[Constants.DI_TARGET] = target; } }