From c97feab05cc1cd532b4e67171c0a8823e5984b7f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Nov 2018 14:22:43 -0800 Subject: [PATCH 001/104] Initial webgl renderer implementation --- demo/client.ts | 8 +- src/Terminal.ts | 3 + src/Types.ts | 1 + src/renderer/atlas/CharAtlasCache.ts | 11 +- src/renderer/atlas/CharAtlasUtils.ts | 4 +- src/renderer/webgl/GlyphRenderer.ts | 343 +++++++++++++++++++ src/renderer/webgl/RectangleRenderer.ts | 320 ++++++++++++++++++ src/renderer/webgl/RenderModel.ts | 59 ++++ src/renderer/webgl/Types.ts | 75 +++++ src/renderer/webgl/WebglCharAtlas.ts | 386 +++++++++++++++++++++ src/renderer/webgl/WebglRenderer.ts | 427 ++++++++++++++++++++++++ src/renderer/webgl/WebglUtils.ts | 51 +++ src/shared/atlas/Types.ts | 2 +- typings/xterm.d.ts | 6 +- 14 files changed, 1683 insertions(+), 13 deletions(-) create mode 100644 src/renderer/webgl/GlyphRenderer.ts create mode 100644 src/renderer/webgl/RectangleRenderer.ts create mode 100644 src/renderer/webgl/RenderModel.ts create mode 100644 src/renderer/webgl/Types.ts create mode 100644 src/renderer/webgl/WebglCharAtlas.ts create mode 100644 src/renderer/webgl/WebglRenderer.ts create mode 100644 src/renderer/webgl/WebglUtils.ts diff --git a/demo/client.ts b/demo/client.ts index d5196d37..23b3a502 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -203,11 +203,11 @@ function initOptions(term: TerminalType): void { bellSound: null, bellStyle: ['none', 'sound'], cursorStyle: ['block', 'underline', 'bar'], - experimentalCharAtlas: ['none', 'static', 'dynamic'], + experimentalCharAtlas: ['none', 'static', 'dynamic', 'webgl'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - rendererType: ['dom', 'canvas'], + rendererType: ['dom', 'canvas', 'webgl'], experimentalBufferLineImpl: ['JsArray', 'TypedArray'] }; const options = Object.keys((term)._core.options); @@ -235,7 +235,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; numberOptions.forEach(o => { - html += `
`; + html += `
`; }); html += '
'; Object.keys(stringOptions).forEach(o => { @@ -265,7 +265,7 @@ function initOptions(term: TerminalType): void { if (o === 'cols' || o === 'rows') { updateTerminalSize(); } else { - term.setOption(o, parseInt(input.value, 10)); + term.setOption(o, o === 'lineHeight' ? parseFloat(input.value) : parseInt(input.value, 10)); } }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index c9bc98ff..84fd192b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; +import { WebglRenderer } from './renderer/webgl/WebglRenderer'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -462,6 +463,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.renderer.onResize(this.cols, this.rows); this.refresh(0, this.rows - 1); } + break; case 'rendererType': if (this.renderer) { this.unregister(this.renderer); @@ -761,6 +763,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II switch (this.options.rendererType) { case 'canvas': this.renderer = new Renderer(this, this.options.theme); break; case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; + case 'webgl': this.renderer = new WebglRenderer(this, this.options.theme); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } this.register(this.renderer); diff --git a/src/Types.ts b/src/Types.ts index e8578426..7f2f8816 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -311,6 +311,7 @@ export interface IBufferSet extends IEventEmitter { export interface ISelectionManager { selectionText: string; + hasSelection: boolean; selectionStart: [number, number]; selectionEnd: [number, number]; diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index eee93d6c..61b2ed13 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -11,11 +11,13 @@ import BaseCharAtlas from './BaseCharAtlas'; import DynamicCharAtlas from './DynamicCharAtlas'; import NoneCharAtlas from './NoneCharAtlas'; import StaticCharAtlas from './StaticCharAtlas'; +import WebglCharAtlas from '../webgl/WebglCharAtlas'; const charAtlasImplementations = { 'none': NoneCharAtlas, 'static': StaticCharAtlas, - 'dynamic': DynamicCharAtlas + 'dynamic': DynamicCharAtlas, + 'webgl': WebglCharAtlas }; interface ICharAtlasCacheEntry { @@ -38,9 +40,10 @@ export function acquireCharAtlas( terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, - scaledCharHeight: number + scaledCharHeight: number, + devicePixelRatio?: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // TODO: Currently if a terminal changes configs it will not free the entry reference (until it's disposed) @@ -54,6 +57,7 @@ export function acquireCharAtlas( } // The configs differ, release the terminal from the entry if (entry.ownedBy.length === 1) { + entry.atlas.dispose(); charAtlasCache.splice(i, 1); } else { entry.ownedBy.splice(ownedByIndex, 1); @@ -94,6 +98,7 @@ export function removeTerminalFromCache(terminal: ITerminal): void { if (index !== -1) { if (charAtlasCache[i].ownedBy.length === 1) { // Remove the cache entry if it's the only terminal + charAtlasCache[i].atlas.dispose(); charAtlasCache.splice(i, 1); } else { // Remove the reference from the cache entry diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 59ac07df..47cdf45a 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -7,7 +7,7 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; import { ICharAtlasConfig } from '../../shared/atlas/Types'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet, devicePixelRatio: number = window.devicePixelRatio): ICharAtlasConfig { // null out some fields that don't matter const clonedColors = { foreground: colors.foreground, @@ -21,7 +21,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number }; return { type: terminal.options.experimentalCharAtlas, - devicePixelRatio: window.devicePixelRatio, + devicePixelRatio, scaledCharWidth, scaledCharHeight, fontFamily: terminal.options.fontFamily, diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts new file mode 100644 index 00000000..0600f758 --- /dev/null +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -0,0 +1,343 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; +import { IRenderDimensions } from '../Types'; +import { ITerminal, IBufferLine } from '../../Types'; +import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import WebglCharAtlas from './WebglCharAtlas'; +import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; +import { INDICIES_PER_CELL } from './WebglRenderer'; +import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; + +interface IVertices { + attributes: Float32Array; + selectionAttributes: Float32Array; + cellPosition: Float32Array; + count: number; +} + +const enum VertexAttribLocations { + UNIT_QUAD = 0, + CELL_POSITION = 1, + OFFSET = 2, + SIZE = 3, + TEXCOORD = 4, + TEXSIZE = 5 +} + +const vertexShaderSource = `#version 300 es +layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad; +layout (location = ${VertexAttribLocations.CELL_POSITION}) in vec2 a_cellpos; +layout (location = ${VertexAttribLocations.OFFSET}) in vec2 a_offset; +layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size; +layout (location = ${VertexAttribLocations.TEXCOORD}) in vec2 a_texcoord; +layout (location = ${VertexAttribLocations.TEXSIZE}) in vec2 a_texsize; + +uniform mat4 u_projection; +uniform vec2 u_resolution; + +out vec2 v_texcoord; + +void main() { + vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size); + gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0); + v_texcoord = a_texcoord + a_unitquad * a_texsize; +}`; + +const fragmentShaderSource = `#version 300 es +precision mediump float; + +in vec2 v_texcoord; + +uniform sampler2D u_texture; + +out vec4 outColor; + +void main() { + outColor = texture(u_texture, v_texcoord); +}`; + +const INDICES_PER_CELL = 8; +const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; + +export class GlyphRenderer { + private _atlas: WebglCharAtlas; + + private _program: WebGLProgram; + private _vertexArrayObject: IWebGLVertexArrayObject; + private _projectionLocation: WebGLUniformLocation; + private _resolutionLocation: WebGLUniformLocation; + private _textureLocation: WebGLUniformLocation; + private _atlasTexture: WebGLTexture; + private _attributesBuffer: WebGLBuffer; + private _cellPositionBuffer: WebGLBuffer; + + private _lineLengths: Int16Array = new Int16Array(0); + private _vertices: IVertices = { + count: 0, + attributes: new Float32Array(0), + selectionAttributes: new Float32Array(0), + cellPosition: new Float32Array(0) + }; + + constructor( + private _terminal: ITerminal, + private _gl: IWebGL2RenderingContext, + private _dimensions: IRenderDimensions + ) { + const gl = this._gl; + + this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource); + + // Uniform locations + this._projectionLocation = gl.getUniformLocation(this._program, 'u_projection'); + this._resolutionLocation = gl.getUniformLocation(this._program, 'u_resolution'); + this._textureLocation = gl.getUniformLocation(this._program, 'u_texture'); + + // Create and set the vertex array object + this._vertexArrayObject = gl.createVertexArray(); + gl.bindVertexArray(this._vertexArrayObject); + + // Setup a_unitquad, this defines the 4 vertices of a rectangle + const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]); + const unitQuadVerticesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW); + gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD); + gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0); + + // Setup the unit quad element array buffer, this points to indices in + // unitQuadVertuces to allow is to draw 2 triangles from the vertices + const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]); + const elementIndicesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); + + // Setup a_cellpos, this is separate as it rarely changed + this._cellPositionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); + gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION); + gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, 0, 0); + gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); + + // Setup attributes + this._attributesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.enableVertexAttribArray(VertexAttribLocations.OFFSET); + gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, 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.vertexAttribDivisor(VertexAttribLocations.SIZE, 1); + gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD); + gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1); + gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE); + gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 6 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1); + + // Setup empty texture atlas + this._atlasTexture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + // Allow drawing of transparent texture + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + // Set viewport + this.onResize(); + } + + public beginFrame(): boolean { + return this._atlas.beginFrame(); + } + + public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void { + this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars); + } + + private _updateCell(array: Float32Array, x: number, y: number, code: number, attr: number, bg: number, fg: number, chars?: string): void { + const terminal = this._terminal; + + const i = ((y * terminal.cols) + x) * INDICES_PER_CELL; + + // Exit early if this is a null/space character + if (code === NULL_CELL_CODE) { + array.fill(0, i, i + INDICES_PER_CELL - 1); + return; + } + + let rasterizedGlyph: IRasterizedGlyph; + if (chars && chars.length > 1) { + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.options.enableBold); + } else { + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.options.enableBold); + } + + // Fill empty if no glyph was found + if (!rasterizedGlyph) { + array.fill(0, i, i + INDICES_PER_CELL - 1); + return; + } + + // a_origin + array[i ] = -rasterizedGlyph.offset.x + this._dimensions.scaledCharLeft; + array[i + 1] = -rasterizedGlyph.offset.y + this._dimensions.scaledCharTop; + // a_size + array[i + 2] = rasterizedGlyph.size.x / this._dimensions.scaledCanvasWidth; + array[i + 3] = rasterizedGlyph.size.y / this._dimensions.scaledCanvasHeight; + // a_texcoord + array[i + 4] = rasterizedGlyph.texturePositionClipSpace.x; + array[i + 5] = rasterizedGlyph.texturePositionClipSpace.y; + // a_texsize + array[i + 6] = rasterizedGlyph.sizeClipSpace.x; + array[i + 7] = rasterizedGlyph.sizeClipSpace.y; + } + + public updateLineEnd(x: number, y: number): void { + // Clears all cells to the right of the line end + const i = (y * this._terminal.cols + x + 1) * INDICES_PER_CELL; + this._vertices.attributes.fill(0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); + } + + public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { + const terminal = this._terminal; + + this._vertices.selectionAttributes = this._vertices.attributes.slice(0); + + // TODO: Make fg and bg configurable, currently since the buffer doesn't + // support truecolor the char atlas cannot store it. + const fg = 0; + const bg = 7; + + if (columnSelectMode) { + const startCol = model.selection.startCol; + const width = model.selection.endCol - startCol; + const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1; + for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) { + this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg); + } + } else { + // Draw first row + const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0; + const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; + this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg); + + // Draw middle rows + const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0); + for (let y = (model.selection.viewportCappedStartRow + 1); y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { + this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg); + } + + // Draw final row + if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) { + // Only draw viewportEndRow if it's not the same as viewportStartRow + const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; + this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg); + } + } + } + + private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void { + const terminal = this._terminal; + const row = y + terminal.buffer.ydisp; + let line: IBufferLine; + for (let x = startCol; x < endCol; x++) { + const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; + // Because the cache uses attr as a lookup key it needs to contain the selection colors as well + let attr = model.cells[offset + 1]; + attr = attr & ~0x3ffff | bg << 9 | fg; + const code = model.cells[offset]; + if (code & COMBINED_CHAR_BIT_MASK) { + if (!line) { + line = terminal.buffer.lines.get(row); + } + const charData = line.get(x); + const chars = charData[CHAR_DATA_CHAR_INDEX]; + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars); + } else { + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg); + } + } + } + + public onResize(): void { + const terminal = this._terminal; + const gl = this._gl; + + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + + // Update vertices + const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL; + if (this._vertices.count !== newCount) { + this._vertices.count = newCount; + this._vertices.attributes = new Float32Array(newCount); + this._lineLengths = new Int16Array(terminal.rows); + + this._vertices.cellPosition = new Float32Array(terminal.cols * terminal.rows * 2); + + let i = 0; + for (let y = 0; y < terminal.rows; y++) { + for (let x = 0; x < terminal.cols; x++) { + this._vertices.cellPosition[i++] = x / terminal.cols; + this._vertices.cellPosition[i++] = y / terminal.rows; + } + } + } + } + + public onThemeChanged(): void { + } + + public render(isSelectionVisible: boolean): void { + if (!this._atlas) { + return; + } + + const gl = this._gl; + + gl.useProgram(this._program); + gl.bindVertexArray(this._vertexArrayObject); + + gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this._vertices.cellPosition, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes, gl.DYNAMIC_DRAW); + + // Bind the texture atlas if it's changed + if (this._atlas.hasCanvasChanged) { + this._atlas.hasCanvasChanged = false; + gl.uniform1i(this._textureLocation, 0); + gl.activeTexture(gl.TEXTURE0 + 0); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas); + gl.generateMipmap(gl.TEXTURE_2D); + } + + // Set uniforms + gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX); + gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); + + // Draw the viewport + gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count / INDICES_PER_CELL); + } + + public setAtlas(atlas: WebglCharAtlas): void { + const gl = this._gl; + this._atlas = atlas; + + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas); + gl.generateMipmap(gl.TEXTURE_2D); + } + + public setDimensions(dimensions: IRenderDimensions): void { + this._dimensions = dimensions; + } +} diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts new file mode 100644 index 00000000..eca89b3a --- /dev/null +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -0,0 +1,320 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../../Types'; +import { IColorManager, IRenderDimensions } from '../Types'; +import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; +import { IColor } from '../../shared/Types'; +import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; +import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; + +const enum VertexAttribLocations { + POSITION = 0, + SIZE = 1, + COLOR = 2, + UNIT_QUAD = 3 +} + +const vertexShaderSource = `#version 300 es +layout (location = ${VertexAttribLocations.POSITION}) in vec2 a_position; +layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size; +layout (location = ${VertexAttribLocations.COLOR}) in vec3 a_color; +layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad; + +uniform mat4 u_projection; +uniform vec2 u_resolution; + +out vec3 v_color; + +void main() { + vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution; + gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0); + v_color = a_color; +}`; + +const fragmentShaderSource = `#version 300 es +precision mediump float; + +in vec3 v_color; + +out vec4 outColor; + +void main() { + outColor = vec4(v_color, 1); +}`; + +interface IVertices { + attributes: Float32Array; + selection: Float32Array; + count: number; +} + +const INDICES_PER_RECTANGLE = 8; +const BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEMENT; + +const INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE; + +export class RectangleRenderer { + + private _program: WebGLProgram; + private _vertexArrayObject: IWebGLVertexArrayObject; + private _resolutionLocation: WebGLUniformLocation; + private _attributesBuffer: WebGLBuffer; + private _projectionLocation: WebGLUniformLocation; + private _bgFloat: Float32Array; + private _selectionFloat: Float32Array; + + private _vertices: IVertices = { + count: 0, + attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY), + selection: new Float32Array(3 * INDICES_PER_RECTANGLE) + }; + + constructor( + private _terminal: ITerminal, + private _colorManager: IColorManager, + private _gl: IWebGL2RenderingContext, + private _dimensions: IRenderDimensions + ) { + const gl = this._gl; + + this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource); + + // Uniform locations + this._resolutionLocation = gl.getUniformLocation(this._program, 'u_resolution'); + this._projectionLocation = gl.getUniformLocation(this._program, 'u_projection'); + + // Create and set the vertex array object + this._vertexArrayObject = gl.createVertexArray(); + gl.bindVertexArray(this._vertexArrayObject); + + // Setup a_unitquad, this defines the 4 vertices of a rectangle + const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]); + const unitQuadVerticesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW); + gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD); + gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0); + + // Setup the unit quad element array buffer, this points to indices in + // unitQuadVertuces to allow is to draw 2 triangles from the vertices + const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]); + const elementIndicesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); + + // Setup attributes + this._attributesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.enableVertexAttribArray(VertexAttribLocations.POSITION); + gl.vertexAttribPointer(VertexAttribLocations.POSITION, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 0); + gl.vertexAttribDivisor(VertexAttribLocations.POSITION, 1); + gl.enableVertexAttribArray(VertexAttribLocations.SIZE); + gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 2 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1); + gl.enableVertexAttribArray(VertexAttribLocations.COLOR); + gl.vertexAttribPointer(VertexAttribLocations.COLOR, 4, gl.FLOAT, false, BYTES_PER_RECTANGLE, 4 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.COLOR, 1); + + this._updateCachedColors(); + } + + public render(): void { + const gl = this._gl; + + gl.useProgram(this._program); + + gl.bindVertexArray(this._vertexArrayObject); + + gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX); + gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); + + // Bind attributes buffer and draw + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this._vertices.attributes, gl.DYNAMIC_DRAW); + gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count); + + // Bind selection buffer and draw + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this._vertices.selection, gl.DYNAMIC_DRAW); + gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 3); + } + + public onResize(): void { + this._updateViewportRectangle(); + } + + public onThemeChanged(): void { + this._updateCachedColors(); + this._updateViewportRectangle(); + } + + private _updateCachedColors(): void { + this._bgFloat = this._colorToFloat32Array(this._colorManager.colors.background); + this._selectionFloat = this._colorToFloat32Array(this._colorManager.colors.selection); + } + + private _updateViewportRectangle(): void { + // Set first rectangle that clears the screen + this._addRectangleFloat( + this._vertices.attributes, + 0, + 0, + 0, + this._terminal.cols * this._dimensions.scaledCellWidth, + this._terminal.rows * this._dimensions.scaledCellHeight, + this._bgFloat + ); + } + + public updateSelection(model: ISelectionRenderModel, columnSelectMode: boolean): void { + const terminal = this._terminal; + + if (!model.hasSelection) { + this._vertices.selection.fill(0, 0); + return; + } + + if (columnSelectMode) { + const startCol = model.startCol; + const width = model.endCol - startCol; + const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1; + this._addRectangleFloat( + this._vertices.selection, + 0, + startCol * this._dimensions.scaledCellWidth, + model.viewportCappedStartRow * this._dimensions.scaledCellHeight, + width * this._dimensions.scaledCellWidth, + height * this._dimensions.scaledCellHeight, + this._selectionFloat + ); + this._vertices.selection.fill(0, INDICES_PER_RECTANGLE); + } else { + // Draw first row + const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0; + const startRowEndCol = model.viewportCappedStartRow === model.viewportCappedEndRow ? model.endCol : terminal.cols; + this._addRectangleFloat( + this._vertices.selection, + 0, + startCol * this._dimensions.scaledCellWidth, + model.viewportCappedStartRow * this._dimensions.scaledCellHeight, + (startRowEndCol - startCol) * this._dimensions.scaledCellWidth, + this._dimensions.scaledCellHeight, + this._selectionFloat + ); + + // Draw middle rows + const middleRowsCount = Math.max(model.viewportCappedEndRow - model.viewportCappedStartRow - 1, 0); + this._addRectangleFloat( + this._vertices.selection, + INDICES_PER_RECTANGLE, + 0, + (model.viewportCappedStartRow + 1) * this._dimensions.scaledCellHeight, + terminal.cols * this._dimensions.scaledCellWidth, + middleRowsCount * this._dimensions.scaledCellHeight, + this._selectionFloat + ); + + // Draw final row + if (model.viewportCappedStartRow !== model.viewportCappedEndRow) { + // Only draw viewportEndRow if it's not the same as viewportStartRow + const endCol = model.viewportEndRow === model.viewportCappedEndRow ? model.endCol : terminal.cols; + this._addRectangleFloat( + this._vertices.selection, + INDICES_PER_RECTANGLE * 2, + 0, + model.viewportCappedEndRow * this._dimensions.scaledCellHeight, + endCol * this._dimensions.scaledCellWidth, + this._dimensions.scaledCellHeight, + this._selectionFloat + ); + } else { + this._vertices.selection.fill(0, INDICES_PER_RECTANGLE * 2); + } + } + } + + public updateBackgrounds(model: IRenderModel): void { + const terminal = this._terminal; + const vertices = this._vertices; + + let rectangleCount = 1; + + const DEFAULT_BACKGROUND_COLOR = 256; + for (let y = 0; y < terminal.rows; y++) { + let currentStartX = -1; + let currentBg = DEFAULT_BACKGROUND_COLOR; + for (let x = 0; x < terminal.cols; x++) { + const modelIndex = ((y * terminal.cols) + x) * 4; + const bg = model.cells[modelIndex + 2]; + if (bg !== currentBg) { + // A rectangle needs to be drawn if going from non-default to another color + if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + const offset = rectangleCount++ * INDICES_PER_RECTANGLE; + this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y); + } + currentStartX = x; + currentBg = bg; + } + } + // Finish rectangle if it's still going + if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + const offset = rectangleCount++ * INDICES_PER_RECTANGLE; + this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y); + } + } + vertices.count = rectangleCount; + } + + private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { + let color: IColor | null = null; + if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + color = this._colorManager.colors.foreground; + } else if (bg < 256) { + color = this._colorManager.colors.ansi[bg]; + } + if (vertices.attributes.length < offset + 4) { + vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); + } + const x1 = startX * this._dimensions.scaledCellWidth; + const y1 = y * this._dimensions.scaledCellHeight; + const r = ((color.rgba >> 24) & 0xFF) / 255; + const g = ((color.rgba >> 16) & 0xFF) / 255; + const b = ((color.rgba >> 8 ) & 0xFF) / 255; + + this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1); + } + + private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void { + array[offset ] = x1; + array[offset + 1] = y1; + array[offset + 2] = width; + array[offset + 3] = height; + array[offset + 4] = r; + array[offset + 5] = g; + array[offset + 6] = b; + array[offset + 7] = a; + } + + private _addRectangleFloat(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, color: Float32Array): void { + array[offset ] = x1; + array[offset + 1] = y1; + array[offset + 2] = width; + array[offset + 3] = height; + array[offset + 4] = color[0]; + array[offset + 5] = color[1]; + array[offset + 6] = color[2]; + array[offset + 7] = color[3]; + } + + private _colorToFloat32Array(color: IColor): Float32Array { + return new Float32Array([ + ((color.rgba >> 24) & 0xFF) / 255, + ((color.rgba >> 16) & 0xFF) / 255, + ((color.rgba >> 8 ) & 0xFF) / 255, + ((color.rgba ) & 0xFF) / 255 + ]); + } +} diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts new file mode 100644 index 00000000..a14616c2 --- /dev/null +++ b/src/renderer/webgl/RenderModel.ts @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IRenderModel, ISelectionRenderModel } from './Types'; + +export const RENDER_MODEL_INDICIES_PER_CELL = 4; + +// HACK: Cannot use INVERTED_DEFAULT_COLOR (-1) here because _model.cells is a +// Uint32Array. This should be changed when true color is introduced to whatever +// the mechanism is for the buffer. +export const RENDER_INVERTED_DEFAULT_COLOR = 258; + +export const COMBINED_CHAR_BIT_MASK = 0x80000000; + +export class RenderModel implements IRenderModel { + public cells: Uint32Array; + public lineLengths: Uint32Array; + public selection: ISelectionRenderModel; + + constructor() { + this.cells = new Uint32Array(0); + this.lineLengths = new Uint32Array(0); + this.selection = { + hasSelection: false, + viewportStartRow: 0, + viewportEndRow: 0, + viewportCappedStartRow: 0, + viewportCappedEndRow: 0, + startCol: 0, + endCol: 0 + }; + } + + public resize(cols: number, rows: number): void { + const indexCount = cols * rows * RENDER_MODEL_INDICIES_PER_CELL; + if (indexCount !== this.cells.length) { + this.cells = new Uint32Array(indexCount); + this.lineLengths = new Uint32Array(rows); + } + } + + public clear(): void { + this.cells.fill(0, 0); + this.lineLengths.fill(0, 0); + this.clearSelection(); + } + + public clearSelection(): void { + this.selection.hasSelection = false; + this.selection.viewportStartRow = 0; + this.selection.viewportEndRow = 0; + this.selection.viewportCappedStartRow = 0; + this.selection.viewportCappedEndRow = 0; + this.selection.startCol = 0; + this.selection.endCol = 0; + } +} diff --git a/src/renderer/webgl/Types.ts b/src/renderer/webgl/Types.ts new file mode 100644 index 00000000..6ebc8d64 --- /dev/null +++ b/src/renderer/webgl/Types.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export interface IRasterizedGlyphSet { + [flags: number]: IRasterizedGlyph; +} + +/** + * Represents a rasterized glyph within a texture atlas. Some numbers are + * tracked in CSS pixels as well in order to reduce calculations during the + * render loop. + */ +export interface IRasterizedGlyph { + /** + * The x and y offset between the glyph's top/left and the top/left of a cell + * in pixels. + */ + offset: IVector; + /** + * the x and y position of the glyph in the texture in pixels. + */ + texturePosition: IVector; + /** + * the x and y position of the glyph in the texture in clip space coordinates. + */ + texturePositionClipSpace: IVector; + /** + * The width and height of the glyph in the texture in pixels. + */ + size: IVector; + /** + * The width and height of the glyph in the texture in clip space coordinates. + */ + sizeClipSpace: IVector; +} + +export interface IVector { + x: number; + y: number; +} + +export interface IBoundingBox { + top: number; + left: number; + right: number; + bottom: number; +} + +export interface IRenderModel { + cells: Uint32Array; + lineLengths: Uint32Array; + selection: ISelectionRenderModel; +} + +export interface ISelectionRenderModel { + hasSelection: boolean; + viewportStartRow: number; + viewportEndRow: number; + viewportCappedStartRow: number; + viewportCappedEndRow: number; + startCol: number; + endCol: number; +} + +export interface IWebGL2RenderingContext extends WebGLRenderingContext { + vertexAttribDivisor(index: number, divisor: number): void; + createVertexArray(): IWebGLVertexArrayObject; + bindVertexArray(vao: IWebGLVertexArrayObject): void; + drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void; +} + +export interface IWebGLVertexArrayObject { +} diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts new file mode 100644 index 00000000..86a60d07 --- /dev/null +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -0,0 +1,386 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { DIM_OPACITY, IGlyphIdentifier } from '../atlas/Types'; +import { ICharAtlasConfig } from '../../shared/atlas/Types'; +import { IColor } from '../../shared/Types'; +import BaseCharAtlas from '../atlas/BaseCharAtlas'; +import { DEFAULT_ANSI_COLORS } from '../ColorManager'; +import { clearColor } from '../../shared/atlas/CharAtlasGenerator'; +import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; +import { DEFAULT_ATTR } from '../../Buffer'; +import { FLAGS } from '../Types'; +import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; + +// In practice we're probably never going to exhaust a texture this large. For debugging purposes, +// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. +const TEXTURE_WIDTH = 1024; +const TEXTURE_HEIGHT = 1024; + +/** + * The amount of the texture to be filled before throwing it away and starting + * again. Since the throw away and individual glyph draws don't cost too much, + * this prevent juggling multiple textures in the GL context. + */ +const TEXTURE_CAPACITY = Math.floor(TEXTURE_HEIGHT * 0.8); + +const TRANSPARENT_COLOR = { + css: 'rgba(0, 0, 0, 0)', + rgba: 0 +}; + +/** + * A shared object which is used to draw nothing for a particular cell. + */ +const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = { + offset: { x: 0, y: 0 }, + texturePosition: { x: 0, y: 0 }, + texturePositionClipSpace: { x: 0, y: 0 }, + size: { x: 0, y: 0 }, + sizeClipSpace: { x: 0, y: 0 } +}; + +const TMP_CANVAS_GLYPH_PADDING = 2; + +export default class WebglCharAtlas extends BaseCharAtlas { + private _cacheMap: { [code: number]: IRasterizedGlyphSet } = {}; + private _cacheMapCombined: { [chars: string]: IRasterizedGlyphSet } = {}; + + // The texture that the atlas is drawn to + public cacheCanvas: HTMLCanvasElement; + private _cacheCtx: CanvasRenderingContext2D; + + private _tmpCanvas: HTMLCanvasElement; + // A temporary context that glyphs are drawn to before being transfered to the atlas. + private _tmpCtx: CanvasRenderingContext2D; + + // Since glyphs are expected to be around the same height, the packing + // strategy used it to fill a row with glyphs while keeping track of the + // tallest glyph in the row. Once the row is full a new row is started at + // (0,lastRow+lastRowTallestGlyph). + private _currentRowY: number = 0; + private _currentRowX: number = 0; + private _currentRowHeight: number = 0; + + public hasCanvasChanged = false; + + private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; + + constructor(document: Document, private _config: ICharAtlasConfig) { + super(); + + this.cacheCanvas = document.createElement('canvas'); + this.cacheCanvas.width = TEXTURE_WIDTH; + this.cacheCanvas.height = TEXTURE_HEIGHT; + // The canvas needs alpha because we use clearColor to convert the background color to alpha. + // It might also contain some characters with transparent backgrounds if allowTransparency is + // set. + this._cacheCtx = this.cacheCanvas.getContext('2d', {alpha: true}); + + this._tmpCanvas = document.createElement('canvas'); + this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2; + this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2; + this._tmpCtx = this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}); + + // This is useful for debugging + document.body.appendChild(this.cacheCanvas); + } + + public dispose(): void { + if (this.cacheCanvas.parentElement) { + this.cacheCanvas.parentElement.removeChild(this.cacheCanvas); + } + } + + protected _doWarmUp(): void { + // Pre-fill with ASCII 33-126 + for (let i = 33; i < 126; i++) { + const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, 256, 257, true); + this._cacheMap[i] = { + [DEFAULT_ATTR]: rasterizedGlyph + }; + } + } + + public beginFrame(): boolean { + if (this._currentRowY > TEXTURE_CAPACITY) { + this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + this._cacheMap = {}; + this._currentRowHeight = 0; + this._currentRowX = 0; + this._currentRowY = 0; + this._doWarmUp(); + return true; + } + return false; + } + + public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + let rasterizedGlyphSet = this._cacheMapCombined[chars]; + if (!rasterizedGlyphSet) { + rasterizedGlyphSet = {}; + this._cacheMapCombined[chars] = rasterizedGlyphSet; + } + let rasterizedGlyph = rasterizedGlyphSet[attr]; + if (!rasterizedGlyph) { + rasterizedGlyph = this._drawToCache(chars, attr, bg, fg, enableBold); + rasterizedGlyphSet[attr] = rasterizedGlyph; + } + return rasterizedGlyph; + } + + /** + * Gets the glyphs texture coords, drawing the texture if it's not already + */ + public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + // Space is always an empty cell, special case this as it's so common + if (code === 32) { + return; + } + + let rasterizedGlyphSet = this._cacheMap[code]; + if (!rasterizedGlyphSet) { + rasterizedGlyphSet = {}; + this._cacheMap[code] = rasterizedGlyphSet; + } + let rasterizedGlyph = rasterizedGlyphSet[attr]; + if (!rasterizedGlyph) { + rasterizedGlyph = this._drawToCache(code, attr, bg, fg, enableBold); + rasterizedGlyphSet[attr] = rasterizedGlyph; + } + return rasterizedGlyph; + } + + public draw( + ctx: CanvasRenderingContext2D, + glyph: IGlyphIdentifier, + x: number, + y: number + ): boolean { + throw new Error('WebglCharAtlas is only compatible with the webgl renderer'); + } + + private _getColorFromAnsiIndex(idx: number): IColor { + if (idx < this._config.colors.ansi.length) { + return this._config.colors.ansi[idx]; + } + return DEFAULT_ANSI_COLORS[idx]; + } + + private _getBackgroundColor(bg: number): IColor { + if (this._config.allowTransparency) { + // The background color might have some transparency, so we need to render it as fully + // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice + // around the anti-aliased edges of the glyph, and it would look too dark. + return TRANSPARENT_COLOR; + } else if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + return this._config.colors.foreground; + } else if (bg < 256) { + return this._getColorFromAnsiIndex(bg); + } + return this._config.colors.background; + } + + private _getForegroundColor(fg: number): IColor { + if (fg === RENDER_INVERTED_DEFAULT_COLOR) { + return this._config.colors.background; + } else if (fg < 256) { + // 256 color support + return this._getColorFromAnsiIndex(fg); + } + return this._config.colors.foreground; + } + + private _drawToCache(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; + private _drawToCache(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; + private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; + + this.hasCanvasChanged = true; + + const flags = attr >> 18; + + const bold = !!(flags & FLAGS.BOLD) && enableBold; + const dim = !!(flags & FLAGS.DIM); + const italic = !!(flags & FLAGS.ITALIC); + + this._tmpCtx.save(); + + // draw the background + const backgroundColor = this._getBackgroundColor(bg); + // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of + // transparency in backgroundColor + this._tmpCtx.globalCompositeOperation = 'copy'; + this._tmpCtx.fillStyle = backgroundColor.css; + this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height); + this._tmpCtx.globalCompositeOperation = 'source-over'; + + // draw the foreground/glyph + const fontWeight = bold ? this._config.fontWeightBold : this._config.fontWeight; + const fontStyle = italic ? 'italic' : ''; + this._tmpCtx.font = + `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; + this._tmpCtx.textBaseline = 'top'; + + this._tmpCtx.fillStyle = this._getForegroundColor(fg).css; + + // Apply alpha to dim the character + if (dim) { + this._tmpCtx.globalAlpha = DIM_OPACITY; + } + + // Draw the character + this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING); + this._tmpCtx.restore(); + + // clear the background from the character to avoid issues with drawing over the previous + // character if it extends past it's bounds + const imageData = this._tmpCtx.getImageData( + 0, 0, this._tmpCanvas.width, this._tmpCanvas.height + ); + + // TODO: Support transparency + // let isEmpty = false; + // if (!this._config.allowTransparency) { + // isEmpty = clearColor(imageData, backgroundColor); + // } + + // Clear out the background color and determine if the glyph is empty. + const isEmpty = clearColor(imageData, backgroundColor); + + // Handle empty glyphs + if (isEmpty) { + return NULL_RASTERIZED_GLYPH; + } + + const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox); + const clippedImageData = this._clipImageData(imageData, this._workBoundingBox); + + // Check if there is enough room in the current row and go to next if needed + if (this._currentRowX + this._config.scaledCharWidth > TEXTURE_WIDTH) { + this._currentRowX = 0; + this._currentRowY += this._currentRowHeight; + this._currentRowHeight = 0; + } + + // Record texture position + rasterizedGlyph.texturePosition.x = this._currentRowX; + rasterizedGlyph.texturePosition.y = this._currentRowY; + rasterizedGlyph.texturePositionClipSpace.x = this._currentRowX / TEXTURE_WIDTH; + rasterizedGlyph.texturePositionClipSpace.y = this._currentRowY / TEXTURE_HEIGHT; + + // Update atlas current row + this._currentRowHeight = Math.max(this._currentRowHeight, rasterizedGlyph.size.y); + this._currentRowX += rasterizedGlyph.size.x; + + // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us + this._cacheCtx.putImageData(clippedImageData, rasterizedGlyph.texturePosition.x, rasterizedGlyph.texturePosition.y); + + return rasterizedGlyph; + } + + /** + * Given an ImageData object, find the bounding box of the non-transparent + * portion of the texture and return an IRasterizedGlyph with these + * dimensions. + * @param imageData The image data to read. + * @param boundingBox An IBoundingBox to put the clipped bounding box values. + */ + private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox): IRasterizedGlyph { + boundingBox.top = 0; + let found = false; + for (let y = 0; y < this._tmpCanvas.height; y++) { + for (let x = 0; x < this._tmpCanvas.width; x++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.top = y; + found = true; + break; + } + } + if (found) { + break; + } + } + boundingBox.left = 0; + found = false; + for (let x = 0; x < this._tmpCanvas.width; x++) { + for (let y = 0; y < this._tmpCanvas.height; y++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.left = x; + found = true; + break; + } + } + if (found) { + break; + } + } + boundingBox.right = this._tmpCanvas.width; + found = false; + for (let x = this._tmpCanvas.width - 1; x >= 0; x--) { + for (let y = 0; y < this._tmpCanvas.height; y++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.right = x; + found = true; + break; + } + } + if (found) { + break; + } + } + boundingBox.bottom = this._tmpCanvas.height; + found = false; + for (let y = this._tmpCanvas.height - 1; y >= 0; y--) { + for (let x = 0; x < this._tmpCanvas.width; x++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.bottom = y; + found = true; + break; + } + } + if (found) { + break; + } + } + return { + texturePosition: { x: 0, y: 0 }, + texturePositionClipSpace: { x: 0, y: 0 }, + size: { + x: boundingBox.right - boundingBox.left + 1, + y: boundingBox.bottom - boundingBox.top + 1 + }, + sizeClipSpace: { + x: (boundingBox.right - boundingBox.left + 1) / TEXTURE_WIDTH, + y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT + }, + offset: { + x: -boundingBox.left + TMP_CANVAS_GLYPH_PADDING, + y: -boundingBox.top + TMP_CANVAS_GLYPH_PADDING + } + }; + } + + private _clipImageData(imageData: ImageData, boundingBox: IBoundingBox): ImageData { + const width = boundingBox.right - boundingBox.left + 1; + const height = boundingBox.bottom - boundingBox.top + 1; + const clippedData = new Uint8ClampedArray(width * height * 4); + for (let y = boundingBox.top; y <= boundingBox.bottom; y++) { + for (let x = boundingBox.left; x <= boundingBox.right; x++) { + const oldOffset = y * this._tmpCanvas.width * 4 + x * 4; + const newOffset = (y - boundingBox.top) * width * 4 + (x - boundingBox.left) * 4; + clippedData[newOffset] = imageData.data[oldOffset]; + clippedData[newOffset + 1] = imageData.data[oldOffset + 1]; + clippedData[newOffset + 2] = imageData.data[oldOffset + 2]; + clippedData[newOffset + 3] = imageData.data[oldOffset + 3]; + } + } + return new ImageData(clippedData, width, height); + } +} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts new file mode 100644 index 00000000..25030319 --- /dev/null +++ b/src/renderer/webgl/WebglRenderer.ts @@ -0,0 +1,427 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { EventEmitter } from '../../common/EventEmitter'; +import { IRenderer, IRenderDimensions, IColorSet, IRenderLayer, FLAGS } from '../Types'; +import { ITheme } from 'xterm'; +import { CharacterJoinerHandler, ITerminal } from '../../Types'; +import { ColorManager } from '../ColorManager'; +import { RenderDebouncer } from '../../ui/RenderDebouncer'; +import { GlyphRenderer } from './GlyphRenderer'; +import { LinkRenderLayer } from '../LinkRenderLayer'; +import { CursorRenderLayer } from '../CursorRenderLayer'; +import { acquireCharAtlas } from '../atlas/CharAtlasCache'; +import WebglCharAtlas from './WebglCharAtlas'; +import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; +import { RectangleRenderer } from './RectangleRenderer'; +import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import { IWebGL2RenderingContext } from './Types'; +import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { RenderModel, RENDER_INVERTED_DEFAULT_COLOR, COMBINED_CHAR_BIT_MASK } from './RenderModel'; + +export const INDICIES_PER_CELL = 4; + +export class WebglRenderer extends EventEmitter implements IRenderer { + private _renderDebouncer: RenderDebouncer; + private _renderLayers: IRenderLayer[]; + private _charAtlas: WebglCharAtlas; + private _screenDprMonitor: ScreenDprMonitor; + private _devicePixelRatio: number; + + private _model: RenderModel = new RenderModel(); + + private _canvas: HTMLCanvasElement; + private _gl: IWebGL2RenderingContext; + private _rectangleRenderer: RectangleRenderer; + private _glyphRenderer: GlyphRenderer; + + private _isPaused: boolean = false; + private _needsFullRefresh: boolean = false; + + public dimensions: IRenderDimensions; + public colorManager: ColorManager; + + constructor( + private _terminal: ITerminal, + theme: ITheme + ) { + super(); + const allowTransparency = this._terminal.options.allowTransparency; + this.colorManager = new ColorManager(document, allowTransparency); + + this._renderLayers = [ + new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), + new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors) + ]; + this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledCellWidth: null, + scaledCellHeight: null, + scaledCharLeft: null, + scaledCharTop: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null, + actualCellWidth: null, + actualCellHeight: null + }; + this._devicePixelRatio = window.devicePixelRatio; + this._updateDimensions(); + + this._screenDprMonitor = new ScreenDprMonitor(); + this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); + this.register(this._screenDprMonitor); + + this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + + this._canvas = document.createElement('canvas'); + this._gl = this._canvas.getContext('webgl2') as IWebGL2RenderingContext; + if (!this._gl) { + throw new Error('WebGL2 not supported'); + } + this._terminal.screenElement.appendChild(this._canvas); + + this._rectangleRenderer = new RectangleRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); + this._glyphRenderer = new GlyphRenderer(this._terminal, this._gl, this.dimensions); + + // Detect whether IntersectionObserver is detected and enable renderer pause + // and resume based on terminal visibility if so + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), { threshold: 0 }); + observer.observe(this._terminal.element); + this.register({ dispose: () => observer.disconnect() }); + } + } + + public dispose(): void { + this._renderLayers.forEach(l => l.dispose()); + this._terminal.screenElement.removeChild(this._canvas); + } + + public onIntersectionChange(entry: IntersectionObserverEntry): void { + this._isPaused = entry.intersectionRatio === 0; + if (!this._isPaused && this._needsFullRefresh) { + this._terminal.refresh(0, this._terminal.rows - 1); + } + } + + private _refreshViewport(): void { + // Force a refresh + this._model.clear(); + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._terminal.refresh(0, this._terminal.rows - 1); + } + } + + public setTheme(theme: ITheme | undefined): IColorSet { + if (theme) { + this.colorManager.setTheme(theme); + } + + // Clear layers and force a full render + this._renderLayers.forEach(l => { + l.onThemeChanged(this._terminal, this.colorManager.colors); + l.reset(this._terminal); + }); + + this._rectangleRenderer.onThemeChanged(); + this._glyphRenderer.onThemeChanged(); + + this._refreshCharAtlas(); + this._refreshViewport(); + + return this.colorManager.colors; + } + + public onWindowResize(devicePixelRatio: number): void { + // If the device pixel ratio changed, the char atlas needs to be regenerated + // and the terminal needs to refreshed + if (this._devicePixelRatio !== devicePixelRatio) { + this._devicePixelRatio = devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows, devicePixelRatio); + } + } + + public onResize(cols: number, rows: number, devicePixelRatio: number = window.devicePixelRatio): void { + // Update character and canvas dimensions + this._updateDimensions(devicePixelRatio); + + this._model.resize(this._terminal.cols, this._terminal.rows); + this._rectangleRenderer.onResize(); + + // Resize all render layers + this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); + + // Resize the canvas + this._canvas.width = this.dimensions.scaledCanvasWidth; + this._canvas.height = this.dimensions.scaledCanvasHeight; + this._canvas.style.width = `${this.dimensions.canvasWidth}px`; + this._canvas.style.height = `${this.dimensions.canvasHeight}px`; + + // Resize the screen + this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; + this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._glyphRenderer.setDimensions(this.dimensions); + this._glyphRenderer.onResize(); + + this._refreshCharAtlas(devicePixelRatio); + this._refreshViewport(); + + this.emit('resize', { + width: this.dimensions.canvasWidth, + height: this.dimensions.canvasHeight + }); + } + + public onCharSizeChanged(): void { + this.onResize(this._terminal.cols, this._terminal.rows); + } + + public onBlur(): void { + this._renderLayers.forEach(l => l.onBlur(this._terminal)); + } + + public onFocus(): void { + this._renderLayers.forEach(l => l.onFocus(this._terminal)); + } + + public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); + + this._updateSelectionModel(start, end); + + this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode); + this._glyphRenderer.updateSelection(this._model, columnSelectMode); + this.refreshRows(0, this._terminal.rows - 1); + } + + public onCursorMove(): void { + this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); + } + + public onOptionsChanged(): void { + this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); + this._updateDimensions(); + this._refreshCharAtlas(); + } + + /** + * Refreshes the char atlas, aquiring a new one if necessary. + * @param terminal The terminal. + * @param colorSet The color set to use for the char atlas. + */ + private _refreshCharAtlas(devicePixelRatio: number = window.devicePixelRatio): void { + if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { + return; + } + + const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, devicePixelRatio); + if (!('getRasterizedGlyph' in atlas)) { + throw new Error('The webgl renderer only works with the webgl char atlas'); + } + this._charAtlas = atlas as WebglCharAtlas; + this._charAtlas.warmUp(); + this._glyphRenderer.setAtlas(this._charAtlas); + } + + public clear(): void { + this._renderLayers.forEach(l => l.reset(this._terminal)); + } + + public refreshRows(start: number, end: number): void { + if (this._isPaused) { + this._needsFullRefresh = true; + return; + } + this._renderDebouncer.refresh(start, end); + } + + public registerCharacterJoiner(handler: CharacterJoinerHandler): number { + return -1; + } + + public deregisterCharacterJoiner(joinerId: number): boolean { + return false; + } + + private _renderRows(start: number, end: number): void { + // Update render layers + this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); + + // Tell renderer the frame is beginning + if (this._glyphRenderer.beginFrame()) { + this._model.clear(); + } + + // Update model to reflect what's drawn + this._updateModel(start, end); + + // Render + this._rectangleRenderer.render(); + this._glyphRenderer.render(this._model.selection.hasSelection); + + // Emit event + this._terminal.emit('refresh', { start, end }); + } + + private _updateModel(start: number, end: number): void { + const terminal = this._terminal; + + for (let y = start; y <= end; y++) { + const row = y + terminal.buffer.ydisp; + const line = terminal.buffer.lines.get(row); + this._model.lineLengths[y] = 0; + for (let x = 0; x < terminal.cols; x++) { + const charData = line.get(x); + const chars = charData[CHAR_DATA_CHAR_INDEX]; + let code = charData[CHAR_DATA_CODE_INDEX]; + const attr = charData[CHAR_DATA_ATTR_INDEX]; + const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + + // Nothing has changed, no updates needed + if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) { + continue; + } + + // Resolve bg and fg and cache in the model + const flags = attr >> 18; + let bg = attr & 0x1ff; + let fg = (attr >> 9) & 0x1ff; + + // If inverse flag is on, the foreground should become the background. + if (flags & FLAGS.INVERSE) { + const temp = bg; + bg = fg; + fg = temp; + if (fg === 256) { + fg = RENDER_INVERTED_DEFAULT_COLOR; + } + if (bg === 257) { + bg = RENDER_INVERTED_DEFAULT_COLOR; + } + } + const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + fg += drawInBrightColor ? 8 : 0; + + // Flag combined chars with a bit mask so they're easily identifiable + if (chars.length > 1) { + code = code | COMBINED_CHAR_BIT_MASK; + } + + this._model.cells[i ] = code; + this._model.cells[i + 1] = attr; + this._model.cells[i + 2] = bg; + this._model.cells[i + 3] = fg; + + this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars); + } + } + this._rectangleRenderer.updateBackgrounds(this._model); + } + + private _updateSelectionModel(start: [number, number], end: [number, number]): void { + const terminal = this._terminal; + + // Selection does not exist + if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { + this._model.clearSelection(); + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - terminal.buffer.ydisp; + const viewportEndRow = end[1] - terminal.buffer.ydisp; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + this._model.clearSelection(); + return; + } + + this._model.selection.hasSelection = true; + this._model.selection.viewportStartRow = viewportStartRow; + this._model.selection.viewportEndRow = viewportEndRow; + this._model.selection.viewportCappedStartRow = viewportCappedStartRow; + this._model.selection.viewportCappedEndRow = viewportCappedEndRow; + this._model.selection.startCol = start[0]; + this._model.selection.endCol = end[0]; + } + + /** + * Recalculates the character and canvas dimensions. + */ + private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { + // Perform a new measure if the CharMeasure dimensions are not yet available + if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + return; + } + + // Calculate the scaled character width. Width is floored as it must be + // drawn to an integer grid in order for the CharAtlas "stamps" to not be + // blurry. When text is drawn to the grid not using the CharAtlas, it is + // clipped to ensure there is no overlap with the next cell. + + // NOTE: ceil fixes sometime, floor does others :s + + this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * devicePixelRatio); + + // Calculate the scaled character height. Height is ceiled in case + // devicePixelRatio is a floating point number in order to ensure there is + // enough space to draw the character to the cell. + this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * devicePixelRatio); + + // Calculate the scaled cell height, if lineHeight is not 1 then the value + // 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._terminal.options.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._terminal.options.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._terminal.options.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._terminal.options.letterSpacing / 2); + + // Recalculate the canvas dimensions; scaled* define the actual number of + // pixel in the canvas + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + + // The the size of the canvas on the page. It's very important that this + // rounds to nearest integer and not ceils as browsers often set + // window.devicePixelRatio as something like 1.100000023841858, when it's + // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 + // pixel too large for the canvas element size. + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / devicePixelRatio); + + // this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio; + // this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio; + + // Get the _actual_ dimensions of an individual cell. This needs to be + // derived from the canvasWidth/Height calculated above which takes into + // account window.devicePixelRatio. CharMeasure.width/height by itself is + // insufficient when the page is not at 100% zoom level as CharMeasure is + // measured in CSS pixels, but the actual char size on the canvas can + // differ. + // this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; + // this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; + + // This fixes 110% and 125%, not 150% or 175% though + this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / devicePixelRatio; + } +} diff --git a/src/renderer/webgl/WebglUtils.ts b/src/renderer/webgl/WebglUtils.ts new file mode 100644 index 00000000..8f166a23 --- /dev/null +++ b/src/renderer/webgl/WebglUtils.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * A matrix that when multiplies will translate 0-1 coordinates (left to right, + * top to bottom) to clip space. + */ +export const PROJECTION_MATRIX = new Float32Array([ + 2, 0, 0, 0, + 0, -2, 0, 0, + 0, 0, 1, 0, + -1, 1, 0, 1 +]); + +export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined { + const program = gl.createProgram(); + gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexSource)); + gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)); + gl.linkProgram(program); + const success = gl.getProgramParameter(program, gl.LINK_STATUS); + if (success) { + return program; + } + + console.log(gl.getProgramInfoLog(program)); + gl.deleteProgram(program); +} + +export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined { + const shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (success) { + return shader; + } + + console.log(gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); +} + +export function expandFloat32Array(source: Float32Array, max: number): Float32Array { + const newLength = Math.min(source.length * 2, max); + const newArray = new Float32Array(newLength); + for (let i = 0; i < source.length; i++) { + newArray[i] = source[i]; + } + return newArray; +} diff --git a/src/shared/atlas/Types.ts b/src/shared/atlas/Types.ts index 25eaa716..7a69f78b 100644 --- a/src/shared/atlas/Types.ts +++ b/src/shared/atlas/Types.ts @@ -9,7 +9,7 @@ import { IColorSet } from '../Types'; export const CHAR_ATLAS_CELL_SPACING = 1; export interface ICharAtlasConfig { - type: 'none' | 'static' | 'dynamic'; + type: 'none' | 'static' | 'dynamic' | 'webgl'; devicePixelRatio: number; fontSize: number; fontFamily: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c6b6b1e5..881ea292 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -18,7 +18,7 @@ declare module 'xterm' { /** * A string representing a renderer type. */ - export type RendererType = 'dom' | 'canvas'; + export type RendererType = 'dom' | 'canvas' | 'webgl'; /** * An object containing start up options for the terminal. @@ -99,7 +99,7 @@ declare module 'xterm' { * Currently defaults to 'static'. This option may be removed in the future. If it is, passed * parameters will be ignored. */ - experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; + experimentalCharAtlas?: 'none' | 'static' | 'dynamic' | 'webgl'; /** * (EXPERIMENTAL) Defines which implementation to use for buffer lines. @@ -213,7 +213,7 @@ declare module 'xterm' { cursor?: string, /** The accent color of the cursor (used as the foreground color for a block cursor) */ cursorAccent?: string, - /** The selection color (can be transparent) */ + /** The selection background color (can be transparent) */ selection?: string, /** ANSI black (eg. `\x1b[30m`) */ black?: string, From e6d339a28954eec0290fe7a9c454b326d97fd130 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 19 Nov 2018 16:54:53 -0800 Subject: [PATCH 002/104] Fix wide char caching in webgl renderer --- src/renderer/webgl/GlyphRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 0600f758..b6e4066d 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -162,13 +162,13 @@ export class GlyphRenderer { this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars); } - private _updateCell(array: Float32Array, x: number, y: number, code: number, attr: number, bg: number, fg: number, chars?: string): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void { const terminal = this._terminal; const i = ((y * terminal.cols) + x) * INDICES_PER_CELL; // Exit early if this is a null/space character - if (code === NULL_CELL_CODE) { + 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); return; } From 42eda149c71f8192076750bdb9be8a38b45266a6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 22 Nov 2018 10:57:45 -0800 Subject: [PATCH 003/104] Set theme in ctor --- src/renderer/webgl/WebglRenderer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 25030319..61fb9d34 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -50,6 +50,9 @@ export class WebglRenderer extends EventEmitter implements IRenderer { super(); const allowTransparency = this._terminal.options.allowTransparency; this.colorManager = new ColorManager(document, allowTransparency); + if (theme) { + this.colorManager.setTheme(theme); + } this._renderLayers = [ new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), From 128b1b0a483319a1aa9c6dac1349436247503dbc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 07:14:56 -0800 Subject: [PATCH 004/104] Use polyfill for TypedArray.fill --- src/renderer/webgl/GlyphRenderer.ts | 7 ++++--- src/renderer/webgl/RectangleRenderer.ts | 7 ++++--- src/renderer/webgl/RenderModel.ts | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index b6e4066d..b431aec7 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,6 +11,7 @@ import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { fill } from '../../core/TypedArrayUtils'; interface IVertices { attributes: Float32Array; @@ -169,7 +170,7 @@ export class GlyphRenderer { // Exit early if this is a null/space character 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); + fill(array, 0, i, i + INDICES_PER_CELL - 1); return; } @@ -182,7 +183,7 @@ export class GlyphRenderer { // Fill empty if no glyph was found if (!rasterizedGlyph) { - array.fill(0, i, i + INDICES_PER_CELL - 1); + fill(array, 0, i, i + INDICES_PER_CELL - 1); return; } @@ -203,7 +204,7 @@ export class GlyphRenderer { public updateLineEnd(x: number, y: number): void { // Clears all cells to the right of the line end const i = (y * this._terminal.cols + x + 1) * INDICES_PER_CELL; - this._vertices.attributes.fill(0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); + fill(this._vertices.attributes, 0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); } public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index eca89b3a..2642ec73 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -9,6 +9,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUti import { IColor } from '../../shared/Types'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; +import { fill } from '../../core/TypedArrayUtils'; const enum VertexAttribLocations { POSITION = 0, @@ -173,7 +174,7 @@ export class RectangleRenderer { const terminal = this._terminal; if (!model.hasSelection) { - this._vertices.selection.fill(0, 0); + fill(this._vertices.selection, 0, 0); return; } @@ -190,7 +191,7 @@ export class RectangleRenderer { height * this._dimensions.scaledCellHeight, this._selectionFloat ); - this._vertices.selection.fill(0, INDICES_PER_RECTANGLE); + fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE); } else { // Draw first row const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0; @@ -231,7 +232,7 @@ export class RectangleRenderer { this._selectionFloat ); } else { - this._vertices.selection.fill(0, INDICES_PER_RECTANGLE * 2); + fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2); } } } diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index a14616c2..a3b2b2c9 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,6 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; +import { fill } from '../../core/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; @@ -42,8 +43,8 @@ export class RenderModel implements IRenderModel { } public clear(): void { - this.cells.fill(0, 0); - this.lineLengths.fill(0, 0); + fill(this.cells, 0, 0); + fill(this.lineLengths, 0, 0); this.clearSelection(); } From 53dc2af2b772552fbc7bc069c6698265e1d9c852 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 08:00:27 -0800 Subject: [PATCH 005/104] Use polyfill for TypedArray.slice --- src/core/TypedArrayUtils.test.ts | 122 ++++++++++++++++++++++++++-- src/core/TypedArrayUtils.ts | 26 ++++++ src/renderer/webgl/GlyphRenderer.ts | 4 +- 3 files changed, 142 insertions(+), 10 deletions(-) diff --git a/src/core/TypedArrayUtils.test.ts b/src/core/TypedArrayUtils.test.ts index ef86d314..d720d957 100644 --- a/src/core/TypedArrayUtils.test.ts +++ b/src/core/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fill } from './TypedArrayUtils'; +import { fill, sliceFallback } from './TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -29,7 +29,7 @@ function loopFill(array: TypedArray, value: number, start: number = 0, end?: num return array; } -describe('polyfill conformance tests', function(): void { +describe('polyfill conformance tests', () => { function deepEquals(a: TypedArray, b: TypedArray): void { assert.equal(a.length, b.length); @@ -38,8 +38,8 @@ describe('polyfill conformance tests', function(): void { } } - describe('TypedArray.fill', function(): void { - it('should work with all typed array types', function(): void { + describe('TypedArray.fill', () => { + it('should work with all typed array types', () => { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); deepEquals(fill(u81, 2), u82.fill(2)); @@ -79,7 +79,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(fill(u8Clamped1, 2), u8Clamped2.fill(2)); deepEquals(fill(u8Clamped1, 257), u8Clamped2.fill(257)); }); - it('should work with all typed array types - explicit looping', function(): void { + it('should work with all typed array types - explicit looping', () => { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); deepEquals(loopFill(u81, 2), u82.fill(2)); @@ -119,7 +119,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(loopFill(u8Clamped1, 2), u8Clamped2.fill(2)); deepEquals(loopFill(u8Clamped1, 257), u8Clamped2.fill(257)); }); - it('start offset', function(): void { + it('start offset', () => { for (let i = -2; i < 10; ++i) { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); @@ -130,7 +130,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(loopFill(u82, -1, i), u83.fill(-1, i)); } }); - it('end offset', function(): void { + it('end offset', () => { for (let i = -2; i < 10; ++i) { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); @@ -141,7 +141,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(loopFill(u82, -1, 0, i), u83.fill(-1, 0, i)); } }); - it('start/end offset', function(): void { + it('start/end offset', () => { for (let i = -2; i < 10; ++i) { for (let j = -2; j < 10; ++j) { const u81 = new Uint8Array(5); @@ -155,4 +155,110 @@ describe('polyfill conformance tests', function(): void { } }); }); + + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + console.log(a); + console.log(a.slice(65535)); + console.log(sliceFallback(a, 65535)); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); }); diff --git a/src/core/TypedArrayUtils.ts b/src/core/TypedArrayUtils.ts index 56e9d7b0..262ae25b 100644 --- a/src/core/TypedArrayUtils.ts +++ b/src/core/TypedArrayUtils.ts @@ -38,3 +38,29 @@ export function fill(array: TypedArray, value: number, start: number = 0, end?: } return array; } + +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array).__proto__.constructor(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index b431aec7..bb6ebc88 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,7 +11,7 @@ import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill } from '../../core/TypedArrayUtils'; +import { fill, slice } from '../../core/TypedArrayUtils'; interface IVertices { attributes: Float32Array; @@ -210,7 +210,7 @@ export class GlyphRenderer { public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { const terminal = this._terminal; - this._vertices.selectionAttributes = this._vertices.attributes.slice(0); + this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. From 186b2be4b91b4e6d7c222829d5fc40b2f464b26b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Nov 2018 05:48:05 -0800 Subject: [PATCH 006/104] Fix imports --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 2 +- src/renderer/webgl/RenderModel.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index bb6ebc88..63170f39 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,7 +11,7 @@ import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from '../../core/TypedArrayUtils'; +import { fill, slice } from '../../common/TypedArrayUtils'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 2642ec73..51b8bdce 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -9,7 +9,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUti import { IColor } from '../../shared/Types'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; -import { fill } from '../../core/TypedArrayUtils'; +import { fill } from '../../common/TypedArrayUtils'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index a3b2b2c9..c55a6f73 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,7 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; -import { fill } from '../../core/TypedArrayUtils'; +import { fill } from '../../common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; From 60e3ddbfd03ea61e2837e51f6fbedd8c04e58507 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Nov 2018 05:52:23 -0800 Subject: [PATCH 007/104] Update color codes after merging the move to constants --- src/renderer/webgl/RectangleRenderer.ts | 14 +++++++------- src/renderer/webgl/RenderModel.ts | 5 ----- src/renderer/webgl/WebglCharAtlas.ts | 15 +++++++-------- src/renderer/webgl/WebglRenderer.ts | 12 ++++++------ 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 51b8bdce..cd368621 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -8,8 +8,9 @@ import { IColorManager, IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IColor } from '../../shared/Types'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; import { fill } from '../../common/TypedArrayUtils'; +import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; +import { is256Color } from '../atlas/CharAtlasUtils'; const enum VertexAttribLocations { POSITION = 0, @@ -243,16 +244,15 @@ export class RectangleRenderer { let rectangleCount = 1; - const DEFAULT_BACKGROUND_COLOR = 256; for (let y = 0; y < terminal.rows; y++) { let currentStartX = -1; - let currentBg = DEFAULT_BACKGROUND_COLOR; + let currentBg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * 4; const bg = model.cells[modelIndex + 2]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color - if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y); } @@ -261,7 +261,7 @@ export class RectangleRenderer { } } // Finish rectangle if it's still going - if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y); } @@ -271,9 +271,9 @@ export class RectangleRenderer { private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { let color: IColor | null = null; - if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + if (bg === INVERTED_DEFAULT_COLOR) { color = this._colorManager.colors.foreground; - } else if (bg < 256) { + } else if (is256Color(bg)) { color = this._colorManager.colors.ansi[bg]; } if (vertices.attributes.length < offset + 4) { diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index c55a6f73..54fc91ca 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -8,11 +8,6 @@ import { fill } from '../../common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; -// HACK: Cannot use INVERTED_DEFAULT_COLOR (-1) here because _model.cells is a -// Uint32Array. This should be changed when true color is introduced to whatever -// the mechanism is for the buffer. -export const RENDER_INVERTED_DEFAULT_COLOR = 258; - export const COMBINED_CHAR_BIT_MASK = 0x80000000; export class RenderModel implements IRenderModel { diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts index 86a60d07..03e9b95b 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier } from '../atlas/Types'; +import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; import { ICharAtlasConfig } from '../../shared/atlas/Types'; import { IColor } from '../../shared/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; @@ -12,7 +12,7 @@ import { clearColor } from '../../shared/atlas/CharAtlasGenerator'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; import { DEFAULT_ATTR } from '../../Buffer'; import { FLAGS } from '../Types'; -import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; +import { is256Color } from '../atlas/CharAtlasUtils'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -97,7 +97,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { protected _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, 256, 257, true); + const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR, true); this._cacheMap[i] = { [DEFAULT_ATTR]: rasterizedGlyph }; @@ -175,19 +175,18 @@ export default class WebglCharAtlas extends BaseCharAtlas { // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. return TRANSPARENT_COLOR; - } else if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + } else if (bg === INVERTED_DEFAULT_COLOR) { return this._config.colors.foreground; - } else if (bg < 256) { + } else if (is256Color(bg)) { return this._getColorFromAnsiIndex(bg); } return this._config.colors.background; } private _getForegroundColor(fg: number): IColor { - if (fg === RENDER_INVERTED_DEFAULT_COLOR) { + if (fg === INVERTED_DEFAULT_COLOR) { return this._config.colors.background; - } else if (fg < 256) { - // 256 color support + } else if (is256Color(fg)) { return this._getColorFromAnsiIndex(fg); } return this._config.colors.foreground; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 61fb9d34..dfefad48 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -18,8 +18,8 @@ import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; import { RectangleRenderer } from './RectangleRenderer'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { RenderModel, RENDER_INVERTED_DEFAULT_COLOR, COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; +import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; export const INDICIES_PER_CELL = 4; @@ -302,11 +302,11 @@ export class WebglRenderer extends EventEmitter implements IRenderer { const temp = bg; bg = fg; fg = temp; - if (fg === 256) { - fg = RENDER_INVERTED_DEFAULT_COLOR; + if (fg === DEFAULT_COLOR) { + fg = INVERTED_DEFAULT_COLOR; } - if (bg === 257) { - bg = RENDER_INVERTED_DEFAULT_COLOR; + if (bg === DEFAULT_COLOR) { + bg = INVERTED_DEFAULT_COLOR; } } const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; From b8b111f4a6b5350fd2509affd576f7a711ba2ebf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 25 Nov 2018 09:23:08 -0800 Subject: [PATCH 008/104] Only pass a subset of the attributes buffer to GPU --- src/renderer/webgl/GlyphRenderer.ts | 73 ++++++++++++++++------------- src/renderer/webgl/WebglRenderer.ts | 8 +++- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 63170f39..3cd5679e 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -15,8 +15,14 @@ import { fill, slice } from '../../common/TypedArrayUtils'; interface IVertices { attributes: Float32Array; + /** + * These buffers are the ones used to bind to WebGL, the reason there are + * multiple is to allow double buffering to work as you cannot modify the + * buffer while it's being used by the GPU. Having multiple lets us start + * working on the next frame. + */ + attributesBuffers: Float32Array[]; selectionAttributes: Float32Array; - cellPosition: Float32Array; count: number; } @@ -61,8 +67,9 @@ void main() { outColor = texture(u_texture, v_texcoord); }`; -const INDICES_PER_CELL = 8; +const INDICES_PER_CELL = 10; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; +const CELL_POSITION_INDICES = 2; export class GlyphRenderer { private _atlas: WebglCharAtlas; @@ -74,14 +81,16 @@ export class GlyphRenderer { private _textureLocation: WebGLUniformLocation; private _atlasTexture: WebGLTexture; private _attributesBuffer: WebGLBuffer; - private _cellPositionBuffer: WebGLBuffer; + private _activeBuffer: number = 0; - private _lineLengths: Int16Array = new Int16Array(0); private _vertices: IVertices = { count: 0, attributes: new Float32Array(0), - selectionAttributes: new Float32Array(0), - cellPosition: new Float32Array(0) + attributesBuffers: [ + new Float32Array(0), + new Float32Array(0) + ], + selectionAttributes: new Float32Array(0) }; constructor( @@ -117,13 +126,6 @@ export class GlyphRenderer { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); - // Setup a_cellpos, this is separate as it rarely changed - this._cellPositionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); - gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION); - gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, 0, 0); - gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); - // Setup attributes this._attributesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); @@ -139,6 +141,9 @@ export class GlyphRenderer { gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE); gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 6 * 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, 8 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); // Setup empty texture atlas this._atlasTexture = gl.createTexture(); @@ -166,11 +171,11 @@ export class GlyphRenderer { private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void { const terminal = this._terminal; - const i = ((y * terminal.cols) + x) * INDICES_PER_CELL; + const i = (y * terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null/space character if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { - fill(array, 0, i, i + INDICES_PER_CELL - 1); + fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -183,7 +188,7 @@ export class GlyphRenderer { // Fill empty if no glyph was found if (!rasterizedGlyph) { - fill(array, 0, i, i + INDICES_PER_CELL - 1); + fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -199,12 +204,7 @@ export class GlyphRenderer { // a_texsize array[i + 6] = rasterizedGlyph.sizeClipSpace.x; array[i + 7] = rasterizedGlyph.sizeClipSpace.y; - } - - public updateLineEnd(x: number, y: number): void { - // Clears all cells to the right of the line end - const i = (y * this._terminal.cols + x + 1) * INDICES_PER_CELL; - fill(this._vertices.attributes, 0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); + // a_cellpos only changes on resize } public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { @@ -279,15 +279,16 @@ export class GlyphRenderer { if (this._vertices.count !== newCount) { this._vertices.count = newCount; this._vertices.attributes = new Float32Array(newCount); - this._lineLengths = new Int16Array(terminal.rows); - - this._vertices.cellPosition = new Float32Array(terminal.cols * terminal.rows * 2); + for (let i = 0; i < this._vertices.attributesBuffers.length; i++) { + this._vertices.attributesBuffers[i] = new Float32Array(newCount); + } let i = 0; for (let y = 0; y < terminal.rows; y++) { for (let x = 0; x < terminal.cols; x++) { - this._vertices.cellPosition[i++] = x / terminal.cols; - this._vertices.cellPosition[i++] = y / terminal.rows; + i += 8; + this._vertices.attributes[i++] = x / terminal.cols; + this._vertices.attributes[i++] = y / terminal.rows; } } } @@ -296,7 +297,7 @@ export class GlyphRenderer { public onThemeChanged(): void { } - public render(isSelectionVisible: boolean): void { + public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { if (!this._atlas) { return; } @@ -306,10 +307,18 @@ export class GlyphRenderer { gl.useProgram(this._program); gl.bindVertexArray(this._vertexArrayObject); - gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this._vertices.cellPosition, gl.STATIC_DRAW); + this._activeBuffer = (this._activeBuffer + 1) % 2; + this._vertices.attributesBuffers[this._activeBuffer]; + let bufferLength = 0; + for (let y = 0; y < renderModel.lineLengths.length; y++) { + const si = y * this._terminal.cols * INDICES_PER_CELL; + const sub = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); + this._vertices.attributesBuffers[this._activeBuffer].set(sub, bufferLength); + bufferLength += sub.length; + } gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); - gl.bufferData(gl.ARRAY_BUFFER, isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes, gl.DYNAMIC_DRAW); + const buffer = this._vertices.attributesBuffers[this._activeBuffer].subarray(0, bufferLength); + gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STREAM_DRAW); // Bind the texture atlas if it's changed if (this._atlas.hasCanvasChanged) { @@ -326,7 +335,7 @@ export class GlyphRenderer { gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); // Draw the viewport - gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count / INDICES_PER_CELL); + gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, buffer.length / INDICES_PER_CELL); } public setAtlas(atlas: WebglCharAtlas): void { diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index dfefad48..f8d73a32 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -16,7 +16,7 @@ import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import WebglCharAtlas from './WebglCharAtlas'; import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; import { RectangleRenderer } from './RectangleRenderer'; -import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, NULL_CELL_CODE } from '../../Buffer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; @@ -267,7 +267,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { // Render this._rectangleRenderer.render(); - this._glyphRenderer.render(this._model.selection.hasSelection); + this._glyphRenderer.render(this._model, this._model.selection.hasSelection); // Emit event this._terminal.emit('refresh', { start, end }); @@ -287,6 +287,10 @@ export class WebglRenderer extends EventEmitter implements IRenderer { const attr = charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + if (code !== NULL_CELL_CODE) { + this._model.lineLengths[y] = x + 1; + } + // Nothing has changed, no updates needed if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) { continue; From 6bc9b9b14d86c8d7bf91e2530d39c847e74b3ff3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 29 Nov 2018 10:26:04 -0800 Subject: [PATCH 009/104] Improve constructor use in sliceFallback --- src/common/TypedArrayUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index 7c647861..e712e0a7 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -59,7 +59,7 @@ export function sliceFallback(array: T, start: number = 0, } start = Math.min(start, end); - const result: T = new (array).__proto__.constructor(end - start); + const result: T = new (array.constructor as any)(end - start); for (let i = 0; i < end - start; ++i) { result[i] = array[i + start]; } From 3d8b304af4dcd5c2447e10ee0a0c2ee94d22973f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 9 Dec 2018 08:31:49 -0800 Subject: [PATCH 010/104] Reduce precision of floats in fragment shader ~0.43ms/frame -> ~0.37ms/frame --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 3cd5679e..51adc999 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -55,7 +55,7 @@ void main() { }`; const fragmentShaderSource = `#version 300 es -precision mediump float; +precision lowp float; in vec2 v_texcoord; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 37732703..addc13c5 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -36,7 +36,7 @@ void main() { }`; const fragmentShaderSource = `#version 300 es -precision mediump float; +precision lowp float; in vec3 v_color; From bb9ccf40b62a6730dcf9b5faba77e170f0d44304 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 9 Dec 2018 09:28:04 -0800 Subject: [PATCH 011/104] Reduce diff with master --- src/renderer/atlas/CharAtlasCache.ts | 5 ++--- src/renderer/atlas/CharAtlasUtils.ts | 4 ++-- src/renderer/webgl/WebglRenderer.ts | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index 5a9c1a6e..db7db118 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -40,10 +40,9 @@ export function acquireCharAtlas( terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, - scaledCharHeight: number, - devicePixelRatio?: number + scaledCharHeight: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); // TODO: Currently if a terminal changes configs it will not free the entry reference (until it's disposed) diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 52c8a9bd..5b1add39 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -7,7 +7,7 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; import { DEFAULT_COLOR, ICharAtlasConfig } from './Types'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet, devicePixelRatio: number = window.devicePixelRatio): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter const clonedColors = { foreground: colors.foreground, @@ -21,7 +21,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number }; return { type: terminal.options.experimentalCharAtlas, - devicePixelRatio, + devicePixelRatio: window.devicePixelRatio, scaledCharWidth, scaledCharHeight, fontFamily: terminal.options.fontFamily, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index f8d73a32..92e9828a 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -173,7 +173,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); - this._refreshCharAtlas(devicePixelRatio); + this._refreshCharAtlas(); this._refreshViewport(); this.emit('resize', { @@ -219,12 +219,12 @@ export class WebglRenderer extends EventEmitter implements IRenderer { * @param terminal The terminal. * @param colorSet The color set to use for the char atlas. */ - private _refreshCharAtlas(devicePixelRatio: number = window.devicePixelRatio): void { + private _refreshCharAtlas(): void { if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { return; } - const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, devicePixelRatio); + const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight); if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } From 5f9144dfd71b2aaedf43ed6a1ffd38167863b9a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 27 Dec 2018 16:56:20 -0800 Subject: [PATCH 012/104] Fix null/whitespace early exit --- src/renderer/webgl/GlyphRenderer.ts | 6 +++--- src/renderer/webgl/WebglCharAtlas.ts | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 51adc999..3f8f6edf 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderDimensions } from '../Types'; import { ITerminal, IBufferLine } from '../../Types'; -import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CODE } from '../../Buffer'; import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; @@ -174,7 +174,7 @@ export class GlyphRenderer { const i = (y * terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null/space character - if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { + if (code === NULL_CELL_CODE || code === WHITESPACE_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -232,7 +232,7 @@ export class GlyphRenderer { // Draw middle rows const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0); - for (let y = (model.selection.viewportCappedStartRow + 1); y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { + for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg); } diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts index d181acbf..6eae9579 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -133,11 +133,6 @@ export default class WebglCharAtlas extends BaseCharAtlas { * Gets the glyphs texture coords, drawing the texture if it's not already */ public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { - // Space is always an empty cell, special case this as it's so common - if (code === 32) { - return; - } - let rasterizedGlyphSet = this._cacheMap[code]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; From 9cf3d14d8a5705eab65c8c3512c0191cc3205205 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 27 Dec 2018 17:01:13 -0800 Subject: [PATCH 013/104] Clean up loop that sets cell coordinates --- src/renderer/webgl/GlyphRenderer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 3f8f6edf..7b280f19 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -286,9 +286,9 @@ export class GlyphRenderer { let i = 0; for (let y = 0; y < terminal.rows; y++) { for (let x = 0; x < terminal.cols; x++) { - i += 8; - this._vertices.attributes[i++] = x / terminal.cols; - this._vertices.attributes[i++] = y / terminal.rows; + this._vertices.attributes[i + 8] = x / terminal.cols; + this._vertices.attributes[i + 9] = y / terminal.rows; + i += INDICES_PER_CELL; } } } From 01b37b2fef2acfc9a1ef6f6e700d1823c4fbf993 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 27 Dec 2018 17:10:18 -0800 Subject: [PATCH 014/104] Document how GlyphRenderer.render works --- src/renderer/webgl/GlyphRenderer.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 7b280f19..41cab75e 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -307,18 +307,28 @@ export class GlyphRenderer { gl.useProgram(this._program); gl.bindVertexArray(this._vertexArrayObject); + // Alternate buffers each frame as the active buffer gets locked while it's in use by the GPU this._activeBuffer = (this._activeBuffer + 1) % 2; - this._vertices.attributesBuffers[this._activeBuffer]; + const activeBuffer = this._vertices.attributesBuffers[this._activeBuffer]; + + // Copy data for each cell of each line up to its line length (the last non-whitespace cell) + // from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU. + // The reasons for this are as follows: + // - So the active buffer can be alternated so we don't get blocked on rendering finishing + // - To copy either the normal attributes buffer or the selection attributes buffer when there + // is a selection + // - 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 = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); - this._vertices.attributesBuffers[this._activeBuffer].set(sub, bufferLength); + activeBuffer.set(sub, bufferLength); bufferLength += sub.length; } + + // Bind the attributes buffer gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); - const buffer = this._vertices.attributesBuffers[this._activeBuffer].subarray(0, bufferLength); - gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STREAM_DRAW); + gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW); // Bind the texture atlas if it's changed if (this._atlas.hasCanvasChanged) { From b0e51b9ec9bdbeaeed3ab27e372c6482bc173fe9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 15:09:38 -0800 Subject: [PATCH 015/104] Fix build error --- src/renderer/webgl/GlyphRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 41cab75e..bd231eb7 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -345,7 +345,7 @@ export class GlyphRenderer { gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); // Draw the viewport - gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, buffer.length / INDICES_PER_CELL); + gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL); } public setAtlas(atlas: WebglCharAtlas): void { From 98706aa7d2100e40829583ed61de402cdb41242f Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Tue, 8 Jan 2019 14:52:55 -0500 Subject: [PATCH 016/104] Use bg luminance to determine background color --- src/renderer/ColorManager.ts | 18 +++++++++++++++++- src/renderer/Types.ts | 1 + src/renderer/webgl/GlyphRenderer.ts | 8 +++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 8a463670..05727530 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -103,6 +103,14 @@ export class ColorManager implements IColorManager { }; } + // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast + public getLuminance(color: IColor) : number { + const r = color.rgba >> 24 & 0xff; + const g = color.rgba >> 16 & 0xff; + const b = color.rgba >> 8 & 0xff; + return (0.299 * r + 0.587 * g + 0.114 * b) / 255; + } + /** * Sets the terminal's theme. * @param theme The theme to use. If a partial theme is provided then default @@ -113,7 +121,15 @@ export class ColorManager implements IColorManager { this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); - this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + + // HACK: while webgl renderer adds support for selection colors + // this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + if (this.getLuminance(this.colors.background) > 0.5) { + this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); + } else { + this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); + } + this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index f2271f95..c02211f1 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -45,6 +45,7 @@ export interface IRenderer extends IEventEmitter, IDisposable { export interface IColorManager { colors: IColorSet; + getLuminance(color: IColor): number; } export interface IRenderDimensions { diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index bd231eb7..a149dd86 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,7 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import { IRenderDimensions } from '../Types'; +import { IColorManager, IRenderDimensions } from '../Types'; import { ITerminal, IBufferLine } from '../../Types'; import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CODE } from '../../Buffer'; import WebglCharAtlas from './WebglCharAtlas'; @@ -95,6 +95,7 @@ export class GlyphRenderer { constructor( private _terminal: ITerminal, + private _colorManager: IColorManager, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { @@ -214,8 +215,9 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. - const fg = 0; - const bg = 7; + const lumi = this._colorManager.getLuminance(this._colorManager.colors.background) + const fg = lumi > 0.5 ? 7 : 0; + const bg = lumi > 0.5 ? 0 : 7; if (columnSelectMode) { const startCol = model.selection.startCol; From 1d7a39bcfdde1bb2b6bcecf4a718a9e650baf7e7 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Tue, 15 Jan 2019 16:36:38 -0500 Subject: [PATCH 017/104] Disable antialiasing and depth buffer for WebGL2 contexts --- src/renderer/webgl/WebglRenderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 92e9828a..133f18f9 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -82,7 +82,8 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); this._canvas = document.createElement('canvas'); - this._gl = this._canvas.getContext('webgl2') as IWebGL2RenderingContext; + const contextAttributes = { antialias: false, depth: false }; + this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext; if (!this._gl) { throw new Error('WebGL2 not supported'); } From 4181321377e56ffeb200f6bc5ad12338fb8bdaf5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 16 Jan 2019 11:15:38 -0800 Subject: [PATCH 018/104] Add super.dispose to WebglRenderer.dispose --- src/renderer/webgl/WebglRenderer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 133f18f9..1df95f09 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -104,6 +104,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { public dispose(): void { this._renderLayers.forEach(l => l.dispose()); this._terminal.screenElement.removeChild(this._canvas); + super.dispose(); } public onIntersectionChange(entry: IntersectionObserverEntry): void { From e387b20b56d59c0d9f101afbb993621ab118fff2 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Fri, 18 Jan 2019 15:25:58 -0500 Subject: [PATCH 019/104] Adding missing parameter to GlyphRenderer --- src/renderer/webgl/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 92e9828a..715d79eb 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -89,7 +89,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._terminal.screenElement.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); - this._glyphRenderer = new GlyphRenderer(this._terminal, this._gl, this.dimensions); + this._glyphRenderer = new GlyphRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so From 3077ae531145ce71ae8e0911a1a3a5557c3a9a56 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Mon, 21 Jan 2019 18:56:54 -0500 Subject: [PATCH 020/104] Only apply the selection hack for the WebGL renderer --- src/renderer/ColorManager.ts | 19 ++++++++++--------- src/renderer/webgl/WebglRenderer.ts | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 05727530..329a6439 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -121,15 +121,7 @@ export class ColorManager implements IColorManager { this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); - - // HACK: while webgl renderer adds support for selection colors - // this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); - if (this.getLuminance(this.colors.background) > 0.5) { - this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); - } else { - this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); - } - + this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); @@ -148,6 +140,15 @@ export class ColorManager implements IColorManager { this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); } + public applyWebglSelectionColorHack() { + // HACK: while webgl renderer adds support for selection colors + if (this.getLuminance(this.colors.background) > 0.5) { + this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); + } else { + this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); + } + } + private _parseColor( css: string, fallback: IColor, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index c627fa6f..ec9e140d 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -52,6 +52,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this.colorManager = new ColorManager(document, allowTransparency); if (theme) { this.colorManager.setTheme(theme); + this.colorManager.applyWebglSelectionColorHack(); } this._renderLayers = [ From 1927bc899a54ed33c20ed65bb8ac320f8c5cd33d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 25 Jan 2019 20:51:19 -0800 Subject: [PATCH 021/104] Fix lint --- src/renderer/ColorManager.ts | 2 +- src/renderer/webgl/GlyphRenderer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 05727530..b1ad94c0 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -104,7 +104,7 @@ export class ColorManager implements IColorManager { } // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast - public getLuminance(color: IColor) : number { + public getLuminance(color: IColor): number { const r = color.rgba >> 24 & 0xff; const g = color.rgba >> 16 & 0xff; const b = color.rgba >> 8 & 0xff; diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index a149dd86..9ef24a71 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -215,7 +215,7 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. - const lumi = this._colorManager.getLuminance(this._colorManager.colors.background) + const lumi = this._colorManager.getLuminance(this._colorManager.colors.background); const fg = lumi > 0.5 ? 7 : 0; const bg = lumi > 0.5 ? 0 : 7; From 1ce75cd4e9f92a3361438a93718d99c58747102a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 8 Mar 2019 12:16:27 -0800 Subject: [PATCH 022/104] Encapsulate hacks inside WebglRenderer --- src/renderer/ColorManager.ts | 9 --------- src/renderer/webgl/WebglRenderer.ts | 12 +++++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index ae23fd7a..3459e48f 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -140,15 +140,6 @@ export class ColorManager implements IColorManager { this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); } - public applyWebglSelectionColorHack() { - // HACK: while webgl renderer adds support for selection colors - if (this.getLuminance(this.colors.background) > 0.5) { - this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); - } else { - this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); - } - } - private _parseColor( css: string, fallback: IColor, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index ec9e140d..f4eeba37 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -52,7 +52,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this.colorManager = new ColorManager(document, allowTransparency); if (theme) { this.colorManager.setTheme(theme); - this.colorManager.applyWebglSelectionColorHack(); + this._applyBgLuminanceBasedSelection(); } this._renderLayers = [ @@ -108,6 +108,15 @@ export class WebglRenderer extends EventEmitter implements IRenderer { super.dispose(); } + private _applyBgLuminanceBasedSelection(): void { + // HACK: While webgl renderer adds support for selection colors + if (this.colorManager.getLuminance(this.colorManager.colors.background) > 0.5) { + this.colorManager.colors.selection = { css: '#000', rgba: 255 }; + } else { + this.colorManager.colors.selection = { css: '#fff', rgba: 4294967295 }; + } + } + public onIntersectionChange(entry: IntersectionObserverEntry): void { this._isPaused = entry.intersectionRatio === 0; if (!this._isPaused && this._needsFullRefresh) { @@ -128,6 +137,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { public setTheme(theme: ITheme | undefined): IColorSet { if (theme) { this.colorManager.setTheme(theme); + this._applyBgLuminanceBasedSelection(); } // Clear layers and force a full render From 5ebb4e82d1cdd5f90d605260aeff5ec3dd7a52d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 8 Mar 2019 12:18:27 -0800 Subject: [PATCH 023/104] Clarify comment --- src/renderer/webgl/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index f4eeba37..b727a0a6 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -109,7 +109,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { } private _applyBgLuminanceBasedSelection(): void { - // HACK: While webgl renderer adds support for selection colors + // HACK: This is needed until webgl renderer adds support for selection colors if (this.colorManager.getLuminance(this.colorManager.colors.background) > 0.5) { this.colorManager.colors.selection = { css: '#000', rgba: 255 }; } else { From 72719a6d69f26b927c4208245d0e02270f1276ac Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 14:22:47 -0700 Subject: [PATCH 024/104] Fix WebGL rendering after UTF-32 by fixing get chardata attr --- src/BufferLine.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 2bd5a113..248ae663 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -5,6 +5,7 @@ import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer'; import { stringFromCodePoint } from './core/input/TextDecoder'; +import { FLAGS } from './renderer/Types'; /** @@ -321,8 +322,26 @@ export class BufferLine implements IBufferLine { public get(index: number): CharData { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; + + // TODO: Need to move WebGL over to the new system and remove this block + const cell = new CellData(); + this.loadCell(index, cell); + const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); + const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); + const oldAttr = + (cell.isBold() ? FLAGS.BOLD : 0) | + (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | + (cell.isBlink() ? FLAGS.BLINK : 0) | + (cell.isInverse() ? FLAGS.INVERSE : 0) | + (cell.isDim() ? FLAGS.DIM : 0) | + (cell.isItalic() ? FLAGS.ITALIC : 0); + const attrCompat = + oldBg | + (oldFg << 9) | + (oldAttr << 18); + return [ - this._data[index * CELL_SIZE + Cell.FG], + attrCompat, (content & Content.IS_COMBINED_MASK) ? this._combined[index] : (cp) ? stringFromCodePoint(cp) : '', From 30567ec3cf07fd9343a30209c99584072eadd162 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 3 May 2019 21:23:50 -0700 Subject: [PATCH 025/104] Fix event emitter in webgl renderer --- src/renderer/webgl/WebglRenderer.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index b727a0a6..a144b947 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { EventEmitter } from '../../common/EventEmitter'; import { IRenderer, IRenderDimensions, IColorSet, IRenderLayer, FLAGS } from '../Types'; import { ITheme } from 'xterm'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; @@ -20,10 +19,12 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, NULL_ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; +import { Disposable } from '../../common/Lifecycle'; export const INDICIES_PER_CELL = 4; -export class WebglRenderer extends EventEmitter implements IRenderer { +export class WebglRenderer extends Disposable implements IRenderer { private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; private _charAtlas: WebglCharAtlas; @@ -43,11 +44,17 @@ export class WebglRenderer extends EventEmitter implements IRenderer { public dimensions: IRenderDimensions; public colorManager: ColorManager; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); + public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + constructor( private _terminal: ITerminal, theme: ITheme ) { super(); + const allowTransparency = this._terminal.options.allowTransparency; this.colorManager = new ColorManager(document, allowTransparency); if (theme) { @@ -80,7 +87,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); this.register(this._screenDprMonitor); - this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._canvas = document.createElement('canvas'); const contextAttributes = { antialias: false, depth: false }; @@ -189,7 +196,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._refreshCharAtlas(); this._refreshViewport(); - this.emit('resize', { + this._onCanvasResize.fire({ width: this.dimensions.canvasWidth, height: this.dimensions.canvasHeight }); @@ -255,7 +262,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._needsFullRefresh = true; return; } - this._renderDebouncer.refresh(start, end); + this._renderDebouncer.refresh(start, end, this._terminal.rows); } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { @@ -283,7 +290,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._glyphRenderer.render(this._model, this._model.selection.hasSelection); // Emit event - this._terminal.emit('refresh', { start, end }); + this._onRender.fire({ start, end }); } private _updateModel(start: number, end: number): void { From cf867602d184cf29aa184d6e6a581524c66c041c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 18:24:54 -0700 Subject: [PATCH 026/104] Use IColorSet instead of IColorManager --- src/renderer/webgl/ColorUtils.ts | 14 ++++++++ src/renderer/webgl/GlyphRenderer.ts | 10 +++--- src/renderer/webgl/RectangleRenderer.ts | 15 ++++---- src/renderer/webgl/WebglCharAtlas.ts | 5 +-- src/renderer/webgl/WebglRenderer.ts | 47 ++++++++++--------------- src/ui/ColorManager.ts | 8 ----- src/ui/Types.ts | 1 - 7 files changed, 49 insertions(+), 51 deletions(-) create mode 100644 src/renderer/webgl/ColorUtils.ts diff --git a/src/renderer/webgl/ColorUtils.ts b/src/renderer/webgl/ColorUtils.ts new file mode 100644 index 00000000..9efdeb9c --- /dev/null +++ b/src/renderer/webgl/ColorUtils.ts @@ -0,0 +1,14 @@ +/** + * @license MIT + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + */ + +import { IColor } from '../../ui/Types'; + +export function getLuminance(color: IColor): number { + // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast + const r = color.rgba >> 24 & 0xff; + const g = color.rgba >> 16 & 0xff; + const b = color.rgba >> 8 & 0xff; + return (0.299 * r + 0.587 * g + 0.114 * b) / 255; +} diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 6a6a873d..9bd4573d 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,7 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import { IColorManager, IRenderDimensions } from '../Types'; +import { IRenderDimensions } from '../Types'; import { ITerminal } from '../../Types'; import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; @@ -13,6 +13,8 @@ import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from '../../common/TypedArrayUtils'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../core/buffer/BufferLine'; import { IBufferLine } from '../../core/Types'; +import { IColorSet } from '../../ui/Types'; +import { getLuminance } from './ColorUtils'; interface IVertices { attributes: Float32Array; @@ -96,7 +98,7 @@ export class GlyphRenderer { constructor( private _terminal: ITerminal, - private _colorManager: IColorManager, + private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { @@ -216,7 +218,7 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. - const lumi = this._colorManager.getLuminance(this._colorManager.colors.background); + const lumi = getLuminance(this._colors.background); const fg = lumi > 0.5 ? 7 : 0; const bg = lumi > 0.5 ? 0 : 7; @@ -297,7 +299,7 @@ export class GlyphRenderer { } } - public onThemeChanged(): void { + public onThemeChange(): void { } public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 7399cc83..e047d7ac 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -4,13 +4,14 @@ */ import { ITerminal } from '../../Types'; -import { IColorManager, IRenderDimensions, IColor } from '../Types'; +import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from '../../common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { is256Color } from '../atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; +import { IColorSet, IColor } from '../../ui/Types'; const enum VertexAttribLocations { POSITION = 0, @@ -76,7 +77,7 @@ export class RectangleRenderer { constructor( private _terminal: ITerminal, - private _colorManager: IColorManager, + private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { @@ -148,14 +149,14 @@ export class RectangleRenderer { this._updateViewportRectangle(); } - public onThemeChanged(): void { + public onThemeChange(): void { this._updateCachedColors(); this._updateViewportRectangle(); } private _updateCachedColors(): void { - this._bgFloat = this._colorToFloat32Array(this._colorManager.colors.background); - this._selectionFloat = this._colorToFloat32Array(this._colorManager.colors.selection); + this._bgFloat = this._colorToFloat32Array(this._colors.background); + this._selectionFloat = this._colorToFloat32Array(this._colors.selection); } private _updateViewportRectangle(): void { @@ -272,9 +273,9 @@ export class RectangleRenderer { private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { let color: IColor | null = null; if (bg === INVERTED_DEFAULT_COLOR) { - color = this._colorManager.colors.foreground; + color = this._colors.foreground; } else if (is256Color(bg)) { - color = this._colorManager.colors.ansi[bg]; + color = this._colors.ansi[bg]; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts index 6963e714..a6bd4013 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -5,13 +5,14 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from '../atlas/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; -import { DEFAULT_ANSI_COLORS } from '../ColorManager'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; -import { FLAGS, IColor } from '../Types'; +import { FLAGS } from '../Types'; import { is256Color } from '../atlas/CharAtlasUtils'; import { clearColor } from '../atlas/CharAtlasGenerator'; import { DEFAULT_ATTR } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; +import { IColor } from '../../ui/Types'; +import { DEFAULT_ANSI_COLORS } from '../../ui/ColorManager'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 15fe77d9..f7ad0e58 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -3,10 +3,8 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, IColorSet, IRenderLayer, FLAGS } from '../Types'; -import { ITheme } from 'xterm'; +import { IRenderer, IRenderDimensions, IRenderLayer, FLAGS } from '../Types'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; -import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from '../LinkRenderLayer'; @@ -22,6 +20,8 @@ import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; +import { IColorSet } from '../../ui/Types'; +import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; @@ -43,7 +43,6 @@ export class WebglRenderer extends Disposable implements IRenderer { private _needsFullRefresh: boolean = false; public dimensions: IRenderDimensions; - public colorManager: ColorManager; private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } @@ -52,20 +51,15 @@ export class WebglRenderer extends Disposable implements IRenderer { constructor( private _terminal: ITerminal, - theme: ITheme + private _colors: IColorSet ) { super(); - const allowTransparency = this._terminal.options.allowTransparency; - this.colorManager = new ColorManager(document, allowTransparency); - if (theme) { - this.colorManager.setTheme(theme); - this._applyBgLuminanceBasedSelection(); - } + this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), - new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors) + new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal), + new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) ]; this.dimensions = { scaledCharWidth: null, @@ -98,8 +92,8 @@ export class WebglRenderer extends Disposable implements IRenderer { } this._terminal.screenElement.appendChild(this._canvas); - this._rectangleRenderer = new RectangleRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); - this._glyphRenderer = new GlyphRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); + this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); + this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so @@ -118,10 +112,10 @@ export class WebglRenderer extends Disposable implements IRenderer { private _applyBgLuminanceBasedSelection(): void { // HACK: This is needed until webgl renderer adds support for selection colors - if (this.colorManager.getLuminance(this.colorManager.colors.background) > 0.5) { - this.colorManager.colors.selection = { css: '#000', rgba: 255 }; + if (getLuminance(this._colors.background) > 0.5) { + this._colors.selection = { css: '#000', rgba: 255 }; } else { - this.colorManager.colors.selection = { css: '#fff', rgba: 4294967295 }; + this._colors.selection = { css: '#fff', rgba: 4294967295 }; } } @@ -142,25 +136,20 @@ export class WebglRenderer extends Disposable implements IRenderer { } } - public setTheme(theme: ITheme | undefined): IColorSet { - if (theme) { - this.colorManager.setTheme(theme); - this._applyBgLuminanceBasedSelection(); - } + public onThemeChange(colors: IColorSet): void { + this._applyBgLuminanceBasedSelection(); // Clear layers and force a full render this._renderLayers.forEach(l => { - l.onThemeChanged(this._terminal, this.colorManager.colors); + l.onThemeChange(this._terminal, this._colors); l.reset(this._terminal); }); - this._rectangleRenderer.onThemeChanged(); - this._glyphRenderer.onThemeChanged(); + this._rectangleRenderer.onThemeChange(); + this._glyphRenderer.onThemeChange(); this._refreshCharAtlas(); this._refreshViewport(); - - return this.colorManager.colors; } public onWindowResize(devicePixelRatio: number): void { @@ -245,7 +234,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight); + const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight); if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } diff --git a/src/ui/ColorManager.ts b/src/ui/ColorManager.ts index c751c442..6d68c58d 100644 --- a/src/ui/ColorManager.ts +++ b/src/ui/ColorManager.ts @@ -103,14 +103,6 @@ export class ColorManager implements IColorManager { }; } - // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast - public getLuminance(color: IColor): number { - const r = color.rgba >> 24 & 0xff; - const g = color.rgba >> 16 & 0xff; - const b = color.rgba >> 8 & 0xff; - return (0.299 * r + 0.587 * g + 0.114 * b) / 255; - } - /** * Sets the terminal's theme. * @param theme The theme to use. If a partial theme is provided then default diff --git a/src/ui/Types.ts b/src/ui/Types.ts index cc360684..ef725ba6 100644 --- a/src/ui/Types.ts +++ b/src/ui/Types.ts @@ -5,7 +5,6 @@ export interface IColorManager { colors: IColorSet; - getLuminance(color: IColor): number; } export interface IColor { From f42e53f15a4bc3efbb66a857814112fae39fe3d4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 21:56:42 -0700 Subject: [PATCH 027/104] Update for recent changes --- demo/client.ts | 4 ++ src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 2 +- src/renderer/webgl/WebglRenderer.ts | 85 +++++-------------------- 4 files changed, 21 insertions(+), 72 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index bcaabdd1..3369a82b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -283,6 +283,10 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); + if (o === 'rendererType' && input.value === 'webgl') { + term.setOption('experimentalCharAtlas', 'webgl'); + setTimeout(() => (document.getElementById(`opt-experimentalCharAtlas`)).value = 'webgl', 0); + } term.setOption(o, input.value); }); }); diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 9bd4573d..698a2f19 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -299,7 +299,7 @@ export class GlyphRenderer { } } - public onThemeChange(): void { + public setColors(): void { } public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index e047d7ac..7aeb5880 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -149,7 +149,7 @@ export class RectangleRenderer { this._updateViewportRectangle(); } - public onThemeChange(): void { + public setColors(): void { this._updateCachedColors(); this._updateViewportRectangle(); } diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index e3425173..b0d32e28 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -5,18 +5,15 @@ import { IRenderer, IRenderDimensions, IRenderLayer, FLAGS } from '../Types'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; -import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from '../LinkRenderLayer'; import { CursorRenderLayer } from '../CursorRenderLayer'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import WebglCharAtlas from './WebglCharAtlas'; -import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; @@ -26,10 +23,8 @@ import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; export class WebglRenderer extends Disposable implements IRenderer { - private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; private _charAtlas: WebglCharAtlas; - private _screenDprMonitor: ScreenDprMonitor; private _devicePixelRatio: number; private _model: RenderModel = new RenderModel(); @@ -39,16 +34,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _rectangleRenderer: RectangleRenderer; private _glyphRenderer: GlyphRenderer; - private _isPaused: boolean = false; - private _needsFullRefresh: boolean = false; - public dimensions: IRenderDimensions; - private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); - public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } - private _onRender = new EventEmitter2<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - constructor( private _terminal: ITerminal, private _colors: IColorSet @@ -78,12 +65,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); - this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); - this.register(this._screenDprMonitor); - - this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); - this._canvas = document.createElement('canvas'); const contextAttributes = { antialias: false, depth: false }; this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext; @@ -95,13 +76,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); - // Detect whether IntersectionObserver is detected and enable renderer pause - // and resume based on terminal visibility if so - if ('IntersectionObserver' in window) { - const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), { threshold: 0 }); - observer.observe(this._terminal.element); - this.register({ dispose: () => observer.disconnect() }); - } + // Update dimensions and acquire char atlas + this.onCharSizeChanged(); } public dispose(): void { @@ -119,24 +95,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } } - public onIntersectionChange(entry: IntersectionObserverEntry): void { - this._isPaused = entry.intersectionRatio === 0; - if (!this._isPaused && this._needsFullRefresh) { - this._terminal.refresh(0, this._terminal.rows - 1); - } - } - - private _refreshViewport(): void { - // Force a refresh - this._model.clear(); - if (this._isPaused) { - this._needsFullRefresh = true; - } else { - this._terminal.refresh(0, this._terminal.rows - 1); - } - } - public setColors(colors: IColorSet): void { + this._colors = colors; + this._applyBgLuminanceBasedSelection(); // Clear layers and force a full render @@ -145,23 +106,22 @@ export class WebglRenderer extends Disposable implements IRenderer { l.reset(this._terminal); }); - this._rectangleRenderer.onThemeChange(); - this._glyphRenderer.onThemeChange(); + this._rectangleRenderer.setColors(); + this._glyphRenderer.setColors(); this._refreshCharAtlas(); - this._refreshViewport(); } - public onWindowResize(devicePixelRatio: number): void { + public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed - if (this._devicePixelRatio !== devicePixelRatio) { - this._devicePixelRatio = devicePixelRatio; - this.onResize(this._terminal.cols, this._terminal.rows, devicePixelRatio); + if (this._devicePixelRatio !== window.devicePixelRatio) { + this._devicePixelRatio = window.devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows); } } - public onResize(cols: number, rows: number, devicePixelRatio: number = window.devicePixelRatio): void { + public onResize(cols: number, rows: number): void { // Update character and canvas dimensions this._updateDimensions(devicePixelRatio); @@ -184,12 +144,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.onResize(); this._refreshCharAtlas(); - this._refreshViewport(); - - this._onCanvasResize.fire({ - width: this.dimensions.canvasWidth, - height: this.dimensions.canvasHeight - }); } public onCharSizeChanged(): void { @@ -211,7 +165,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode); this._glyphRenderer.updateSelection(this._model, columnSelectMode); - this.refreshRows(0, this._terminal.rows - 1); + + // TODO: #2102 Should this move to RenderCoordinator? + this._terminal.refresh(0, this._terminal.rows - 1); } public onCursorMove(): void { @@ -247,14 +203,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.reset(this._terminal)); } - public refreshRows(start: number, end: number): void { - if (this._isPaused) { - this._needsFullRefresh = true; - return; - } - this._renderDebouncer.refresh(start, end, this._terminal.rows); - } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; } @@ -263,7 +211,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return false; } - private _renderRows(start: number, end: number): void { + public renderRows(start: number, end: number): void { // Update render layers this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); @@ -278,9 +226,6 @@ export class WebglRenderer extends Disposable implements IRenderer { // Render this._rectangleRenderer.render(); this._glyphRenderer.render(this._model, this._model.selection.hasSelection); - - // Emit event - this._onRender.fire({ start, end }); } private _updateModel(start: number, end: number): void { From 6922156d07a438a70e49d43b729e93ef0e7a5896 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 23:30:10 -0700 Subject: [PATCH 028/104] Move atlas parts into webgl/ --- src/Terminal.ts | 12 +- src/TestUtils.test.ts | 3 + src/Types.ts | 1 + src/public/Terminal.ts | 7 + src/renderer/atlas/CharAtlasCache.ts | 4 +- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 4 +- src/renderer/webgl/WebglRenderer.ts | 6 +- src/renderer/webgl/WebglRendererAddon.ts | 26 ++++ src/renderer/webgl/atlas/BaseCharAtlas.ts | 56 ++++++++ src/renderer/webgl/atlas/CharAtlasCache.ts | 94 ++++++++++++ .../webgl/atlas/CharAtlasGenerator.ts | 129 +++++++++++++++++ src/renderer/webgl/atlas/CharAtlasUtils.ts | 58 ++++++++ src/renderer/webgl/atlas/LRUMap.test.ts | 65 +++++++++ src/renderer/webgl/atlas/LRUMap.ts | 136 ++++++++++++++++++ src/renderer/webgl/atlas/Types.ts | 35 +++++ .../webgl/{ => atlas}/WebglCharAtlas.ts | 20 +-- typings/xterm.d.ts | 9 +- 18 files changed, 642 insertions(+), 25 deletions(-) create mode 100644 src/renderer/webgl/WebglRendererAddon.ts create mode 100644 src/renderer/webgl/atlas/BaseCharAtlas.ts create mode 100644 src/renderer/webgl/atlas/CharAtlasCache.ts create mode 100644 src/renderer/webgl/atlas/CharAtlasGenerator.ts create mode 100644 src/renderer/webgl/atlas/CharAtlasUtils.ts create mode 100644 src/renderer/webgl/atlas/LRUMap.test.ts create mode 100644 src/renderer/webgl/atlas/LRUMap.ts create mode 100644 src/renderer/webgl/atlas/Types.ts rename src/renderer/webgl/{ => atlas}/WebglCharAtlas.ts (96%) diff --git a/src/Terminal.ts b/src/Terminal.ts index 7083c0f0..8517fabc 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,7 +47,6 @@ import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; -import { WebglRenderer } from './renderer/webgl/WebglRenderer'; import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from './core/Types'; import { clone } from './common/Clone'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; @@ -818,11 +817,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } + public setRenderer(renderer: IRenderer): void { + this._renderCoordinator.setRenderer(renderer); + // this._renderCoordinator.onOptionsChanged(); + this.refresh(0, this.rows - 1); + } + private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return new Renderer(this, this._colorManager.colors); break; - case 'dom': return new DomRenderer(this, this._colorManager.colors); break; - case 'webgl': return new WebglRenderer(this, this._colorManager.colors); break; + case 'canvas': return new Renderer(this, this._colorManager.colors); + case 'dom': return new DomRenderer(this, this._colorManager.colors); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index f83e7d22..e95d4741 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -22,6 +22,9 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { + setRenderer(renderer: any): void { + throw new Error('Method not implemented.'); + } onCursorMove: IEvent; onLineFeed: IEvent; onSelectionChange: IEvent; diff --git a/src/Types.ts b/src/Types.ts index 91bee283..8db46901 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -270,6 +270,7 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { setOption(key: string, value: any): void; refresh(start: number, end: number): void; reset(): void; + setRenderer(renderer: any): void; } export interface IBufferAccessor { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index f1874a3f..ce469b97 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -10,6 +10,7 @@ import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from '../common/EventEmitter2'; import { AddonManager } from './AddonManager'; +import { WebglRendererAddon } from '../renderer/webgl/WebglRendererAddon'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -183,6 +184,12 @@ export class Terminal implements ITerminalApi { public loadAddon(addon: ITerminalAddon): void { return this._addonManager.loadAddon(this, addon); } + public setRenderer(renderer: any): void { + this._core.setRenderer(renderer); + } + public loadWebgl(): void { + this.loadAddon(new WebglRendererAddon()); + } public static get strings(): ILocalizableStrings { return Strings; } diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index c5dcdc6b..30826d5b 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -9,15 +9,13 @@ import BaseCharAtlas from './BaseCharAtlas'; import DynamicCharAtlas from './DynamicCharAtlas'; import NoneCharAtlas from './NoneCharAtlas'; import StaticCharAtlas from './StaticCharAtlas'; -import WebglCharAtlas from '../webgl/WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; import { IColorSet } from '../../ui/Types'; const charAtlasImplementations = { 'none': NoneCharAtlas, 'static': StaticCharAtlas, - 'dynamic': DynamicCharAtlas, - 'webgl': WebglCharAtlas + 'dynamic': DynamicCharAtlas }; interface ICharAtlasCacheEntry { diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 698a2f19..95b1e325 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderDimensions } from '../Types'; import { ITerminal } from '../../Types'; -import WebglCharAtlas from './WebglCharAtlas'; +import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 7aeb5880..62731bc5 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -8,8 +8,8 @@ import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from '../../common/TypedArrayUtils'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { is256Color } from '../atlas/CharAtlasUtils'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; import { IColorSet, IColor } from '../../ui/Types'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index b0d32e28..d93080d7 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -8,11 +8,11 @@ import { CharacterJoinerHandler, ITerminal } from '../../Types'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from '../LinkRenderLayer'; import { CursorRenderLayer } from '../CursorRenderLayer'; -import { acquireCharAtlas } from '../atlas/CharAtlasCache'; -import WebglCharAtlas from './WebglCharAtlas'; +import { acquireCharAtlas } from './atlas/CharAtlasCache'; +import WebglCharAtlas from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts new file mode 100644 index 00000000..764caa0f --- /dev/null +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ITerminalAddon } from 'xterm'; +import { WebglRenderer } from './WebglRenderer'; + +export class WebglRendererAddon implements ITerminalAddon { + private _terminal: Terminal | undefined; + + constructor() {} + + public activate(terminal: Terminal): void { + if (!terminal.element) { + throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); + } + this._terminal = terminal; + const core = (terminal as any)._core; + this._terminal.setRenderer(new WebglRenderer(core, core._colorManager.colors)); + } + + public dispose(): void { + throw new Error('WebglRendererAddon.dispose Not yet implemented'); + } +} diff --git a/src/renderer/webgl/atlas/BaseCharAtlas.ts b/src/renderer/webgl/atlas/BaseCharAtlas.ts new file mode 100644 index 00000000..ee69b381 --- /dev/null +++ b/src/renderer/webgl/atlas/BaseCharAtlas.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IGlyphIdentifier } from './Types'; +import { IDisposable } from 'xterm'; + +export default abstract class BaseCharAtlas implements IDisposable { + private _didWarmUp: boolean = false; + + public dispose(): void { } + + /** + * Perform any work needed to warm the cache before it can be used. May be called multiple times. + * Implement _doWarmUp instead if you only want to get called once. + */ + public warmUp(): void { + if (!this._didWarmUp) { + this._doWarmUp(); + this._didWarmUp = true; + } + } + + /** + * Perform any work needed to warm the cache before it can be used. Used by the default + * implementation of warmUp(), and will only be called once. + */ + protected _doWarmUp(): void { } + + /** + * Called when we start drawing a new frame. + * + * TODO: We rely on this getting called by TextRenderLayer. This should really be called by + * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead + * of BaseRenderLayer. + */ + public beginFrame(): void { } + + /** + * May be called before warmUp finishes, however it is okay for the implementation to + * do nothing and return false in that case. + * + * @param ctx Where to draw the character onto. + * @param glyph Information about what to draw + * @param x The position on the context to start drawing at + * @param y The position on the context to start drawing at + * @returns The success state. True if we drew the character. + */ + public abstract draw( + ctx: CanvasRenderingContext2D, + glyph: IGlyphIdentifier, + x: number, + y: number + ): boolean; +} diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts new file mode 100644 index 00000000..9d16893b --- /dev/null +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../../../Types'; +import { generateConfig, configEquals } from './CharAtlasUtils'; +import BaseCharAtlas from './BaseCharAtlas'; +import WebglCharAtlas from './WebglCharAtlas'; +import { ICharAtlasConfig } from './Types'; +import { IColorSet } from '../../../ui/Types'; + +interface ICharAtlasCacheEntry { + atlas: BaseCharAtlas; + config: ICharAtlasConfig; + // N.B. This implementation potentially holds onto copies of the terminal forever, so + // this may cause memory leaks. + ownedBy: ITerminal[]; +} + +const charAtlasCache: ICharAtlasCacheEntry[] = []; + +/** + * Acquires a char atlas, either generating a new one or returning an existing + * one that is in use by another terminal. + * @param terminal The terminal. + * @param colors The colors to use. + */ +export function acquireCharAtlas( + terminal: ITerminal, + colors: IColorSet, + scaledCharWidth: number, + scaledCharHeight: number +): BaseCharAtlas { + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + + // Check to see if the terminal already owns this config + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + const ownedByIndex = entry.ownedBy.indexOf(terminal); + if (ownedByIndex >= 0) { + if (configEquals(entry.config, newConfig)) { + return entry.atlas; + } + // The configs differ, release the terminal from the entry + if (entry.ownedBy.length === 1) { + entry.atlas.dispose(); + charAtlasCache.splice(i, 1); + } else { + entry.ownedBy.splice(ownedByIndex, 1); + } + break; + } + } + + // Try match a char atlas from the cache + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + if (configEquals(entry.config, newConfig)) { + // Add the terminal to the cache entry and return + entry.ownedBy.push(terminal); + return entry.atlas; + } + } + + const newEntry: ICharAtlasCacheEntry = { + atlas: new WebglCharAtlas(document, newConfig), + config: newConfig, + ownedBy: [terminal] + }; + charAtlasCache.push(newEntry); + return newEntry.atlas; +} + +/** + * Removes a terminal reference from the cache, allowing its memory to be freed. + * @param terminal The terminal to remove. + */ +export function removeTerminalFromCache(terminal: ITerminal): void { + for (let i = 0; i < charAtlasCache.length; i++) { + const index = charAtlasCache[i].ownedBy.indexOf(terminal); + if (index !== -1) { + if (charAtlasCache[i].ownedBy.length === 1) { + // Remove the cache entry if it's the only terminal + charAtlasCache[i].atlas.dispose(); + charAtlasCache.splice(i, 1); + } else { + // Remove the reference from the cache entry + charAtlasCache[i].ownedBy.splice(index, 1); + } + break; + } + } +} diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts new file mode 100644 index 00000000..e844f37a --- /dev/null +++ b/src/renderer/webgl/atlas/CharAtlasGenerator.ts @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { FontWeight } from 'xterm'; +import { isFirefox, isSafari } from '../../../common/Platform'; +import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; +import { IColor } from '../../../ui/Types'; + +/** + * Generates a char atlas. + * @param context The window or worker context. + * @param canvasFactory A function to generate a canvas with a width or height. + * @param config The config for the new char atlas. + */ +export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise { + const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; + const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; + const canvas = canvasFactory( + /*255 ascii chars*/255 * cellWidth, + (/*default+default bold*/2 + /*0-15*/16 + /*0-15 bold*/16) * cellHeight + ); + const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); + + ctx.fillStyle = config.colors.background.css; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.save(); + ctx.fillStyle = config.colors.foreground.css; + ctx.font = getFont(config.fontWeight, config); + ctx.textBaseline = 'middle'; + + // Default color + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2); + ctx.restore(); + } + // Default color bold + ctx.save(); + ctx.font = getFont(config.fontWeightBold, config); + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5); + ctx.restore(); + } + ctx.restore(); + + // Colors 0-15 + ctx.font = getFont(config.fontWeight, config); + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + const y = (colorIndex + 2) * cellHeight; + // Draw ascii characters + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); + ctx.restore(); + } + } + + // Colors 0-15 bold + ctx.font = getFont(config.fontWeightBold, config); + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + const y = (colorIndex + 2 + 16) * cellHeight; + // Draw ascii characters + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); + ctx.restore(); + } + } + ctx.restore(); + + // Support is patchy for createImageBitmap at the moment, pass a canvas back + // if support is lacking as drawImage works there too. Firefox is also + // included here as ImageBitmap appears both buggy and has horrible + // performance (tested on v55). + if (!('createImageBitmap' in context) || isFirefox || isSafari) { + // Don't attempt to clear background colors if createImageBitmap is not supported + return canvas; + } + + const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + // Remove the background color from the image so characters may overlap + clearColor(charAtlasImageData, config.colors.background); + + return context.createImageBitmap(charAtlasImageData); +} + +/** + * Makes a partiicular rgb color in an ImageData completely transparent. + * @returns True if the result is "empty", meaning all pixels are fully transparent. + */ +export function clearColor(imageData: ImageData, color: IColor): boolean { + let isEmpty = true; + const r = color.rgba >>> 24; + const g = color.rgba >>> 16 & 0xFF; + const b = color.rgba >>> 8 & 0xFF; + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } else { + isEmpty = false; + } + } + return isEmpty; +} + +function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string { + return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`; +} diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts new file mode 100644 index 00000000..b11ca085 --- /dev/null +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../../../Types'; +import { ICharAtlasConfig } from './Types'; +import { DEFAULT_COLOR } from '../../../common/Types'; +import { IColorSet } from '../../../ui/Types'; + +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { + // null out some fields that don't matter + const clonedColors = { + foreground: colors.foreground, + background: colors.background, + cursor: null, + cursorAccent: null, + selection: null, + // For the static char atlas, we only use the first 16 colors, but we need all 256 for the + // dynamic character atlas. + ansi: colors.ansi.slice(0, 16) + }; + return { + type: terminal.options.experimentalCharAtlas, + devicePixelRatio: window.devicePixelRatio, + scaledCharWidth, + scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + fontWeight: terminal.options.fontWeight, + fontWeightBold: terminal.options.fontWeightBold, + allowTransparency: terminal.options.allowTransparency, + colors: clonedColors + }; +} + +export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { + for (let i = 0; i < a.colors.ansi.length; i++) { + if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) { + return false; + } + } + return a.type === b.type && + a.devicePixelRatio === b.devicePixelRatio && + a.fontFamily === b.fontFamily && + a.fontSize === b.fontSize && + a.fontWeight === b.fontWeight && + a.fontWeightBold === b.fontWeightBold && + a.allowTransparency === b.allowTransparency && + a.scaledCharWidth === b.scaledCharWidth && + a.scaledCharHeight === b.scaledCharHeight && + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; +} + +export function is256Color(colorCode: number): boolean { + return colorCode < DEFAULT_COLOR; +} diff --git a/src/renderer/webgl/atlas/LRUMap.test.ts b/src/renderer/webgl/atlas/LRUMap.test.ts new file mode 100644 index 00000000..197d1159 --- /dev/null +++ b/src/renderer/webgl/atlas/LRUMap.test.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import LRUMap from './LRUMap'; + +describe('LRUMap', () => { + it('can be used to store and retrieve values', () => { + const map = new LRUMap(10); + map.set(1, 'valuea'); + map.set(2, 'valueb'); + map.set(3, 'valuec'); + assert.strictEqual(map.get(1), 'valuea'); + assert.strictEqual(map.get(2), 'valueb'); + assert.strictEqual(map.get(3), 'valuec'); + }); + + it('maintains a size from insertions', () => { + const map = new LRUMap(10); + assert.strictEqual(map.size, 0); + map.set(1, 'value'); + assert.strictEqual(map.size, 1); + map.set(2, 'value'); + assert.strictEqual(map.size, 2); + }); + + it('deletes the oldest entry when the capacity is exceeded', () => { + const map = new LRUMap(4); + map.set(1, 'value'); + map.set(2, 'value'); + map.set(3, 'value'); + map.set(4, 'value'); + map.set(5, 'value'); + assert.isNull(map.get(1)); + assert.isNotNull(map.get(2)); + assert.isNotNull(map.get(3)); + assert.isNotNull(map.get(4)); + assert.isNotNull(map.get(5)); + assert.strictEqual(map.size, 4); + }); + + it('prevents a recently accessed entry from getting deleted', () => { + const map = new LRUMap(2); + map.set(1, 'value'); + map.set(2, 'value'); + map.get(1); + // a would normally get deleted here, except that we called get() + map.set(3, 'value'); + assert.isNotNull(map.get(1)); + // b got deleted instead of a + assert.isNull(map.get(2)); + assert.isNotNull(map.get(3)); + }); + + it('supports mutation', () => { + const map = new LRUMap(10); + map.set(1, 'oldvalue'); + map.set(1, 'newvalue'); + // mutation doesn't change the size + assert.strictEqual(map.size, 1); + assert.strictEqual(map.get(1), 'newvalue'); + }); +}); diff --git a/src/renderer/webgl/atlas/LRUMap.ts b/src/renderer/webgl/atlas/LRUMap.ts new file mode 100644 index 00000000..d7e01ec6 --- /dev/null +++ b/src/renderer/webgl/atlas/LRUMap.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +interface ILinkedListNode { + prev: ILinkedListNode; + next: ILinkedListNode; + key: number; + value: T; +} + +export default class LRUMap { + private _map: { [key: number]: ILinkedListNode } = {}; + private _head: ILinkedListNode = null; + private _tail: ILinkedListNode = null; + private _nodePool: ILinkedListNode[] = []; + public size: number = 0; + + constructor(public capacity: number) { } + + private _unlinkNode(node: ILinkedListNode): void { + const prev = node.prev; + const next = node.next; + if (node === this._head) { + this._head = next; + } + if (node === this._tail) { + this._tail = prev; + } + if (prev !== null) { + prev.next = next; + } + if (next !== null) { + next.prev = prev; + } + } + + private _appendNode(node: ILinkedListNode): void { + const tail = this._tail; + if (tail !== null) { + tail.next = node; + } + node.prev = tail; + node.next = null; + this._tail = node; + if (this._head === null) { + this._head = node; + } + } + + /** + * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that + * they're more likely to live next to each other in memory, which seems to improve performance. + * + * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for + * large maps. + */ + public prealloc(count: number): void { + const nodePool = this._nodePool; + for (let i = 0; i < count; i++) { + nodePool.push({ + prev: null, + next: null, + key: null, + value: null + }); + } + } + + public get(key: number): T | null { + // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However, + // it's faster than calling hasOwnProperty, and in our case, it would never overlap. + const node = this._map[key]; + if (node !== undefined) { + this._unlinkNode(node); + this._appendNode(node); + return node.value; + } + return null; + } + + /** + * Gets a value from a key without marking it as the most recently used item. + */ + public peekValue(key: number): T | null { + const node = this._map[key]; + if (node !== undefined) { + return node.value; + } + return null; + } + + public peek(): T | null { + const head = this._head; + return head === null ? null : head.value; + } + + public set(key: number, value: T): void { + // This is unsafe: See note above. + let node = this._map[key]; + if (node !== undefined) { + // already exists, we just need to mutate it and move it to the end of the list + node = this._map[key]; + this._unlinkNode(node); + node.value = value; + } else if (this.size >= this.capacity) { + // we're out of space: recycle the head node, move it to the tail + node = this._head; + this._unlinkNode(node); + delete this._map[node.key]; + node.key = key; + node.value = value; + this._map[key] = node; + } else { + // make a new element + const nodePool = this._nodePool; + if (nodePool.length > 0) { + // use a preallocated node if we can + node = nodePool.pop(); + node.key = key; + node.value = value; + } else { + node = { + prev: null, + next: null, + key, + value + }; + } + this._map[key] = node; + this.size++; + } + this._appendNode(node); + } +} diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts new file mode 100644 index 00000000..1bdaf3b9 --- /dev/null +++ b/src/renderer/webgl/atlas/Types.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { FontWeight } from 'xterm'; +import { IColorSet } from '../../../ui/Types'; + +export const INVERTED_DEFAULT_COLOR = 257; +export const DIM_OPACITY = 0.5; + +export const CHAR_ATLAS_CELL_SPACING = 1; + +export interface IGlyphIdentifier { + chars: string; + code: number; + bg: number; + fg: number; + bold: boolean; + dim: boolean; + italic: boolean; +} + +export interface ICharAtlasConfig { + type: 'none' | 'static' | 'dynamic' | 'webgl'; + devicePixelRatio: number; + fontSize: number; + fontFamily: string; + fontWeight: FontWeight; + fontWeightBold: FontWeight; + scaledCharWidth: number; + scaledCharHeight: number; + allowTransparency: boolean; + colors: IColorSet; +} diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts similarity index 96% rename from src/renderer/webgl/WebglCharAtlas.ts rename to src/renderer/webgl/atlas/WebglCharAtlas.ts index a6bd4013..5da955bc 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -3,16 +3,16 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from '../atlas/Types'; -import BaseCharAtlas from '../atlas/BaseCharAtlas'; -import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; -import { FLAGS } from '../Types'; -import { is256Color } from '../atlas/CharAtlasUtils'; -import { clearColor } from '../atlas/CharAtlasGenerator'; -import { DEFAULT_ATTR } from '../../core/buffer/BufferLine'; -import { DEFAULT_COLOR } from '../../common/Types'; -import { IColor } from '../../ui/Types'; -import { DEFAULT_ANSI_COLORS } from '../../ui/ColorManager'; +import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; +import BaseCharAtlas from './BaseCharAtlas'; +import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; +import { FLAGS } from '../../Types'; +import { is256Color } from './CharAtlasUtils'; +import { clearColor } from './CharAtlasGenerator'; +import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; +import { DEFAULT_COLOR } from '../../../common/Types'; +import { IColor } from '../../../ui/Types'; +import { DEFAULT_ANSI_COLORS } from '../../../ui/ColorManager'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a0fb74ce..de4051ed 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -18,7 +18,7 @@ declare module 'xterm' { /** * A string representing a renderer type. */ - export type RendererType = 'dom' | 'canvas' | 'webgl'; + export type RendererType = 'dom' | 'canvas'; /** * An object containing start up options for the terminal. @@ -99,7 +99,7 @@ declare module 'xterm' { * Currently defaults to 'static'. This option may be removed in the future. If it is, passed * parameters will be ignored. */ - experimentalCharAtlas?: 'none' | 'static' | 'dynamic' | 'webgl'; + experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; /** * The font size used to render text. @@ -890,6 +890,11 @@ declare module 'xterm' { * @param addon The addon to load. */ loadAddon(addon: ITerminalAddon): void; + + /** + * (EXPERIMENTAL) + */ + setRenderer(renderer: any): void; } /** From 29d3b8281b475a204a499b83ac2741c6cce1caf6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 23:38:49 -0700 Subject: [PATCH 029/104] Copy over typed array utils to webgl --- src/common/TypedArrayUtils.test.ts | 105 +--------- src/common/TypedArrayUtils.ts | 26 --- src/renderer/webgl/ColorUtils.ts | 2 +- src/renderer/webgl/GlyphRenderer.ts | 4 +- src/renderer/webgl/RectangleRenderer.ts | 4 +- src/renderer/webgl/RenderModel.ts | 2 +- src/renderer/webgl/TypedArray.test.ts | 191 ++++++++++++++++++ src/renderer/webgl/TypedArray.ts | 67 ++++++ src/renderer/webgl/WebglRenderer.ts | 2 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 2 +- .../webgl/atlas/CharAtlasGenerator.ts | 3 +- src/renderer/webgl/atlas/CharAtlasUtils.ts | 4 +- src/renderer/webgl/atlas/Types.ts | 3 +- src/renderer/webgl/atlas/WebglCharAtlas.ts | 2 +- typings/xterm.d.ts | 20 ++ 15 files changed, 292 insertions(+), 145 deletions(-) create mode 100644 src/renderer/webgl/TypedArray.test.ts create mode 100644 src/renderer/webgl/TypedArray.ts diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index 8e838d2c..99b0fd82 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fillFallback, sliceFallback, concat } from './TypedArrayUtils'; +import { fillFallback, concat } from './TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -85,109 +85,6 @@ describe('polyfill conformance tests', function(): void { } }); }); - - describe('TypedArray.slice', () => { - describe('should work with all typed array types', () => { - it('Uint8Array', () => { - const a = new Uint8Array(5); - deepEquals(sliceFallback(a, 2), a.slice(2)); - deepEquals(sliceFallback(a, 65535), a.slice(65535)); - deepEquals(sliceFallback(a, -1), a.slice(-1)); - }); - it('Uint16Array', () => { - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(sliceFallback(u161, 2), u162.slice(2)); - deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); - deepEquals(sliceFallback(u161, -1), u162.slice(-1)); - }); - it('Uint32Array', () => { - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(sliceFallback(u321, 2), u322.slice(2)); - deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); - deepEquals(sliceFallback(u321, -1), u322.slice(-1)); - }); - it('Int8Array', () => { - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(sliceFallback(i81, 2), i82.slice(2)); - deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); - deepEquals(sliceFallback(i81, -1), i82.slice(-1)); - }); - it('Int16Array', () => { - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(sliceFallback(i161, 2), i162.slice(2)); - deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); - deepEquals(sliceFallback(i161, -1), i162.slice(-1)); - }); - it('Int32Array', () => { - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(sliceFallback(i321, 2), i322.slice(2)); - deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); - deepEquals(sliceFallback(i321, -1), i322.slice(-1)); - }); - it('Float32Array', () => { - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(sliceFallback(f321, 2), f322.slice(2)); - deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); - deepEquals(sliceFallback(f321, -1), f322.slice(-1)); - }); - it('Float64Array', () => { - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(sliceFallback(f641, 2), f642.slice(2)); - deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); - deepEquals(sliceFallback(f641, -1), f642.slice(-1)); - }); - it('Uint8ClampedArray', () => { - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); - deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); - deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); - }); - }); - it('start', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1), arr.slice(-1)); - deepEquals(sliceFallback(arr, 0), arr.slice(0)); - deepEquals(sliceFallback(arr, 1), arr.slice(1)); - deepEquals(sliceFallback(arr, 2), arr.slice(2)); - deepEquals(sliceFallback(arr, 3), arr.slice(3)); - deepEquals(sliceFallback(arr, 4), arr.slice(4)); - deepEquals(sliceFallback(arr, 5), arr.slice(5)); - }); - it('end', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); - deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); - deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); - deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); - deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); - deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); - deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); - - deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); - deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); - deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); - deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); - deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); - deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); - deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); - - deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); - deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); - deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); - deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); - deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); - deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); - deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); - }); - }); }); describe('typed array convenience functions', () => { diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index f2651aa0..54699835 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -40,32 +40,6 @@ export function fillFallback(array: T, value: number, star return array; } -export function slice(array: T, start?: number, end?: number): T { - // all modern engines that support .slice - if (array.slice) { - return array.slice(start, end) as T; - } - return sliceFallback(array, start, end); -} - -export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { - if (start < 0) { - start = (array.length + start) % array.length; - } - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - start = Math.min(start, end); - - const result: T = new (array.constructor as any)(end - start); - for (let i = 0; i < end - start; ++i) { - result[i] = array[i + start]; - } - return result; -} - /** * Concat two typed arrays `a` and `b`. * Returns a new typed array. diff --git a/src/renderer/webgl/ColorUtils.ts b/src/renderer/webgl/ColorUtils.ts index 9efdeb9c..56127656 100644 --- a/src/renderer/webgl/ColorUtils.ts +++ b/src/renderer/webgl/ColorUtils.ts @@ -3,7 +3,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. */ -import { IColor } from '../../ui/Types'; +import { IColor } from 'xterm'; export function getLuminance(color: IColor): number { // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 95b1e325..9d61b065 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -10,11 +10,11 @@ import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from '../../common/TypedArrayUtils'; +import { fill, slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../core/buffer/BufferLine'; import { IBufferLine } from '../../core/Types'; -import { IColorSet } from '../../ui/Types'; import { getLuminance } from './ColorUtils'; +import { IColorSet } from 'xterm'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 62731bc5..a133abd4 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -7,11 +7,11 @@ import { ITerminal } from '../../Types'; import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { fill } from '../../common/TypedArrayUtils'; +import { fill } from './TypedArray'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, IColor } from '../../ui/Types'; +import { IColorSet, IColor } from 'xterm'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index 54fc91ca..71d256d3 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,7 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; -import { fill } from '../../common/TypedArrayUtils'; +import { fill } from './TypedArray'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; diff --git a/src/renderer/webgl/TypedArray.test.ts b/src/renderer/webgl/TypedArray.test.ts new file mode 100644 index 00000000..25676248 --- /dev/null +++ b/src/renderer/webgl/TypedArray.test.ts @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { assert } from 'chai'; +import { fillFallback, sliceFallback } from './TypedArray'; + +type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +function deepEquals(a: TypedArray, b: TypedArray): void { + assert.equal(a.length, b.length); + for (let i = 0; i < a.length; ++i) { + assert.equal(a[i], b[i]); + } +} + +describe('polyfill conformance tests', function(): void { + describe('TypedArray.fill', function(): void { + it('should work with all typed array types', function(): void { + const u81 = new Uint8Array(5); + const u82 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2), u82.fill(2)); + deepEquals(fillFallback(u81, -1), u82.fill(-1)); + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(fillFallback(u161, 2), u162.fill(2)); + deepEquals(fillFallback(u161, 65535), u162.fill(65535)); + deepEquals(fillFallback(u161, -1), u162.fill(-1)); + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(fillFallback(u321, 2), u322.fill(2)); + deepEquals(fillFallback(u321, 65537), u322.fill(65537)); + deepEquals(fillFallback(u321, -1), u322.fill(-1)); + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(fillFallback(i81, 2), i82.fill(2)); + deepEquals(fillFallback(i81, -1), i82.fill(-1)); + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(fillFallback(i161, 2), i162.fill(2)); + deepEquals(fillFallback(i161, 65535), i162.fill(65535)); + deepEquals(fillFallback(i161, -1), i162.fill(-1)); + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(fillFallback(i321, 2), i322.fill(2)); + deepEquals(fillFallback(i321, 65537), i322.fill(65537)); + deepEquals(fillFallback(i321, -1), i322.fill(-1)); + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(fillFallback(f321, 1.2345), f322.fill(1.2345)); + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(fillFallback(f641, 1.2345), f642.fill(1.2345)); + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(fillFallback(u8Clamped1, 2), u8Clamped2.fill(2)); + deepEquals(fillFallback(u8Clamped1, 257), u8Clamped2.fill(257)); + }); + it('start offset', function(): void { + for (let i = -2; i < 10; ++i) { + const u81 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2, i), u83.fill(2, i)); + deepEquals(fillFallback(u81, -1, i), u83.fill(-1, i)); + } + }); + it('end offset', function(): void { + for (let i = -2; i < 10; ++i) { + const u81 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2, 0, i), u83.fill(2, 0, i)); + deepEquals(fillFallback(u81, -1, 0, i), u83.fill(-1, 0, i)); + } + }); + it('start/end offset', function(): void { + for (let i = -2; i < 10; ++i) { + for (let j = -2; j < 10; ++j) { + const u81 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2, i, j), u83.fill(2, i, j)); + deepEquals(fillFallback(u81, -1, i, j), u83.fill(-1, i, j)); + } + } + }); + }); + + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); +}); diff --git a/src/renderer/webgl/TypedArray.ts b/src/renderer/webgl/TypedArray.ts new file mode 100644 index 00000000..33d59ea1 --- /dev/null +++ b/src/renderer/webgl/TypedArray.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + + +/** + * polyfill for TypedArray.fill + * This is needed to support .fill in all safari versions and IE 11. + */ +export function fill(array: T, value: number, start?: number, end?: number): T { + // all modern engines that support .fill + if (array.fill) { + return array.fill(value, start, end) as T; + } + return fillFallback(array, value, start, end); +} + +export function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T { + // safari and IE 11 + // since IE 11 does not support Array.prototype.fill either + // we cannot use the suggested polyfill from MDN + // instead we simply fall back to looping + if (start >= array.length) { + return array; + } + start = (array.length + start) % array.length; + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + for (let i = start; i < end; ++i) { + array[i] = value; + } + return array; +} + +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array.constructor as any)(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index d93080d7..27404a8a 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -17,7 +17,7 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet } from '../../ui/Types'; +import { IColorSet } from 'xterm'; import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index 9d16893b..05bdadc2 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -8,7 +8,7 @@ import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import WebglCharAtlas from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; -import { IColorSet } from '../../../ui/Types'; +import { IColorSet } from 'xterm'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts index e844f37a..0d343a43 100644 --- a/src/renderer/webgl/atlas/CharAtlasGenerator.ts +++ b/src/renderer/webgl/atlas/CharAtlasGenerator.ts @@ -3,10 +3,9 @@ * @license MIT */ -import { FontWeight } from 'xterm'; +import { FontWeight, IColor } from 'xterm'; import { isFirefox, isSafari } from '../../../common/Platform'; import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; -import { IColor } from '../../../ui/Types'; /** * Generates a char atlas. diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index b11ca085..855e32c8 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -6,11 +6,11 @@ import { ITerminal } from '../../../Types'; import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { IColorSet } from '../../../ui/Types'; +import { IColorSet } from 'xterm'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter - const clonedColors = { + const clonedColors: IColorSet = { foreground: colors.foreground, background: colors.background, cursor: null, diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts index 1bdaf3b9..ee6dc665 100644 --- a/src/renderer/webgl/atlas/Types.ts +++ b/src/renderer/webgl/atlas/Types.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { FontWeight } from 'xterm'; -import { IColorSet } from '../../../ui/Types'; +import { FontWeight, IColorSet } from 'xterm'; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 5da955bc..a9cd8e7d 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -11,8 +11,8 @@ import { is256Color } from './CharAtlasUtils'; import { clearColor } from './CharAtlasGenerator'; import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { IColor } from '../../../ui/Types'; import { DEFAULT_ANSI_COLORS } from '../../../ui/ColorManager'; +import { IColor } from 'xterm'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index de4051ed..7d79d959 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -897,6 +897,26 @@ declare module 'xterm' { setRenderer(renderer: any): void; } + /** + * (EXPERIMENTAL) + */ + export interface IColor { + css: string; + rgba: number; + } + + /** + * (EXPERIMENTAL) + */ + export interface IColorSet { + foreground: IColor; + background: IColor; + cursor: IColor; + cursorAccent: IColor; + selection: IColor; + ansi: IColor[]; + } + /** * An addon that can provide additional functionality to the terminal. */ From 134c2ff85921685276dd4ac2b0cc026f386b176b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 00:21:07 -0700 Subject: [PATCH 030/104] Move parts of webgl renderer to public API --- src/public/Terminal.ts | 1 + src/renderer/LinkRenderLayer.ts | 1 + src/renderer/webgl/GlyphRenderer.ts | 21 ++++---- src/renderer/webgl/RectangleRenderer.ts | 5 +- src/renderer/webgl/WebglRenderer.ts | 58 ++++++++++++---------- src/renderer/webgl/WebglRendererAddon.ts | 3 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 9 ++-- src/renderer/webgl/atlas/CharAtlasUtils.ts | 19 +++---- src/renderer/webgl/atlas/Types.ts | 1 - typings/xterm.d.ts | 5 ++ 10 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index ce469b97..3af5162c 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -32,6 +32,7 @@ export class Terminal implements ITerminalApi { public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } public get element(): HTMLElement { return this._core.element; } + public get screenElement(): HTMLElement { return this._core.screenElement; } public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index c92b10dc..6bba167c 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -15,6 +15,7 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); + // TODO: Need to expose link-related renderer API terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 9d61b065..de9c0bf7 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -5,16 +5,14 @@ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderDimensions } from '../Types'; -import { ITerminal } from '../../Types'; import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../core/buffer/BufferLine'; -import { IBufferLine } from '../../core/Types'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../core/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal, IBufferLine } from 'xterm'; interface IVertices { attributes: Float32Array; @@ -97,7 +95,7 @@ export class GlyphRenderer { }; constructor( - private _terminal: ITerminal, + private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions @@ -185,9 +183,9 @@ export class GlyphRenderer { let rasterizedGlyph: IRasterizedGlyph; if (chars && chars.length > 1) { - rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.options.enableBold); + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.getOption('enableBold')); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.options.enableBold); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.getOption('enableBold')); } // Fill empty if no glyph was found @@ -252,8 +250,8 @@ export class GlyphRenderer { private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void { const terminal = this._terminal; - const row = y + terminal.buffer.ydisp; - let line: IBufferLine; + const row = y + terminal.buffer.viewportY; + let line: IBufferLine | undefined; for (let x = startCol; x < endCol; x++) { const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; // Because the cache uses attr as a lookup key it needs to contain the selection colors as well @@ -262,10 +260,9 @@ export class GlyphRenderer { const code = model.cells[offset]; if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { - line = terminal.buffer.lines.get(row); + line = terminal.buffer.getLine(row); } - const charData = line.get(x); - const chars = charData[CHAR_DATA_CHAR_INDEX]; + const chars = line.getCell(x).char; this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars); } else { this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg); diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index a133abd4..ca4c062c 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { ITerminal } from '../../Types'; import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; @@ -11,7 +10,7 @@ import { fill } from './TypedArray'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, IColor } from 'xterm'; +import { IColorSet, IColor, Terminal } from 'xterm'; const enum VertexAttribLocations { POSITION = 0, @@ -76,7 +75,7 @@ export class RectangleRenderer { }; constructor( - private _terminal: ITerminal, + private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 27404a8a..2fadebbd 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -17,7 +17,7 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal } from 'xterm'; import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; @@ -36,16 +36,20 @@ export class WebglRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; + private _core: ITerminal; + constructor( - private _terminal: ITerminal, + private _terminal: Terminal, private _colors: IColorSet ) { super(); + this._core = (this._terminal as any)._core; + this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal), + new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._core), new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) ]; this.dimensions = { @@ -102,8 +106,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Clear layers and force a full render this._renderLayers.forEach(l => { - l.setColors(this._terminal, this._colors); - l.reset(this._terminal); + l.setColors(this._core, this._colors); + l.reset(this._core); }); this._rectangleRenderer.setColors(); @@ -117,7 +121,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // and the terminal needs to refreshed if (this._devicePixelRatio !== window.devicePixelRatio) { this._devicePixelRatio = window.devicePixelRatio; - this.onResize(this._terminal.cols, this._terminal.rows); + this.onResize(this._core.cols, this._core.rows); } } @@ -125,11 +129,11 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update character and canvas dimensions this._updateDimensions(devicePixelRatio); - this._model.resize(this._terminal.cols, this._terminal.rows); + this._model.resize(this._core.cols, this._core.rows); this._rectangleRenderer.onResize(); // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); + this._renderLayers.forEach(l => l.resize(this._core, this.dimensions)); // Resize the canvas this._canvas.width = this.dimensions.scaledCanvasWidth; @@ -151,15 +155,15 @@ export class WebglRenderer extends Disposable implements IRenderer { } public onBlur(): void { - this._renderLayers.forEach(l => l.onBlur(this._terminal)); + this._renderLayers.forEach(l => l.onBlur(this._core)); } public onFocus(): void { - this._renderLayers.forEach(l => l.onFocus(this._terminal)); + this._renderLayers.forEach(l => l.onFocus(this._core)); } public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { - this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); + this._renderLayers.forEach(l => l.onSelectionChanged(this._core, start, end, columnSelectMode)); this._updateSelectionModel(start, end); @@ -167,15 +171,15 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.updateSelection(this._model, columnSelectMode); // TODO: #2102 Should this move to RenderCoordinator? - this._terminal.refresh(0, this._terminal.rows - 1); + this._core.refresh(0, this._core.rows - 1); } public onCursorMove(): void { - this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); + this._renderLayers.forEach(l => l.onCursorMove(this._core)); } public onOptionsChanged(): void { - this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); + this._renderLayers.forEach(l => l.onOptionsChanged(this._core)); this._updateDimensions(); this._refreshCharAtlas(); } @@ -200,7 +204,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } public clear(): void { - this._renderLayers.forEach(l => l.reset(this._terminal)); + this._renderLayers.forEach(l => l.reset(this._core)); } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { @@ -213,7 +217,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { // Update render layers - this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); + this._renderLayers.forEach(l => l.onGridChanged(this._core, start, end)); // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { @@ -229,7 +233,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _updateModel(start: number, end: number): void { - const terminal = this._terminal; + const terminal = this._core; for (let y = start; y <= end; y++) { const row = y + terminal.buffer.ydisp; @@ -288,7 +292,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _updateSelectionModel(start: [number, number], end: [number, number]): void { - const terminal = this._terminal; + const terminal = this._core; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { @@ -322,7 +326,7 @@ export class WebglRenderer extends Disposable implements IRenderer { */ private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { // Perform a new measure if the CharMeasure dimensions are not yet available - if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + if (!this._core.charMeasure.width || !this._core.charMeasure.height) { return; } @@ -333,34 +337,34 @@ export class WebglRenderer extends Disposable implements IRenderer { // NOTE: ceil fixes sometime, floor does others :s - this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * devicePixelRatio); + this.dimensions.scaledCharWidth = Math.floor(this._core.charMeasure.width * devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is // enough space to draw the character to the cell. - this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil(this._core.charMeasure.height * devicePixelRatio); // Calculate the scaled cell height, if lineHeight is not 1 then the value // 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._terminal.options.lineHeight); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._core.options.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._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCharTop = this._core.options.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._terminal.options.letterSpacing); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._core.options.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._terminal.options.letterSpacing / 2); + this.dimensions.scaledCharLeft = Math.floor(this._core.options.letterSpacing / 2); // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas - this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; - this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + this.dimensions.scaledCanvasHeight = this._core.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._core.cols * this.dimensions.scaledCellWidth; // The the size of the canvas on the page. It's very important that this // rounds to nearest integer and not ceils as browsers often set diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 764caa0f..6c835ba8 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -16,8 +16,7 @@ export class WebglRendererAddon implements ITerminalAddon { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - const core = (terminal as any)._core; - this._terminal.setRenderer(new WebglRenderer(core, core._colorManager.colors)); + this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors)); } public dispose(): void { diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index 05bdadc2..8d8459fd 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -3,19 +3,18 @@ * @license MIT */ -import { ITerminal } from '../../../Types'; import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import WebglCharAtlas from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal } from 'xterm'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. - ownedBy: ITerminal[]; + ownedBy: Terminal[]; } const charAtlasCache: ICharAtlasCacheEntry[] = []; @@ -27,7 +26,7 @@ const charAtlasCache: ICharAtlasCacheEntry[] = []; * @param colors The colors to use. */ export function acquireCharAtlas( - terminal: ITerminal, + terminal: Terminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number @@ -76,7 +75,7 @@ export function acquireCharAtlas( * Removes a terminal reference from the cache, allowing its memory to be freed. * @param terminal The terminal to remove. */ -export function removeTerminalFromCache(terminal: ITerminal): void { +export function removeTerminalFromCache(terminal: Terminal): void { for (let i = 0; i < charAtlasCache.length; i++) { const index = charAtlasCache[i].ownedBy.indexOf(terminal); if (index !== -1) { diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index 855e32c8..2bda420f 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -3,12 +3,11 @@ * @license MIT */ -import { ITerminal } from '../../../Types'; import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal, FontWeight } from 'xterm'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter const clonedColors: IColorSet = { foreground: colors.foreground, @@ -21,15 +20,14 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number ansi: colors.ansi.slice(0, 16) }; return { - type: terminal.options.experimentalCharAtlas, devicePixelRatio: window.devicePixelRatio, scaledCharWidth, scaledCharHeight, - fontFamily: terminal.options.fontFamily, - fontSize: terminal.options.fontSize, - fontWeight: terminal.options.fontWeight, - fontWeightBold: terminal.options.fontWeightBold, - allowTransparency: terminal.options.allowTransparency, + fontFamily: terminal.getOption('fontFamily'), + fontSize: terminal.getOption('fontSize'), + fontWeight: terminal.getOption('fontWeight') as FontWeight, + fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight, + allowTransparency: terminal.getOption('allowTransparency'), colors: clonedColors }; } @@ -40,8 +38,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean return false; } } - return a.type === b.type && - a.devicePixelRatio === b.devicePixelRatio && + return a.devicePixelRatio === b.devicePixelRatio && a.fontFamily === b.fontFamily && a.fontSize === b.fontSize && a.fontWeight === b.fontWeight && diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts index ee6dc665..1df4f82d 100644 --- a/src/renderer/webgl/atlas/Types.ts +++ b/src/renderer/webgl/atlas/Types.ts @@ -21,7 +21,6 @@ export interface IGlyphIdentifier { } export interface ICharAtlasConfig { - type: 'none' | 'static' | 'dynamic' | 'webgl'; devicePixelRatio: number; fontSize: number; fontFamily: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 7d79d959..8712c3d8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -895,6 +895,11 @@ declare module 'xterm' { * (EXPERIMENTAL) */ setRenderer(renderer: any): void; + screenElement: HTMLElement; + } + + export namespace Renderer { + const DEFAULT_COLOR: number; } /** From 061854b5356f972095579cfb7b35dfb73b178acc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 00:55:02 -0700 Subject: [PATCH 031/104] Remove more dependencies on rest of project --- src/renderer/webgl/GlyphRenderer.ts | 3 +- src/renderer/webgl/Lifecycle.test.ts | 45 ++ src/renderer/webgl/Lifecycle.ts | 47 +++ src/renderer/webgl/Platform.ts | 10 + src/renderer/webgl/RectangleRenderer.ts | 3 +- src/renderer/webgl/WebglRenderer.ts | 31 +- .../webgl/atlas/CharAtlasGenerator.ts | 2 +- .../webgl/renderLayer/BaseRenderLayer.ts | 384 ++++++++++++++++++ .../webgl/renderLayer/CursorRenderLayer.ts | 358 ++++++++++++++++ .../webgl/renderLayer/LinkRenderLayer.ts | 71 ++++ src/renderer/webgl/renderLayer/Types.ts | 65 +++ typings/xterm.d.ts | 41 +- 12 files changed, 1039 insertions(+), 21 deletions(-) create mode 100644 src/renderer/webgl/Lifecycle.test.ts create mode 100644 src/renderer/webgl/Lifecycle.ts create mode 100644 src/renderer/webgl/Platform.ts create mode 100644 src/renderer/webgl/renderLayer/BaseRenderLayer.ts create mode 100644 src/renderer/webgl/renderLayer/CursorRenderLayer.ts create mode 100644 src/renderer/webgl/renderLayer/LinkRenderLayer.ts create mode 100644 src/renderer/webgl/renderLayer/Types.ts diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index de9c0bf7..545a666e 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,6 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import { IRenderDimensions } from '../Types'; import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; @@ -12,7 +11,7 @@ import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../core/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; -import { IColorSet, Terminal, IBufferLine } from 'xterm'; +import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/Lifecycle.test.ts b/src/renderer/webgl/Lifecycle.test.ts new file mode 100644 index 00000000..4b696fa5 --- /dev/null +++ b/src/renderer/webgl/Lifecycle.test.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { Disposable } from './Lifecycle'; + +class TestDisposable extends Disposable { + public get isDisposed(): boolean { + return this._isDisposed; + } +} + +describe('Disposable', () => { + describe('register', () => { + it('should register disposables', () => { + const d = new TestDisposable(); + const d2 = { + dispose: () => { throw new Error(); } + }; + d.register(d2); + assert.throws(() => d.dispose()); + }); + }); + describe('unregister', () => { + it('should unregister disposables', () => { + const d = new TestDisposable(); + const d2 = { + dispose: () => { throw new Error(); } + }; + d.register(d2); + d.unregister(d2); + assert.doesNotThrow(() => d.dispose()); + }); + }); + describe('dispose', () => { + it('should set is disposed flag', () => { + const d = new TestDisposable(); + assert.isFalse(d.isDisposed); + d.dispose(); + assert.isTrue(d.isDisposed); + }); + }); +}); diff --git a/src/renderer/webgl/Lifecycle.ts b/src/renderer/webgl/Lifecycle.ts new file mode 100644 index 00000000..209a3e2a --- /dev/null +++ b/src/renderer/webgl/Lifecycle.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'xterm'; + +/** + * A base class that can be extended to provide convenience methods for managing the lifecycle of an + * object and its components. + */ +export abstract class Disposable implements IDisposable { + protected _disposables: IDisposable[] = []; + protected _isDisposed: boolean = false; + + constructor() { + } + + /** + * Disposes the object, triggering the `dispose` method on all registered IDisposables. + */ + public dispose(): void { + this._isDisposed = true; + this._disposables.forEach(d => d.dispose()); + this._disposables.length = 0; + } + + /** + * Registers a disposable object. + * @param d The disposable to register. + */ + public register(d: T): void { + this._disposables.push(d); + } + + /** + * Unregisters a disposable object if it has been registered, if not do + * nothing. + * @param d The disposable to unregister. + */ + public unregister(d: T): void { + const index = this._disposables.indexOf(d); + if (index !== -1) { + this._disposables.splice(index, 1); + } + } +} diff --git a/src/renderer/webgl/Platform.ts b/src/renderer/webgl/Platform.ts new file mode 100644 index 00000000..55a10b7f --- /dev/null +++ b/src/renderer/webgl/Platform.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const isNode = (typeof navigator === 'undefined') ? true : false; +const userAgent = (isNode) ? 'node' : navigator.userAgent; + +export const isFirefox = !!~userAgent.indexOf('Firefox'); +export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index ca4c062c..3fc8413c 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -3,14 +3,13 @@ * @license MIT */ -import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from './TypedArray'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, IColor, Terminal } from 'xterm'; +import { IColorSet, IColor, Terminal, IRenderDimensions } from 'xterm'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 2fadebbd..68f74c7b 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -3,22 +3,23 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, IRenderLayer, FLAGS } from '../Types'; +import { FLAGS } from '../Types'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; import { GlyphRenderer } from './GlyphRenderer'; -import { LinkRenderLayer } from '../LinkRenderLayer'; -import { CursorRenderLayer } from '../CursorRenderLayer'; +import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; +import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import WebglCharAtlas from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { Disposable } from '../../common/Lifecycle'; +import { Disposable } from './Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, Terminal } from 'xterm'; +import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; import { getLuminance } from './ColorUtils'; +import { IRenderLayer } from './renderLayer/Types'; export const INDICIES_PER_CELL = 4; @@ -106,8 +107,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Clear layers and force a full render this._renderLayers.forEach(l => { - l.setColors(this._core, this._colors); - l.reset(this._core); + l.setColors(this._terminal, this._colors); + l.reset(this._terminal); }); this._rectangleRenderer.setColors(); @@ -133,7 +134,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.onResize(); // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._core, this.dimensions)); + this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); // Resize the canvas this._canvas.width = this.dimensions.scaledCanvasWidth; @@ -155,15 +156,15 @@ export class WebglRenderer extends Disposable implements IRenderer { } public onBlur(): void { - this._renderLayers.forEach(l => l.onBlur(this._core)); + this._renderLayers.forEach(l => l.onBlur(this._terminal)); } public onFocus(): void { - this._renderLayers.forEach(l => l.onFocus(this._core)); + this._renderLayers.forEach(l => l.onFocus(this._terminal)); } public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { - this._renderLayers.forEach(l => l.onSelectionChanged(this._core, start, end, columnSelectMode)); + this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); this._updateSelectionModel(start, end); @@ -175,11 +176,11 @@ export class WebglRenderer extends Disposable implements IRenderer { } public onCursorMove(): void { - this._renderLayers.forEach(l => l.onCursorMove(this._core)); + this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); } public onOptionsChanged(): void { - this._renderLayers.forEach(l => l.onOptionsChanged(this._core)); + this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); this._updateDimensions(); this._refreshCharAtlas(); } @@ -204,7 +205,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } public clear(): void { - this._renderLayers.forEach(l => l.reset(this._core)); + this._renderLayers.forEach(l => l.reset(this._terminal)); } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { @@ -217,7 +218,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { // Update render layers - this._renderLayers.forEach(l => l.onGridChanged(this._core, start, end)); + this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts index 0d343a43..c01ea630 100644 --- a/src/renderer/webgl/atlas/CharAtlasGenerator.ts +++ b/src/renderer/webgl/atlas/CharAtlasGenerator.ts @@ -4,8 +4,8 @@ */ import { FontWeight, IColor } from 'xterm'; -import { isFirefox, isSafari } from '../../../common/Platform'; import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; +import { isFirefox, isSafari } from '../Platform'; /** * Generates a char atlas. diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts new file mode 100644 index 00000000..36c5bd53 --- /dev/null +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -0,0 +1,384 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IRenderLayer } from './Types'; +import { ICellData } from '../../../core/Types'; +import { DEFAULT_COLOR } from '../../../common/Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; +import BaseCharAtlas from '../atlas/BaseCharAtlas'; +import { acquireCharAtlas } from '../atlas/CharAtlasCache'; +import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../core/buffer/BufferLine'; +import { IColorSet, IRenderDimensions, Terminal } from 'xterm'; + +export abstract class BaseRenderLayer implements IRenderLayer { + private _canvas: HTMLCanvasElement; + protected _ctx: CanvasRenderingContext2D; + private _scaledCharWidth: number = 0; + private _scaledCharHeight: number = 0; + private _scaledCellWidth: number = 0; + private _scaledCellHeight: number = 0; + private _scaledCharLeft: number = 0; + private _scaledCharTop: number = 0; + + protected _charAtlas: BaseCharAtlas; + + /** + * An object that's reused when drawing glyphs in order to reduce GC. + */ + private _currentGlyphIdentifier: IGlyphIdentifier = { + chars: '', + code: 0, + bg: 0, + fg: 0, + bold: false, + dim: false, + italic: false + }; + + constructor( + private _container: HTMLElement, + id: string, + zIndex: number, + private _alpha: boolean, + protected _colors: IColorSet + ) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add(`xterm-${id}-layer`); + this._canvas.style.zIndex = zIndex.toString(); + this._initCanvas(); + this._container.appendChild(this._canvas); + } + + public dispose(): void { + this._container.removeChild(this._canvas); + if (this._charAtlas) { + this._charAtlas.dispose(); + } + } + + private _initCanvas(): void { + this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); + // Draw the background if this is an opaque layer + if (!this._alpha) { + this.clearAll(); + } + } + + public onOptionsChanged(terminal: Terminal): void {} + public onBlur(terminal: Terminal): void {} + public onFocus(terminal: Terminal): void {} + public onCursorMove(terminal: Terminal): void {} + public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {} + public onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} + + public setColors(terminal: Terminal, colorSet: IColorSet): void { + this._refreshCharAtlas(terminal, colorSet); + } + + protected setTransparency(terminal: Terminal, alpha: boolean): void { + // Do nothing when alpha doesn't change + if (alpha === this._alpha) { + return; + } + + // Create new canvas and replace old one + const oldCanvas = this._canvas; + this._alpha = alpha; + // Cloning preserves properties + this._canvas = this._canvas.cloneNode(); + this._initCanvas(); + this._container.replaceChild(this._canvas, oldCanvas); + + // Regenerate char atlas and force a full redraw + this._refreshCharAtlas(terminal, this._colors); + this.onGridChanged(terminal, 0, terminal.rows - 1); + } + + /** + * Refreshes the char atlas, aquiring a new one if necessary. + * @param terminal The terminal. + * @param colorSet The color set to use for the char atlas. + */ + private _refreshCharAtlas(terminal: Terminal, colorSet: IColorSet): void { + if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { + return; + } + this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas.warmUp(); + } + + public resize(terminal: Terminal, dim: IRenderDimensions): void { + this._scaledCellWidth = dim.scaledCellWidth; + this._scaledCellHeight = dim.scaledCellHeight; + this._scaledCharWidth = dim.scaledCharWidth; + this._scaledCharHeight = dim.scaledCharHeight; + this._scaledCharLeft = dim.scaledCharLeft; + this._scaledCharTop = dim.scaledCharTop; + this._canvas.width = dim.scaledCanvasWidth; + this._canvas.height = dim.scaledCanvasHeight; + this._canvas.style.width = `${dim.canvasWidth}px`; + this._canvas.style.height = `${dim.canvasHeight}px`; + + // Draw the background if this is an opaque layer + if (!this._alpha) { + this.clearAll(); + } + + this._refreshCharAtlas(terminal, this._colors); + } + + public abstract reset(terminal: Terminal): void; + + /** + * Fills 1+ cells completely. This uses the existing fillStyle on the context. + * @param x The column to start at. + * @param y The row to start at + * @param width The number of columns to fill. + * @param height The number of rows to fill. + */ + protected fillCells(x: number, y: number, width: number, height: number): void { + this._ctx.fillRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + width * this._scaledCellWidth, + height * this._scaledCellHeight); + } + + /** + * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void { + this._ctx.fillRect( + x * this._scaledCellWidth, + (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + width * this._scaledCellWidth, + window.devicePixelRatio); + } + + /** + * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected fillLeftLineAtCell(x: number, y: number): void { + this._ctx.fillRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + window.devicePixelRatio, + this._scaledCellHeight); + } + + /** + * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing + * strokeStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected strokeRectAtCell(x: number, y: number, width: number, height: number): void { + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.strokeRect( + x * this._scaledCellWidth + window.devicePixelRatio / 2, + y * this._scaledCellHeight + (window.devicePixelRatio / 2), + width * this._scaledCellWidth - window.devicePixelRatio, + (height * this._scaledCellHeight) - window.devicePixelRatio); + } + + /** + * Clears the entire canvas. + */ + protected clearAll(): void { + if (this._alpha) { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + } + } + + /** + * Clears 1+ cells completely. + * @param x The column to start at. + * @param y The row to start at. + * @param width The number of columns to clear. + * @param height The number of rows to clear. + */ + protected clearCells(x: number, y: number, width: number, height: number): void { + if (this._alpha) { + this._ctx.clearRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + width * this._scaledCellWidth, + height * this._scaledCellHeight); + } else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + width * this._scaledCellWidth, + height * this._scaledCellHeight); + } + } + + /** + * Draws a truecolor character at the cell. The character will be clipped to + * ensure that it fits with the cell, including the cell to the right if it's + * a wide character. This uses the existing fillStyle on the context. + * @param terminal The terminal. + * @param cell The cell data for the character to draw. + * @param x The column to draw at. + * @param y The row to draw at. + * @param color The color of the character. + */ + protected fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void { + this._ctx.font = this._getFont(terminal, false, false); + this._ctx.textBaseline = 'middle'; + this._clipRow(terminal, y); + this._ctx.fillText( + cell.getChars(), + x * this._scaledCellWidth + this._scaledCharLeft, + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + } + + /** + * Draws one or more characters at a cell. If possible this will draw using + * the character atlas to reduce draw time. + * @param terminal The terminal. + * @param chars The character or characters. + * @param code The character code. + * @param width The width of the characters. + * @param x The column to draw at. + * @param y The row to draw at. + * @param fg The foreground color, in the format stored within the attributes. + * @param bg The background color, in the format stored within the attributes. + * This is used to validate whether a cached image can be used. + * @param bold Whether the text is bold. + */ + protected drawChars(terminal: Terminal, cell: ICellData, x: number, y: number): void { + + // skip cache right away if we draw in RGB + // Note: to avoid bad runtime JoinedCellData will be skipped + // in the cache handler itself (atlasDidDraw == false) and + // fall through to uncached later down below + if (cell.isFgRGB() || cell.isBgRGB()) { + this._drawUncachedChars(terminal, cell, x, y); + return; + } + + let fg; + let bg; + if (cell.isInverse()) { + fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor(); + bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor(); + } else { + bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor(); + fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); + } + + const drawInBrightColor = terminal.getOption('drawBoldTextInBrightColors') && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + + fg += drawInBrightColor ? 8 : 0; + this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; + this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE; + this._currentGlyphIdentifier.bg = bg; + this._currentGlyphIdentifier.fg = fg; + this._currentGlyphIdentifier.bold = cell.isBold() && terminal.getOption('enableBold'); + this._currentGlyphIdentifier.dim = !!cell.isDim(); + this._currentGlyphIdentifier.italic = !!cell.isItalic(); + const atlasDidDraw = this._charAtlas && this._charAtlas.draw( + this._ctx, + this._currentGlyphIdentifier, + x * this._scaledCellWidth + this._scaledCharLeft, + y * this._scaledCellHeight + this._scaledCharTop + ); + + if (!atlasDidDraw) { + this._drawUncachedChars(terminal, cell, x, y); + } + } + + /** + * Draws one or more characters at one or more cells. The character(s) will be + * clipped to ensure that they fit with the cell(s), including the cell to the + * right if the last character is a wide character. + * @param terminal The terminal. + * @param chars The character. + * @param width The width of the character. + * @param fg The foreground color, in the format stored within the attributes. + * @param x The column to draw at. + * @param y The row to draw at. + */ + private _drawUncachedChars(terminal: Terminal, cell: ICellData, x: number, y: number): void { + this._ctx.save(); + this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.getOption('enableBold'), !!cell.isItalic()); + this._ctx.textBaseline = 'middle'; + + if (cell.isInverse()) { + if (cell.isBgDefault()) { + this._ctx.fillStyle = this._colors.background.css; + } else if (cell.isBgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; + } else { + this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + } + } else { + if (cell.isFgDefault()) { + this._ctx.fillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else { + let fg = cell.getFgColor(); + if (terminal.getOption('drawBoldTextInBrightColors') && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; + } + } + + this._clipRow(terminal, y); + + // Apply alpha to dim the character + if (cell.isDim()) { + this._ctx.globalAlpha = DIM_OPACITY; + } + // Draw the character + this._ctx.fillText( + cell.getChars(), + x * this._scaledCellWidth + this._scaledCharLeft, + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + this._ctx.restore(); + } + + /** + * Clips a row to ensure no pixels will be drawn outside the cells in the row. + * @param terminal The terminal. + * @param y The row to clip. + */ + private _clipRow(terminal: Terminal, y: number): void { + this._ctx.beginPath(); + this._ctx.rect( + 0, + y * this._scaledCellHeight, + terminal.cols * this._scaledCellWidth, + this._scaledCellHeight); + this._ctx.clip(); + } + + /** + * Gets the current font. + * @param terminal The terminal. + * @param isBold If we should use the bold fontWeight. + */ + protected _getFont(terminal: Terminal, isBold: boolean, isItalic: boolean): string { + const fontWeight = isBold ? terminal.getOption('fontWeightBold') : terminal.getOption('fontWeight'); + const fontStyle = isItalic ? 'italic' : ''; + + return `${fontStyle} ${fontWeight} ${terminal.getOption('fontSize') * window.devicePixelRatio}px ${terminal.getOption('fontFamily')}`; + } +} + diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts new file mode 100644 index 00000000..e51bdb7c --- /dev/null +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -0,0 +1,358 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { BaseRenderLayer } from './BaseRenderLayer'; +import { ICellData } from '../../../core/Types'; +import { CellData } from '../../../core/buffer/BufferLine'; + +interface ICursorState { + x: number; + y: number; + isFocused: boolean; + style: string; + width: number; +} + +/** + * The time between cursor blinks. + */ +const BLINK_INTERVAL = 600; + +export class CursorRenderLayer extends BaseRenderLayer { + private _state: ICursorState; + private _cursorRenderers: {[key: string]: (terminal: Terminal, x: number, y: number, cell: ICellData) => void}; + private _cursorBlinkStateManager: CursorBlinkStateManager; + private _cell: ICellData = new CellData(); + + constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { + super(container, 'cursor', zIndex, true, colors); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + this._cursorRenderers = { + 'bar': this._renderBarCursor.bind(this), + 'block': this._renderBlockCursor.bind(this), + 'underline': this._renderUnderlineCursor.bind(this) + }; + // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? + } + + public resize(terminal: Terminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); + // Resizing the canvas discards the contents of the canvas so clear state + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + } + + public reset(terminal: Terminal): void { + this._clearCursor(); + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + this.onOptionsChanged(terminal); + } + } + + public onBlur(terminal: Terminal): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.pause(); + } + terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + } + + public onFocus(terminal: Terminal): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.resume(terminal); + } else { + terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + } + } + + public onOptionsChanged(terminal: Terminal): void { + if (terminal.getOption('cursorBlink')) { + if (!this._cursorBlinkStateManager) { + this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => { + this._render(terminal, true); + }); + } + } else { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + } + // Request a refresh from the terminal as management of rendering is being + // moved back to the terminal + terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + } + } + + public onCursorMove(terminal: Terminal): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + } + + public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void { + if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + this._render(terminal, false); + } else { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + } + + private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void { + // Don't draw the cursor if it's hidden + // TODO: Need to expose API for this + if (!(terminal as any)._core.cursorState || (terminal as any)._core.cursorHidden) { + this._clearCursor(); + return; + } + + const cursorY = terminal.buffer.baseY + terminal.buffer.cursorY; + const viewportRelativeCursorY = cursorY - terminal.buffer.viewportY; + + // Don't draw the cursor if it's off-screen + if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { + this._clearCursor(); + return; + } + + // TODO: Need fast buffere API for loading cell + (terminal as any)._core.buffer.getLine(cursorY).loadCell(terminal.buffer.cursorX, this._cell); + if (this._cell.content === undefined) { + return; + } + + if (!isTerminalFocused(terminal)) { + this._clearCursor(); + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell); + this._ctx.restore(); + this._state.x = terminal.buffer.cursorX; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.getOption('cursorStyle'); + this._state.width = this._cell.getWidth(); + return; + } + + // Don't draw the cursor if it's blinking + if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + this._clearCursor(); + return; + } + + if (this._state) { + // The cursor is already in the correct spot, don't redraw + if (this._state.x === terminal.buffer.cursorX && + this._state.y === viewportRelativeCursorY && + this._state.isFocused === isTerminalFocused(terminal) && + this._state.style === terminal.getOption('cursorStyle') && + this._state.width === this._cell.getWidth()) { + return; + } + this._clearCursor(); + } + + this._ctx.save(); + this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell); + this._ctx.restore(); + + this._state.x = terminal.buffer.cursorX; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.getOption('cursorStyle'); + this._state.width = this._cell.getWidth(); + } + + private _clearCursor(): void { + if (this._state) { + this.clearCells(this._state.x, this._state.y, this._state.width, 1); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + } + } + + private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillLeftLineAtCell(x, y); + this._ctx.restore(); + } + + private _renderBlockCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillCells(x, y, cell.getWidth(), 1); + this._ctx.fillStyle = this._colors.cursorAccent.css; + this.fillCharTrueColor(terminal, cell, x, y); + this._ctx.restore(); + } + + private _renderUnderlineCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillBottomLineAtCells(x, y); + this._ctx.restore(); + } + + private _renderBlurCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.strokeStyle = this._colors.cursor.css; + this.strokeRectAtCell(x, y, cell.getWidth(), 1); + this._ctx.restore(); + } +} + +class CursorBlinkStateManager { + public isCursorVisible: boolean; + + private _animationFrame: number; + private _blinkStartTimeout: number; + private _blinkInterval: number; + + /** + * The time at which the animation frame was restarted, this is used on the + * next render to restart the timers so they don't need to restart the timers + * multiple times over a short period. + */ + private _animationTimeRestarted: number; + + constructor( + terminal: Terminal, + private _renderCallback: () => void + ) { + this.isCursorVisible = true; + if (isTerminalFocused(terminal)) { + this._restartInterval(); + } + } + + public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); } + + public dispose(): void { + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + } + + public restartBlinkAnimation(terminal: Terminal): void { + if (this.isPaused) { + return; + } + // Save a timestamp so that the restart can be done on the next interval + this._animationTimeRestarted = Date.now(); + // Force a cursor render to ensure it's visible and in the correct position + this.isCursorVisible = true; + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(() => { + this._renderCallback(); + this._animationFrame = null; + }); + } + } + + private _restartInterval(timeToStart: number = BLINK_INTERVAL): void { + // Clear any existing interval + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + } + + // Setup the initial timeout which will hide the cursor, this is done before + // the regular interval is setup in order to support restarting the blink + // animation in a lightweight way (without thrashing clearInterval and + // setInterval). + this._blinkStartTimeout = setTimeout(() => { + // Check if another animation restart was requested while this was being + // started + if (this._animationTimeRestarted) { + const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); + this._animationTimeRestarted = null; + if (time > 0) { + this._restartInterval(time); + return; + } + } + + // Hide the cursor + this.isCursorVisible = false; + this._animationFrame = window.requestAnimationFrame(() => { + this._renderCallback(); + this._animationFrame = null; + }); + + // Setup the blink interval + this._blinkInterval = setInterval(() => { + // Adjust the animation time if it was restarted + if (this._animationTimeRestarted) { + // calc time diff + // Make restart interval do a setTimeout initially? + const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); + this._animationTimeRestarted = null; + this._restartInterval(time); + return; + } + + // Invert visibility and render + this.isCursorVisible = !this.isCursorVisible; + this._animationFrame = window.requestAnimationFrame(() => { + this._renderCallback(); + this._animationFrame = null; + }); + }, BLINK_INTERVAL); + }, timeToStart); + } + + public pause(): void { + this.isCursorVisible = true; + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + } + + public resume(terminal: Terminal): void { + this._animationTimeRestarted = null; + this._restartInterval(); + this.restartBlinkAnimation(terminal); + } +} + +function isTerminalFocused(terminal: Terminal): boolean { + return document.activeElement === terminal.textarea && document.hasFocus(); +} diff --git a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts new file mode 100644 index 00000000..eb47f7e5 --- /dev/null +++ b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ILinkifierEvent, ILinkifierAccessor } from '../../../Types'; +import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { BaseRenderLayer } from './BaseRenderLayer'; +import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { is256Color } from '../atlas/CharAtlasUtils'; + +export class LinkRenderLayer extends BaseRenderLayer { + private _state: ILinkifierEvent = null; + + constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { + super(container, 'link', zIndex, true, colors); + // TODO: Need to expose link-related renderer API + terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); + terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); + } + + public resize(terminal: Terminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); + // Resizing the canvas discards the contents of the canvas so clear state + this._state = null; + } + + public reset(terminal: Terminal): void { + this._clearCurrentLink(); + } + + private _clearCurrentLink(): void { + if (this._state) { + this.clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1); + const middleRowCount = this._state.y2 - this._state.y1 - 1; + if (middleRowCount > 0) { + this.clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount); + } + this.clearCells(0, this._state.y2, this._state.x2, 1); + this._state = null; + } + } + + private _onLinkHover(e: ILinkifierEvent): void { + if (e.fg === INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this._colors.background.css; + } else if (is256Color(e.fg)) { + // 256 color support + this._ctx.fillStyle = this._colors.ansi[e.fg].css; + } else { + this._ctx.fillStyle = this._colors.foreground.css; + } + + if (e.y1 === e.y2) { + // Single line link + this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1); + } else { + // Multi-line link + this.fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1); + for (let y = e.y1 + 1; y < e.y2; y++) { + this.fillBottomLineAtCells(0, y, e.cols); + } + this.fillBottomLineAtCells(0, e.y2, e.x2); + } + this._state = e; + } + + private _onLinkLeave(e: ILinkifierEvent): void { + this._clearCurrentLink(); + } +} diff --git a/src/renderer/webgl/renderLayer/Types.ts b/src/renderer/webgl/renderLayer/Types.ts new file mode 100644 index 00000000..61e69f38 --- /dev/null +++ b/src/renderer/webgl/renderLayer/Types.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable, IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { ICharacterJoiner } from '../../Types'; + +export interface IRenderLayer extends IDisposable { + /** + * Called when the terminal loses focus. + */ + onBlur(terminal: Terminal): void; + + /** + * * Called when the terminal gets focus. + */ + onFocus(terminal: Terminal): void; + + /** + * Called when the cursor is moved. + */ + onCursorMove(terminal: Terminal): void; + + /** + * Called when options change. + */ + onOptionsChanged(terminal: Terminal): void; + + /** + * Called when the theme changes. + */ + setColors(terminal: Terminal, colorSet: IColorSet): void; + + /** + * Called when the data in the grid has changed (or needs to be rendered + * again). + */ + onGridChanged(terminal: Terminal, startRow: number, endRow: number): void; + + /** + * Calls when the selection changes. + */ + onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void; + + /** + * Registers a handler to join characters to render as a group + */ + registerCharacterJoiner?(joiner: ICharacterJoiner): void; + + /** + * Deregisters the specified character joiner handler + */ + deregisterCharacterJoiner?(joinerId: number): void; + + /** + * Resize the render layer. + */ + resize(terminal: Terminal, dim: IRenderDimensions): void; + + /** + * Clear the state of the render layer. + */ + reset(terminal: Terminal): void; +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 8712c3d8..a48e8a21 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -894,12 +894,51 @@ declare module 'xterm' { /** * (EXPERIMENTAL) */ - setRenderer(renderer: any): void; + setRenderer(renderer: IRenderer): void; screenElement: HTMLElement; } export namespace Renderer { const DEFAULT_COLOR: number; + const NULL_CELL_CODE: number; + const WHITESPACE_CELL_CODE: number; + const DEFAULT_ATTR: number; + const DEFAULT_ANSI_COLORS: string[]; + const FLAGS: any; + } + + export interface IRenderer extends IDisposable { + readonly dimensions: IRenderDimensions; + + dispose(): void; + setColors(colors: IColorSet): void; + onDevicePixelRatioChange(): void; + onResize(cols: number, rows: number): void; + onCharSizeChanged(): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onCursorMove(): void; + onOptionsChanged(): void; + clear(): void; + renderRows(start: number, end: number): void; + registerCharacterJoiner(handler: (text: string) => [number, number][]): number; + deregisterCharacterJoiner(joinerId: number): boolean; + } + + export interface IRenderDimensions { + scaledCharWidth: number; + scaledCharHeight: number; + scaledCellWidth: number; + scaledCellHeight: number; + scaledCharLeft: number; + scaledCharTop: number; + scaledCanvasWidth: number; + scaledCanvasHeight: number; + canvasWidth: number; + canvasHeight: number; + actualCellWidth: number; + actualCellHeight: number; } /** From 20dfaae500654cd9fef72ecfcfc846a9d5876b9b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 00:59:21 -0700 Subject: [PATCH 032/104] Fix runtime error --- src/renderer/webgl/renderLayer/CursorRenderLayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts index e51bdb7c..161606bd 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -129,8 +129,8 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } - // TODO: Need fast buffere API for loading cell - (terminal as any)._core.buffer.getLine(cursorY).loadCell(terminal.buffer.cursorX, this._cell); + // TODO: Need fast buffer API for loading cell + (terminal as any)._core.buffer.lines.get(cursorY).loadCell(terminal.buffer.cursorX, this._cell); if (this._cell.content === undefined) { return; } From 510985fb4e6c855d4ea709c1d8ef42333aaa47e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 01:49:42 -0700 Subject: [PATCH 033/104] Add a basic test --- src/public/Terminal.api.ts | 9 +++++++++ src/renderer/dom/DomRenderer.ts | 1 + src/renderer/webgl/WebglRenderer.ts | 4 ++-- src/renderer/webgl/renderLayer/Types.ts | 3 +-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index d8325229..80ba489d 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -475,6 +475,15 @@ describe('API Integration Tests', () => { }); }); }); + + describe('WebGL Renderer', () => { + it('load doesn\'t throw', async function(): Promise { + this.timeout(10000); + await openTerminal({ rendererType: 'dom' }); + await page.waitForSelector('.xterm style'); + await page.evaluate(`window.term.loadWebgl();`); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 2797639d..16f02549 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -69,6 +69,7 @@ export class DomRenderer extends Disposable implements IRenderer { actualCellHeight: null }; this._updateDimensions(); + this._injectCss(); this._rowFactory = new DomRendererRowFactory(_terminal.options, document); diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 68f74c7b..b9690433 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -4,7 +4,7 @@ */ import { FLAGS } from '../Types'; -import { CharacterJoinerHandler, ITerminal } from '../../Types'; +import { ITerminal } from '../../Types'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; @@ -208,7 +208,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.reset(this._terminal)); } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { + public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { return -1; } diff --git a/src/renderer/webgl/renderLayer/Types.ts b/src/renderer/webgl/renderLayer/Types.ts index 61e69f38..50a76a2c 100644 --- a/src/renderer/webgl/renderLayer/Types.ts +++ b/src/renderer/webgl/renderLayer/Types.ts @@ -4,7 +4,6 @@ */ import { IDisposable, IRenderDimensions, IColorSet, Terminal } from 'xterm'; -import { ICharacterJoiner } from '../../Types'; export interface IRenderLayer extends IDisposable { /** @@ -46,7 +45,7 @@ export interface IRenderLayer extends IDisposable { /** * Registers a handler to join characters to render as a group */ - registerCharacterJoiner?(joiner: ICharacterJoiner): void; + registerCharacterJoiner?(handler: (text: string) => [number, number][]): void; /** * Deregisters the specified character joiner handler From 250683364f87621296850fd4a82fc472ae99583d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 02:23:07 -0700 Subject: [PATCH 034/104] Use more API in WebglRenderer --- src/renderer/webgl/WebglRenderer.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index b9690433..7cf68464 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -122,7 +122,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // and the terminal needs to refreshed if (this._devicePixelRatio !== window.devicePixelRatio) { this._devicePixelRatio = window.devicePixelRatio; - this.onResize(this._core.cols, this._core.rows); + this.onResize(this._terminal.cols, this._terminal.rows); } } @@ -130,7 +130,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update character and canvas dimensions this._updateDimensions(devicePixelRatio); - this._model.resize(this._core.cols, this._core.rows); + this._model.resize(this._terminal.cols, this._terminal.rows); this._rectangleRenderer.onResize(); // Resize all render layers @@ -172,7 +172,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.updateSelection(this._model, columnSelectMode); // TODO: #2102 Should this move to RenderCoordinator? - this._core.refresh(0, this._core.rows - 1); + this._core.refresh(0, this._terminal.rows - 1); } public onCursorMove(): void { @@ -293,7 +293,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _updateSelectionModel(start: [number, number], end: [number, number]): void { - const terminal = this._core; + const terminal = this._terminal; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { @@ -302,8 +302,8 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.ydisp; - const viewportEndRow = end[1] - terminal.buffer.ydisp; + const viewportStartRow = start[1] - terminal.buffer.viewportY; + const viewportEndRow = end[1] - terminal.buffer.viewportY; const viewportCappedStartRow = Math.max(viewportStartRow, 0); const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); @@ -349,23 +349,23 @@ export class WebglRenderer 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._core.options.lineHeight); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.getOption('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._core.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCharTop = this._terminal.getOption('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._core.options.letterSpacing); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.getOption('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._core.options.letterSpacing / 2); + this.dimensions.scaledCharLeft = Math.floor(this._terminal.getOption('letterSpacing') / 2); // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas - this.dimensions.scaledCanvasHeight = this._core.rows * this.dimensions.scaledCellHeight; - this.dimensions.scaledCanvasWidth = this._core.cols * this.dimensions.scaledCellWidth; + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; // The the size of the canvas on the page. It's very important that this // rounds to nearest integer and not ceils as browsers often set From 4beea9b7048db193cc1d496e515c9b16578b162b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 15:44:13 -0700 Subject: [PATCH 035/104] Add tests for webgl bg colors --- src/public/Terminal.api.ts | 9 -- src/public/Terminal.ts | 4 +- src/renderer/webgl/WebglRenderer.api.ts | 136 +++++++++++++++++++++++ src/renderer/webgl/WebglRenderer.ts | 10 +- src/renderer/webgl/WebglRendererAddon.ts | 6 +- 5 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 src/renderer/webgl/WebglRenderer.api.ts diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 80ba489d..d8325229 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -475,15 +475,6 @@ describe('API Integration Tests', () => { }); }); }); - - describe('WebGL Renderer', () => { - it('load doesn\'t throw', async function(): Promise { - this.timeout(10000); - await openTerminal({ rendererType: 'dom' }); - await page.waitForSelector('.xterm style'); - await page.evaluate(`window.term.loadWebgl();`); - }); - }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 3af5162c..ace272d8 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -188,8 +188,8 @@ export class Terminal implements ITerminalApi { public setRenderer(renderer: any): void { this._core.setRenderer(renderer); } - public loadWebgl(): void { - this.loadAddon(new WebglRendererAddon()); + public loadWebgl(preserveDrawingBuffer?: boolean): void { + this.loadAddon(new WebglRendererAddon(preserveDrawingBuffer)); } public static get strings(): ILocalizableStrings { return Strings; diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/src/renderer/webgl/WebglRenderer.api.ts new file mode 100644 index 00000000..0eb111e5 --- /dev/null +++ b/src/renderer/webgl/WebglRenderer.api.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; +import { assert } from 'chai'; +import { ITerminalOptions } from '../../Types'; + +const APP = 'http://127.0.0.1:3000/test'; + +let browser: puppeteer.Browser; +let page: puppeteer.Page; +const width = 800; +const height = 600; + +describe('WebGL Renderer Integration Tests', () => { + before(async function(): Promise { + this.timeout(10000); + browser = await puppeteer.launch({ + headless: process.argv.indexOf('--headless') !== -1, + slowMo: 80, + args: [`--window-size=${width},${height}`] + }); + page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + }); + + after(() => { + browser.close(); + }); + + beforeEach(async () => { + await page.goto(APP); + }); + + describe('WebGL Renderer', () => { + it.only('background colors normal', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + } + }); + // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); + await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + }); + + it.only('background colors bright', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + } + }); + // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); + await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + }); + }); +}); + +async function openTerminal(options: ITerminalOptions = {}): Promise { + await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + if (options.rendererType === 'dom') { + await page.waitForSelector('.xterm-rows'); + } else { + await page.waitForSelector('.xterm-text-layer'); + } +} + +async function writeSync(data: string): Promise { + await page.evaluate(`window.term.write('${data}');`); + while (true) { + if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) { + break; + } + } +} + +// async function getPixelAt(x: number, y: number): Promise { +// await page.evaluate(` +// window.gl = window.term._core._renderCoordinator._renderer._gl; +// window.result = new Uint8Array(4); +// window.gl.readPixels(${x}, window.gl.drawingBufferHeight - 1 - ${y}, 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result); +// `); +// return await page.evaluate(`Array.from(window.result)`); +// } + +async function getCellBgColor(col: number, row: number): Promise { + await page.evaluate(` + window.gl = window.term._core._renderCoordinator._renderer._gl; + window.result = new Uint8Array(4); + window.d = window.term._core._renderCoordinator.dimensions; + window.gl.readPixels( + Math.floor(${col - 1} * window.d.scaledCellWidth), + Math.floor(window.gl.drawingBufferHeight - 1 - ${row - 1} * window.d.scaledCellHeight), + 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result + ); + `); + return await page.evaluate(`Array.from(window.result)`); +} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 7cf68464..1ef53cfa 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -41,7 +41,8 @@ export class WebglRenderer extends Disposable implements IRenderer { constructor( private _terminal: Terminal, - private _colors: IColorSet + private _colors: IColorSet, + preserveDrawingBuffer?: boolean ) { super(); @@ -71,7 +72,12 @@ export class WebglRenderer extends Disposable implements IRenderer { this._updateDimensions(); this._canvas = document.createElement('canvas'); - const contextAttributes = { antialias: false, depth: false }; + + const contextAttributes = { + antialias: false, + depth: false, + preserveDrawingBuffer + }; this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext; if (!this._gl) { throw new Error('WebGL2 not supported'); diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 6c835ba8..6ce27b0a 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -9,14 +9,16 @@ import { WebglRenderer } from './WebglRenderer'; export class WebglRendererAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - constructor() {} + constructor( + private _preserveDrawingBuffer?: boolean + ) {} public activate(terminal: Terminal): void { if (!terminal.element) { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors)); + this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); } public dispose(): void { From b79d73d7c0e39f6d148e09bcfee6624b9cf3cbd4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 15:54:49 -0700 Subject: [PATCH 036/104] Add foreground tests --- src/renderer/webgl/WebglRenderer.api.ts | 100 ++++++++++++++++++------ 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/src/renderer/webgl/WebglRenderer.api.ts index 0eb111e5..d2dc06f0 100644 --- a/src/renderer/webgl/WebglRenderer.api.ts +++ b/src/renderer/webgl/WebglRenderer.api.ts @@ -35,7 +35,7 @@ describe('WebGL Renderer Integration Tests', () => { }); describe('WebGL Renderer', () => { - it.only('background colors normal', async function(): Promise { + it('foreground colors normal', async function(): Promise { this.timeout(10000); await openTerminal({ rendererType: 'dom', @@ -50,20 +50,73 @@ describe('WebGL Renderer Integration Tests', () => { white: '#161718' } }); - // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); - await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await writeSync(`\\x1b[30mâ–ˆ\\x1b[31mâ–ˆ\\x1b[32mâ–ˆ\\x1b[33mâ–ˆ\\x1b[34mâ–ˆ\\x1b[35mâ–ˆ\\x1b[36mâ–ˆ\\x1b[37mâ–ˆ`); await page.evaluate(`window.term.loadWebgl(true);`); - assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it.only('background colors bright', async function(): Promise { + it('foreground colors bright', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + } + }); + await writeSync(`\\x1b[90mâ–ˆ\\x1b[91mâ–ˆ\\x1b[92mâ–ˆ\\x1b[93mâ–ˆ\\x1b[94mâ–ˆ\\x1b[95mâ–ˆ\\x1b[96mâ–ˆ\\x1b[97mâ–ˆ`); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + }); + + it('background colors normal', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + } + }); + await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + }); + + it('background colors bright', async function(): Promise { this.timeout(10000); await openTerminal({ rendererType: 'dom', @@ -78,17 +131,16 @@ describe('WebGL Renderer Integration Tests', () => { brightWhite: '#161718' } }); - // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); await page.evaluate(`window.term.loadWebgl(true);`); - assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); }); }); @@ -121,14 +173,14 @@ async function writeSync(data: string): Promise { // return await page.evaluate(`Array.from(window.result)`); // } -async function getCellBgColor(col: number, row: number): Promise { +async function getCellColor(col: number, row: number): Promise { await page.evaluate(` window.gl = window.term._core._renderCoordinator._renderer._gl; window.result = new Uint8Array(4); window.d = window.term._core._renderCoordinator.dimensions; window.gl.readPixels( - Math.floor(${col - 1} * window.d.scaledCellWidth), - Math.floor(window.gl.drawingBufferHeight - 1 - ${row - 1} * window.d.scaledCellHeight), + Math.floor((${col - 0.5}) * window.d.scaledCellWidth), + Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.scaledCellHeight), 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result ); `); From 493e8ad758ecb1f119a8bef3a5f8ad64c6273f1b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 18:06:25 -0700 Subject: [PATCH 037/104] Remove webgl dependence on DEFAULT_ANSI_COLORS --- src/renderer/webgl/atlas/CharAtlasUtils.ts | 2 +- src/renderer/webgl/atlas/WebglCharAtlas.ts | 7 +++---- src/ui/ColorManager.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index 2bda420f..9bbd614a 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -17,7 +17,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number selection: null, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. - ansi: colors.ansi.slice(0, 16) + ansi: colors.ansi.slice() }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index a9cd8e7d..85496aa1 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -11,7 +11,6 @@ import { is256Color } from './CharAtlasUtils'; import { clearColor } from './CharAtlasGenerator'; import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { DEFAULT_ANSI_COLORS } from '../../../ui/ColorManager'; import { IColor } from 'xterm'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, @@ -158,10 +157,10 @@ export default class WebglCharAtlas extends BaseCharAtlas { } private _getColorFromAnsiIndex(idx: number): IColor { - if (idx < this._config.colors.ansi.length) { - return this._config.colors.ansi[idx]; + if (idx >= this._config.colors.ansi.length) { + throw new Error('No color found for idx ' + idx); } - return DEFAULT_ANSI_COLORS[idx]; + return this._config.colors.ansi[idx]; } private _getBackgroundColor(bg: number): IColor { diff --git a/src/ui/ColorManager.ts b/src/ui/ColorManager.ts index 923b2823..276b6e14 100644 --- a/src/ui/ColorManager.ts +++ b/src/ui/ColorManager.ts @@ -16,7 +16,7 @@ const DEFAULT_SELECTION = { // An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy // and mutate that. -export const DEFAULT_ANSI_COLORS = (() => { +export const DEFAULT_ANSI_COLORS: IColor[] = (() => { const colors = [ // dark: fromHex('#2e3436'), From 5b86f9291e9303d7981a4a475eaff7ca2a31dd2c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 31 May 2019 18:10:46 -0700 Subject: [PATCH 038/104] Fix conflicts in webgl renderer --- demo/client.ts | 6 +----- src/renderer/webgl/GlyphRenderer.ts | 4 ++-- src/renderer/webgl/atlas/WebglCharAtlas.ts | 18 +++++++++--------- .../webgl/renderLayer/BaseRenderLayer.ts | 4 ++-- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index ede79334..10d27c86 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -218,7 +218,7 @@ function initOptions(term: TerminalType): void { fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - rendererType: ['dom', 'canvas', 'webgl'] + rendererType: ['dom', 'canvas'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; @@ -286,10 +286,6 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); - if (o === 'rendererType' && input.value === 'webgl') { - term.setOption('experimentalCharAtlas', 'webgl'); - setTimeout(() => (document.getElementById(`opt-experimentalCharAtlas`)).value = 'webgl', 0); - } term.setOption(o, input.value); }); }); diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 545a666e..4a7c44e0 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -182,9 +182,9 @@ export class GlyphRenderer { let rasterizedGlyph: IRasterizedGlyph; if (chars && chars.length > 1) { - rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.getOption('enableBold')); + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.getOption('enableBold')); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg); } // Fill empty if no glyph was found diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 85496aa1..6832d4dc 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -96,7 +96,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { protected _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR, true); + const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR); this._cacheMap[i] = { [DEFAULT_ATTR]: rasterizedGlyph }; @@ -116,7 +116,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { return false; } - public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMapCombined[chars]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; @@ -124,7 +124,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(chars, attr, bg, fg, enableBold); + rasterizedGlyph = this._drawToCache(chars, attr, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -133,7 +133,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { /** * Gets the glyphs texture coords, drawing the texture if it's not already */ - public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMap[code]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; @@ -141,7 +141,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(code, attr, bg, fg, enableBold); + rasterizedGlyph = this._drawToCache(code, attr, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -186,16 +186,16 @@ export default class WebglCharAtlas extends BaseCharAtlas { return this._config.colors.foreground; } - private _drawToCache(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; - private _drawToCache(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; - private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + private _drawToCache(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; this.hasCanvasChanged = true; const flags = attr >> 18; - const bold = !!(flags & FLAGS.BOLD) && enableBold; + const bold = !!(flags & FLAGS.BOLD); const dim = !!(flags & FLAGS.DIM); const italic = !!(flags & FLAGS.ITALIC); diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index 36c5bd53..6d20c614 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -287,7 +287,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE; this._currentGlyphIdentifier.bg = bg; this._currentGlyphIdentifier.fg = fg; - this._currentGlyphIdentifier.bold = cell.isBold() && terminal.getOption('enableBold'); + this._currentGlyphIdentifier.bold = !!cell.isBold(); this._currentGlyphIdentifier.dim = !!cell.isDim(); this._currentGlyphIdentifier.italic = !!cell.isItalic(); const atlasDidDraw = this._charAtlas && this._charAtlas.draw( @@ -315,7 +315,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ private _drawUncachedChars(terminal: Terminal, cell: ICellData, x: number, y: number): void { this._ctx.save(); - this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.getOption('enableBold'), !!cell.isItalic()); + this._ctx.font = this._getFont(terminal, !!cell.isBold(), !!cell.isItalic()); this._ctx.textBaseline = 'middle'; if (cell.isInverse()) { From e05ae83d6a0255de50440202ddbe488c186d683f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 6 Jun 2019 02:31:41 +0200 Subject: [PATCH 039/104] inital integration of xterm-benchmark --- benchmark.json | 11 + bin/benchmark.js | 53 +++ package.json | 6 +- .../parser/EscapeSequenceParser.benchmark.ts | 251 ++++++++++ yarn.lock | 441 +++++++++++++++++- 5 files changed, 756 insertions(+), 6 deletions(-) create mode 100644 benchmark.json create mode 100644 bin/benchmark.js create mode 100644 src/core/parser/EscapeSequenceParser.benchmark.ts diff --git a/benchmark.json b/benchmark.json new file mode 100644 index 00000000..4d70f0ab --- /dev/null +++ b/benchmark.json @@ -0,0 +1,11 @@ +{ + "evalConfig": { + "tolerance": { + "*": [0.75, 1.5] + }, + "skip": [ + "*.median", + "*.runs" + ] + } +} \ No newline at end of file diff --git a/bin/benchmark.js b/bin/benchmark.js new file mode 100644 index 00000000..a03f6e16 --- /dev/null +++ b/bin/benchmark.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const cp = require('child_process'); +const path = require('path'); +const glob = require('glob'); + +// Add `out` to the NODE_PATH so absolute paths can be resolved. +const env = { ...process.env }; +env.NODE_PATH = path.resolve(__dirname, '../out'); + +/** + * Default commands for yarn: + * yarn benchmark single single run of all benchmarks without statistics + * yarn benchmark baseline 10 runs of all benchmarks with baseline statistics + * yarn benchmark eval 10 runs of all benchmarks with eval against last baseline + */ +const commands = { + single : '-c benchmark.json', + baseline: '--baseline -r 10 -c benchmark.json', + eval : '--eval -r 10 -c benchmark.json' +} + +let testFiles = [ + './out/**/*benchmark.js' +]; + +// allow overriding cmdline args (see yarn benchmark --help) +if (process.argv.length === 3 && process.argv[2] in commands) { + testFiles.push(commands[process.argv[2]]); +} else if (process.argv.length > 2) { + testFiles = process.argv.slice(2); +} + +cp.spawnSync( + path.resolve(__dirname, '../node_modules/.bin/xterm-benchmark'), + testFiles.reduce((accu, cur) => { + const expanded = glob.sync(cur); + if (!expanded.length) { + accu.push(cur); + return accu; + } + return accu.concat(expanded); + }, []), + { + cwd: path.resolve(__dirname, '..'), + env, + stdio: 'inherit', + shell: true + } +); diff --git a/package.json b/package.json index 16ebca92..8ad844e9 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "prepare": "npm run build", "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", - "clean": "rm -rf lib out addons/*/lib" + "clean": "rm -rf lib out addons/*/lib", + "benchmark": "node ./bin/benchmark.js" }, "devDependencies": { "@types/chai": "^3.4.34", @@ -47,6 +48,7 @@ "utf8": "^3.0.0", "webpack": "^4.17.1", "webpack-cli": "^3.1.0", - "ws": "^7.0.0" + "ws": "^7.0.0", + "xterm-benchmark": "../xterm-benchmark" } } diff --git a/src/core/parser/EscapeSequenceParser.benchmark.ts b/src/core/parser/EscapeSequenceParser.benchmark.ts new file mode 100644 index 00000000..9494f6cf --- /dev/null +++ b/src/core/parser/EscapeSequenceParser.benchmark.ts @@ -0,0 +1,251 @@ +import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-benchmark'; + +import { EscapeSequenceParser } from 'core/parser/EscapeSequenceParser'; +import { C0, C1 } from 'common/data/EscapeSequences'; +import { IDcsHandler } from './Types'; + + +function toUtf32(s: string) { + const result = new Uint32Array(s.length); + for (let i = 0; i < s.length; ++i) { + result[i] = s.charCodeAt(i); + } + return result; +} + + +perfContext('Parser performance - 50MB data', () => { + let content; + let taContent: Uint32Array; + let parser: EscapeSequenceParser; + let dcsHandler: IDcsHandler = { + hook: (collect, params, flag) => {}, + put: (data, start, end) => {}, + unhook: () => {} + }; + beforeEach(() => { + parser = new EscapeSequenceParser(); + parser.setPrintHandler((data, start, end) => {}); + parser.setCsiHandler('@', (params, collect) => {}); + parser.setCsiHandler('A', (params, collect) => {}); + parser.setCsiHandler('B', (params, collect) => {}); + parser.setCsiHandler('C', (params, collect) => {}); + parser.setCsiHandler('D', (params, collect) => {}); + parser.setCsiHandler('E', (params, collect) => {}); + parser.setCsiHandler('F', (params, collect) => {}); + parser.setCsiHandler('G', (params, collect) => {}); + parser.setCsiHandler('H', (params, collect) => {}); + parser.setCsiHandler('I', (params, collect) => {}); + parser.setCsiHandler('J', (params, collect) => {}); + parser.setCsiHandler('K', (params, collect) => {}); + parser.setCsiHandler('L', (params, collect) => {}); + parser.setCsiHandler('M', (params, collect) => {}); + parser.setCsiHandler('P', (params, collect) => {}); + parser.setCsiHandler('S', (params, collect) => {}); + parser.setCsiHandler('T', (params, collect) => {}); + parser.setCsiHandler('X', (params, collect) => {}); + parser.setCsiHandler('Z', (params, collect) => {}); + parser.setCsiHandler('`', (params, collect) => {}); + parser.setCsiHandler('a', (params, collect) => {}); + parser.setCsiHandler('b', (params, collect) => {}); + parser.setCsiHandler('c', (params, collect) => {}); + parser.setCsiHandler('d', (params, collect) => {}); + parser.setCsiHandler('e', (params, collect) => {}); + parser.setCsiHandler('f', (params, collect) => {}); + parser.setCsiHandler('g', (params, collect) => {}); + parser.setCsiHandler('h', (params, collect) => {}); + parser.setCsiHandler('l', (params, collect) => {}); + parser.setCsiHandler('m', (params, collect) => {}); + parser.setCsiHandler('n', (params, collect) => {}); + parser.setCsiHandler('p', (params, collect) => {}); + parser.setCsiHandler('q', (params, collect) => {}); + parser.setCsiHandler('r', (params, collect) => {}); + parser.setCsiHandler('s', (params, collect) => {}); + parser.setCsiHandler('u', (params, collect) => {}); + parser.setExecuteHandler(C0.BEL, () => {}); + parser.setExecuteHandler(C0.LF, () => {}); + parser.setExecuteHandler(C0.VT, () => {}); + parser.setExecuteHandler(C0.FF, () => {}); + parser.setExecuteHandler(C0.CR, () => {}); + parser.setExecuteHandler(C0.BS, () => {}); + parser.setExecuteHandler(C0.HT, () => {}); + parser.setExecuteHandler(C0.SO, () => {}); + parser.setExecuteHandler(C0.SI, () => {}); + parser.setExecuteHandler(C1.IND, () => {}); + parser.setExecuteHandler(C1.NEL, () => {}); + parser.setExecuteHandler(C1.HTS, () => {}); + parser.setOscHandler(0, (data) => {}); + parser.setOscHandler(2, (data) => {}); + parser.setEscHandler('7', () => {}); + parser.setEscHandler('8', () => {}); + parser.setEscHandler('D', () => {}); + parser.setEscHandler('E', () => {}); + parser.setEscHandler('H', () => {}); + parser.setEscHandler('M', () => {}); + parser.setEscHandler('=', () => {}); + parser.setEscHandler('>', () => {}); + parser.setEscHandler('c', () => {}); + parser.setEscHandler('n', () => {}); + parser.setEscHandler('o', () => {}); + parser.setEscHandler('|', () => {}); + parser.setEscHandler('}', () => {}); + parser.setEscHandler('~', () => {}); + parser.setEscHandler('%@', () => {}); + parser.setEscHandler('%G', () => {}); + parser.setDcsHandler('q', dcsHandler); + }); + + perfContext('print - a', () => { + before(() => { + let data = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', async () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('execute - \\n', () => { + before(() => { + let data = '\n\n\n\n\n\n\n'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('escape - ESC E', () => { + before(() => { + let data = '\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('escape with collect - ESC % G', () => { + before(() => { + let data = '\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('simple csi - CSI A', () => { + before(() => { + let data = '\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('csi with collect - CSI ? p', () => { + before(() => { + let data = '\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('csi with params - CSI 1;2 m', () => { + before(() => { + let data = '\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('osc (small payload) - OSC 0;hi ST', () => { + before(() => { + let data = '\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\x1b]0;hi\x1b\\'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('osc (big payload) - OSC 0; ST', () => { + before(() => { + let data = '\x1b]0;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS (small payload)', () => { + before(() => { + let data = '\x1bPq~~\x1b\\'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', async () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS (big payload)', () => { + before(() => { + let data = '\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\'; + content = ''; + while (content.length < 50000000) + content += data; + taContent = toUtf32(content); + }); + new ThroughputRuntimeCase('throughput', async () => { + parser.parse(taContent, taContent.length); + return {payloadSize: taContent.length}; + }, {fork: true}).showAverageThroughput(); + }); +}); diff --git a/yarn.lock b/yarn.lock index 61a70629..a6857edf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,22 @@ # yarn lockfile v1 +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + "@fimbul/bifrost@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@fimbul/bifrost/-/bifrost-0.11.0.tgz#83cacc21464198b12e3cc1c2204ae6c6d7afd158" @@ -21,16 +37,33 @@ reflect-metadata "^0.1.12" tslib "^1.8.1" +"@types/app-root-path@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.4.tgz#a78b703282b32ac54de768f5512ecc3569919dc7" + integrity sha1-p4twMoKzKsVN52j1US7MNWmRncc= + "@types/chai@^3.4.34": version "3.5.2" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-3.5.2.tgz#c11cd2817d3a401b7ba0f5a420f35c56139b1c1e" integrity sha1-wRzSgX06QBt7oPWkIPNcVhObHB4= +"@types/cli-table@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@types/cli-table/-/cli-table-0.3.0.tgz#f1857156bf5fd115c6a2db260ba0be1f8fc5671c" + integrity sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ== + "@types/events@*": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== +"@types/fs-extra@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-7.0.0.tgz#9c4ad9e1339e7448a76698829def1f159c1b636c" + integrity sha512-ndoMMbGyuToTy4qB6Lex/inR98nPiNHacsgMPvy+zqMLgSxbt8VtWpDArpGp69h1fEDQHn1KB+9DWD++wgbwYA== + dependencies: + "@types/node" "*" + "@types/glob@^5.0.35": version "5.0.35" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" @@ -49,6 +82,13 @@ "@types/tough-cookie" "*" parse5 "^3.0.2" +"@types/mathjs@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/mathjs/-/mathjs-5.0.1.tgz#b98e163ea396b4f27bec20ee25ffb8fe9e656af8" + integrity sha512-EFBuueI+BRed9bnUO6/9my55b4FH+VQIvqMm58h9JGbtaGCkqr3YSDhnmVbM1SJjF//8SURERSypzNwejOk7lA== + dependencies: + decimal.js "^10.0.0" + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -59,6 +99,11 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" integrity sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw== +"@types/mocha@^5.2.7": + version "5.2.7" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" + integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== + "@types/node@*": version "10.5.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.5.2.tgz#f19f05314d5421fe37e74153254201a7bf00a707" @@ -69,6 +114,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.108.tgz#852e8496bcfc5e74cae83a5eb3b30e5661e9b7b9" integrity sha512-5q14jNJCPW+Iwk6Y1JxtA7T5ov1aVRS2VA2PvRgFMZtCjoIo8WT1WO56dSV0MSiHR7BEoe2QNuXigBQNqbWdAw== +"@types/node@^12.0.4": + version "12.0.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.5.tgz#ac14404c33d1a789973c45379a67f7f7e58a01b9" + integrity sha512-CFLSALoE+93+Hcb5pFjp0J1uMrrbLRe+L1+gFwerJ776R3TACSF0kTVRQ7AvRa7aFx70nqYHAc7wQPlt9kY2Mg== + "@types/puppeteer@^1.12.4": version "1.12.4" resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.4.tgz#8388efdb0b30a54a7e7c4831ca0d709191d77ff1" @@ -389,6 +439,11 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +app-root-path@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" + integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -492,6 +547,13 @@ async@^2.5.0: dependencies: lodash "^4.17.10" +async@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" + integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== + dependencies: + lodash "^4.17.11" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -815,6 +877,25 @@ chownr@^1.0.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE= +chrome-devtools-frontend@1.0.445684: + version "1.0.445684" + resolved "https://registry.yarnpkg.com/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.445684.tgz#8540131836024df2b70fe90d0322af368931d762" + integrity sha1-hUATGDYCTfK3D+kNAyKvNokx12I= + +chrome-timeline@0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/chrome-timeline/-/chrome-timeline-0.0.12.tgz#1516223b4bf289750b4b244c3f88d0095b10f914" + integrity sha512-lDVZGV2VYVS7kTmoLxwOmoTvKLxBHRiBQqGNvD87Bky+uIsnK6ThwESqIj+rsjx218Vq9tU5H6Bv2u6IsafS/g== + dependencies: + "@types/app-root-path" "^1.2.4" + "@types/fs-extra" "^7.0.0" + "@types/puppeteer" "^1.12.4" + app-root-path "^2.2.1" + devtools-timeline-model "^1.4.0" + puppeteer "^1.17.0" + simple-git "^1.113.0" + winston "^3.2.1" + chrome-trace-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" @@ -847,6 +928,13 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-table@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" + integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= + dependencies: + colors "1.0.3" + cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" @@ -861,6 +949,11 @@ cliui@^4.0.0: strip-ansi "^4.0.0" wrap-ansi "^2.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -886,11 +979,75 @@ color-convert@^1.9.0: dependencies: color-name "1.1.1" +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-name@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" integrity sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok= +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" + integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colornames@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" + integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= + +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= + +colors@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" + integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== + +colorspace@1.1.x: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" + integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== + dependencies: + color "3.0.x" + text-hex "1.0.x" + +columnify@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" + integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= + dependencies: + strip-ansi "^3.0.0" + wcwidth "^1.0.0" + combined-stream@1.0.6, combined-stream@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" @@ -903,6 +1060,11 @@ commander@^2.12.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50" integrity sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew== +commander@^2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -913,6 +1075,11 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +complex.js@2.0.11: + version "2.0.11" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.11.tgz#09a873fbf15ffd8c18c9c2201ccef425c32b8bf1" + integrity sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw== + component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -1119,7 +1286,7 @@ debug@^3.1.0: dependencies: ms "2.0.0" -debug@^4.1.0: +debug@^4.0.1, debug@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -1145,6 +1312,11 @@ decamelize@^2.0.0: dependencies: xregexp "4.0.0" +decimal.js@10.2.0, decimal.js@^10.0.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" + integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -1167,6 +1339,13 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + define-properties@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -1229,6 +1408,23 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +devtools-timeline-model@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/devtools-timeline-model/-/devtools-timeline-model-1.4.0.tgz#91f9624fb0313fa3ebeda7bf99865357bc66c726" + integrity sha512-zjC31eo4yhPaGC6NnjpksA9ejq9sKtWrQXW9i790RfACXQFrZ/Ba/qcGcU3sqWww/3iZsnZpVDEiTUniKs4CvQ== + dependencies: + chrome-devtools-frontend "1.0.445684" + resolve "1.1.7" + +diagnostics@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" + integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== + dependencies: + colorspace "1.1.x" + enabled "1.0.x" + kuler "1.0.x" + diff@3.5.0, diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -1300,6 +1496,13 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +enabled@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" + integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= + dependencies: + env-variable "0.0.x" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -1316,6 +1519,11 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: memory-fs "^0.4.0" tapable "^1.0.0" +env-variable@0.0.x: + version "0.0.5" + resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" + integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== + errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -1361,6 +1569,11 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= +escape-latex@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" + integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== + escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1591,6 +1804,11 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-safe-stringify@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2" + integrity sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg== + fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" @@ -1598,6 +1816,11 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" +fecha@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" + integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -1687,6 +1910,11 @@ forwarded@~0.1.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +fraction.js@4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.12.tgz#0526d47c65a5fb4854df78bc77f7bec708d7b8c3" + integrity sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -2112,6 +2340,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -2289,12 +2522,22 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +javascript-natural-sort@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= + js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@3.13.1: +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@3.13.1, js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -2411,6 +2654,13 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== +kuler@1.0.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" + integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== + dependencies: + colornames "^1.1.1" + lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -2495,6 +2745,17 @@ log-symbols@2.2.0: dependencies: chalk "^2.0.1" +logform@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" + integrity sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ== + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^2.3.3" + ms "^2.1.1" + triple-beam "^1.3.0" + long@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -2544,6 +2805,20 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +mathjs@^5.10.3: + version "5.10.3" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-5.10.3.tgz#e998885f932ea8886db8b40f7f5b199f89b427f1" + integrity sha512-ySjg30BC3dYjQm73ILZtwcWzFJde0VU6otkXW/57IjjuYRa3Qaf0Kb8pydEuBZYtqW2OxreAtsricrAmOj3jIw== + dependencies: + complex.js "2.0.11" + decimal.js "10.2.0" + escape-latex "1.2.0" + fraction.js "4.0.12" + javascript-natural-sort "0.7.1" + seed-random "2.2.0" + tiny-emitter "2.1.0" + typed-function "1.1.0" + md5.js@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -3015,6 +3290,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +one-time@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" + integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= + onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -3376,6 +3656,20 @@ puppeteer@^1.15.0: rimraf "^2.6.1" ws "^6.1.0" +puppeteer@^1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.17.0.tgz#371957d227a2f450fa74b78e78a2dadb2be7f14f" + integrity sha512-3EXZSximCzxuVKpIHtyec8Wm2dWZn1fc5tQi34qWfiUgubEVYHjUvr0GOJojqf3mifI6oyKnCdrGxaOI+lWReA== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + qs@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607" @@ -3439,6 +3733,15 @@ rc@^1.2.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" + integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -3551,6 +3854,11 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + resolve@^1.3.2: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" @@ -3637,6 +3945,11 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" +seed-random@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" + integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= + semver@^5.0.1: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" @@ -3762,6 +4075,20 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +simple-git@^1.113.0: + version "1.113.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.113.0.tgz#668989728a1e9cf4ec6c72b69ea2eecc93489bea" + integrity sha512-i9WVsrK2u0G/cASI9nh7voxOk9mhanWY9eGtWBDSYql6m49Yk5/Fan6uZsDr/xmzv8n+eQ8ahKCoEr8cvU3h+g== + dependencies: + debug "^4.0.1" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -3866,6 +4193,11 @@ ssri@^5.2.4: dependencies: safe-buffer "^5.1.1" +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -3954,6 +4286,13 @@ string_decoder@^1.0.0, string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -4034,6 +4373,11 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + through2@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" @@ -4054,6 +4398,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-emitter@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -4113,6 +4462,11 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + ts-loader@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.5.0.tgz#a1ce70b2dc799941fb2197605f0d67874097859b" @@ -4138,6 +4492,25 @@ tslint-consistent-codestyle@^1.13.0: tslib "^1.7.1" tsutils "^2.27.0" +tslint@^5.17.0: + version "5.17.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.17.0.tgz#f9f0ce2011d8e90debaa6e9b4975f24cd16852b8" + integrity sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.1" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.29.0" + tslint@^5.9.1: version "5.10.0" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.10.0.tgz#11e26bccb88afa02dd0d9956cae3d4540b5f54c3" @@ -4163,6 +4536,13 @@ tsutils@^2.12.1, tsutils@^2.24.0, tsutils@^2.27.0: dependencies: tslib "^1.8.1" +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -4205,12 +4585,17 @@ type-is@~1.6.6: media-typer "0.3.0" mime-types "~2.1.18" +typed-function@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-1.1.0.tgz#ea149706e0fb42aca1791c053a6d94ccd6c4fdcb" + integrity sha512-TuQzwiT4DDg19beHam3E66oRXhyqlyfgjHB/5fcvsRXbfmWPJfto9B4a0TBdTrQAPGlGmXh/k7iUI+WsObgORA== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.5: +typescript@3.5, typescript@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== @@ -4316,7 +4701,7 @@ utf8@^3.0.0: resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -4387,6 +4772,13 @@ watchpack@^1.5.0: graceful-fs "^4.1.2" neo-async "^2.5.0" +wcwidth@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -4496,6 +4888,29 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +winston-transport@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66" + integrity sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A== + dependencies: + readable-stream "^2.3.6" + triple-beam "^1.2.0" + +winston@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07" + integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw== + dependencies: + async "^2.6.1" + diagnostics "^1.1.1" + is-stream "^1.1.0" + logform "^2.1.1" + one-time "0.0.4" + readable-stream "^3.1.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.3.0" + wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -4566,6 +4981,24 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= +xterm-benchmark@../xterm-benchmark: + version "0.1.2" + dependencies: + "@types/app-root-path" "^1.2.4" + "@types/cli-table" "^0.3.0" + "@types/mathjs" "^5.0.1" + "@types/mocha" "^5.2.7" + "@types/node" "^12.0.4" + app-root-path "^2.2.1" + chrome-timeline "0.0.12" + cli-table "^0.3.1" + columnify "^1.5.4" + commander "^2.20.0" + mathjs "^5.10.3" + mocha "^6.1.4" + tslint "^5.17.0" + typescript "^3.5.1" + "y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" From 06949df56057cfdd6d2f7cf588a28646a2f9b768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 6 Jun 2019 02:43:22 +0200 Subject: [PATCH 040/104] make linter happy --- .../parser/EscapeSequenceParser.benchmark.ts | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/core/parser/EscapeSequenceParser.benchmark.ts b/src/core/parser/EscapeSequenceParser.benchmark.ts index 9494f6cf..3680f60c 100644 --- a/src/core/parser/EscapeSequenceParser.benchmark.ts +++ b/src/core/parser/EscapeSequenceParser.benchmark.ts @@ -5,7 +5,7 @@ import { C0, C1 } from 'common/data/EscapeSequences'; import { IDcsHandler } from './Types'; -function toUtf32(s: string) { +function toUtf32(s: string): Uint32Array { const result = new Uint32Array(s.length); for (let i = 0; i < s.length; ++i) { result[i] = s.charCodeAt(i); @@ -18,7 +18,7 @@ perfContext('Parser performance - 50MB data', () => { let content; let taContent: Uint32Array; let parser: EscapeSequenceParser; - let dcsHandler: IDcsHandler = { + const dcsHandler: IDcsHandler = { hook: (collect, params, flag) => {}, put: (data, start, end) => {}, unhook: () => {} @@ -97,10 +97,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('print - a', () => { before(() => { - let data = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const data = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', async () => { @@ -111,10 +112,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('execute - \\n', () => { before(() => { - let data = '\n\n\n\n\n\n\n'; + const data = '\n\n\n\n\n\n\n'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -125,10 +127,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('escape - ESC E', () => { before(() => { - let data = '\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE'; + const data = '\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -139,10 +142,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('escape with collect - ESC % G', () => { before(() => { - let data = '\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G'; + const data = '\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -153,10 +157,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('simple csi - CSI A', () => { before(() => { - let data = '\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A'; + const data = '\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -167,10 +172,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('csi with collect - CSI ? p', () => { before(() => { - let data = '\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p'; + const data = '\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -181,10 +187,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('csi with params - CSI 1;2 m', () => { before(() => { - let data = '\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m'; + const data = '\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -195,10 +202,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('osc (small payload) - OSC 0;hi ST', () => { before(() => { - let data = '\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\x1b]0;hi\x1b\\'; + const data = '\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\x1b]0;hi\x1b\\'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -209,10 +217,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('osc (big payload) - OSC 0; ST', () => { before(() => { - let data = '\x1b]0;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; + const data = '\x1b]0;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', () => { @@ -223,10 +232,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('DCS (small payload)', () => { before(() => { - let data = '\x1bPq~~\x1b\\'; + const data = '\x1bPq~~\x1b\\'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', async () => { @@ -237,10 +247,11 @@ perfContext('Parser performance - 50MB data', () => { perfContext('DCS (big payload)', () => { before(() => { - let data = '\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\'; + const data = '\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\'; content = ''; - while (content.length < 50000000) + while (content.length < 50000000) { content += data; + } taContent = toUtf32(content); }); new ThroughputRuntimeCase('throughput', async () => { From e1f7c8a2dfd0ff6138dbe834d7f1cfac5f9c58c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 6 Jun 2019 02:59:42 +0200 Subject: [PATCH 041/104] link against npm package --- package.json | 2 +- yarn.lock | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8ad844e9..6b14d216 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,6 @@ "webpack": "^4.17.1", "webpack-cli": "^3.1.0", "ws": "^7.0.0", - "xterm-benchmark": "../xterm-benchmark" + "xterm-benchmark": "^0.1.3" } } diff --git a/yarn.lock b/yarn.lock index a6857edf..ab7f1bbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4981,8 +4981,10 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= -xterm-benchmark@../xterm-benchmark: - version "0.1.2" +xterm-benchmark@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.1.3.tgz#c637d078f7b73f77a4342299e706b4d0e52ab46a" + integrity sha512-HBSeUOFlr9JVMFNkL5w8EkuWccczkfZAX6adK5fSot1sRRAFJS9NZcXH/yRLZp9S24qOUBJznMZGa9CjWB3h3g== dependencies: "@types/app-root-path" "^1.2.4" "@types/cli-table" "^0.3.0" From 3ff3a5d78de10ab95654d7ac13c8ba2cff302422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 6 Jun 2019 15:46:52 +0200 Subject: [PATCH 042/104] add benchmark for write and writeUtf8 --- benchmark.json | 13 +++++-- bin/benchmark.js | 4 +-- src/Terminal.benchmark.ts | 75 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 src/Terminal.benchmark.ts diff --git a/benchmark.json b/benchmark.json index 4d70f0ab..7d8e2223 100644 --- a/benchmark.json +++ b/benchmark.json @@ -1,11 +1,18 @@ { "evalConfig": { "tolerance": { - "*": [0.75, 1.5] + "*": [0.75, 1.5], + "*.dev": [0.01, 1.5], + "*.cv": [0.01, 1.5], + "EscapeSequenceParser.benchmark.js.*.averageThroughput.mean": [0.9, 5] }, "skip": [ "*.median", - "*.runs" + "*.runs", + "*.dev", + "*.cv", + "EscapeSequenceParser.benchmark.js.*.averageRuntime", + "Terminal.benchmark.js.*.averageRuntime" ] } -} \ No newline at end of file +} diff --git a/bin/benchmark.js b/bin/benchmark.js index a03f6e16..894d98db 100644 --- a/bin/benchmark.js +++ b/bin/benchmark.js @@ -19,8 +19,8 @@ env.NODE_PATH = path.resolve(__dirname, '../out'); */ const commands = { single : '-c benchmark.json', - baseline: '--baseline -r 10 -c benchmark.json', - eval : '--eval -r 10 -c benchmark.json' + baseline: '--baseline -r 5 -c benchmark.json', + eval : '--eval -r 5 -c benchmark.json' } let testFiles = [ diff --git a/src/Terminal.benchmark.ts b/src/Terminal.benchmark.ts new file mode 100644 index 00000000..fafd85cf --- /dev/null +++ b/src/Terminal.benchmark.ts @@ -0,0 +1,75 @@ +import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark'; + +import { Terminal } from 'Terminal'; +import { spawn } from 'node-pty'; +import { Utf8ToUtf32, stringFromCodePoint } from '../out/core/input/TextDecoder'; + + +class TestTerminal extends Terminal { + writeSync(data: string): void { + this.writeBuffer.push(data); + this._innerWrite(); + } + writeSyncUtf8(data: Uint8Array): void { + this.writeBufferUtf8.push(data); + this._innerWriteUtf8(); + } +} + +perfContext('Terminal: ls -lR /usr', () => { + let content = ''; + let contentUtf8: Uint8Array; + + before(async () => { + // grab output from "ls -lR /usr" + const p = spawn('ls', ['--color=auto', '-lR', '/usr'], { + name: 'xterm-color', + cols: 80, + rows: 25, + cwd: process.env.HOME, + env: process.env, + encoding: null + }); + const chunks: Buffer[] = []; + let length = 0; + p.on('data', data => { + chunks.push(data as unknown as Buffer); + length += data.length; + }); + await new Promise(resolve => p.on('exit', () => resolve())); + contentUtf8 = Buffer.concat(chunks, length); + // translate to content string + const buffer = new Uint32Array(contentUtf8.length); + const decoder = new Utf8ToUtf32(); + const codepoints = decoder.decode(contentUtf8, buffer); + for (let i = 0; i < codepoints; ++i) { + content += stringFromCodePoint(buffer[i]); + // peek into content to force flat repr in v8 + if (!(i % 10000000)) { + content[i]; + } + } + }); + + perfContext('write', () => { + let terminal: TestTerminal; + before(() => { + terminal = new TestTerminal({cols: 80, rows: 25, scrollback: 1000}); + }); + new ThroughputRuntimeCase('', () => { + terminal.writeSync(content); + return {payloadSize: contentUtf8.length}; + }, {fork: false}).showAverageThroughput(); + }); + + perfContext('writeUtf8', () => { + let terminal: TestTerminal; + before(() => { + terminal = new TestTerminal({cols: 80, rows: 25, scrollback: 1000}); + }); + new ThroughputRuntimeCase('', () => { + terminal.writeSyncUtf8(contentUtf8); + return {payloadSize: contentUtf8.length}; + }, {fork: false}).showAverageThroughput(); + }); +}); From b17164520cba7ac5e29deea4d43bae3f035e9b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 7 Jun 2019 19:14:04 +0200 Subject: [PATCH 043/104] skip benchmark folders in git --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index cd350bb8..01c17961 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ package-lock.json # Keep bundled code out of Git dist/ demo/dist/ + +# dont commit benahcmark folders +benchmark/ +timeline/ From ce91fba1d9d61fc6235aa1789bbbb2fa8ba5fe74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 7 Jun 2019 19:14:52 +0200 Subject: [PATCH 044/104] fix typo --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 01c17961..f221a5bc 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,6 @@ package-lock.json dist/ demo/dist/ -# dont commit benahcmark folders +# dont commit benchmark folders benchmark/ timeline/ From 8e1ebe42b1a407289241d13faafb0603a55abc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 7 Jun 2019 21:39:07 +0200 Subject: [PATCH 045/104] fix import, set copyright note --- src/Terminal.benchmark.ts | 9 +++++++-- src/core/parser/EscapeSequenceParser.benchmark.ts | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Terminal.benchmark.ts b/src/Terminal.benchmark.ts index fafd85cf..650ed655 100644 --- a/src/Terminal.benchmark.ts +++ b/src/Terminal.benchmark.ts @@ -1,8 +1,13 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark'; import { Terminal } from 'Terminal'; import { spawn } from 'node-pty'; -import { Utf8ToUtf32, stringFromCodePoint } from '../out/core/input/TextDecoder'; +import { Utf8ToUtf32, stringFromCodePoint } from 'core/input/TextDecoder'; class TestTerminal extends Terminal { @@ -23,7 +28,7 @@ perfContext('Terminal: ls -lR /usr', () => { before(async () => { // grab output from "ls -lR /usr" const p = spawn('ls', ['--color=auto', '-lR', '/usr'], { - name: 'xterm-color', + name: 'xterm-256color', cols: 80, rows: 25, cwd: process.env.HOME, diff --git a/src/core/parser/EscapeSequenceParser.benchmark.ts b/src/core/parser/EscapeSequenceParser.benchmark.ts index 3680f60c..392774dd 100644 --- a/src/core/parser/EscapeSequenceParser.benchmark.ts +++ b/src/core/parser/EscapeSequenceParser.benchmark.ts @@ -1,3 +1,7 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-benchmark'; import { EscapeSequenceParser } from 'core/parser/EscapeSequenceParser'; From 674914bbb70437b421a48ec7e5c9f24a95b7d055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 14:51:44 +0200 Subject: [PATCH 046/104] moving benchmarks out of './src': Harder than I thought for several reasons: - need access to common to tests the subparts, thus the unified tsconfig in tests does not work (need the path translation to those parts) - moving next to src I cannot use direct imports anymore (source files are out of rootDir), thus have to rely on d.ts files in out which is quite suboptimal as many parts do not expose the declaration files (can only type Terminal as any atm) - compiler output has to resemble the '../out/..' import logic of the TS files or require fails, thus moved the ouput to './benchmark' for now Conclusion: Not yet there, maybe not a good idea at all to move those tests out of './src' as they heavily rely on not exported internals. Needs more fiddling with the repo structure. --- .../EscapeSequenceParser.benchmark.ts | 6 ++--- .../Terminal.benchmark.ts | 15 ++++++----- benchmark-tests/benchmark.json | 18 +++++++++++++ benchmark-tests/tsconfig.json | 27 +++++++++++++++++++ tsconfig.all.json | 5 +++- 5 files changed, 61 insertions(+), 10 deletions(-) rename {src/core/parser => benchmark-tests}/EscapeSequenceParser.benchmark.ts (97%) rename {src => benchmark-tests}/Terminal.benchmark.ts (84%) create mode 100644 benchmark-tests/benchmark.json create mode 100644 benchmark-tests/tsconfig.json diff --git a/src/core/parser/EscapeSequenceParser.benchmark.ts b/benchmark-tests/EscapeSequenceParser.benchmark.ts similarity index 97% rename from src/core/parser/EscapeSequenceParser.benchmark.ts rename to benchmark-tests/EscapeSequenceParser.benchmark.ts index 392774dd..d64f5812 100644 --- a/src/core/parser/EscapeSequenceParser.benchmark.ts +++ b/benchmark-tests/EscapeSequenceParser.benchmark.ts @@ -4,9 +4,9 @@ */ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-benchmark'; -import { EscapeSequenceParser } from 'core/parser/EscapeSequenceParser'; -import { C0, C1 } from 'common/data/EscapeSequences'; -import { IDcsHandler } from './Types'; +import { EscapeSequenceParser } from '../out/common/parser/EscapeSequenceParser'; +import { C0, C1 } from '../out/common/data/EscapeSequences'; +import { IDcsHandler } from '../out/common/parser/Types'; function toUtf32(s: string): Uint32Array { diff --git a/src/Terminal.benchmark.ts b/benchmark-tests/Terminal.benchmark.ts similarity index 84% rename from src/Terminal.benchmark.ts rename to benchmark-tests/Terminal.benchmark.ts index 650ed655..9e5e10bc 100644 --- a/src/Terminal.benchmark.ts +++ b/benchmark-tests/Terminal.benchmark.ts @@ -5,19 +5,22 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark'; -import { Terminal } from 'Terminal'; import { spawn } from 'node-pty'; -import { Utf8ToUtf32, stringFromCodePoint } from 'core/input/TextDecoder'; +import { Utf8ToUtf32, stringFromCodePoint } from '../out/common/input/TextDecoder'; +const Terminal: any = require('../out/Terminal').Terminal; class TestTerminal extends Terminal { + constructor(opts: any) { + super(opts); + } writeSync(data: string): void { this.writeBuffer.push(data); - this._innerWrite(); + (this as any)._innerWrite(); } writeSyncUtf8(data: Uint8Array): void { - this.writeBufferUtf8.push(data); - this._innerWriteUtf8(); + (this as any).writeBufferUtf8.push(data); + (this as any)._innerWriteUtf8(); } } @@ -33,7 +36,7 @@ perfContext('Terminal: ls -lR /usr', () => { rows: 25, cwd: process.env.HOME, env: process.env, - encoding: null + encoding: (null as unknown as string) // needs to be fixed in node-pty }); const chunks: Buffer[] = []; let length = 0; diff --git a/benchmark-tests/benchmark.json b/benchmark-tests/benchmark.json new file mode 100644 index 00000000..7d8e2223 --- /dev/null +++ b/benchmark-tests/benchmark.json @@ -0,0 +1,18 @@ +{ + "evalConfig": { + "tolerance": { + "*": [0.75, 1.5], + "*.dev": [0.01, 1.5], + "*.cv": [0.01, 1.5], + "EscapeSequenceParser.benchmark.js.*.averageThroughput.mean": [0.9, 5] + }, + "skip": [ + "*.median", + "*.runs", + "*.dev", + "*.cv", + "EscapeSequenceParser.benchmark.js.*.averageRuntime", + "Terminal.benchmark.js.*.averageRuntime" + ] + } +} diff --git a/benchmark-tests/tsconfig.json b/benchmark-tests/tsconfig.json new file mode 100644 index 00000000..02079d2b --- /dev/null +++ b/benchmark-tests/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "es6", + ], + "rootDir": ".", + "outDir": "../benchmark", + "types": [ + "../node_modules/@types/node" + ], + "sourceMap": true, + "removeComments": true, + "pretty": true, + "strict": true, + "baseUrl": ".", + "paths": { + "common/*": [ "./../out/common/*" ], + "browser/*": [ "./../out/browser/*" ] + }, + "declaration": true + }, + "include": [ + "./**/*", + "../typings/xterm.d.ts" + ] +} diff --git a/tsconfig.all.json b/tsconfig.all.json index d2670811..d8d8d20b 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -7,6 +7,9 @@ { "path": "./addons/xterm-addon-attach/src" }, { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, - { "path": "./addons/xterm-addon-web-links/src" } + { "path": "./addons/xterm-addon-web-links/src" }, + + // currently depends on out, thus must run as last? + { "path": "./benchmark-tests" }, ] } From 3204d9d7c9015db856ff29225f1a949e47b05072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 15:19:48 +0200 Subject: [PATCH 047/104] fix benchmark script --- benchmark-tests/tsconfig.json | 5 ++--- bin/benchmark.js | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/benchmark-tests/tsconfig.json b/benchmark-tests/tsconfig.json index 02079d2b..9012ee4d 100644 --- a/benchmark-tests/tsconfig.json +++ b/benchmark-tests/tsconfig.json @@ -17,11 +17,10 @@ "paths": { "common/*": [ "./../out/common/*" ], "browser/*": [ "./../out/browser/*" ] - }, - "declaration": true + } }, "include": [ "./**/*", "../typings/xterm.d.ts" - ] + ], } diff --git a/bin/benchmark.js b/bin/benchmark.js index 894d98db..077febe4 100644 --- a/bin/benchmark.js +++ b/bin/benchmark.js @@ -18,13 +18,13 @@ env.NODE_PATH = path.resolve(__dirname, '../out'); * yarn benchmark eval 10 runs of all benchmarks with eval against last baseline */ const commands = { - single : '-c benchmark.json', - baseline: '--baseline -r 5 -c benchmark.json', - eval : '--eval -r 5 -c benchmark.json' + single : '-r 5 -c ./benchmark-tests/benchmark.json', + baseline: '--baseline -r 5 -c ./benchmark-tests/benchmark.json', + eval : '--eval -r 5 -c ./benchmark-tests/benchmark.json' } let testFiles = [ - './out/**/*benchmark.js' + './benchmark/*benchmark.js' ]; // allow overriding cmdline args (see yarn benchmark --help) @@ -32,6 +32,8 @@ if (process.argv.length === 3 && process.argv[2] in commands) { testFiles.push(commands[process.argv[2]]); } else if (process.argv.length > 2) { testFiles = process.argv.slice(2); +} else if (process.argv.length === 2) { + testFiles.push(commands['single']); } cp.spawnSync( From e920f1db86e69040720eee1cc33a901a3f79f64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 17:31:57 +0200 Subject: [PATCH 048/104] cleaner approach --- benchmark-tests/benchmark.json | 18 ------ benchmark-tests/tsconfig.json | 26 --------- bin/benchmark.js | 55 ------------------- package.json | 4 +- .../EscapeSequenceParser.benchmark.d.ts | 1 + .../EscapeSequenceParser.benchmark.ts | 6 +- src/benchmark-tests/Terminal.benchmark.d.ts | 1 + .../benchmark-tests}/Terminal.benchmark.ts | 8 +-- src/benchmark-tests/tsconfig.json | 23 ++++++++ tsconfig.all.json | 4 +- 10 files changed, 34 insertions(+), 112 deletions(-) delete mode 100644 benchmark-tests/benchmark.json delete mode 100644 benchmark-tests/tsconfig.json delete mode 100644 bin/benchmark.js create mode 100644 src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts rename {benchmark-tests => src/benchmark-tests}/EscapeSequenceParser.benchmark.ts (97%) create mode 100644 src/benchmark-tests/Terminal.benchmark.d.ts rename {benchmark-tests => src/benchmark-tests}/Terminal.benchmark.ts (92%) create mode 100644 src/benchmark-tests/tsconfig.json diff --git a/benchmark-tests/benchmark.json b/benchmark-tests/benchmark.json deleted file mode 100644 index 7d8e2223..00000000 --- a/benchmark-tests/benchmark.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "evalConfig": { - "tolerance": { - "*": [0.75, 1.5], - "*.dev": [0.01, 1.5], - "*.cv": [0.01, 1.5], - "EscapeSequenceParser.benchmark.js.*.averageThroughput.mean": [0.9, 5] - }, - "skip": [ - "*.median", - "*.runs", - "*.dev", - "*.cv", - "EscapeSequenceParser.benchmark.js.*.averageRuntime", - "Terminal.benchmark.js.*.averageRuntime" - ] - } -} diff --git a/benchmark-tests/tsconfig.json b/benchmark-tests/tsconfig.json deleted file mode 100644 index 9012ee4d..00000000 --- a/benchmark-tests/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "es6", - ], - "rootDir": ".", - "outDir": "../benchmark", - "types": [ - "../node_modules/@types/node" - ], - "sourceMap": true, - "removeComments": true, - "pretty": true, - "strict": true, - "baseUrl": ".", - "paths": { - "common/*": [ "./../out/common/*" ], - "browser/*": [ "./../out/browser/*" ] - } - }, - "include": [ - "./**/*", - "../typings/xterm.d.ts" - ], -} diff --git a/bin/benchmark.js b/bin/benchmark.js deleted file mode 100644 index 077febe4..00000000 --- a/bin/benchmark.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -const cp = require('child_process'); -const path = require('path'); -const glob = require('glob'); - -// Add `out` to the NODE_PATH so absolute paths can be resolved. -const env = { ...process.env }; -env.NODE_PATH = path.resolve(__dirname, '../out'); - -/** - * Default commands for yarn: - * yarn benchmark single single run of all benchmarks without statistics - * yarn benchmark baseline 10 runs of all benchmarks with baseline statistics - * yarn benchmark eval 10 runs of all benchmarks with eval against last baseline - */ -const commands = { - single : '-r 5 -c ./benchmark-tests/benchmark.json', - baseline: '--baseline -r 5 -c ./benchmark-tests/benchmark.json', - eval : '--eval -r 5 -c ./benchmark-tests/benchmark.json' -} - -let testFiles = [ - './benchmark/*benchmark.js' -]; - -// allow overriding cmdline args (see yarn benchmark --help) -if (process.argv.length === 3 && process.argv[2] in commands) { - testFiles.push(commands[process.argv[2]]); -} else if (process.argv.length > 2) { - testFiles = process.argv.slice(2); -} else if (process.argv.length === 2) { - testFiles.push(commands['single']); -} - -cp.spawnSync( - path.resolve(__dirname, '../node_modules/.bin/xterm-benchmark'), - testFiles.reduce((accu, cur) => { - const expanded = glob.sync(cur); - if (!expanded.length) { - accu.push(cur); - return accu; - } - return accu.concat(expanded); - }, []), - { - cwd: path.resolve(__dirname, '..'), - env, - stdio: 'inherit', - shell: true - } -); diff --git a/package.json b/package.json index 6b14d216..52fd7001 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,9 @@ "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", "clean": "rm -rf lib out addons/*/lib", - "benchmark": "node ./bin/benchmark.js" + "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmark.json", + "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmark.json --baseline out/benchmark-tests/*benchmark.js", + "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmark.json --eval out/benchmark-tests/*benchmark.js" }, "devDependencies": { "@types/chai": "^3.4.34", diff --git a/src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts b/src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/benchmark-tests/EscapeSequenceParser.benchmark.ts b/src/benchmark-tests/EscapeSequenceParser.benchmark.ts similarity index 97% rename from benchmark-tests/EscapeSequenceParser.benchmark.ts rename to src/benchmark-tests/EscapeSequenceParser.benchmark.ts index d64f5812..57f8329c 100644 --- a/benchmark-tests/EscapeSequenceParser.benchmark.ts +++ b/src/benchmark-tests/EscapeSequenceParser.benchmark.ts @@ -4,9 +4,9 @@ */ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-benchmark'; -import { EscapeSequenceParser } from '../out/common/parser/EscapeSequenceParser'; -import { C0, C1 } from '../out/common/data/EscapeSequences'; -import { IDcsHandler } from '../out/common/parser/Types'; +import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; +import { C0, C1 } from 'common/data/EscapeSequences'; +import { IDcsHandler } from 'common/parser/Types'; function toUtf32(s: string): Uint32Array { diff --git a/src/benchmark-tests/Terminal.benchmark.d.ts b/src/benchmark-tests/Terminal.benchmark.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/src/benchmark-tests/Terminal.benchmark.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/benchmark-tests/Terminal.benchmark.ts b/src/benchmark-tests/Terminal.benchmark.ts similarity index 92% rename from benchmark-tests/Terminal.benchmark.ts rename to src/benchmark-tests/Terminal.benchmark.ts index 9e5e10bc..a9e5bca7 100644 --- a/benchmark-tests/Terminal.benchmark.ts +++ b/src/benchmark-tests/Terminal.benchmark.ts @@ -6,14 +6,10 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark'; import { spawn } from 'node-pty'; -import { Utf8ToUtf32, stringFromCodePoint } from '../out/common/input/TextDecoder'; - -const Terminal: any = require('../out/Terminal').Terminal; +import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; +import { Terminal } from 'Terminal'; class TestTerminal extends Terminal { - constructor(opts: any) { - super(opts); - } writeSync(data: string): void { this.writeBuffer.push(data); (this as any)._innerWrite(); diff --git a/src/benchmark-tests/tsconfig.json b/src/benchmark-tests/tsconfig.json new file mode 100644 index 00000000..2c59a500 --- /dev/null +++ b/src/benchmark-tests/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../tsconfig-library-base", + "compilerOptions": { + "lib": [ + "dom", + "es6", + ], + "outDir": "../../out", + "types": [ + "../../node_modules/@types/node" + ], + "baseUrl": "..", + "strict": false + }, + "include": [ + "./**/*", + "../**/*", + "../../typings/xterm.d.ts" + ], + "exclude": [ + "../**/*test.ts" + ] +} diff --git a/tsconfig.all.json b/tsconfig.all.json index d8d8d20b..6985b64d 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -8,8 +8,6 @@ { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, { "path": "./addons/xterm-addon-web-links/src" }, - - // currently depends on out, thus must run as last? - { "path": "./benchmark-tests" }, + { "path": "./src/benchmark-tests" }, ] } From fc7411c80756718ffb604262fcdd8a76259e7bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 17:35:25 +0200 Subject: [PATCH 049/104] remove wrong d.ts --- src/benchmark-tests/Terminal.benchmark.d.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/benchmark-tests/Terminal.benchmark.d.ts diff --git a/src/benchmark-tests/Terminal.benchmark.d.ts b/src/benchmark-tests/Terminal.benchmark.d.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/benchmark-tests/Terminal.benchmark.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; From 03839f06b8ed0b08181231060740a8bb349d867a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 18:02:17 +0200 Subject: [PATCH 050/104] remove remnants --- src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts diff --git a/src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts b/src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/benchmark-tests/EscapeSequenceParser.benchmark.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; From e0d69db23e0d28d9568edea7a37c6ee0cfc0dec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 21:53:24 +0200 Subject: [PATCH 051/104] better separation of benchmarks --- .gitignore | 2 +- .../EscapeSequenceParser.benchmark.ts | 0 .../Terminal.benchmark.ts | 6 ++-- benchmark.json => benchmarks/benchmark.json | 1 + benchmarks/tsconfig.json | 32 +++++++++++++++++++ package.json | 6 ++-- src/benchmark-tests/tsconfig.json | 23 ------------- src/tsconfig.json | 1 + tsconfig.all.json | 2 +- 9 files changed, 42 insertions(+), 31 deletions(-) rename {src/benchmark-tests => benchmarks}/EscapeSequenceParser.benchmark.ts (100%) rename {src/benchmark-tests => benchmarks}/Terminal.benchmark.ts (95%) rename benchmark.json => benchmarks/benchmark.json (93%) create mode 100644 benchmarks/tsconfig.json delete mode 100644 src/benchmark-tests/tsconfig.json diff --git a/.gitignore b/.gitignore index f221a5bc..e386a725 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,5 @@ dist/ demo/dist/ # dont commit benchmark folders -benchmark/ +.benchmark/ timeline/ diff --git a/src/benchmark-tests/EscapeSequenceParser.benchmark.ts b/benchmarks/EscapeSequenceParser.benchmark.ts similarity index 100% rename from src/benchmark-tests/EscapeSequenceParser.benchmark.ts rename to benchmarks/EscapeSequenceParser.benchmark.ts diff --git a/src/benchmark-tests/Terminal.benchmark.ts b/benchmarks/Terminal.benchmark.ts similarity index 95% rename from src/benchmark-tests/Terminal.benchmark.ts rename to benchmarks/Terminal.benchmark.ts index a9e5bca7..996c9985 100644 --- a/src/benchmark-tests/Terminal.benchmark.ts +++ b/benchmarks/Terminal.benchmark.ts @@ -12,11 +12,11 @@ import { Terminal } from 'Terminal'; class TestTerminal extends Terminal { writeSync(data: string): void { this.writeBuffer.push(data); - (this as any)._innerWrite(); + this._innerWrite(); } writeSyncUtf8(data: Uint8Array): void { - (this as any).writeBufferUtf8.push(data); - (this as any)._innerWriteUtf8(); + this.writeBufferUtf8.push(data); + this._innerWriteUtf8(); } } diff --git a/benchmark.json b/benchmarks/benchmark.json similarity index 93% rename from benchmark.json rename to benchmarks/benchmark.json index 7d8e2223..f8b99b55 100644 --- a/benchmark.json +++ b/benchmarks/benchmark.json @@ -1,4 +1,5 @@ { + "APP_PATH": ".benchmark", "evalConfig": { "tolerance": { "*": [0.75, 1.5], diff --git a/benchmarks/tsconfig.json b/benchmarks/tsconfig.json new file mode 100644 index 00000000..4dcf9b65 --- /dev/null +++ b/benchmarks/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "es6", + ], + "outDir": "../out/test", + "types": [ + "../../node_modules/@types/node" + ], + "baseUrl": "..", + "strict": true, + "baseUrl": ".", + "paths": { + "common/*": [ "../src/common/*" ], + "browser/*": [ "../src/browser/*" ], + "Terminal": [ "../src/Terminal" ] + }, + }, + "include": [ + "./**/*", + "../typings/xterm.d.ts" + ], + "exclude": [ + "../**/*test.ts" + ], + "references": [ + { "path": "../src/common" }, + { "path": "../src/browser" }, + { "path": "../src" }, + ] +} diff --git a/package.json b/package.json index 52fd7001..9cb9e1aa 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", "clean": "rm -rf lib out addons/*/lib", - "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmark.json", - "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmark.json --baseline out/benchmark-tests/*benchmark.js", - "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmark.json --eval out/benchmark-tests/*benchmark.js" + "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmarks/benchmark.json", + "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmarks/benchmark.json --baseline out/test/*benchmark.js", + "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmarks/benchmark.json --eval out/test/*benchmark.js" }, "devDependencies": { "@types/chai": "^3.4.34", diff --git a/src/benchmark-tests/tsconfig.json b/src/benchmark-tests/tsconfig.json deleted file mode 100644 index 2c59a500..00000000 --- a/src/benchmark-tests/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "../tsconfig-library-base", - "compilerOptions": { - "lib": [ - "dom", - "es6", - ], - "outDir": "../../out", - "types": [ - "../../node_modules/@types/node" - ], - "baseUrl": "..", - "strict": false - }, - "include": [ - "./**/*", - "../**/*", - "../../typings/xterm.d.ts" - ], - "exclude": [ - "../**/*test.ts" - ] -} diff --git a/src/tsconfig.json b/src/tsconfig.json index 97576668..afaabfbb 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -11,6 +11,7 @@ ], "rootDir": ".", "outDir": "../out", + "composite": true, "baseUrl": ".", "paths": { "common/*": [ "./common/*" ], diff --git a/tsconfig.all.json b/tsconfig.all.json index 6985b64d..aa368d5b 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -4,10 +4,10 @@ "references": [ { "path": "./src" }, { "path": "./test" }, + { "path": "./benchmarks" }, { "path": "./addons/xterm-addon-attach/src" }, { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, { "path": "./addons/xterm-addon-web-links/src" }, - { "path": "./src/benchmark-tests" }, ] } From 1794532cb13a609331238c7a0431438f6a3b3de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 22:08:41 +0200 Subject: [PATCH 052/104] test pipeline --- azure-pipelines.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e8b23d34..cb04f785 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -83,6 +83,25 @@ jobs: yarn test-api --headless displayName: 'Integration tests' +- job: BenchmarkTests + pool: + vmImage: 'ubuntu-16.04' + steps: + - task: NodeTool@0 + inputs: + versionSpec: '8.x' + displayName: 'Install Node.js' + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.9.4" + displayName: 'Install Yarn' + - script: | + yarn + displayName: 'Install dependencies and build' + - script: | + yarn benchmark-baseline + displayName: 'Benchmark tests' + - job: Release dependsOn: - Linux From b559a5a39b0c612d3c556c55d6da40f8258b346b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 22:23:15 +0200 Subject: [PATCH 053/104] lower resources needed for test --- azure-pipelines.yml | 4 ++-- benchmarks/Terminal.benchmark.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cb04f785..af4160b2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -83,7 +83,7 @@ jobs: yarn test-api --headless displayName: 'Integration tests' -- job: BenchmarkTests +- job: Benchmarks pool: vmImage: 'ubuntu-16.04' steps: @@ -100,7 +100,7 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn benchmark-baseline - displayName: 'Benchmark tests' + displayName: 'Benchmarks' - job: Release dependsOn: diff --git a/benchmarks/Terminal.benchmark.ts b/benchmarks/Terminal.benchmark.ts index 996c9985..a0b8fd29 100644 --- a/benchmarks/Terminal.benchmark.ts +++ b/benchmarks/Terminal.benchmark.ts @@ -20,13 +20,13 @@ class TestTerminal extends Terminal { } } -perfContext('Terminal: ls -lR /usr', () => { +perfContext('Terminal: ls -lR /usr/lib', () => { let content = ''; let contentUtf8: Uint8Array; before(async () => { // grab output from "ls -lR /usr" - const p = spawn('ls', ['--color=auto', '-lR', '/usr'], { + const p = spawn('ls', ['--color=auto', '-lR', '/usr/lib'], { name: 'xterm-256color', cols: 80, rows: 25, From e77969372bbd1e31cec3a633a6c83cd04119510f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 9 Jun 2019 23:00:11 +0200 Subject: [PATCH 054/104] setup benchmark pipeline with eval run --- azure-pipelines.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index af4160b2..33c3e100 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -100,7 +100,14 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn benchmark-baseline - displayName: 'Benchmarks' + displayName: 'Baseline data' + - script: | + git checkout + yarn clean && yarn + displayName: 'Checkout target' + - script: | + yarn benchmark-eval + displayName: 'Eval changes' - job: Release dependsOn: From ee81f50f3a0320a8fb57da6fa13c8d0bbd2a5063 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 12 Jun 2019 18:25:05 -0700 Subject: [PATCH 055/104] Fix conflicts --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/WebglRenderer.ts | 10 ++++++---- src/renderer/webgl/atlas/WebglCharAtlas.ts | 2 +- src/renderer/webgl/renderLayer/BaseRenderLayer.ts | 4 ++-- src/renderer/webgl/renderLayer/CursorRenderLayer.ts | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 4a7c44e0..56beb364 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -9,7 +9,7 @@ import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRaster import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../core/buffer/BufferLine'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 1ef53cfa..c6819f24 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -15,7 +15,7 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from './Lifecycle'; -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; import { getLuminance } from './ColorUtils'; @@ -332,8 +332,10 @@ export class WebglRenderer extends Disposable implements IRenderer { * Recalculates the character and canvas dimensions. */ private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { + // TODO: Acquire CharSizeService properly + // Perform a new measure if the CharMeasure dimensions are not yet available - if (!this._core.charMeasure.width || !this._core.charMeasure.height) { + if (!(this._core)._charSizeService.width || !(this._core)._charSizeService.height) { return; } @@ -344,12 +346,12 @@ export class WebglRenderer extends Disposable implements IRenderer { // NOTE: ceil fixes sometime, floor does others :s - this.dimensions.scaledCharWidth = Math.floor(this._core.charMeasure.width * devicePixelRatio); + this.dimensions.scaledCharWidth = Math.floor((this._core)._charSizeService.width * devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is // enough space to draw the character to the cell. - this.dimensions.scaledCharHeight = Math.ceil(this._core.charMeasure.height * devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil((this._core)._charSizeService.height * devicePixelRatio); // Calculate the scaled cell height, if lineHeight is not 1 then the value // will be floored because since lineHeight can never be lower then 1, there diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 6832d4dc..9c6f9e9b 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -9,7 +9,7 @@ import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS } from '../../Types'; import { is256Color } from './CharAtlasUtils'; import { clearColor } from './CharAtlasGenerator'; -import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; +import { DEFAULT_ATTR } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; import { IColor } from 'xterm'; diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index 6d20c614..fa6cd95f 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -4,12 +4,12 @@ */ import { IRenderLayer } from './Types'; -import { ICellData } from '../../../core/Types'; +import { ICellData } from '../../../common/Types'; import { DEFAULT_COLOR } from '../../../common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; -import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../core/buffer/BufferLine'; +import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/BufferLine'; import { IColorSet, IRenderDimensions, Terminal } from 'xterm'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts index 161606bd..425cbef3 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -5,8 +5,8 @@ import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { ICellData } from '../../../core/Types'; -import { CellData } from '../../../core/buffer/BufferLine'; +import { ICellData } from '../../../common/Types'; +import { CellData } from '../../../common/buffer/BufferLine'; interface ICursorState { x: number; From a5e466869c1eb5f151f7628e519349e9ad9e2064 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 13 Jun 2019 09:51:22 -0700 Subject: [PATCH 056/104] Fix lint --- src/renderer/webgl/renderLayer/BaseRenderLayer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index fa6cd95f..2abe3633 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -4,8 +4,7 @@ */ import { IRenderLayer } from './Types'; -import { ICellData } from '../../../common/Types'; -import { DEFAULT_COLOR } from '../../../common/Types'; +import { ICellData, DEFAULT_COLOR } from '../../../common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; From a19ea1ec5242382ec693cf0a0c8b82f365dec86b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 15:46:07 -0700 Subject: [PATCH 057/104] Remove forked LifeCycle and TypedArray files --- src/common/TypedArrayUtils.test.ts | 105 ++++++++++++- src/common/TypedArrayUtils.ts | 26 ++++ src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/Lifecycle.test.ts | 45 ------ src/renderer/webgl/Lifecycle.ts | 47 ------ src/renderer/webgl/RectangleRenderer.ts | 2 +- src/renderer/webgl/RenderModel.ts | 2 +- src/renderer/webgl/TypedArray.test.ts | 191 ------------------------ src/renderer/webgl/TypedArray.ts | 67 --------- src/renderer/webgl/WebglRenderer.ts | 2 +- 10 files changed, 134 insertions(+), 355 deletions(-) delete mode 100644 src/renderer/webgl/Lifecycle.test.ts delete mode 100644 src/renderer/webgl/Lifecycle.ts delete mode 100644 src/renderer/webgl/TypedArray.test.ts delete mode 100644 src/renderer/webgl/TypedArray.ts diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index 429a3a33..ed3541db 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fillFallback, concat } from 'common/TypedArrayUtils'; +import { fillFallback, concat, sliceFallback } from 'common/TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -85,6 +85,109 @@ describe('polyfill conformance tests', function(): void { } }); }); + + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); }); describe('typed array convenience functions', () => { diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index 54699835..f2651aa0 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -40,6 +40,32 @@ export function fillFallback(array: T, value: number, star return array; } +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array.constructor as any)(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} + /** * Concat two typed arrays `a` and `b`. * Returns a new typed array. diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 56beb364..fe40c375 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -8,7 +8,7 @@ import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from './TypedArray'; +import { fill, slice } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; diff --git a/src/renderer/webgl/Lifecycle.test.ts b/src/renderer/webgl/Lifecycle.test.ts deleted file mode 100644 index 4b696fa5..00000000 --- a/src/renderer/webgl/Lifecycle.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { Disposable } from './Lifecycle'; - -class TestDisposable extends Disposable { - public get isDisposed(): boolean { - return this._isDisposed; - } -} - -describe('Disposable', () => { - describe('register', () => { - it('should register disposables', () => { - const d = new TestDisposable(); - const d2 = { - dispose: () => { throw new Error(); } - }; - d.register(d2); - assert.throws(() => d.dispose()); - }); - }); - describe('unregister', () => { - it('should unregister disposables', () => { - const d = new TestDisposable(); - const d2 = { - dispose: () => { throw new Error(); } - }; - d.register(d2); - d.unregister(d2); - assert.doesNotThrow(() => d.dispose()); - }); - }); - describe('dispose', () => { - it('should set is disposed flag', () => { - const d = new TestDisposable(); - assert.isFalse(d.isDisposed); - d.dispose(); - assert.isTrue(d.isDisposed); - }); - }); -}); diff --git a/src/renderer/webgl/Lifecycle.ts b/src/renderer/webgl/Lifecycle.ts deleted file mode 100644 index 209a3e2a..00000000 --- a/src/renderer/webgl/Lifecycle.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'xterm'; - -/** - * A base class that can be extended to provide convenience methods for managing the lifecycle of an - * object and its components. - */ -export abstract class Disposable implements IDisposable { - protected _disposables: IDisposable[] = []; - protected _isDisposed: boolean = false; - - constructor() { - } - - /** - * Disposes the object, triggering the `dispose` method on all registered IDisposables. - */ - public dispose(): void { - this._isDisposed = true; - this._disposables.forEach(d => d.dispose()); - this._disposables.length = 0; - } - - /** - * Registers a disposable object. - * @param d The disposable to register. - */ - public register(d: T): void { - this._disposables.push(d); - } - - /** - * Unregisters a disposable object if it has been registered, if not do - * nothing. - * @param d The disposable to unregister. - */ - public unregister(d: T): void { - const index = this._disposables.indexOf(d); - if (index !== -1) { - this._disposables.splice(index, 1); - } - } -} diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 3fc8413c..0a1c31d9 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -5,7 +5,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { fill } from './TypedArray'; +import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index 71d256d3..a9ee24e9 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,7 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; -import { fill } from './TypedArray'; +import { fill } from 'common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; diff --git a/src/renderer/webgl/TypedArray.test.ts b/src/renderer/webgl/TypedArray.test.ts deleted file mode 100644 index 25676248..00000000 --- a/src/renderer/webgl/TypedArray.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ -import { assert } from 'chai'; -import { fillFallback, sliceFallback } from './TypedArray'; - -type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray - | Int8Array | Int16Array | Int32Array - | Float32Array | Float64Array; - -function deepEquals(a: TypedArray, b: TypedArray): void { - assert.equal(a.length, b.length); - for (let i = 0; i < a.length; ++i) { - assert.equal(a[i], b[i]); - } -} - -describe('polyfill conformance tests', function(): void { - describe('TypedArray.fill', function(): void { - it('should work with all typed array types', function(): void { - const u81 = new Uint8Array(5); - const u82 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2), u82.fill(2)); - deepEquals(fillFallback(u81, -1), u82.fill(-1)); - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(fillFallback(u161, 2), u162.fill(2)); - deepEquals(fillFallback(u161, 65535), u162.fill(65535)); - deepEquals(fillFallback(u161, -1), u162.fill(-1)); - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(fillFallback(u321, 2), u322.fill(2)); - deepEquals(fillFallback(u321, 65537), u322.fill(65537)); - deepEquals(fillFallback(u321, -1), u322.fill(-1)); - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(fillFallback(i81, 2), i82.fill(2)); - deepEquals(fillFallback(i81, -1), i82.fill(-1)); - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(fillFallback(i161, 2), i162.fill(2)); - deepEquals(fillFallback(i161, 65535), i162.fill(65535)); - deepEquals(fillFallback(i161, -1), i162.fill(-1)); - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(fillFallback(i321, 2), i322.fill(2)); - deepEquals(fillFallback(i321, 65537), i322.fill(65537)); - deepEquals(fillFallback(i321, -1), i322.fill(-1)); - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(fillFallback(f321, 1.2345), f322.fill(1.2345)); - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(fillFallback(f641, 1.2345), f642.fill(1.2345)); - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(fillFallback(u8Clamped1, 2), u8Clamped2.fill(2)); - deepEquals(fillFallback(u8Clamped1, 257), u8Clamped2.fill(257)); - }); - it('start offset', function(): void { - for (let i = -2; i < 10; ++i) { - const u81 = new Uint8Array(5); - const u83 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2, i), u83.fill(2, i)); - deepEquals(fillFallback(u81, -1, i), u83.fill(-1, i)); - } - }); - it('end offset', function(): void { - for (let i = -2; i < 10; ++i) { - const u81 = new Uint8Array(5); - const u83 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2, 0, i), u83.fill(2, 0, i)); - deepEquals(fillFallback(u81, -1, 0, i), u83.fill(-1, 0, i)); - } - }); - it('start/end offset', function(): void { - for (let i = -2; i < 10; ++i) { - for (let j = -2; j < 10; ++j) { - const u81 = new Uint8Array(5); - const u83 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2, i, j), u83.fill(2, i, j)); - deepEquals(fillFallback(u81, -1, i, j), u83.fill(-1, i, j)); - } - } - }); - }); - - describe('TypedArray.slice', () => { - describe('should work with all typed array types', () => { - it('Uint8Array', () => { - const a = new Uint8Array(5); - deepEquals(sliceFallback(a, 2), a.slice(2)); - deepEquals(sliceFallback(a, 65535), a.slice(65535)); - deepEquals(sliceFallback(a, -1), a.slice(-1)); - }); - it('Uint16Array', () => { - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(sliceFallback(u161, 2), u162.slice(2)); - deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); - deepEquals(sliceFallback(u161, -1), u162.slice(-1)); - }); - it('Uint32Array', () => { - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(sliceFallback(u321, 2), u322.slice(2)); - deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); - deepEquals(sliceFallback(u321, -1), u322.slice(-1)); - }); - it('Int8Array', () => { - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(sliceFallback(i81, 2), i82.slice(2)); - deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); - deepEquals(sliceFallback(i81, -1), i82.slice(-1)); - }); - it('Int16Array', () => { - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(sliceFallback(i161, 2), i162.slice(2)); - deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); - deepEquals(sliceFallback(i161, -1), i162.slice(-1)); - }); - it('Int32Array', () => { - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(sliceFallback(i321, 2), i322.slice(2)); - deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); - deepEquals(sliceFallback(i321, -1), i322.slice(-1)); - }); - it('Float32Array', () => { - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(sliceFallback(f321, 2), f322.slice(2)); - deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); - deepEquals(sliceFallback(f321, -1), f322.slice(-1)); - }); - it('Float64Array', () => { - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(sliceFallback(f641, 2), f642.slice(2)); - deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); - deepEquals(sliceFallback(f641, -1), f642.slice(-1)); - }); - it('Uint8ClampedArray', () => { - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); - deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); - deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); - }); - }); - it('start', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1), arr.slice(-1)); - deepEquals(sliceFallback(arr, 0), arr.slice(0)); - deepEquals(sliceFallback(arr, 1), arr.slice(1)); - deepEquals(sliceFallback(arr, 2), arr.slice(2)); - deepEquals(sliceFallback(arr, 3), arr.slice(3)); - deepEquals(sliceFallback(arr, 4), arr.slice(4)); - deepEquals(sliceFallback(arr, 5), arr.slice(5)); - }); - it('end', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); - deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); - deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); - deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); - deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); - deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); - deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); - - deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); - deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); - deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); - deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); - deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); - deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); - deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); - - deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); - deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); - deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); - deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); - deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); - deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); - deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); - }); - }); -}); diff --git a/src/renderer/webgl/TypedArray.ts b/src/renderer/webgl/TypedArray.ts deleted file mode 100644 index 33d59ea1..00000000 --- a/src/renderer/webgl/TypedArray.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray - | Int8Array | Int16Array | Int32Array - | Float32Array | Float64Array; - - -/** - * polyfill for TypedArray.fill - * This is needed to support .fill in all safari versions and IE 11. - */ -export function fill(array: T, value: number, start?: number, end?: number): T { - // all modern engines that support .fill - if (array.fill) { - return array.fill(value, start, end) as T; - } - return fillFallback(array, value, start, end); -} - -export function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T { - // safari and IE 11 - // since IE 11 does not support Array.prototype.fill either - // we cannot use the suggested polyfill from MDN - // instead we simply fall back to looping - if (start >= array.length) { - return array; - } - start = (array.length + start) % array.length; - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - for (let i = start; i < end; ++i) { - array[i] = value; - } - return array; -} - -export function slice(array: T, start?: number, end?: number): T { - // all modern engines that support .slice - if (array.slice) { - return array.slice(start, end) as T; - } - return sliceFallback(array, start, end); -} - -export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { - if (start < 0) { - start = (array.length + start) % array.length; - } - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - start = Math.min(start, end); - - const result: T = new (array.constructor as any)(end - start); - for (let i = 0; i < end - start; ++i) { - result[i] = array[i + start]; - } - return result; -} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index c6819f24..69663338 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -14,7 +14,7 @@ import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { Disposable } from './Lifecycle'; +import { Disposable } from 'common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; From d4169f353881ad98027754c85fdfdd5f10111191 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 19:31:42 -0700 Subject: [PATCH 058/104] Fix tests --- src/renderer/webgl/WebglRenderer.api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/src/renderer/webgl/WebglRenderer.api.ts index d2dc06f0..62a4c7cf 100644 --- a/src/renderer/webgl/WebglRenderer.api.ts +++ b/src/renderer/webgl/WebglRenderer.api.ts @@ -166,7 +166,7 @@ async function writeSync(data: string): Promise { // async function getPixelAt(x: number, y: number): Promise { // await page.evaluate(` -// window.gl = window.term._core._renderCoordinator._renderer._gl; +// window.gl = window.term._core._renderService._renderer._gl; // window.result = new Uint8Array(4); // window.gl.readPixels(${x}, window.gl.drawingBufferHeight - 1 - ${y}, 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result); // `); @@ -175,9 +175,9 @@ async function writeSync(data: string): Promise { async function getCellColor(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderCoordinator._renderer._gl; + window.gl = window.term._core._renderService._renderer._gl; window.result = new Uint8Array(4); - window.d = window.term._core._renderCoordinator.dimensions; + window.d = window.term._core._renderService.dimensions; window.gl.readPixels( Math.floor((${col - 0.5}) * window.d.scaledCellWidth), Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.scaledCellHeight), From 21d07d1f0ed6812eb6dea4017ef21cb581486b9b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 19:34:31 -0700 Subject: [PATCH 059/104] Remove unused portion of CharAtlasGenerator --- .../webgl/atlas/CharAtlasGenerator.ts | 128 ------------------ src/renderer/webgl/atlas/WebglCharAtlas.ts | 22 ++- 2 files changed, 21 insertions(+), 129 deletions(-) delete mode 100644 src/renderer/webgl/atlas/CharAtlasGenerator.ts diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts deleted file mode 100644 index c01ea630..00000000 --- a/src/renderer/webgl/atlas/CharAtlasGenerator.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { FontWeight, IColor } from 'xterm'; -import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; -import { isFirefox, isSafari } from '../Platform'; - -/** - * Generates a char atlas. - * @param context The window or worker context. - * @param canvasFactory A function to generate a canvas with a width or height. - * @param config The config for the new char atlas. - */ -export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise { - const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; - const canvas = canvasFactory( - /*255 ascii chars*/255 * cellWidth, - (/*default+default bold*/2 + /*0-15*/16 + /*0-15 bold*/16) * cellHeight - ); - const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); - - ctx.fillStyle = config.colors.background.css; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - ctx.save(); - ctx.fillStyle = config.colors.foreground.css; - ctx.font = getFont(config.fontWeight, config); - ctx.textBaseline = 'middle'; - - // Default color - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2); - ctx.restore(); - } - // Default color bold - ctx.save(); - ctx.font = getFont(config.fontWeightBold, config); - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5); - ctx.restore(); - } - ctx.restore(); - - // Colors 0-15 - ctx.font = getFont(config.fontWeight, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); - ctx.restore(); - } - } - - // Colors 0-15 bold - ctx.font = getFont(config.fontWeightBold, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2 + 16) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); - ctx.restore(); - } - } - ctx.restore(); - - // Support is patchy for createImageBitmap at the moment, pass a canvas back - // if support is lacking as drawImage works there too. Firefox is also - // included here as ImageBitmap appears both buggy and has horrible - // performance (tested on v55). - if (!('createImageBitmap' in context) || isFirefox || isSafari) { - // Don't attempt to clear background colors if createImageBitmap is not supported - return canvas; - } - - const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - - // Remove the background color from the image so characters may overlap - clearColor(charAtlasImageData, config.colors.background); - - return context.createImageBitmap(charAtlasImageData); -} - -/** - * Makes a partiicular rgb color in an ImageData completely transparent. - * @returns True if the result is "empty", meaning all pixels are fully transparent. - */ -export function clearColor(imageData: ImageData, color: IColor): boolean { - let isEmpty = true; - const r = color.rgba >>> 24; - const g = color.rgba >>> 16 & 0xFF; - const b = color.rgba >>> 8 & 0xFF; - for (let offset = 0; offset < imageData.data.length; offset += 4) { - if (imageData.data[offset] === r && - imageData.data[offset + 1] === g && - imageData.data[offset + 2] === b) { - imageData.data[offset + 3] = 0; - } else { - isEmpty = false; - } - } - return isEmpty; -} - -function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string { - return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`; -} diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 9c6f9e9b..6c952e0f 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -8,7 +8,6 @@ import BaseCharAtlas from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS } from '../../Types'; import { is256Color } from './CharAtlasUtils'; -import { clearColor } from './CharAtlasGenerator'; import { DEFAULT_ATTR } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; import { IColor } from 'xterm'; @@ -377,3 +376,24 @@ export default class WebglCharAtlas extends BaseCharAtlas { return new ImageData(clippedData, width, height); } } + +/** + * Makes a partiicular rgb color in an ImageData completely transparent. + * @returns True if the result is "empty", meaning all pixels are fully transparent. + */ +function clearColor(imageData: ImageData, color: IColor): boolean { + let isEmpty = true; + const r = color.rgba >>> 24; + const g = color.rgba >>> 16 & 0xFF; + const b = color.rgba >>> 8 & 0xFF; + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } else { + isEmpty = false; + } + } + return isEmpty; +} From e959cce8999fb9a1662d0c7c8e0ff2b1621bb181 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 19:35:05 -0700 Subject: [PATCH 060/104] Remove webgl LRUMap --- src/renderer/webgl/atlas/LRUMap.test.ts | 65 ----------- src/renderer/webgl/atlas/LRUMap.ts | 136 ------------------------ 2 files changed, 201 deletions(-) delete mode 100644 src/renderer/webgl/atlas/LRUMap.test.ts delete mode 100644 src/renderer/webgl/atlas/LRUMap.ts diff --git a/src/renderer/webgl/atlas/LRUMap.test.ts b/src/renderer/webgl/atlas/LRUMap.test.ts deleted file mode 100644 index 197d1159..00000000 --- a/src/renderer/webgl/atlas/LRUMap.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import LRUMap from './LRUMap'; - -describe('LRUMap', () => { - it('can be used to store and retrieve values', () => { - const map = new LRUMap(10); - map.set(1, 'valuea'); - map.set(2, 'valueb'); - map.set(3, 'valuec'); - assert.strictEqual(map.get(1), 'valuea'); - assert.strictEqual(map.get(2), 'valueb'); - assert.strictEqual(map.get(3), 'valuec'); - }); - - it('maintains a size from insertions', () => { - const map = new LRUMap(10); - assert.strictEqual(map.size, 0); - map.set(1, 'value'); - assert.strictEqual(map.size, 1); - map.set(2, 'value'); - assert.strictEqual(map.size, 2); - }); - - it('deletes the oldest entry when the capacity is exceeded', () => { - const map = new LRUMap(4); - map.set(1, 'value'); - map.set(2, 'value'); - map.set(3, 'value'); - map.set(4, 'value'); - map.set(5, 'value'); - assert.isNull(map.get(1)); - assert.isNotNull(map.get(2)); - assert.isNotNull(map.get(3)); - assert.isNotNull(map.get(4)); - assert.isNotNull(map.get(5)); - assert.strictEqual(map.size, 4); - }); - - it('prevents a recently accessed entry from getting deleted', () => { - const map = new LRUMap(2); - map.set(1, 'value'); - map.set(2, 'value'); - map.get(1); - // a would normally get deleted here, except that we called get() - map.set(3, 'value'); - assert.isNotNull(map.get(1)); - // b got deleted instead of a - assert.isNull(map.get(2)); - assert.isNotNull(map.get(3)); - }); - - it('supports mutation', () => { - const map = new LRUMap(10); - map.set(1, 'oldvalue'); - map.set(1, 'newvalue'); - // mutation doesn't change the size - assert.strictEqual(map.size, 1); - assert.strictEqual(map.get(1), 'newvalue'); - }); -}); diff --git a/src/renderer/webgl/atlas/LRUMap.ts b/src/renderer/webgl/atlas/LRUMap.ts deleted file mode 100644 index d7e01ec6..00000000 --- a/src/renderer/webgl/atlas/LRUMap.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -interface ILinkedListNode { - prev: ILinkedListNode; - next: ILinkedListNode; - key: number; - value: T; -} - -export default class LRUMap { - private _map: { [key: number]: ILinkedListNode } = {}; - private _head: ILinkedListNode = null; - private _tail: ILinkedListNode = null; - private _nodePool: ILinkedListNode[] = []; - public size: number = 0; - - constructor(public capacity: number) { } - - private _unlinkNode(node: ILinkedListNode): void { - const prev = node.prev; - const next = node.next; - if (node === this._head) { - this._head = next; - } - if (node === this._tail) { - this._tail = prev; - } - if (prev !== null) { - prev.next = next; - } - if (next !== null) { - next.prev = prev; - } - } - - private _appendNode(node: ILinkedListNode): void { - const tail = this._tail; - if (tail !== null) { - tail.next = node; - } - node.prev = tail; - node.next = null; - this._tail = node; - if (this._head === null) { - this._head = node; - } - } - - /** - * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that - * they're more likely to live next to each other in memory, which seems to improve performance. - * - * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for - * large maps. - */ - public prealloc(count: number): void { - const nodePool = this._nodePool; - for (let i = 0; i < count; i++) { - nodePool.push({ - prev: null, - next: null, - key: null, - value: null - }); - } - } - - public get(key: number): T | null { - // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However, - // it's faster than calling hasOwnProperty, and in our case, it would never overlap. - const node = this._map[key]; - if (node !== undefined) { - this._unlinkNode(node); - this._appendNode(node); - return node.value; - } - return null; - } - - /** - * Gets a value from a key without marking it as the most recently used item. - */ - public peekValue(key: number): T | null { - const node = this._map[key]; - if (node !== undefined) { - return node.value; - } - return null; - } - - public peek(): T | null { - const head = this._head; - return head === null ? null : head.value; - } - - public set(key: number, value: T): void { - // This is unsafe: See note above. - let node = this._map[key]; - if (node !== undefined) { - // already exists, we just need to mutate it and move it to the end of the list - node = this._map[key]; - this._unlinkNode(node); - node.value = value; - } else if (this.size >= this.capacity) { - // we're out of space: recycle the head node, move it to the tail - node = this._head; - this._unlinkNode(node); - delete this._map[node.key]; - node.key = key; - node.value = value; - this._map[key] = node; - } else { - // make a new element - const nodePool = this._nodePool; - if (nodePool.length > 0) { - // use a preallocated node if we can - node = nodePool.pop(); - node.key = key; - node.value = value; - } else { - node = { - prev: null, - next: null, - key, - value - }; - } - this._map[key] = node; - this.size++; - } - this._appendNode(node); - } -} From 4b4b58e66251c3d282ef42e97baf2702da06f9ca Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 20:37:54 -0700 Subject: [PATCH 061/104] Fix screen dpr change not refreshing canvas renderer --- src/browser/services/RenderService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 5035ed3d..00f3a769 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -43,7 +43,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(this._renderDebouncer); this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this._renderer.onDevicePixelRatioChange()); + this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); @@ -51,7 +51,7 @@ export class RenderService extends Disposable implements IRenderService { // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. - this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onDevicePixelRatioChange())); + this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange())); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so @@ -122,6 +122,7 @@ export class RenderService extends Disposable implements IRenderService { public onDevicePixelRatioChange(): void { this._renderer.onDevicePixelRatioChange(); + this.refreshRows(0, this._rowCount); } public onResize(cols: number, rows: number): void { From 2584de86302efd465dc8fff40c00e561f0f6e8bb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 20:55:52 -0700 Subject: [PATCH 062/104] Clear webgl model after theme or dpr change --- src/renderer/webgl/WebglRenderer.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 69663338..eee31849 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -121,6 +121,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.setColors(); this._refreshCharAtlas(); + + // Force a full refresh + this._model.clear(); } public onDevicePixelRatioChange(): void { @@ -134,7 +137,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public onResize(cols: number, rows: number): void { // Update character and canvas dimensions - this._updateDimensions(devicePixelRatio); + this._updateDimensions(); this._model.resize(this._terminal.cols, this._terminal.rows); this._rectangleRenderer.onResize(); @@ -155,6 +158,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.onResize(); this._refreshCharAtlas(); + + // Force a full refresh + this._model.clear(); } public onCharSizeChanged(): void { @@ -331,7 +337,7 @@ export class WebglRenderer extends Disposable implements IRenderer { /** * Recalculates the character and canvas dimensions. */ - private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { + private _updateDimensions(): void { // TODO: Acquire CharSizeService properly // Perform a new measure if the CharMeasure dimensions are not yet available @@ -346,12 +352,12 @@ export class WebglRenderer extends Disposable implements IRenderer { // NOTE: ceil fixes sometime, floor does others :s - this.dimensions.scaledCharWidth = Math.floor((this._core)._charSizeService.width * devicePixelRatio); + this.dimensions.scaledCharWidth = Math.floor((this._core)._charSizeService.width * this._devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is // enough space to draw the character to the cell. - this.dimensions.scaledCharHeight = Math.ceil((this._core)._charSizeService.height * devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil((this._core)._charSizeService.height * this._devicePixelRatio); // Calculate the scaled cell height, if lineHeight is not 1 then the value // will be floored because since lineHeight can never be lower then 1, there @@ -380,8 +386,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // window.devicePixelRatio as something like 1.100000023841858, when it's // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 // pixel too large for the canvas element size. - this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / devicePixelRatio); - this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / devicePixelRatio); + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); // this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio; // this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio; @@ -396,7 +402,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; // This fixes 110% and 125%, not 150% or 175% though - this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / devicePixelRatio; - this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / devicePixelRatio; + this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } } From 6c232a0e117c2331f280f3babb98c67f12e1a078 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:03:55 -0700 Subject: [PATCH 063/104] Remove renderer API --- src/renderer/webgl/ColorUtils.ts | 2 +- src/renderer/webgl/GlyphRenderer.ts | 4 +- src/renderer/webgl/RectangleRenderer.ts | 4 +- src/renderer/webgl/WebglRenderer.ts | 16 +++-- src/renderer/webgl/WebglRendererAddon.ts | 2 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 3 +- src/renderer/webgl/atlas/CharAtlasUtils.ts | 3 +- src/renderer/webgl/atlas/Types.ts | 3 +- src/renderer/webgl/atlas/WebglCharAtlas.ts | 2 +- .../webgl/renderLayer/BaseRenderLayer.ts | 4 +- .../webgl/renderLayer/CursorRenderLayer.ts | 4 +- .../webgl/renderLayer/LinkRenderLayer.ts | 4 +- src/renderer/webgl/renderLayer/Types.ts | 4 +- typings/xterm.d.ts | 69 ------------------- 14 files changed, 36 insertions(+), 88 deletions(-) diff --git a/src/renderer/webgl/ColorUtils.ts b/src/renderer/webgl/ColorUtils.ts index 56127656..80372e65 100644 --- a/src/renderer/webgl/ColorUtils.ts +++ b/src/renderer/webgl/ColorUtils.ts @@ -3,7 +3,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. */ -import { IColor } from 'xterm'; +import { IColor } from 'browser/Types'; export function getLuminance(color: IColor): number { // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index fe40c375..e76b2f7b 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,7 +11,9 @@ import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; -import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; +import { Terminal, IBufferLine } from 'xterm'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 93748a04..54fddc7d 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -9,7 +9,9 @@ import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/buffer/Constants'; -import { IColorSet, IColor, Terminal, IRenderDimensions } from 'xterm'; +import { Terminal } from 'xterm'; +import { IColorSet, IColor } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 444dde71..d5896b9c 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -16,9 +16,11 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR, FLAGS } from '../../common/buffer/Constants'; -import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; +import { Terminal } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; +import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; +import { IColorSet } from 'browser/Types'; export const INDICIES_PER_CELL = 4; @@ -50,8 +52,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._core), - new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) + new LinkRenderLayer((this._terminal).screenElement, 2, this._colors, this._core), + new CursorRenderLayer((this._terminal).screenElement, 3, this._colors) ]; this.dimensions = { scaledCharWidth: null, @@ -81,7 +83,7 @@ export class WebglRenderer extends Disposable implements IRenderer { if (!this._gl) { throw new Error('WebGL2 not supported'); } - this._terminal.screenElement.appendChild(this._canvas); + (this._terminal).screenElement.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); @@ -92,7 +94,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public dispose(): void { this._renderLayers.forEach(l => l.dispose()); - this._terminal.screenElement.removeChild(this._canvas); + (this._terminal).screenElement.removeChild(this._canvas); super.dispose(); } @@ -151,8 +153,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._canvas.style.height = `${this.dimensions.canvasHeight}px`; // Resize the screen - this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; - this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + (this._terminal).screenElement.style.width = `${this.dimensions.canvasWidth}px`; + (this._terminal).screenElement.style.height = `${this.dimensions.canvasHeight}px`; this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 6ce27b0a..9ebe954f 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -18,7 +18,7 @@ export class WebglRendererAddon implements ITerminalAddon { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); + (this._terminal)._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); } public dispose(): void { diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index 8d8459fd..bd5b8201 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -7,7 +7,8 @@ import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import WebglCharAtlas from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; -import { IColorSet, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; +import { IColorSet } from 'browser/Types'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index 5d1f160f..0554746d 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -5,7 +5,8 @@ import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from '../../../common/buffer/Constants'; -import { IColorSet, Terminal, FontWeight } from 'xterm'; +import { Terminal, FontWeight } from 'xterm'; +import { IColorSet } from 'browser/Types'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts index 1df4f82d..2cb1db40 100644 --- a/src/renderer/webgl/atlas/Types.ts +++ b/src/renderer/webgl/atlas/Types.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { FontWeight, IColorSet } from 'xterm'; +import { FontWeight } from 'xterm'; +import { IColorSet } from 'browser/Types'; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index bf1f805e..af5d2122 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -9,7 +9,7 @@ import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS, DEFAULT_COLOR } from '../../../common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { DEFAULT_ATTR } from 'common/buffer/BufferLine'; -import { IColor } from 'xterm'; +import { IColor } from 'browser/Types'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index b7730a94..1b880c12 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -10,7 +10,9 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/ import BaseCharAtlas from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/BufferLine'; -import { IColorSet, IRenderDimensions, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts index 425cbef3..da7d212c 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -3,10 +3,12 @@ * @license MIT */ -import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from '../../../common/Types'; import { CellData } from '../../../common/buffer/BufferLine'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; interface ICursorState { x: number; diff --git a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts index eb47f7e5..50a3e111 100644 --- a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts @@ -4,10 +4,12 @@ */ import { ILinkifierEvent, ILinkifierAccessor } from '../../../Types'; -import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { is256Color } from '../atlas/CharAtlasUtils'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent = null; diff --git a/src/renderer/webgl/renderLayer/Types.ts b/src/renderer/webgl/renderLayer/Types.ts index 50a76a2c..148ea469 100644 --- a/src/renderer/webgl/renderLayer/Types.ts +++ b/src/renderer/webgl/renderLayer/Types.ts @@ -3,7 +3,9 @@ * @license MIT */ -import { IDisposable, IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { IDisposable, Terminal } from 'xterm'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; export interface IRenderLayer extends IDisposable { /** diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 564f4935..f435367d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -793,75 +793,6 @@ declare module 'xterm' { * @param addon The addon to load. */ loadAddon(addon: ITerminalAddon): void; - - /** - * (EXPERIMENTAL) - */ - setRenderer(renderer: IRenderer): void; - screenElement: HTMLElement; - } - - export namespace Renderer { - const DEFAULT_COLOR: number; - const NULL_CELL_CODE: number; - const WHITESPACE_CELL_CODE: number; - const DEFAULT_ATTR: number; - const DEFAULT_ANSI_COLORS: string[]; - const FLAGS: any; - } - - export interface IRenderer extends IDisposable { - readonly dimensions: IRenderDimensions; - - dispose(): void; - setColors(colors: IColorSet): void; - onDevicePixelRatioChange(): void; - onResize(cols: number, rows: number): void; - onCharSizeChanged(): void; - onBlur(): void; - onFocus(): void; - onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; - onCursorMove(): void; - onOptionsChanged(): void; - clear(): void; - renderRows(start: number, end: number): void; - registerCharacterJoiner(handler: (text: string) => [number, number][]): number; - deregisterCharacterJoiner(joinerId: number): boolean; - } - - export interface IRenderDimensions { - scaledCharWidth: number; - scaledCharHeight: number; - scaledCellWidth: number; - scaledCellHeight: number; - scaledCharLeft: number; - scaledCharTop: number; - scaledCanvasWidth: number; - scaledCanvasHeight: number; - canvasWidth: number; - canvasHeight: number; - actualCellWidth: number; - actualCellHeight: number; - } - - /** - * (EXPERIMENTAL) - */ - export interface IColor { - css: string; - rgba: number; - } - - /** - * (EXPERIMENTAL) - */ - export interface IColorSet { - foreground: IColor; - background: IColor; - cursor: IColor; - cursorAccent: IColor; - selection: IColor; - ansi: IColor[]; } /** From 3a7aa77950af84058859d2577c9e94ecf1505821 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:18:53 -0700 Subject: [PATCH 064/104] Remove remaining parts of renderer API, fix webgl tests --- src/Terminal.ts | 6 ------ src/TestUtils.test.ts | 3 --- src/Types.d.ts | 1 - src/browser/services/RenderService.ts | 5 +++-- src/public/Terminal.ts | 3 --- src/renderer/webgl/WebglRendererAddon.ts | 2 +- 6 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index f2b2a8c8..eaf2a41d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -686,12 +686,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } - public setRenderer(renderer: IRenderer): void { - this._renderService.setRenderer(renderer); - // this._renderCoordinator.onOptionsChanged(); - this.refresh(0, this.rows - 1); - } - private _createRenderer(): IRenderer { switch (this.options.rendererType) { case 'canvas': return new Renderer(this, this._colorManager.colors, this._charSizeService); break; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 1446c41a..6b92bf0b 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -24,9 +24,6 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { - setRenderer(renderer: any): void { - throw new Error('Method not implemented.'); - } onBlur: IEvent; onFocus: IEvent; onA11yChar: IEvent; diff --git a/src/Types.d.ts b/src/Types.d.ts index 8aadfa51..9a08a7a1 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -271,7 +271,6 @@ export interface IPublicTerminal extends IDisposable { writeUtf8(data: Uint8Array): void; refresh(start: number, end: number): void; reset(): void; - setRenderer(renderer: any): void; } export interface IBufferAccessor { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 00f3a769..b41f1ebc 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -105,13 +105,14 @@ export class RenderService extends Disposable implements IRenderService { // TODO: RenderCoordinator should be the only one to dispose the renderer this._renderer.dispose(); this._renderer = renderer; + this.refreshRows(0, this._rowCount - 1); } private _fullRefresh(): void { if (this._isPaused) { this._needsFullRefresh = true; } else { - this.refreshRows(0, this._rowCount); + this.refreshRows(0, this._rowCount - 1); } } @@ -122,7 +123,7 @@ export class RenderService extends Disposable implements IRenderService { public onDevicePixelRatioChange(): void { this._renderer.onDevicePixelRatioChange(); - this.refreshRows(0, this._rowCount); + this.refreshRows(0, this._rowCount - 1); } public onResize(cols: number, rows: number): void { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index a1830576..2026d70e 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -162,9 +162,6 @@ export class Terminal implements ITerminalApi { public loadAddon(addon: ITerminalAddon): void { return this._addonManager.loadAddon(this, addon); } - public setRenderer(renderer: any): void { - this._core.setRenderer(renderer); - } public loadWebgl(preserveDrawingBuffer?: boolean): void { this.loadAddon(new WebglRendererAddon(preserveDrawingBuffer)); } diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 9ebe954f..e29a7f07 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -18,7 +18,7 @@ export class WebglRendererAddon implements ITerminalAddon { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - (this._terminal)._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); + (this._terminal)._core._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); } public dispose(): void { From ddb9fbd1d6bc6183eb2b16fcc7487b8f5c5f8722 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:25:40 -0700 Subject: [PATCH 065/104] Improve type safety in webgl --- src/public/Terminal.ts | 1 - src/renderer/webgl/WebglRenderer.ts | 14 +++++++------- src/renderer/webgl/WebglRendererAddon.ts | 9 +++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 2026d70e..23869218 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -33,7 +33,6 @@ export class Terminal implements ITerminalApi { public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } public get element(): HTMLElement { return this._core.element; } - public get screenElement(): HTMLElement { return this._core.screenElement; } public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index d5896b9c..a01098df 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -47,13 +47,13 @@ export class WebglRenderer extends Disposable implements IRenderer { ) { super(); - this._core = (this._terminal as any)._core; + this._core = (this._terminal)._core; this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer((this._terminal).screenElement, 2, this._colors, this._core), - new CursorRenderLayer((this._terminal).screenElement, 3, this._colors) + new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core), + new CursorRenderLayer(this._core.screenElement, 3, this._colors) ]; this.dimensions = { scaledCharWidth: null, @@ -83,7 +83,7 @@ export class WebglRenderer extends Disposable implements IRenderer { if (!this._gl) { throw new Error('WebGL2 not supported'); } - (this._terminal).screenElement.appendChild(this._canvas); + this._core.screenElement.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); @@ -94,7 +94,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public dispose(): void { this._renderLayers.forEach(l => l.dispose()); - (this._terminal).screenElement.removeChild(this._canvas); + this._core.screenElement.removeChild(this._canvas); super.dispose(); } @@ -153,8 +153,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._canvas.style.height = `${this.dimensions.canvasHeight}px`; // Resize the screen - (this._terminal).screenElement.style.width = `${this.dimensions.canvasWidth}px`; - (this._terminal).screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._core.screenElement.style.width = `${this.dimensions.canvasWidth}px`; + this._core.screenElement.style.height = `${this.dimensions.canvasHeight}px`; this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index e29a7f07..4b67e380 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -5,10 +5,10 @@ import { Terminal, ITerminalAddon } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; +import { IRenderService } from 'browser/services/Services'; +import { IColorSet } from 'browser/Types'; export class WebglRendererAddon implements ITerminalAddon { - private _terminal: Terminal | undefined; - constructor( private _preserveDrawingBuffer?: boolean ) {} @@ -17,8 +17,9 @@ export class WebglRendererAddon implements ITerminalAddon { if (!terminal.element) { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } - this._terminal = terminal; - (this._terminal)._core._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); + const renderService: IRenderService = (terminal)._core._renderService; + const colors: IColorSet = (terminal)._core._colorManager.colors; + renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer)); } public dispose(): void { From b7cdbb99ca4daebfb8c75b71e2f7336eb87f733e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:27:07 -0700 Subject: [PATCH 066/104] Remove default exports in webgl char atlas files --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/WebglRenderer.ts | 2 +- src/renderer/webgl/atlas/BaseCharAtlas.ts | 2 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 4 ++-- src/renderer/webgl/atlas/WebglCharAtlas.ts | 4 ++-- src/renderer/webgl/renderLayer/BaseRenderLayer.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index e76b2f7b..f95b7530 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,7 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import WebglCharAtlas from './atlas/WebglCharAtlas'; +import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index a01098df..c4d2c27e 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -8,7 +8,7 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import WebglCharAtlas from './atlas/WebglCharAtlas'; +import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; diff --git a/src/renderer/webgl/atlas/BaseCharAtlas.ts b/src/renderer/webgl/atlas/BaseCharAtlas.ts index ee69b381..470736f1 100644 --- a/src/renderer/webgl/atlas/BaseCharAtlas.ts +++ b/src/renderer/webgl/atlas/BaseCharAtlas.ts @@ -6,7 +6,7 @@ import { IGlyphIdentifier } from './Types'; import { IDisposable } from 'xterm'; -export default abstract class BaseCharAtlas implements IDisposable { +export abstract class BaseCharAtlas implements IDisposable { private _didWarmUp: boolean = false; public dispose(): void { } diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index bd5b8201..647b96b4 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -4,8 +4,8 @@ */ import { generateConfig, configEquals } from './CharAtlasUtils'; -import BaseCharAtlas from './BaseCharAtlas'; -import WebglCharAtlas from './WebglCharAtlas'; +import { BaseCharAtlas } from './BaseCharAtlas'; +import { WebglCharAtlas } from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index af5d2122..f7624285 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -4,7 +4,7 @@ */ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; -import BaseCharAtlas from './BaseCharAtlas'; +import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS, DEFAULT_COLOR } from '../../../common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; @@ -41,7 +41,7 @@ const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = { const TMP_CANVAS_GLYPH_PADDING = 2; -export default class WebglCharAtlas extends BaseCharAtlas { +export class WebglCharAtlas extends BaseCharAtlas { private _cacheMap: { [code: number]: IRasterizedGlyphSet } = {}; private _cacheMapCombined: { [chars: string]: IRasterizedGlyphSet } = {}; diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index 1b880c12..b88d7330 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderLayer } from './Types'; import { ICellData } from '../../../common/Types'; import { DEFAULT_COLOR } from '../../../common/buffer/Constants'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; -import BaseCharAtlas from '../atlas/BaseCharAtlas'; +import { BaseCharAtlas } from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/BufferLine'; import { Terminal } from 'xterm'; From d7daa53b5094c7e02b6ac6a609df66d3a720ad4f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 23:55:17 -0700 Subject: [PATCH 067/104] Move webgl renderer into addon dir --- addons/xterm-addon-webgl/.gitignore | 2 + addons/xterm-addon-webgl/.npmignore | 4 ++ addons/xterm-addon-webgl/LICENSE | 19 ++++++ addons/xterm-addon-webgl/package.json | 20 ++++++ .../xterm-addon-webgl/src}/ColorUtils.ts | 0 .../xterm-addon-webgl/src}/GlyphRenderer.ts | 2 +- .../xterm-addon-webgl/src}/Platform.ts | 0 .../src}/RectangleRenderer.ts | 2 +- .../xterm-addon-webgl/src}/RenderModel.ts | 0 .../xterm-addon-webgl/src}/Types.ts | 0 .../src}/WebglRenderer.api.ts | 2 +- .../xterm-addon-webgl/src}/WebglRenderer.ts | 2 +- .../src}/WebglRendererAddon.ts | 0 .../xterm-addon-webgl/src}/WebglUtils.ts | 0 .../src}/atlas/BaseCharAtlas.ts | 0 .../src}/atlas/CharAtlasCache.ts | 0 .../src}/atlas/CharAtlasUtils.ts | 2 +- .../xterm-addon-webgl/src}/atlas/Types.ts | 0 .../src}/atlas/WebglCharAtlas.ts | 2 +- .../src}/renderLayer/BaseRenderLayer.ts | 4 +- .../src}/renderLayer/CursorRenderLayer.ts | 4 +- .../src}/renderLayer/LinkRenderLayer.ts | 2 +- .../src}/renderLayer/Types.ts | 0 addons/xterm-addon-webgl/src/tsconfig.json | 27 ++++++++ .../typings/xterm-addon-webgl.d.ts | 68 +++++++++++++++++++ addons/xterm-addon-webgl/webpack.config.js | 31 +++++++++ demo/client.ts | 3 + src/public/Terminal.ts | 4 -- tsconfig.all.json | 1 + 29 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 addons/xterm-addon-webgl/.gitignore create mode 100644 addons/xterm-addon-webgl/.npmignore create mode 100644 addons/xterm-addon-webgl/LICENSE create mode 100644 addons/xterm-addon-webgl/package.json rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/ColorUtils.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/GlyphRenderer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/Platform.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/RectangleRenderer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/RenderModel.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/Types.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglRenderer.api.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglRenderer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglRendererAddon.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglUtils.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/BaseCharAtlas.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/CharAtlasCache.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/CharAtlasUtils.ts (96%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/Types.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/WebglCharAtlas.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/BaseRenderLayer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/CursorRenderLayer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/LinkRenderLayer.ts (96%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/Types.ts (100%) create mode 100644 addons/xterm-addon-webgl/src/tsconfig.json create mode 100644 addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts create mode 100644 addons/xterm-addon-webgl/webpack.config.js diff --git a/addons/xterm-addon-webgl/.gitignore b/addons/xterm-addon-webgl/.gitignore new file mode 100644 index 00000000..a9f4ed54 --- /dev/null +++ b/addons/xterm-addon-webgl/.gitignore @@ -0,0 +1,2 @@ +lib +node_modules \ No newline at end of file diff --git a/addons/xterm-addon-webgl/.npmignore b/addons/xterm-addon-webgl/.npmignore new file mode 100644 index 00000000..e8fc8237 --- /dev/null +++ b/addons/xterm-addon-webgl/.npmignore @@ -0,0 +1,4 @@ +**/*.api.js +**/*.api.ts +tsconfig.json +.yarnrc diff --git a/addons/xterm-addon-webgl/LICENSE b/addons/xterm-addon-webgl/LICENSE new file mode 100644 index 00000000..b9dc26fe --- /dev/null +++ b/addons/xterm-addon-webgl/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018, The xterm.js authors (https://github.com/xtermjs/xterm.js) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json new file mode 100644 index 00000000..34c678dd --- /dev/null +++ b/addons/xterm-addon-webgl/package.json @@ -0,0 +1,20 @@ +{ + "name": "xterm-addon-webgl", + "version": "0.1.0-beta6", + "author": { + "name": "The xterm.js authors", + "url": "https://xtermjs.org/" + }, + "main": "lib/xterm-addon-webgl.js", + "types": "typings/xterm-addon-webgl.d.ts", + "license": "MIT", + "scripts": { + "build": "../../node_modules/.bin/tsc -p src", + "prepackage": "npm run build", + "package": "../../node_modules/.bin/webpack", + "prepublishOnly": "npm run package" + }, + "peerDependencies": { + "xterm": "^3.14.0" + } +} diff --git a/src/renderer/webgl/ColorUtils.ts b/addons/xterm-addon-webgl/src/ColorUtils.ts similarity index 100% rename from src/renderer/webgl/ColorUtils.ts rename to addons/xterm-addon-webgl/src/ColorUtils.ts diff --git a/src/renderer/webgl/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts similarity index 99% rename from src/renderer/webgl/GlyphRenderer.ts rename to addons/xterm-addon-webgl/src/GlyphRenderer.ts index 768060e4..50357b28 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -9,7 +9,7 @@ import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRaster import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from 'common/TypedArrayUtils'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; import { IColorSet } from 'browser/Types'; diff --git a/src/renderer/webgl/Platform.ts b/addons/xterm-addon-webgl/src/Platform.ts similarity index 100% rename from src/renderer/webgl/Platform.ts rename to addons/xterm-addon-webgl/src/Platform.ts diff --git a/src/renderer/webgl/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts similarity index 99% rename from src/renderer/webgl/RectangleRenderer.ts rename to addons/xterm-addon-webgl/src/RectangleRenderer.ts index 54fddc7d..401f4568 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -8,7 +8,7 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelect import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; -import { DEFAULT_COLOR } from '../../common/buffer/Constants'; +import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; diff --git a/src/renderer/webgl/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts similarity index 100% rename from src/renderer/webgl/RenderModel.ts rename to addons/xterm-addon-webgl/src/RenderModel.ts diff --git a/src/renderer/webgl/Types.ts b/addons/xterm-addon-webgl/src/Types.ts similarity index 100% rename from src/renderer/webgl/Types.ts rename to addons/xterm-addon-webgl/src/Types.ts diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts similarity index 99% rename from src/renderer/webgl/WebglRenderer.api.ts rename to addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 62a4c7cf..14c27719 100644 --- a/src/renderer/webgl/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -5,7 +5,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; -import { ITerminalOptions } from '../../Types'; +import { ITerminalOptions } from '../../../src/Types'; const APP = 'http://127.0.0.1:3000/test'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts similarity index 99% rename from src/renderer/webgl/WebglRenderer.ts rename to addons/xterm-addon-webgl/src/WebglRenderer.ts index 8a310abc..a743bfd9 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal } from '../../Types'; +import { ITerminal } from '../../../src/Types'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/addons/xterm-addon-webgl/src/WebglRendererAddon.ts similarity index 100% rename from src/renderer/webgl/WebglRendererAddon.ts rename to addons/xterm-addon-webgl/src/WebglRendererAddon.ts diff --git a/src/renderer/webgl/WebglUtils.ts b/addons/xterm-addon-webgl/src/WebglUtils.ts similarity index 100% rename from src/renderer/webgl/WebglUtils.ts rename to addons/xterm-addon-webgl/src/WebglUtils.ts diff --git a/src/renderer/webgl/atlas/BaseCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts similarity index 100% rename from src/renderer/webgl/atlas/BaseCharAtlas.ts rename to addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts similarity index 100% rename from src/renderer/webgl/atlas/CharAtlasCache.ts rename to addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts similarity index 96% rename from src/renderer/webgl/atlas/CharAtlasUtils.ts rename to addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 0554746d..1c43dd70 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR } from '../../../common/buffer/Constants'; +import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; import { IColorSet } from 'browser/Types'; diff --git a/src/renderer/webgl/atlas/Types.ts b/addons/xterm-addon-webgl/src/atlas/Types.ts similarity index 100% rename from src/renderer/webgl/atlas/Types.ts rename to addons/xterm-addon-webgl/src/atlas/Types.ts diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts similarity index 99% rename from src/renderer/webgl/atlas/WebglCharAtlas.ts rename to addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 48ec0bf4..9a833fdf 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,7 +6,7 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { FLAGS, DEFAULT_COLOR, DEFAULT_ATTR } from '../../../common/buffer/Constants'; +import { FLAGS, DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { IColor } from 'browser/Types'; diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts similarity index 99% rename from src/renderer/webgl/renderLayer/BaseRenderLayer.ts rename to addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 0604da8f..263a0d21 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,8 +4,8 @@ */ import { IRenderLayer } from './Types'; -import { ICellData } from '../../../common/Types'; -import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/Constants'; +import { ICellData } from 'common/Types'; +import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; import { BaseCharAtlas } from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts similarity index 99% rename from src/renderer/webgl/renderLayer/CursorRenderLayer.ts rename to addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 3f24c6f0..b0bf581f 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -5,8 +5,8 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { ICellData } from '../../../common/Types'; -import { CellData } from '../../../common/buffer/CellData'; +import { ICellData } from 'common/Types'; +import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; diff --git a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts similarity index 96% rename from src/renderer/webgl/renderLayer/LinkRenderLayer.ts rename to addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 50a3e111..1ae22cf6 100644 --- a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ILinkifierEvent, ILinkifierAccessor } from '../../../Types'; +import { ILinkifierEvent, ILinkifierAccessor } from '../../../../src/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; diff --git a/src/renderer/webgl/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts similarity index 100% rename from src/renderer/webgl/renderLayer/Types.ts rename to addons/xterm-addon-webgl/src/renderLayer/Types.ts diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json new file mode 100644 index 00000000..34149159 --- /dev/null +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": [ + "dom", + "es6", + ], + "rootDir": ".", + "outDir": "../out", + "sourceMap": true, + "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ "../../../src/common/*" ], + "browser/*": [ "../../../src/browser/*" ] + } + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ], + "references": [ + { "path": "../../../src/common" }, + { "path": "../../../src/browser" } + ] +} diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts new file mode 100644 index 00000000..94bc5297 --- /dev/null +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm'; + +declare module 'xterm-addon-search' { + /** + * Options for a search. + */ + export interface ISearchOptions { + /** + * Whether the search term is a regex. + */ + regex?: boolean; + + /** + * Whether to search for a whole word, the result is only valid if it's + * suppounded in "non-word" characters such as `_`, `(`, `)` or space. + */ + wholeWord?: boolean; + + /** + * Whether the search is case sensitive. + */ + caseSensitive?: boolean; + + /** + * Whether to do an indcremental search, this will expand the selection if it + * still matches the term the user typed. Note that this only affects + * `findNext`, not `findPrevious`. + */ + incremental?: boolean; + } + + /** + * An xterm.js addon that provides search functionality. + */ + export class SearchAddon implements ITerminalAddon { + /** + * Activates the addon + * @param terminal The terminal the addon is being loaded in. + */ + public activate(terminal: Terminal): void; + + /** + * Disposes the addon. + */ + public dispose(): void; + + /** + * Search forwards for the next result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findNext(term: string, searchOptions?: ISearchOptions): boolean; + + /** + * Search backwards for the previous result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; + } +} diff --git a/addons/xterm-addon-webgl/webpack.config.js b/addons/xterm-addon-webgl/webpack.config.js new file mode 100644 index 00000000..825f6734 --- /dev/null +++ b/addons/xterm-addon-webgl/webpack.config.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const path = require('path'); + +const addonName = 'WebglAddon'; +const mainFile = 'xterm-addon-weblg.js'; + +module.exports = { + entry: `./out/${addonName}.js`, + devtool: 'source-map', + module: { + rules: [ + { + test: /\.js$/, + use: ["source-map-loader"], + enforce: "pre", + exclude: /node_modules/ + } + ] + }, + output: { + filename: mainFile, + path: path.resolve('./lib'), + library: addonName, + libraryTarget: 'umd' + }, + mode: 'production' +}; diff --git a/demo/client.ts b/demo/client.ts index e0d55d57..03d1bb73 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -13,6 +13,7 @@ import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon'; +import { WebglRendererAddon } from '../addons/xterm-addon-webgl/out/WebglRendererAddon'; // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; @@ -43,6 +44,8 @@ let socketURL; let socket; let pid; +(window).webgl = () => term.loadAddon(new WebglRendererAddon()); + const terminalContainer = document.getElementById('terminal-container'); const actionElements = { findNext: document.querySelector('#find-next'), diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 23869218..0dae4def 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -11,7 +11,6 @@ import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../browser/LocalizableStrings'; import { IEvent } from 'common/EventEmitter'; import { AddonManager } from './AddonManager'; -import { WebglRendererAddon } from '../renderer/webgl/WebglRendererAddon'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -161,9 +160,6 @@ export class Terminal implements ITerminalApi { public loadAddon(addon: ITerminalAddon): void { return this._addonManager.loadAddon(this, addon); } - public loadWebgl(preserveDrawingBuffer?: boolean): void { - this.loadAddon(new WebglRendererAddon(preserveDrawingBuffer)); - } public static get strings(): ILocalizableStrings { return Strings; } diff --git a/tsconfig.all.json b/tsconfig.all.json index d2670811..bc633fe6 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -8,5 +8,6 @@ { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, { "path": "./addons/xterm-addon-web-links/src" } + { "path": "./addons/xterm-addon-webgl/src" } ] } From 50d6711fc8bb527f65fff453f6ed154ab6f025b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:01:00 -0700 Subject: [PATCH 068/104] Fix webgl packaging --- .../src/{WebglRendererAddon.ts => WebglAddon.ts} | 2 +- addons/xterm-addon-webgl/webpack.config.js | 10 +++++++++- demo/client.ts | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) rename addons/xterm-addon-webgl/src/{WebglRendererAddon.ts => WebglAddon.ts} (93%) diff --git a/addons/xterm-addon-webgl/src/WebglRendererAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts similarity index 93% rename from addons/xterm-addon-webgl/src/WebglRendererAddon.ts rename to addons/xterm-addon-webgl/src/WebglAddon.ts index 4b67e380..0074f9b2 100644 --- a/addons/xterm-addon-webgl/src/WebglRendererAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -8,7 +8,7 @@ import { WebglRenderer } from './WebglRenderer'; import { IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; -export class WebglRendererAddon implements ITerminalAddon { +export class WebglAddon implements ITerminalAddon { constructor( private _preserveDrawingBuffer?: boolean ) {} diff --git a/addons/xterm-addon-webgl/webpack.config.js b/addons/xterm-addon-webgl/webpack.config.js index 825f6734..578b1102 100644 --- a/addons/xterm-addon-webgl/webpack.config.js +++ b/addons/xterm-addon-webgl/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'WebglAddon'; -const mainFile = 'xterm-addon-weblg.js'; +const mainFile = 'xterm-addon-webgl.js'; module.exports = { entry: `./out/${addonName}.js`, @@ -21,6 +21,14 @@ module.exports = { } ] }, + resolve: { + modules: ['./node_modules'], + extensions: [ '.js' ], + alias: { + common: path.resolve('../../out/common'), + browser: path.resolve('../../out/browser') + } + }, output: { filename: mainFile, path: path.resolve('./lib'), diff --git a/demo/client.ts b/demo/client.ts index 03d1bb73..83d9b841 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -13,7 +13,7 @@ import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon'; -import { WebglRendererAddon } from '../addons/xterm-addon-webgl/out/WebglRendererAddon'; +import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon'; // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; @@ -21,6 +21,7 @@ import { WebglRendererAddon } from '../addons/xterm-addon-webgl/out/WebglRendere // import { FitAddon } from 'xterm-addon-fit'; // import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; // import { WebLinksAddon } from 'xterm-addon-web-links'; +// import { WebglAddon } from 'xterm-addon-webgl'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module @@ -44,7 +45,7 @@ let socketURL; let socket; let pid; -(window).webgl = () => term.loadAddon(new WebglRendererAddon()); +(window).webgl = () => term.loadAddon(new WebglAddon()); const terminalContainer = document.getElementById('terminal-container'); const actionElements = { From eae0ab9e026f5ef9d40c1184bc163473d02d8cc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:04:02 -0700 Subject: [PATCH 069/104] Add a webgl button to the demo --- demo/client.ts | 3 +-- demo/index.html | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 83d9b841..ca0fb1da 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -45,8 +45,6 @@ let socketURL; let socket; let pid; -(window).webgl = () => term.loadAddon(new WebglAddon()); - const terminalContainer = document.getElementById('terminal-container'); const actionElements = { findNext: document.querySelector('#find-next'), @@ -91,6 +89,7 @@ if (document.location.pathname === '/test') { } else { createTerminal(); document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); + document.getElementById('webgl').addEventListener('click', () => term.loadAddon(new WebglAddon())); } function createTerminal(): void { diff --git a/demo/index.html b/demo/index.html index 7a939da5..28ce91ce 100644 --- a/demo/index.html +++ b/demo/index.html @@ -36,6 +36,7 @@

Attention: The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.

+ From d272422c0bc53daf2cb5ba4c625d2110a439adb1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:06:04 -0700 Subject: [PATCH 070/104] Fix webgl addon version --- addons/xterm-addon-webgl/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index 34c678dd..7ba43b1d 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-webgl", - "version": "0.1.0-beta6", + "version": "0.1.0-beta1", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" From 765297e67cf599cd3a4d1d4ff56dd23e8fa6bbbf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:11:34 -0700 Subject: [PATCH 071/104] Add addon typings --- .../typings/xterm-addon-webgl.d.ts | 52 ++----------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index 94bc5297..5199a260 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -3,41 +3,15 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm'; - -declare module 'xterm-addon-search' { - /** - * Options for a search. - */ - export interface ISearchOptions { - /** - * Whether the search term is a regex. - */ - regex?: boolean; - - /** - * Whether to search for a whole word, the result is only valid if it's - * suppounded in "non-word" characters such as `_`, `(`, `)` or space. - */ - wholeWord?: boolean; - - /** - * Whether the search is case sensitive. - */ - caseSensitive?: boolean; - - /** - * Whether to do an indcremental search, this will expand the selection if it - * still matches the term the user typed. Note that this only affects - * `findNext`, not `findPrevious`. - */ - incremental?: boolean; - } +import { Terminal, IDisposable, ITerminalAddon } from 'xterm'; +declare module 'xterm-addon-webgl' { /** * An xterm.js addon that provides search functionality. */ - export class SearchAddon implements ITerminalAddon { + export class WebglAddon implements ITerminalAddon { + constructor(preserveDrawingBuffer?: boolean); + /** * Activates the addon * @param terminal The terminal the addon is being loaded in. @@ -48,21 +22,5 @@ declare module 'xterm-addon-search' { * Disposes the addon. */ public dispose(): void; - - /** - * Search forwards for the next result that matches the search term and - * options. - * @param term The search term. - * @param searchOptions The options for the search. - */ - public findNext(term: string, searchOptions?: ISearchOptions): boolean; - - /** - * Search backwards for the previous result that matches the search term and - * options. - * @param term The search term. - * @param searchOptions The options for the search. - */ - public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; } } From dd9b7553292c0bf535f54a3f48f85ecc2308952b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:17:48 -0700 Subject: [PATCH 072/104] Fix webgl api tests --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 8 ++++---- demo/client.ts | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 14c27719..864a66ff 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -51,7 +51,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[30mâ–ˆ\\x1b[31mâ–ˆ\\x1b[32mâ–ˆ\\x1b[33mâ–ˆ\\x1b[34mâ–ˆ\\x1b[35mâ–ˆ\\x1b[36mâ–ˆ\\x1b[37mâ–ˆ`); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -78,7 +78,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[90mâ–ˆ\\x1b[91mâ–ˆ\\x1b[92mâ–ˆ\\x1b[93mâ–ˆ\\x1b[94mâ–ˆ\\x1b[95mâ–ˆ\\x1b[96mâ–ˆ\\x1b[97mâ–ˆ`); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -105,7 +105,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -132,7 +132,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); diff --git a/demo/client.ts b/demo/client.ts index ca0fb1da..005d171d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -34,6 +34,7 @@ export interface IWindowWithTerminal extends Window { FitAddon?: typeof FitAddon; SearchAddon?: typeof SearchAddon; WebLinksAddon?: typeof WebLinksAddon; + WebglAddon?: typeof WebglAddon; } declare let window: IWindowWithTerminal; @@ -86,6 +87,7 @@ if (document.location.pathname === '/test') { window.FitAddon = FitAddon; window.SearchAddon = SearchAddon; window.WebLinksAddon = WebLinksAddon; + window.WebglAddon = WebglAddon; } else { createTerminal(); document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); From 15647a030164b8fe7021bf5e20ea86c41661fc8e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:27:12 -0700 Subject: [PATCH 073/104] Speed up webgl tests --- .../src/WebglRenderer.api.ts | 124 ++++++++---------- 1 file changed, 52 insertions(+), 72 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 864a66ff..b26790c4 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from '../../../src/Types'; +import { ITheme } from 'xterm'; const APP = 'http://127.0.0.1:3000/test'; @@ -14,9 +15,10 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', () => { +describe('WebGL Renderer Integration Tests', function(): void { + this.timeout(20000); + before(async function(): Promise { - this.timeout(10000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, slowMo: 80, @@ -24,6 +26,9 @@ describe('WebGL Renderer Integration Tests', () => { }); page = (await browser.pages())[0]; await page.setViewport({ width, height }); + await page.goto(APP); + await openTerminal(); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); }); after(() => { @@ -31,27 +36,23 @@ describe('WebGL Renderer Integration Tests', () => { }); beforeEach(async () => { - await page.goto(APP); + await page.evaluate(`window.term.reset()`); }); describe('WebGL Renderer', () => { it('foreground colors normal', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - } - }); + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[30mâ–ˆ\\x1b[31mâ–ˆ\\x1b[32mâ–ˆ\\x1b[33mâ–ˆ\\x1b[34mâ–ˆ\\x1b[35mâ–ˆ\\x1b[36mâ–ˆ\\x1b[37mâ–ˆ`); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -63,22 +64,18 @@ describe('WebGL Renderer Integration Tests', () => { }); it('foreground colors bright', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - } - }); + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[90mâ–ˆ\\x1b[91mâ–ˆ\\x1b[92mâ–ˆ\\x1b[93mâ–ˆ\\x1b[94mâ–ˆ\\x1b[95mâ–ˆ\\x1b[96mâ–ˆ\\x1b[97mâ–ˆ`); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -90,22 +87,18 @@ describe('WebGL Renderer Integration Tests', () => { }); it('background colors normal', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - } - }); + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -117,22 +110,18 @@ describe('WebGL Renderer Integration Tests', () => { }); it('background colors bright', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - } - }); + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -164,15 +153,6 @@ async function writeSync(data: string): Promise { } } -// async function getPixelAt(x: number, y: number): Promise { -// await page.evaluate(` -// window.gl = window.term._core._renderService._renderer._gl; -// window.result = new Uint8Array(4); -// window.gl.readPixels(${x}, window.gl.drawingBufferHeight - 1 - ${y}, 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result); -// `); -// return await page.evaluate(`Array.from(window.result)`); -// } - async function getCellColor(col: number, row: number): Promise { await page.evaluate(` window.gl = window.term._core._renderService._renderer._gl; From 07b6b69be786c49dec168f5fed27fe3ff6ac03af Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:30:45 -0700 Subject: [PATCH 074/104] Remove old comment --- addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts | 1 - src/renderer/LinkRenderLayer.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 1ae22cf6..c942870f 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -16,7 +16,6 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); - // TODO: Need to expose link-related renderer API terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index e56a10af..99eff4a2 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -15,7 +15,6 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); - // TODO: Need to expose link-related renderer API terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } From 04ba3510e4d1c0bff3484499137d0e185eee016f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 14:37:44 -0700 Subject: [PATCH 075/104] Adopt automated released in webgl addon --- addons/xterm-addon-webgl/.npmignore | 1 + addons/xterm-addon-webgl/package.json | 2 +- bin/publish.js | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/.npmignore b/addons/xterm-addon-webgl/.npmignore index e8fc8237..1c794445 100644 --- a/addons/xterm-addon-webgl/.npmignore +++ b/addons/xterm-addon-webgl/.npmignore @@ -2,3 +2,4 @@ **/*.api.ts tsconfig.json .yarnrc +webpack.config.js diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index 7ba43b1d..e4f34147 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-webgl", - "version": "0.1.0-beta1", + "version": "0.1.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/bin/publish.js b/bin/publish.js index e03fa6ab..0610d116 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -27,7 +27,8 @@ const addonPackageDirs = [ path.resolve(__dirname, '../addons/xterm-addon-attach'), path.resolve(__dirname, '../addons/xterm-addon-fit'), path.resolve(__dirname, '../addons/xterm-addon-search'), - path.resolve(__dirname, '../addons/xterm-addon-web-links') + path.resolve(__dirname, '../addons/xterm-addon-web-links'), + path.resolve(__dirname, '../addons/xterm-addon-webgl') ]; addonPackageDirs.forEach(p => { const addon = path.basename(p); From 6e04058783e02a7d358d682429ddd7919d7a7b69 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 14:47:44 -0700 Subject: [PATCH 076/104] Reduce diff --- demo/index.html | 2 +- src/Types.d.ts | 1 - src/browser/ColorManager.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/demo/index.html b/demo/index.html index 28ce91ce..99ae413d 100644 --- a/demo/index.html +++ b/demo/index.html @@ -36,7 +36,7 @@

Attention: The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.

- + diff --git a/src/Types.d.ts b/src/Types.d.ts index 9a08a7a1..6f587931 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -299,7 +299,6 @@ export interface ITerminalOptions extends IPublicTerminalOptions { export interface ISelectionManager { selectionText: string; - hasSelection: boolean; selectionStart: [number, number]; selectionEnd: [number, number]; diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 4ac8fefe..70d21a7a 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -17,7 +17,7 @@ const DEFAULT_SELECTION = { // An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy // and mutate that. -export const DEFAULT_ANSI_COLORS: IColor[] = (() => { +export const DEFAULT_ANSI_COLORS = (() => { const colors = [ // dark: fromHex('#2e3436'), From 1ebae41a8b1912b9346523616566a16387adff75 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 16:35:38 -0700 Subject: [PATCH 077/104] Pull attribute compat stuff into addon --- .../xterm-addon-webgl/src/CharDataCompat.ts | 28 +++++++++++++++++++ addons/xterm-addon-webgl/src/Constants.ts | 15 ++++++++++ addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 ++-- .../src/atlas/WebglCharAtlas.ts | 3 +- src/common/buffer/BufferLine.ts | 21 ++------------ src/common/buffer/Constants.ts | 11 -------- 6 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 addons/xterm-addon-webgl/src/CharDataCompat.ts create mode 100644 addons/xterm-addon-webgl/src/Constants.ts diff --git a/addons/xterm-addon-webgl/src/CharDataCompat.ts b/addons/xterm-addon-webgl/src/CharDataCompat.ts new file mode 100644 index 00000000..13613c88 --- /dev/null +++ b/addons/xterm-addon-webgl/src/CharDataCompat.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { CellData } from 'common/buffer/CellData'; +import { FLAGS } from './Constants'; +import { IBufferLine } from 'common/Types'; + +export function getCompatAttr(bufferLine: IBufferLine, index: number): number { + // TODO: Need to move WebGL over to the new system and remove this block + const cell = new CellData(); + bufferLine.loadCell(index, cell); + const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); + const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); + const oldAttr = + (cell.isBold() ? FLAGS.BOLD : 0) | + (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | + (cell.isBlink() ? FLAGS.BLINK : 0) | + (cell.isInverse() ? FLAGS.INVERSE : 0) | + (cell.isDim() ? FLAGS.DIM : 0) | + (cell.isItalic() ? FLAGS.ITALIC : 0); + const attrCompat = + oldBg | + (oldFg << 9) | + (oldAttr << 18); + return attrCompat; +} diff --git a/addons/xterm-addon-webgl/src/Constants.ts b/addons/xterm-addon-webgl/src/Constants.ts new file mode 100644 index 00000000..ab18b97a --- /dev/null +++ b/addons/xterm-addon-webgl/src/Constants.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +// TODO: Should be removed after chardata workaround is fixed +export const enum FLAGS { + BOLD = 1, + UNDERLINE = 2, + BLINK = 4, + INVERSE = 8, + INVISIBLE = 16, + DIM = 32, + ITALIC = 64 +} diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a743bfd9..0d8cdf45 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,12 +14,14 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, FLAGS, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; +import { FLAGS } from './Constants'; +import { getCompatAttr } from './CharDataCompat'; export const INDICIES_PER_CELL = 4; @@ -256,7 +258,7 @@ export class WebglRenderer extends Disposable implements IRenderer { const charData = line.get(x); const chars = charData[CHAR_DATA_CHAR_INDEX]; let code = charData[CHAR_DATA_CODE_INDEX]; - const attr = charData[CHAR_DATA_ATTR_INDEX]; + const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; if (code !== NULL_CELL_CODE) { diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9a833fdf..fa18734b 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,9 +6,10 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { FLAGS, DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { IColor } from 'browser/Types'; +import { FLAGS } from '../Constants'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index f0240445..487ea128 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -5,7 +5,7 @@ import { CharData, IBufferLine, ICellData } from 'common/Types'; import { stringFromCodePoint } from 'common/input/TextDecoder'; -import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, FLAGS, Content } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -74,25 +74,8 @@ export class BufferLine implements IBufferLine { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; - // TODO: Need to move WebGL over to the new system and remove this block - const cell = new CellData(); - this.loadCell(index, cell); - const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); - const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); - const oldAttr = - (cell.isBold() ? FLAGS.BOLD : 0) | - (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | - (cell.isBlink() ? FLAGS.BLINK : 0) | - (cell.isInverse() ? FLAGS.INVERSE : 0) | - (cell.isDim() ? FLAGS.DIM : 0) | - (cell.isItalic() ? FLAGS.ITALIC : 0); - const attrCompat = - oldBg | - (oldFg << 9) | - (oldAttr << 18); - return [ - attrCompat, + this._data[index * CELL_SIZE + Cell.FG], (content & Content.IS_COMBINED_MASK) ? this._combined[index] : (cp) ? stringFromCodePoint(cp) : '', diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index fb726a1c..276a5c54 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -126,14 +126,3 @@ export const enum BgFlags { ITALIC = 0x4000000, DIM = 0x8000000 } - -// TODO: Should be removed after chardata workaround is fixed -export const enum FLAGS { - BOLD = 1, - UNDERLINE = 2, - BLINK = 4, - INVERSE = 8, - INVISIBLE = 16, - DIM = 32, - ITALIC = 64 -} From 7148358acf1fe4345393b6d288ed931c17e6699c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 16:40:45 -0700 Subject: [PATCH 078/104] Prevent crash on true color, add TODOs for it --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 4 ++++ addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 401f4568..6dfee505 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -275,7 +275,11 @@ export class RectangleRenderer { if (bg === INVERTED_DEFAULT_COLOR) { color = this._colors.foreground; } else if (is256Color(bg)) { + // TODO: Need to do a separate set for 16 color palette? color = this._colors.ansi[bg]; + } else { + // TODO: Add support for true color + color = this._colors.foreground; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index fa18734b..33464269 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -170,8 +170,10 @@ export class WebglCharAtlas extends BaseCharAtlas { } else if (bg === INVERTED_DEFAULT_COLOR) { return this._config.colors.foreground; } else if (is256Color(bg)) { + // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(bg); } + // TODO: Support true color return this._config.colors.background; } @@ -179,8 +181,10 @@ export class WebglCharAtlas extends BaseCharAtlas { if (fg === INVERTED_DEFAULT_COLOR) { return this._config.colors.background; } else if (is256Color(fg)) { + // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(fg); } + // TODO: Support true color return this._config.colors.foreground; } From 8b4901e8f30659a793c97a91a04b7fafd731aec8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 16:43:01 -0700 Subject: [PATCH 079/104] Reduce diff --- src/common/buffer/BufferLine.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 487ea128..8e742be3 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -73,7 +73,6 @@ export class BufferLine implements IBufferLine { public get(index: number): CharData { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; - return [ this._data[index * CELL_SIZE + Cell.FG], (content & Content.IS_COMBINED_MASK) From 5100c7a30dfe1e06129ba631311fdb95b0b91d5a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 16 Jun 2019 00:17:14 -0700 Subject: [PATCH 080/104] Use Constants from core --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 2 +- addons/xterm-addon-webgl/src/{Types.ts => Types.d.ts} | 0 addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- addons/xterm-addon-webgl/src/atlas/{Types.ts => Types.d.ts} | 5 ----- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 3 ++- addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts | 3 ++- addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts | 2 +- 7 files changed, 7 insertions(+), 10 deletions(-) rename addons/xterm-addon-webgl/src/{Types.ts => Types.d.ts} (100%) rename addons/xterm-addon-webgl/src/atlas/{Types.ts => Types.d.ts} (83%) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 6dfee505..24b4994b 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; diff --git a/addons/xterm-addon-webgl/src/Types.ts b/addons/xterm-addon-webgl/src/Types.d.ts similarity index 100% rename from addons/xterm-addon-webgl/src/Types.ts rename to addons/xterm-addon-webgl/src/Types.d.ts diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 0d8cdf45..defed962 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -11,7 +11,7 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; diff --git a/addons/xterm-addon-webgl/src/atlas/Types.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts similarity index 83% rename from addons/xterm-addon-webgl/src/atlas/Types.ts rename to addons/xterm-addon-webgl/src/atlas/Types.d.ts index 2cb1db40..1de843e0 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -6,11 +6,6 @@ import { FontWeight } from 'xterm'; import { IColorSet } from 'browser/Types'; -export const INVERTED_DEFAULT_COLOR = 257; -export const DIM_OPACITY = 0.5; - -export const CHAR_ATLAS_CELL_SPACING = 1; - export interface IGlyphIdentifier { chars: string; code: number; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 33464269..c3da6955 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; +import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 263a0d21..999d94f4 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -6,7 +6,8 @@ import { IRenderLayer } from './Types'; import { ICellData } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; +import { IGlyphIdentifier } from '../atlas/Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index c942870f..d37f2640 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -6,7 +6,7 @@ import { ILinkifierEvent, ILinkifierAccessor } from '../../../../src/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from '../atlas/CharAtlasUtils'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; From 1b7862ce8a26dc56d10c02dce575aeec9a65c42a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 17 Jun 2019 07:35:52 -0700 Subject: [PATCH 081/104] Move slice typed array util into webgl addon --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 3 +- .../xterm-addon-webgl/src/TypedArray.test.ts | 122 ++++++++++++++++++ addons/xterm-addon-webgl/src/TypedArray.ts | 34 +++++ src/common/TypedArrayUtils.test.ts | 105 +-------------- src/common/TypedArrayUtils.ts | 26 ---- 5 files changed, 159 insertions(+), 131 deletions(-) create mode 100644 addons/xterm-addon-webgl/src/TypedArray.test.ts create mode 100644 addons/xterm-addon-webgl/src/TypedArray.ts diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 50357b28..dc375348 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -8,7 +8,8 @@ import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from 'common/TypedArrayUtils'; +import { fill } from 'common/TypedArrayUtils'; +import { slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; diff --git a/addons/xterm-addon-webgl/src/TypedArray.test.ts b/addons/xterm-addon-webgl/src/TypedArray.test.ts new file mode 100644 index 00000000..f18c6774 --- /dev/null +++ b/addons/xterm-addon-webgl/src/TypedArray.test.ts @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { assert } from 'chai'; +import { sliceFallback } from './TypedArray'; + +type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +function deepEquals(a: TypedArray, b: TypedArray): void { + assert.equal(a.length, b.length); + for (let i = 0; i < a.length; ++i) { + assert.equal(a[i], b[i]); + } +} + +describe('polyfill conformance tests', function(): void { + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); +}); diff --git a/addons/xterm-addon-webgl/src/TypedArray.ts b/addons/xterm-addon-webgl/src/TypedArray.ts new file mode 100644 index 00000000..ee93be35 --- /dev/null +++ b/addons/xterm-addon-webgl/src/TypedArray.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array.constructor as any)(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index ed3541db..429a3a33 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fillFallback, concat, sliceFallback } from 'common/TypedArrayUtils'; +import { fillFallback, concat } from 'common/TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -85,109 +85,6 @@ describe('polyfill conformance tests', function(): void { } }); }); - - describe('TypedArray.slice', () => { - describe('should work with all typed array types', () => { - it('Uint8Array', () => { - const a = new Uint8Array(5); - deepEquals(sliceFallback(a, 2), a.slice(2)); - deepEquals(sliceFallback(a, 65535), a.slice(65535)); - deepEquals(sliceFallback(a, -1), a.slice(-1)); - }); - it('Uint16Array', () => { - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(sliceFallback(u161, 2), u162.slice(2)); - deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); - deepEquals(sliceFallback(u161, -1), u162.slice(-1)); - }); - it('Uint32Array', () => { - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(sliceFallback(u321, 2), u322.slice(2)); - deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); - deepEquals(sliceFallback(u321, -1), u322.slice(-1)); - }); - it('Int8Array', () => { - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(sliceFallback(i81, 2), i82.slice(2)); - deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); - deepEquals(sliceFallback(i81, -1), i82.slice(-1)); - }); - it('Int16Array', () => { - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(sliceFallback(i161, 2), i162.slice(2)); - deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); - deepEquals(sliceFallback(i161, -1), i162.slice(-1)); - }); - it('Int32Array', () => { - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(sliceFallback(i321, 2), i322.slice(2)); - deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); - deepEquals(sliceFallback(i321, -1), i322.slice(-1)); - }); - it('Float32Array', () => { - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(sliceFallback(f321, 2), f322.slice(2)); - deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); - deepEquals(sliceFallback(f321, -1), f322.slice(-1)); - }); - it('Float64Array', () => { - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(sliceFallback(f641, 2), f642.slice(2)); - deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); - deepEquals(sliceFallback(f641, -1), f642.slice(-1)); - }); - it('Uint8ClampedArray', () => { - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); - deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); - deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); - }); - }); - it('start', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1), arr.slice(-1)); - deepEquals(sliceFallback(arr, 0), arr.slice(0)); - deepEquals(sliceFallback(arr, 1), arr.slice(1)); - deepEquals(sliceFallback(arr, 2), arr.slice(2)); - deepEquals(sliceFallback(arr, 3), arr.slice(3)); - deepEquals(sliceFallback(arr, 4), arr.slice(4)); - deepEquals(sliceFallback(arr, 5), arr.slice(5)); - }); - it('end', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); - deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); - deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); - deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); - deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); - deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); - deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); - - deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); - deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); - deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); - deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); - deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); - deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); - deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); - - deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); - deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); - deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); - deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); - deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); - deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); - deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); - }); - }); }); describe('typed array convenience functions', () => { diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index f2651aa0..54699835 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -40,32 +40,6 @@ export function fillFallback(array: T, value: number, star return array; } -export function slice(array: T, start?: number, end?: number): T { - // all modern engines that support .slice - if (array.slice) { - return array.slice(start, end) as T; - } - return sliceFallback(array, start, end); -} - -export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { - if (start < 0) { - start = (array.length + start) % array.length; - } - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - start = Math.min(start, end); - - const result: T = new (array.constructor as any)(end - start); - for (let i = 0; i < end - start; ++i) { - result[i] = array[i + start]; - } - return result; -} - /** * Concat two typed arrays `a` and `b`. * Returns a new typed array. From 5321f0373fc8f1380ec1853c6ae24680fa395712 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 17 Jun 2019 13:42:44 -0700 Subject: [PATCH 082/104] Fix quote and comma --- demo/index.html | 2 +- tsconfig.all.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/index.html b/demo/index.html index 99ae413d..ef8f3891 100644 --- a/demo/index.html +++ b/demo/index.html @@ -36,7 +36,7 @@

Attention: The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.

- + diff --git a/tsconfig.all.json b/tsconfig.all.json index bc633fe6..06efafcb 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -7,7 +7,7 @@ { "path": "./addons/xterm-addon-attach/src" }, { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, - { "path": "./addons/xterm-addon-web-links/src" } + { "path": "./addons/xterm-addon-web-links/src" }, { "path": "./addons/xterm-addon-webgl/src" } ] } From a28761bf3ed12aa2eee1c1e0d8dc761e7aecab0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 19 Jun 2019 00:07:09 +0200 Subject: [PATCH 083/104] restructured test folder --- benchmarks/tsconfig.json | 32 ----------------- package.json | 8 ++--- src/tsconfig.json | 1 - test/{ => api}/CharWidth.api.ts | 0 test/{ => api}/InputHandler.api.ts | 0 test/{ => api}/Terminal.api.ts | 0 test/{ => api}/tsconfig.json | 6 ++-- .../EscapeSequenceParser.benchmark.ts | 0 .../benchmark}/Terminal.benchmark.ts | 0 {benchmarks => test/benchmark}/benchmark.json | 0 test/benchmark/tsconfig.json | 34 +++++++++++++++++++ tsconfig.all.json | 4 +-- 12 files changed, 43 insertions(+), 42 deletions(-) delete mode 100644 benchmarks/tsconfig.json rename test/{ => api}/CharWidth.api.ts (100%) rename test/{ => api}/InputHandler.api.ts (100%) rename test/{ => api}/Terminal.api.ts (100%) rename test/{ => api}/tsconfig.json (69%) rename {benchmarks => test/benchmark}/EscapeSequenceParser.benchmark.ts (100%) rename {benchmarks => test/benchmark}/Terminal.benchmark.ts (100%) rename {benchmarks => test/benchmark}/benchmark.json (100%) create mode 100644 test/benchmark/tsconfig.json diff --git a/benchmarks/tsconfig.json b/benchmarks/tsconfig.json deleted file mode 100644 index 4dcf9b65..00000000 --- a/benchmarks/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "es6", - ], - "outDir": "../out/test", - "types": [ - "../../node_modules/@types/node" - ], - "baseUrl": "..", - "strict": true, - "baseUrl": ".", - "paths": { - "common/*": [ "../src/common/*" ], - "browser/*": [ "../src/browser/*" ], - "Terminal": [ "../src/Terminal" ] - }, - }, - "include": [ - "./**/*", - "../typings/xterm.d.ts" - ], - "exclude": [ - "../**/*test.ts" - ], - "references": [ - { "path": "../src/common" }, - { "path": "../src/browser" }, - { "path": "../src" }, - ] -} diff --git a/package.json b/package.json index 5d7cb5b4..118578aa 100644 --- a/package.json +++ b/package.json @@ -14,16 +14,16 @@ "lint": "tslint 'src/**/*.ts' './demo/**/*.ts' './addons/**/*.ts'", "test": "npm run test-unit", "posttest": "npm run lint", - "test-api": "mocha \"**/*.api.js\"", + "test-api": "mocha \"./out-test/api/*.api.js\"", "test-unit": "node ./bin/test.js", "build": "tsc -b ./tsconfig.all.json", "prepare": "npm run build", "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", "clean": "rm -rf lib out addons/*/lib", - "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmarks/benchmark.json", - "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmarks/benchmark.json --baseline out/test/*benchmark.js", - "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c benchmarks/benchmark.json --eval out/test/*benchmark.js" + "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json", + "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js", + "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js" }, "devDependencies": { "@types/chai": "^3.4.34", diff --git a/src/tsconfig.json b/src/tsconfig.json index afaabfbb..97576668 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -11,7 +11,6 @@ ], "rootDir": ".", "outDir": "../out", - "composite": true, "baseUrl": ".", "paths": { "common/*": [ "./common/*" ], diff --git a/test/CharWidth.api.ts b/test/api/CharWidth.api.ts similarity index 100% rename from test/CharWidth.api.ts rename to test/api/CharWidth.api.ts diff --git a/test/InputHandler.api.ts b/test/api/InputHandler.api.ts similarity index 100% rename from test/InputHandler.api.ts rename to test/api/InputHandler.api.ts diff --git a/test/Terminal.api.ts b/test/api/Terminal.api.ts similarity index 100% rename from test/Terminal.api.ts rename to test/api/Terminal.api.ts diff --git a/test/tsconfig.json b/test/api/tsconfig.json similarity index 69% rename from test/tsconfig.json rename to test/api/tsconfig.json index fce9a1ba..2bd0a92b 100644 --- a/test/tsconfig.json +++ b/test/api/tsconfig.json @@ -5,9 +5,9 @@ "es6", ], "rootDir": ".", - "outDir": "../out/test", + "outDir": "../../out-test/api", "types": [ - "../node_modules/@types/mocha" + "../../node_modules/@types/mocha" ], "sourceMap": true, "removeComments": true, @@ -16,6 +16,6 @@ }, "include": [ "./**/*", - "../typings/xterm.d.ts" + "../../typings/xterm.d.ts" ] } diff --git a/benchmarks/EscapeSequenceParser.benchmark.ts b/test/benchmark/EscapeSequenceParser.benchmark.ts similarity index 100% rename from benchmarks/EscapeSequenceParser.benchmark.ts rename to test/benchmark/EscapeSequenceParser.benchmark.ts diff --git a/benchmarks/Terminal.benchmark.ts b/test/benchmark/Terminal.benchmark.ts similarity index 100% rename from benchmarks/Terminal.benchmark.ts rename to test/benchmark/Terminal.benchmark.ts diff --git a/benchmarks/benchmark.json b/test/benchmark/benchmark.json similarity index 100% rename from benchmarks/benchmark.json rename to test/benchmark/benchmark.json diff --git a/test/benchmark/tsconfig.json b/test/benchmark/tsconfig.json new file mode 100644 index 00000000..8b93dcb1 --- /dev/null +++ b/test/benchmark/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "es6", + ], + "outDir": "../../out-test/benchmark", + "types": [ + "../../node_modules/@types/node" + ], + "moduleResolution": "node", + "strict": false, + "target": "es2015", + "module": "commonjs", + "baseUrl": ".", + "paths": { + "common/*": [ "../../src/common/*" ], + "browser/*": [ "../../src/browser/*" ], + "Terminal": [ "../../src/Terminal" ] + }, + }, + "include": [ + "./**/*", + "../../typings/xterm.d.ts", + "../../out/**/*" + ], + "exclude": [ + "../../**/*test.ts" + ], + "references": [ + { "path": "../../src/common" }, + { "path": "../../src/browser" }, + ] +} \ No newline at end of file diff --git a/tsconfig.all.json b/tsconfig.all.json index aa368d5b..a6ea0e28 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -3,8 +3,8 @@ "include": [], "references": [ { "path": "./src" }, - { "path": "./test" }, - { "path": "./benchmarks" }, + { "path": "./test/api" }, + { "path": "./test/benchmark" }, { "path": "./addons/xterm-addon-attach/src" }, { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, From a0ccc9b78b840227e52c07bcd6644c4293954a16 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Tue, 18 Jun 2019 15:28:41 -0700 Subject: [PATCH 084/104] Fix a type in the README "Perfomant" should be "Performant" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4707f195..fb6883f7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b ## Features - **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim` and `tmux`, this includes support for curses-based apps and mouse event support. -- **Perfomant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer. +- **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer. - **Rich unicode support**: Supports CJK, emojis and IMEs. - **Self-contained**: Requires zero dependencies to work. - **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option. From 1dc4e3697206e33c47e52cb83e1e0cd5fff8c400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 19 Jun 2019 00:42:20 +0200 Subject: [PATCH 085/104] fix CSI bug, cleanup test --- .../EscapeSequenceParser.benchmark.ts | 168 ++++++++++-------- 1 file changed, 92 insertions(+), 76 deletions(-) diff --git a/test/benchmark/EscapeSequenceParser.benchmark.ts b/test/benchmark/EscapeSequenceParser.benchmark.ts index 57f8329c..f92afc80 100644 --- a/test/benchmark/EscapeSequenceParser.benchmark.ts +++ b/test/benchmark/EscapeSequenceParser.benchmark.ts @@ -17,16 +17,17 @@ function toUtf32(s: string): Uint32Array { return result; } +class DcsHandler implements IDcsHandler { + hook(collect: string, params: number[], flag: number) : void {} + put(data: Uint32Array, start: number, end: number) : void {} + unhook() :void {} +} -perfContext('Parser performance - 50MB data', () => { - let content; - let taContent: Uint32Array; + +perfContext('Parser throughput - 50MB data', () => { + let parsed: Uint32Array; let parser: EscapeSequenceParser; - const dcsHandler: IDcsHandler = { - hook: (collect, params, flag) => {}, - put: (data, start, end) => {}, - unhook: () => {} - }; + beforeEach(() => { parser = new EscapeSequenceParser(); parser.setPrintHandler((data, start, end) => {}); @@ -96,171 +97,186 @@ perfContext('Parser performance - 50MB data', () => { parser.setEscHandler('~', () => {}); parser.setEscHandler('%@', () => {}); parser.setEscHandler('%G', () => {}); - parser.setDcsHandler('q', dcsHandler); + parser.setDcsHandler('q', new DcsHandler()); }); - perfContext('print - a', () => { + perfContext('PRINT - a', () => { before(() => { const data = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', async () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', async () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('execute - \\n', () => { + perfContext('EXECUTE - \\n', () => { before(() => { const data = '\n\n\n\n\n\n\n'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('escape - ESC E', () => { + perfContext('ESCAPE - ESC E', () => { before(() => { const data = '\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('escape with collect - ESC % G', () => { + perfContext('ESCAPE with collect - ESC % G', () => { before(() => { const data = '\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('simple csi - CSI A', () => { + perfContext('CSI - CSI A', () => { before(() => { const data = '\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('csi with collect - CSI ? p', () => { + perfContext('CSI with collect - CSI ? p', () => { before(() => { const data = '\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('csi with params - CSI 1;2 m', () => { + perfContext('CSI with params (short) - CSI 1;2 m', () => { before(() => { - const data = '\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m\x1b{1;2m'; - content = ''; + const data = '\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m'; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('osc (small payload) - OSC 0;hi ST', () => { + perfContext('CSI with params (long) - CSI 1;2;3;4;5;6;7;8;9;0 m', () => { + before(() => { + const data = '\x1b[1;2;3;4;5;6;7;8;9;0m\x1b[1;2;3;4;5;6;7;8;9;0m\x1b[1;2;3;4;5;6;7;8;9;0m'; + let content = ''; + while (content.length < 50000000) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('OSC (short) - OSC 0;hi ST', () => { before(() => { const data = '\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\x1b]0;hi\x1b\\'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('osc (big payload) - OSC 0; ST', () => { + perfContext('OSC (long) - OSC 0; ST', () => { before(() => { const data = '\x1b]0;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('DCS (small payload)', () => { + perfContext('DCS (short)', () => { before(() => { const data = '\x1bPq~~\x1b\\'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', async () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', async () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); - perfContext('DCS (big payload)', () => { + perfContext('DCS (long)', () => { before(() => { const data = '\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\'; - content = ''; + let content = ''; while (content.length < 50000000) { content += data; } - taContent = toUtf32(content); + parsed = toUtf32(content); }); - new ThroughputRuntimeCase('throughput', async () => { - parser.parse(taContent, taContent.length); - return {payloadSize: taContent.length}; + new ThroughputRuntimeCase('', async () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; }, {fork: true}).showAverageThroughput(); }); }); From 1b2811075cf1499fcb726c4afd9c4b8cc6ddfcd0 Mon Sep 17 00:00:00 2001 From: Max Risuhin Date: Fri, 21 Jun 2019 16:35:26 +0300 Subject: [PATCH 086/104] Define wordSeparator option --- src/SelectionManager.ts | 8 +------- src/common/services/OptionsService.ts | 4 +++- src/common/services/Services.d.ts | 2 ++ src/public/Terminal.ts | 4 ++-- typings/xterm.d.ts | 10 ++++++++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 0b77d183..fb2caba8 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -38,12 +38,6 @@ const DRAG_SCROLL_INTERVAL = 50; */ const ALT_CLICK_MOVE_CURSOR_TIME = 500; -/** - * A string containing all characters that are considered word separated by the - * double click to select work logic. - */ -const WORD_SEPARATORS = ' ()[]{}\'"'; - const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); const ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); @@ -914,7 +908,7 @@ export class SelectionManager implements ISelectionManager { if (cell.getWidth() === 0) { return false; } - return WORD_SEPARATORS.indexOf(cell.getChars()) >= 0; + return this._terminal.optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0; } /** diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 2d2a08f0..ab041488 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -46,7 +46,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, debug: false, cancelEvents: false, - useFlowControl: false + useFlowControl: false, + wordSeparator: ' ()[]{}\'"' }); /** @@ -99,6 +100,7 @@ export class OptionsService implements IOptionsService { case 'fontWeight': case 'fontWeightBold': case 'rendererType': + case 'wordSeparator': if (!value) { value = DEFAULT_OPTIONS[key]; } diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index 602671ff..b8276f51 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -56,6 +56,7 @@ export interface IPartialTerminalOptions { tabStopWidth?: number; theme?: ITheme; windowsMode?: boolean; + wordSeparator?: string; } export interface ITerminalOptions { @@ -91,6 +92,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; + wordSeparator?: string; } export interface ITheme { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 0dae4def..4a19db9f 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -125,7 +125,7 @@ export class Terminal implements ITerminalApi { public writeUtf8(data: Uint8Array): void { this._core.writeUtf8(data); } - public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string; + public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName' | 'wordSeparator'): string; public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; public getOption(key: 'colors'): string[]; public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; @@ -134,7 +134,7 @@ export class Terminal implements ITerminalApi { public getOption(key: any): any { return this._core.optionsService.getOption(key); } - public setOption(key: 'bellSound' | 'fontFamily' | 'termName', value: string): void; + public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void; public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f435367d..be301884 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -177,6 +177,12 @@ declare module 'xterm' { * not whitespace. */ windowsMode?: boolean; + + /** + * A string containing all characters that are considered word separated by the + * double click to select work logic. + */ + wordSeparator?: string; } /** @@ -673,7 +679,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'rendererType' | 'termName'): string; + getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'rendererType' | 'termName' | 'wordSeparator'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -705,7 +711,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'fontFamily' | 'termName' | 'bellSound', value: string): void; + setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void; /** * Sets an option on the terminal. * @param key The option key. From 62e2b00bd5bbb6cb29583ce2577469a3aac6a580 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 19:54:11 -0700 Subject: [PATCH 087/104] Remove terminal dep in clipboard --- src/Clipboard.ts | 51 ++++++++++++++++++++++++------------------------ src/Terminal.ts | 10 +++++----- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Clipboard.ts b/src/Clipboard.ts index 9461afa1..75b0da8e 100644 --- a/src/Clipboard.ts +++ b/src/Clipboard.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal, ISelectionManager } from './Types'; +import { ISelectionManager } from './Types'; /** * Prepares text to be pasted into the terminal by normalizing the line endings @@ -28,7 +28,7 @@ export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): * Binds copy functionality to the given terminal. * @param ev The original copy event to be handled */ -export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void { +export function copyHandler(ev: ClipboardEvent, selectionManager: ISelectionManager): void { ev.clipboardData.setData('text/plain', selectionManager.selectionText); // Prevent or the original text will be copied. ev.preventDefault(); @@ -39,17 +39,16 @@ export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManage * @param ev The original paste event to be handled * @param term The terminal on which to apply the handled paste event */ -export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { +export function pasteHandler(ev: ClipboardEvent, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, triggerUserInput: (data: string) => void): void { ev.stopPropagation(); let text: string; const dispatchPaste = function(text: string): void { text = prepareTextForTerminal(text); - text = bracketTextForPaste(text, term.bracketedPasteMode); - term.handler(text); - term.textarea.value = ''; - term.cancel(ev); + text = bracketTextForPaste(text, bracketedPasteMode); + triggerUserInput(text); + textarea.value = ''; }; if (ev.clipboardData) { @@ -63,32 +62,32 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { * @param ev The original right click event to be handled. * @param textarea The terminal's textarea. */ -export function moveTextAreaUnderMouseCursor(ev: MouseEvent, term: ITerminal): void { +export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement): void { // Calculate textarea position relative to the screen element - const pos = term.screenElement.getBoundingClientRect(); + const pos = screenElement.getBoundingClientRect(); const left = ev.clientX - pos.left - 10; const top = ev.clientY - pos.top - 10; // Bring textarea at the cursor position - term.textarea.style.position = 'absolute'; - term.textarea.style.width = '20px'; - term.textarea.style.height = '20px'; - term.textarea.style.left = `${left}px`; - term.textarea.style.top = `${top}px`; - term.textarea.style.zIndex = '1000'; + textarea.style.position = 'absolute'; + textarea.style.width = '20px'; + textarea.style.height = '20px'; + textarea.style.left = `${left}px`; + textarea.style.top = `${top}px`; + textarea.style.zIndex = '1000'; - term.textarea.focus(); + textarea.focus(); // Reset the terminal textarea's styling // Timeout needs to be long enough for click event to be handled. setTimeout(() => { - term.textarea.style.position = null; - term.textarea.style.width = null; - term.textarea.style.height = null; - term.textarea.style.left = null; - term.textarea.style.top = null; - term.textarea.style.zIndex = null; + textarea.style.position = null; + textarea.style.width = null; + textarea.style.height = null; + textarea.style.left = null; + textarea.style.top = null; + textarea.style.zIndex = null; }, 200); } @@ -99,14 +98,14 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, term: ITerminal): v * @param selectionManager The terminal's selection manager. * @param shouldSelectWord If true and there is no selection the current word will be selected */ -export function rightClickHandler(ev: MouseEvent, term: ITerminal, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { - moveTextAreaUnderMouseCursor(ev, term); +export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { + moveTextAreaUnderMouseCursor(ev, textarea, screenElement); if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) { selectionManager.selectWordAtCursor(ev); } // Get textarea ready to copy from the context menu - term.textarea.value = selectionManager.selectionText; - term.textarea.select(); + textarea.value = selectionManager.selectionText; + textarea.select(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index b28cfcfa..310aebe9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -478,9 +478,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp if (!this.hasSelection()) { return; } - copyHandler(event, this, this.selectionManager); + copyHandler(event, this.selectionManager); })); - const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this); + const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this.textarea, this.bracketedPasteMode, e => this.handler(e)); this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); @@ -489,12 +489,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Firefox doesn't appear to fire the contextmenu event on right click this.register(addDisposableDomListener(this.element, 'mousedown', (event: MouseEvent) => { if (event.button === 2) { - rightClickHandler(event, this, this.selectionManager, this.options.rightClickSelectsWord); + rightClickHandler(event, this.textarea, this.screenElement, this.selectionManager, this.options.rightClickSelectsWord); } })); } else { this.register(addDisposableDomListener(this.element, 'contextmenu', (event: MouseEvent) => { - rightClickHandler(event, this, this.selectionManager, this.options.rightClickSelectsWord); + rightClickHandler(event, this.textarea, this.screenElement, this.selectionManager, this.options.rightClickSelectsWord); })); } @@ -506,7 +506,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // that the regular click event doesn't fire for the middle mouse button. this.register(addDisposableDomListener(this.element, 'auxclick', (event: MouseEvent) => { if (event.button === 1) { - moveTextAreaUnderMouseCursor(event, this); + moveTextAreaUnderMouseCursor(event, this.textarea, this.screenElement); } })); } From 72f615da14a0e0d627db4cff81172b0e9ef98285 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 20:08:58 -0700 Subject: [PATCH 088/104] Remove some of terminal dependency in SelectionManager --- src/SelectionManager.ts | 82 ++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index fb2caba8..4bd62cdc 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -114,13 +114,13 @@ export class SelectionManager implements ISelectionManager { constructor( private readonly _terminal: ITerminal, private readonly _charSizeService: ICharSizeService, - readonly bufferService: IBufferService, + private readonly _bufferService: IBufferService, private readonly _mouseService: IMouseService ) { this._initListeners(); this.enable(); - this._model = new SelectionModel(bufferService); + this._model = new SelectionModel(this._bufferService); this._activeSelectionMode = SelectionMode.NORMAL; } @@ -128,10 +128,6 @@ export class SelectionManager implements ISelectionManager { this._removeMouseDownListeners(); } - private get _buffer(): IBuffer { - return this._terminal.buffers.active; - } - /** * Initializes listener variables. */ @@ -143,8 +139,8 @@ export class SelectionManager implements ISelectionManager { } public initBuffersListeners(): void { - this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount)); - this._terminal.buffers.onBufferActivate(e => this._onBufferActivate(e)); + this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount)); + this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e)); } /** @@ -188,6 +184,7 @@ export class SelectionManager implements ISelectionManager { return ''; } + const buffer = this._bufferService.buffer; const result: string[] = []; if (this._activeSelectionMode === SelectionMode.COLUMN) { @@ -197,18 +194,18 @@ export class SelectionManager implements ISelectionManager { } for (let i = start[1]; i <= end[1]; i++) { - const lineText = this._buffer.translateBufferLineToString(i, true, start[0], end[0]); + const lineText = buffer.translateBufferLineToString(i, true, start[0], end[0]); result.push(lineText); } } else { // Get first row const startRowEndCol = start[1] === end[1] ? end[0] : undefined; - result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); + result.push(buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); // Get middle rows for (let i = start[1] + 1; i <= end[1] - 1; i++) { - const bufferLine = this._buffer.lines.get(i); - const lineText = this._buffer.translateBufferLineToString(i, true); + const bufferLine = buffer.lines.get(i); + const lineText = buffer.translateBufferLineToString(i, true); if (bufferLine.isWrapped) { result[result.length - 1] += lineText; } else { @@ -218,8 +215,8 @@ export class SelectionManager implements ISelectionManager { // Get final row if (start[1] !== end[1]) { - const bufferLine = this._buffer.lines.get(end[1]); - const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]); + const bufferLine = buffer.lines.get(end[1]); + const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]); if (bufferLine.isWrapped) { result[result.length - 1] += lineText; } else { @@ -329,9 +326,9 @@ export class SelectionManager implements ISelectionManager { public selectLines(start: number, end: number): void { this._model.clearSelection(); start = Math.max(start, 0); - end = Math.min(end, this._terminal.buffer.lines.length - 1); + end = Math.min(end, this._bufferService.buffer.lines.length - 1); this._model.selectionStart = [0, start]; - this._model.selectionEnd = [this._terminal.cols, end]; + this._model.selectionEnd = [this._bufferService.cols, end]; this.refresh(); this._onSelectionChange.fire(); } @@ -362,7 +359,7 @@ export class SelectionManager implements ISelectionManager { coords[1]--; // Convert viewport coords to buffer coords - coords[1] += this._terminal.buffer.ydisp; + coords[1] += this._bufferService.buffer.ydisp; return coords; } @@ -499,7 +496,7 @@ export class SelectionManager implements ISelectionManager { this._model.selectionEnd = null; // Ensure the line exists - const line = this._buffer.lines.get(this._model.selectionStart[1]); + const line = this._bufferService.buffer.lines.get(this._model.selectionStart[1]); if (!line) { return; } @@ -576,7 +573,7 @@ export class SelectionManager implements ISelectionManager { if (this._model.selectionEnd[1] < this._model.selectionStart[1]) { this._model.selectionEnd[0] = 0; } else { - this._model.selectionEnd[0] = this._terminal.cols; + this._model.selectionEnd[0] = this._bufferService.cols; } } else if (this._activeSelectionMode === SelectionMode.WORD) { this._selectToWordAt(this._model.selectionEnd); @@ -590,7 +587,7 @@ export class SelectionManager implements ISelectionManager { // NOT in column select mode. if (this._activeSelectionMode !== SelectionMode.COLUMN) { if (this._dragScrollAmount > 0) { - this._model.selectionEnd[0] = this._terminal.cols; + this._model.selectionEnd[0] = this._bufferService.cols; } else if (this._dragScrollAmount < 0) { this._model.selectionEnd[0] = 0; } @@ -599,8 +596,9 @@ export class SelectionManager implements ISelectionManager { // If the character is a wide character include the cell to the right in the // selection. Note that selections at the very end of the line will never // have a character. - if (this._model.selectionEnd[1] < this._buffer.lines.length) { - if (this._buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) { + const buffer = this._bufferService.buffer; + if (this._model.selectionEnd[1] < buffer.lines.length) { + if (buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) { this._model.selectionEnd[0]++; } } @@ -624,16 +622,17 @@ export class SelectionManager implements ISelectionManager { // If the cursor was above or below the viewport, make sure it's at the // start or end of the viewport respectively. This should only happen when // NOT in column select mode. + const buffer = this._bufferService.buffer; if (this._dragScrollAmount > 0) { if (this._activeSelectionMode !== SelectionMode.COLUMN) { - this._model.selectionEnd[0] = this._terminal.cols; + this._model.selectionEnd[0] = this._bufferService.cols; } - this._model.selectionEnd[1] = Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1); + this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows, buffer.lines.length - 1); } else { if (this._activeSelectionMode !== SelectionMode.COLUMN) { this._model.selectionEnd[0] = 0; } - this._model.selectionEnd[1] = this._terminal.buffer.ydisp; + this._model.selectionEnd[1] = buffer.ydisp; } this.refresh(); } @@ -704,16 +703,17 @@ export class SelectionManager implements ISelectionManager { */ private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition { // Ensure coords are within viewport (eg. not within scroll bar) - if (coords[0] >= this._terminal.cols) { + if (coords[0] >= this._bufferService.cols) { return null; } - const bufferLine = this._buffer.lines.get(coords[1]); + const buffer = this._bufferService.buffer; + const bufferLine = buffer.lines.get(coords[1]); if (!bufferLine) { return null; } - const line = this._buffer.translateBufferLineToString(coords[1], false); + const line = buffer.translateBufferLineToString(coords[1], false); // Get actual index, taking into consideration wide characters let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); @@ -808,7 +808,7 @@ export class SelectionManager implements ISelectionManager { // Calculate the length in _columns_, converting the the string indexes back // to column coordinates. - let length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols + let length = Math.min(this._bufferService.cols, // Disallow lengths larger than the terminal cols endIndex // The index of the selection's end char in the line string - startIndex // The index of the selection's start char in the line string + leftWideCharCount // The number of wide chars left of the initial char @@ -823,11 +823,11 @@ export class SelectionManager implements ISelectionManager { // Recurse upwards if the line is wrapped and the word wraps to the above line if (followWrappedLinesAbove) { if (start === 0 && bufferLine.getCodePoint(0) !== 32 /*' '*/) { - const previousBufferLine = this._buffer.lines.get(coords[1] - 1); - if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) { - const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); + const previousBufferLine = buffer.lines.get(coords[1] - 1); + if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /*' '*/) { + const previousLineWordPosition = this._getWordAt([this._bufferService.cols - 1, coords[1] - 1], false, true, false); if (previousLineWordPosition) { - const offset = this._terminal.cols - previousLineWordPosition.start; + const offset = this._bufferService.cols - previousLineWordPosition.start; start -= offset; length += offset; } @@ -837,8 +837,8 @@ export class SelectionManager implements ISelectionManager { // Recurse downwards if the line is wrapped and the word wraps to the next line if (followWrappedLinesBelow) { - if (start + length === this._terminal.cols && bufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) { - const nextBufferLine = this._buffer.lines.get(coords[1] + 1); + if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /*' '*/) { + const nextBufferLine = buffer.lines.get(coords[1] + 1); if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /*' '*/) { const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); if (nextLineWordPosition) { @@ -861,7 +861,7 @@ export class SelectionManager implements ISelectionManager { if (wordPosition) { // Adjust negative start value while (wordPosition.start < 0) { - wordPosition.start += this._terminal.cols; + wordPosition.start += this._bufferService.cols; coords[1]--; } this._model.selectionStart = [wordPosition.start, coords[1]]; @@ -880,15 +880,15 @@ export class SelectionManager implements ISelectionManager { // Adjust negative start value while (wordPosition.start < 0) { - wordPosition.start += this._terminal.cols; + wordPosition.start += this._bufferService.cols; endRow--; } // Adjust wrapped length value, this only needs to happen when values are reversed as in that // case we're interested in the start of the word, not the end if (!this._model.areSelectionValuesReversed()) { - while (wordPosition.start + wordPosition.length > this._terminal.cols) { - wordPosition.length -= this._terminal.cols; + while (wordPosition.start + wordPosition.length > this._bufferService.cols) { + wordPosition.length -= this._bufferService.cols; endRow++; } } @@ -916,9 +916,9 @@ export class SelectionManager implements ISelectionManager { * @param line The line index. */ protected _selectLineAt(line: number): void { - const wrappedRange = this._buffer.getWrappedRangeForLine(line); + const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line); this._model.selectionStart = [0, wrappedRange.first]; - this._model.selectionEnd = [this._terminal.cols, wrappedRange.last]; + this._model.selectionEnd = [this._bufferService.cols, wrappedRange.last]; this._model.selectionStartLength = 0; } } From 0ba90a934951b08c46bc953c82b912bf76a40125 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 20:12:17 -0700 Subject: [PATCH 089/104] Adopt options service in selection manager --- src/SelectionManager.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 4bd62cdc..060c0366 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -13,7 +13,7 @@ import { CellData } from 'common/buffer/CellData'; import { IDisposable } from 'xterm'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService, IMouseService } from 'browser/services/Services'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IOptionsService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; /** @@ -115,7 +115,8 @@ export class SelectionManager implements ISelectionManager { private readonly _terminal: ITerminal, private readonly _charSizeService: ICharSizeService, private readonly _bufferService: IBufferService, - private readonly _mouseService: IMouseService + private readonly _mouseService: IMouseService, + private readonly _optionsService: IOptionsService ) { this._initListeners(); this.enable(); @@ -349,7 +350,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._terminal.cols, this._terminal.rows, true); + const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._bufferService.cols, this._bufferService.rows, true); if (!coords) { return null; } @@ -370,7 +371,7 @@ export class SelectionManager implements ISelectionManager { */ private _getMouseEventScrollAmount(event: MouseEvent): number { let offset = getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; - const terminalHeight = this._terminal.rows * Math.ceil(this._charSizeService.height * this._terminal.options.lineHeight); + const terminalHeight = this._bufferService.rows * Math.ceil(this._charSizeService.height * this._optionsService.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; } @@ -390,7 +391,7 @@ export class SelectionManager implements ISelectionManager { */ public shouldForceSelection(event: MouseEvent): boolean { if (Browser.isMac) { - return event.altKey && this._terminal.options.macOptionClickForcesSelection; + return event.altKey && this._optionsService.options.macOptionClickForcesSelection; } return event.shiftKey; @@ -543,7 +544,7 @@ export class SelectionManager implements ISelectionManager { * @param event the mouse or keyboard event */ public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean { - return event.altKey && !(Browser.isMac && this._terminal.options.macOptionClickForcesSelection); + return event.altKey && !(Browser.isMac && this._optionsService.options.macOptionClickForcesSelection); } /** @@ -908,7 +909,7 @@ export class SelectionManager implements ISelectionManager { if (cell.getWidth() === 0) { return false; } - return this._terminal.optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0; + return this._optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0; } /** From 9174f60d94fe097f11da7b5f28e03ebb26e7c711 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 21 Jun 2019 20:20:48 -0700 Subject: [PATCH 090/104] Move screen element into ctor --- src/SelectionManager.test.ts | 15 +++++++++------ src/SelectionManager.ts | 15 ++++++++------- src/Terminal.ts | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 0b82466d..814cb5fe 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,9 +10,9 @@ import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import { MockTerminal } from './TestUtils.test'; -import { MockBufferService } from 'common/TestUtils.test'; +import { MockBufferService, MockOptionsService } from 'common/TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IOptionsService } from 'common/services/Services'; import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test'; import { CellData } from 'common/buffer/CellData'; @@ -23,9 +23,10 @@ class TestMockTerminal extends MockTerminal { class TestSelectionManager extends SelectionManager { constructor( terminal: ITerminal, - bufferService: IBufferService + bufferService: IBufferService, + optionsService: IOptionsService ) { - super(terminal, new MockCharSizeService(10, 10), bufferService, new MockMouseService()); + super(terminal, null, new MockCharSizeService(10, 10), bufferService, new MockMouseService(), optionsService); } public get model(): SelectionModel { return this._model; } @@ -46,17 +47,19 @@ describe('SelectionManager', () => { let terminal: ITerminal; let buffer: IBuffer; let bufferService: IBufferService; + let optionsService: IOptionsService; let selectionManager: TestSelectionManager; beforeEach(() => { terminal = new TestMockTerminal(); - bufferService = new MockBufferService(20, 20); + optionsService = new MockOptionsService(); + bufferService = new MockBufferService(20, 20, optionsService); terminal.buffers = bufferService.buffers; terminal.cols = 20; terminal.rows = 20; terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; - selectionManager = new TestSelectionManager(terminal, bufferService); + selectionManager = new TestSelectionManager(terminal, bufferService, optionsService); }); function stringToRow(text: string): IBufferLine { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 060c0366..767cf156 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -113,6 +113,7 @@ export class SelectionManager implements ISelectionManager { constructor( private readonly _terminal: ITerminal, + private readonly _screenElement: HTMLElement, private readonly _charSizeService: ICharSizeService, private readonly _bufferService: IBufferService, private readonly _mouseService: IMouseService, @@ -350,7 +351,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._mouseService.getCoords(event, this._terminal.screenElement, this._bufferService.cols, this._bufferService.rows, true); + const coords = this._mouseService.getCoords(event, this._screenElement, this._bufferService.cols, this._bufferService.rows, true); if (!coords) { return null; } @@ -370,7 +371,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseEventScrollAmount(event: MouseEvent): number { - let offset = getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; + let offset = getCoordsRelativeToElement(event, this._screenElement)[1]; const terminalHeight = this._bufferService.rows * Math.ceil(this._charSizeService.height * this._optionsService.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; @@ -451,8 +452,8 @@ export class SelectionManager implements ISelectionManager { */ private _addMouseDownListeners(): void { // Listen on the document so that dragging outside of viewport works - this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); - this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener); + this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); + this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener); this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL); } @@ -460,9 +461,9 @@ export class SelectionManager implements ISelectionManager { * Removes the listeners that are registered when mousedown is triggered. */ private _removeMouseDownListeners(): void { - if (this._terminal.element.ownerDocument) { - this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); - this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + if (this._screenElement.ownerDocument) { + this._screenElement.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); + this._screenElement.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); } clearInterval(this._dragScrollIntervalTimer); this._dragScrollIntervalTimer = null; diff --git a/src/Terminal.ts b/src/Terminal.ts index 310aebe9..72cecb19 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -640,7 +640,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.register(this.onFocus(() => this._renderService.onFocus())); this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea())); - this.selectionManager = new SelectionManager(this, this._charSizeService, this._bufferService, this._mouseService); + this.selectionManager = new SelectionManager(this, this.screenElement, this._charSizeService, this._bufferService, this._mouseService, this.optionsService); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode))); From 00969508d9352da6156e7d924f2f1b2ce7f3959b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 09:40:11 -0700 Subject: [PATCH 091/104] Remove some private member deps in AltClickHandler --- src/SelectionManager.ts | 2 +- src/handlers/AltClickHandler.ts | 172 ++++++++++++++++---------------- 2 files changed, 89 insertions(+), 85 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 767cf156..0a5daed2 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -650,7 +650,7 @@ export class SelectionManager implements ISelectionManager { this._removeMouseDownListeners(); if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { - (new AltClickHandler(event, this._terminal, this._mouseService)).move(); + (new AltClickHandler(event, this._terminal, this._mouseService)).move(this._bufferService, this._terminal.applicationCursor); } else if (this.hasSelection) { this._onSelectionChange.fire(); } diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 334b78d8..7b46d745 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -7,6 +7,7 @@ import { ITerminal } from '../Types'; import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; import { IMouseService } from 'browser/services/Services'; +import { IBufferService } from 'common/services/Services'; const enum Direction { UP = 'A', @@ -49,9 +50,9 @@ export class AltClickHandler { /** * Writes the escape sequences of arrows to the terminal */ - public move(): void { + public move(bufferService: IBufferService, applicationCursor: boolean): void { if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { - this._terminal.handler(this._arrowSequences()); + this._terminal.handler(this._arrowSequences(bufferService, applicationCursor)); } } @@ -60,14 +61,16 @@ export class AltClickHandler { * Resets the starting row to an unwrapped row, moves to the requested row, * then moves to requested col. */ - private _arrowSequences(): string { + private _arrowSequences(bufferService: IBufferService, applicationCursor: boolean): string { // The alt buffer should try to navigate between rows - if (!this._terminal.buffer.hasScrollback) { - return this._resetStartingRow() + this._moveToRequestedRow() + this._moveToRequestedCol(); + if (!bufferService.buffer.hasScrollback) { + return this._resetStartingRow(bufferService, applicationCursor) + + this._moveToRequestedRow(bufferService, applicationCursor) + + this._moveToRequestedCol(bufferService, applicationCursor); } // Only move horizontally for the normal buffer - return this._moveHorizontallyOnly(); + return this._moveHorizontallyOnly(bufferService, applicationCursor); } /** @@ -75,52 +78,52 @@ export class AltClickHandler { * cursor up to the first row that is not wrapped to have accurate vertical * positioning. */ - private _resetStartingRow(): string { - if (this._moveToRequestedRow().length === 0) { + private _resetStartingRow(bufferService: IBufferService, applicationCursor: boolean): string { + if (this._moveToRequestedRow(bufferService, applicationCursor).length === 0) { return ''; } - return repeat(this._bufferLine( + return repeat(bufferLine( this._startCol, this._startRow, this._startCol, - this._startRow - this._wrappedRowsForRow(this._startRow), false - ).length, this._sequence(Direction.LEFT)); + this._startRow - this._wrappedRowsForRow(bufferService, this._startRow), false, bufferService + ).length, sequence(Direction.LEFT, applicationCursor)); } /** * Using the reset starting and ending row, move to the requested row, * ignoring wrapped rows */ - private _moveToRequestedRow(): string { - const startRow = this._startRow - this._wrappedRowsForRow(this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + private _moveToRequestedRow(bufferService: IBufferService, applicationCursor: boolean): string { + const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); + const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); - const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(); + const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(bufferService); - return repeat(rowsToMove, this._sequence(this._verticalDirection())); + return repeat(rowsToMove, sequence(this._verticalDirection(), applicationCursor)); } /** * Move to the requested col on the ending row */ - private _moveToRequestedCol(): string { + private _moveToRequestedCol(bufferService: IBufferService, applicationCursor: boolean): string { let startRow; - if (this._moveToRequestedRow().length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); } else { startRow = this._startRow; } const endRow = this._endRow; - const direction = this._horizontalDirection(); + const direction = this._horizontalDirection(bufferService, applicationCursor); - return repeat(this._bufferLine( + return repeat(bufferLine( this._startCol, startRow, this._endCol, endRow, - direction === Direction.RIGHT - ).length, this._sequence(direction)); + direction === Direction.RIGHT, bufferService + ).length, sequence(direction, applicationCursor)); } - private _moveHorizontallyOnly(): string { - const direction = this._horizontalDirection(); - return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction)); + private _moveHorizontallyOnly(bufferService: IBufferService, applicationCursor: boolean): string { + const direction = this._horizontalDirection(bufferService, applicationCursor); + return repeat(Math.abs(this._startCol - this._endCol), sequence(direction, applicationCursor)); } /** @@ -131,10 +134,10 @@ export class AltClickHandler { * Calculates the number of wrapped rows between the unwrapped starting and * ending rows. These rows need to ignored since the cursor skips over them. */ - private _wrappedRowsCount(): number { + private _wrappedRowsCount(bufferService: IBufferService): number { let wrappedRows = 0; - const startRow = this._startRow - this._wrappedRowsForRow(this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); + const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); for (let i = 0; i < Math.abs(startRow - endRow); i++) { const direction = this._verticalDirection() === Direction.UP ? -1 : 1; @@ -151,14 +154,14 @@ export class AltClickHandler { * Calculates the number of wrapped rows that make up a given row. * @param currentRow The row to determine how many wrapped rows make it up */ - private _wrappedRowsForRow(currentRow: number): number { + private _wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { let rowCount = 0; - let lineWraps = this._lines.get(currentRow).isWrapped; + let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; - while (lineWraps && currentRow >= 0 && currentRow < this._terminal.rows) { + while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { rowCount++; currentRow--; - lineWraps = this._lines.get(currentRow).isWrapped; + lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; } return rowCount; @@ -171,10 +174,10 @@ export class AltClickHandler { /** * Determines if the right or left arrow is needed */ - private _horizontalDirection(): Direction { + private _horizontalDirection(bufferService: IBufferService, applicationCursor: boolean): Direction { let startRow; - if (this._moveToRequestedRow().length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); } else { startRow = this._startRow; } @@ -197,60 +200,61 @@ export class AltClickHandler { } return Direction.DOWN; } +} - /** - * Constructs the string of chars in the buffer from a starting row and col - * to an ending row and col - * @param startCol The starting column position - * @param startRow The starting row position - * @param endCol The ending column position - * @param endRow The ending row position - * @param forward Direction to move - */ - private _bufferLine( - startCol: number, - startRow: number, - endCol: number, - endRow: number, - forward: boolean - ): string { - let currentCol = startCol; - let currentRow = startRow; - let bufferStr = ''; +/** + * Constructs the string of chars in the buffer from a starting row and col + * to an ending row and col + * @param startCol The starting column position + * @param startRow The starting row position + * @param endCol The ending column position + * @param endRow The ending row position + * @param forward Direction to move + */ +function bufferLine( + startCol: number, + startRow: number, + endCol: number, + endRow: number, + forward: boolean, + bufferService: IBufferService +): string { + let currentCol = startCol; + let currentRow = startRow; + let bufferStr = ''; - while (currentCol !== endCol || currentRow !== endRow) { - currentCol += forward ? 1 : -1; + while (currentCol !== endCol || currentRow !== endRow) { + currentCol += forward ? 1 : -1; - if (forward && currentCol > this._terminal.cols - 1) { - bufferStr += this._terminal.buffer.translateBufferLineToString( - currentRow, false, startCol, currentCol - ); - currentCol = 0; - startCol = 0; - currentRow++; - } else if (!forward && currentCol < 0) { - bufferStr += this._terminal.buffer.translateBufferLineToString( - currentRow, false, 0, startCol + 1 - ); - currentCol = this._terminal.cols - 1; - startCol = currentCol; - currentRow--; - } + if (forward && currentCol > bufferService.cols - 1) { + bufferStr += bufferService.buffer.translateBufferLineToString( + currentRow, false, startCol, currentCol + ); + currentCol = 0; + startCol = 0; + currentRow++; + } else if (!forward && currentCol < 0) { + bufferStr += bufferService.buffer.translateBufferLineToString( + currentRow, false, 0, startCol + 1 + ); + currentCol = bufferService.cols - 1; + startCol = currentCol; + currentRow--; } - - return bufferStr + this._terminal.buffer.translateBufferLineToString( - currentRow, false, startCol, currentCol - ); } - /** - * Constructs the escape sequence for clicking an arrow - * @param direction The direction to move - */ - private _sequence(direction: Direction): string { - const mod = this._terminal.applicationCursor ? 'O' : '['; - return C0.ESC + mod + direction; - } + return bufferStr + bufferService.buffer.translateBufferLineToString( + currentRow, false, startCol, currentCol + ); +} + +/** + * Constructs the escape sequence for clicking an arrow + * @param direction The direction to move + */ +function sequence(direction: Direction, applicationCursor: boolean): string { + const mod = applicationCursor ? 'O' : '['; + return C0.ESC + mod + direction; } /** From da1484ac47d7e40e2ba42f654346903112dea2b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 09:44:25 -0700 Subject: [PATCH 092/104] Return a sequence from AltClickHandler --- src/SelectionManager.ts | 15 ++++++++++++++- src/handlers/AltClickHandler.ts | 27 +++++---------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 0a5daed2..483ea7cb 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -650,7 +650,20 @@ export class SelectionManager implements ISelectionManager { this._removeMouseDownListeners(); if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { - (new AltClickHandler(event, this._terminal, this._mouseService)).move(this._bufferService, this._terminal.applicationCursor); + if (event.altKey) { + const coordinates = this._mouseService.getCoords( + event, + this._terminal.element, + this._bufferService.cols, + this._bufferService.rows, + false + ); + if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { + this._terminal.handler( + (new AltClickHandler(this._terminal)).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) + ); + } + } } else if (this.hasSelection) { this._onSelectionChange.fire(); } diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 7b46d745..918ab60d 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -6,7 +6,6 @@ import { ITerminal } from '../Types'; import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; -import { IMouseService } from 'browser/services/Services'; import { IBufferService } from 'common/services/Services'; const enum Direction { @@ -24,36 +23,20 @@ export class AltClickHandler { private _lines: ICircularList; constructor( - private _mouseEvent: MouseEvent, - private _terminal: ITerminal, - private readonly _mouseService: IMouseService + private _terminal: ITerminal ) { this._lines = this._terminal.buffer.lines; this._startCol = this._terminal.buffer.x; this._startRow = this._terminal.buffer.y; - - const coordinates = this._mouseService.getCoords( - this._mouseEvent, - this._terminal.element, - this._terminal.cols, - this._terminal.rows, - false - ); - - if (coordinates) { - [this._endCol, this._endRow] = coordinates.map((coordinate: number) => { - return coordinate - 1; - }); - } } /** * Writes the escape sequences of arrows to the terminal */ - public move(bufferService: IBufferService, applicationCursor: boolean): void { - if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { - this._terminal.handler(this._arrowSequences(bufferService, applicationCursor)); - } + public move(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + this._endCol = targetX; + this._endRow = targetY; + return this._arrowSequences(bufferService, applicationCursor); } /** From 7454701c0b34459ed8f920bf76be775c5b686e06 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 17:53:11 -0700 Subject: [PATCH 093/104] Remove AltHandler member usage --- src/SelectionManager.ts | 2 +- src/handlers/AltClickHandler.ts | 99 ++++++++++++++------------------- 2 files changed, 44 insertions(+), 57 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 483ea7cb..344978cf 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -660,7 +660,7 @@ export class SelectionManager implements ISelectionManager { ); if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { this._terminal.handler( - (new AltClickHandler(this._terminal)).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) + (new AltClickHandler()).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) ); } } diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 918ab60d..8f8288a6 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -3,8 +3,6 @@ * @license MIT */ -import { ITerminal } from '../Types'; -import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; import { IBufferService } from 'common/services/Services'; @@ -16,27 +14,16 @@ const enum Direction { } export class AltClickHandler { - private _startRow: number; - private _startCol: number; - private _endRow: number; - private _endCol: number; - private _lines: ICircularList; constructor( - private _terminal: ITerminal ) { - this._lines = this._terminal.buffer.lines; - this._startCol = this._terminal.buffer.x; - this._startRow = this._terminal.buffer.y; } /** * Writes the escape sequences of arrows to the terminal */ public move(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - this._endCol = targetX; - this._endRow = targetY; - return this._arrowSequences(bufferService, applicationCursor); + return this._arrowSequences(targetX, targetY, bufferService, applicationCursor); } /** @@ -44,16 +31,19 @@ export class AltClickHandler { * Resets the starting row to an unwrapped row, moves to the requested row, * then moves to requested col. */ - private _arrowSequences(bufferService: IBufferService, applicationCursor: boolean): string { + private _arrowSequences(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startX = bufferService.buffer.x; + const startY = bufferService.buffer.y; + // The alt buffer should try to navigate between rows if (!bufferService.buffer.hasScrollback) { - return this._resetStartingRow(bufferService, applicationCursor) + - this._moveToRequestedRow(bufferService, applicationCursor) + - this._moveToRequestedCol(bufferService, applicationCursor); + return this._resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) + + this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor) + + this._moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor); } // Only move horizontally for the normal buffer - return this._moveHorizontallyOnly(bufferService, applicationCursor); + return this._moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); } /** @@ -61,13 +51,13 @@ export class AltClickHandler { * cursor up to the first row that is not wrapped to have accurate vertical * positioning. */ - private _resetStartingRow(bufferService: IBufferService, applicationCursor: boolean): string { - if (this._moveToRequestedRow(bufferService, applicationCursor).length === 0) { + private _resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) { return ''; } return repeat(bufferLine( - this._startCol, this._startRow, this._startCol, - this._startRow - this._wrappedRowsForRow(bufferService, this._startRow), false, bufferService + startX, startY, startX, + startY - this._wrappedRowsForRow(bufferService, startY), false, bufferService ).length, sequence(Direction.LEFT, applicationCursor)); } @@ -75,38 +65,38 @@ export class AltClickHandler { * Using the reset starting and ending row, move to the requested row, * ignoring wrapped rows */ - private _moveToRequestedRow(bufferService: IBufferService, applicationCursor: boolean): string { - const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + private _moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startRow = startY - this._wrappedRowsForRow(bufferService, startY); + const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(bufferService); + const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(startY, targetY, bufferService); - return repeat(rowsToMove, sequence(this._verticalDirection(), applicationCursor)); + return repeat(rowsToMove, sequence(this._verticalDirection(startY, targetY), applicationCursor)); } /** * Move to the requested col on the ending row */ - private _moveToRequestedCol(bufferService: IBufferService, applicationCursor: boolean): string { + private _moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { let startRow; - if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); } else { - startRow = this._startRow; + startRow = startY; } - const endRow = this._endRow; - const direction = this._horizontalDirection(bufferService, applicationCursor); + const endRow = targetY; + const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); return repeat(bufferLine( - this._startCol, startRow, this._endCol, endRow, + startX, startRow, targetX, endRow, direction === Direction.RIGHT, bufferService ).length, sequence(direction, applicationCursor)); } - private _moveHorizontallyOnly(bufferService: IBufferService, applicationCursor: boolean): string { - const direction = this._horizontalDirection(bufferService, applicationCursor); - return repeat(Math.abs(this._startCol - this._endCol), sequence(direction, applicationCursor)); + private _moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); + return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); } /** @@ -117,15 +107,15 @@ export class AltClickHandler { * Calculates the number of wrapped rows between the unwrapped starting and * ending rows. These rows need to ignored since the cursor skips over them. */ - private _wrappedRowsCount(bufferService: IBufferService): number { + private _wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number { let wrappedRows = 0; - const startRow = this._startRow - this._wrappedRowsForRow(bufferService, this._startRow); - const endRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + const startRow = startY - this._wrappedRowsForRow(bufferService, startY); + const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); for (let i = 0; i < Math.abs(startRow - endRow); i++) { - const direction = this._verticalDirection() === Direction.UP ? -1 : 1; + const direction = this._verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; - if (this._lines.get(startRow + (direction * i)).isWrapped) { + if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { wrappedRows++; } } @@ -157,18 +147,18 @@ export class AltClickHandler { /** * Determines if the right or left arrow is needed */ - private _horizontalDirection(bufferService: IBufferService, applicationCursor: boolean): Direction { + private _horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction { let startRow; - if (this._moveToRequestedRow(bufferService, applicationCursor).length > 0) { - startRow = this._endRow - this._wrappedRowsForRow(bufferService, this._endRow); + if (this._moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); } else { - startRow = this._startRow; + startRow = startY; } - if ((this._startCol < this._endCol && - startRow <= this._endRow) || // down/right or same y/right - (this._startCol >= this._endCol && - startRow < this._endRow)) { // down/left or same y/left + if ((startX < targetX && + startRow <= targetY) || // down/right or same y/right + (startX >= targetX && + startRow < targetY)) { // down/left or same y/left return Direction.RIGHT; } return Direction.LEFT; @@ -177,11 +167,8 @@ export class AltClickHandler { /** * Determines if the up or down arrow is needed */ - private _verticalDirection(): Direction { - if (this._startRow > this._endRow) { - return Direction.UP; - } - return Direction.DOWN; + private _verticalDirection(startY: number, targetY: number): Direction { + return startY > targetY ? Direction.UP : Direction.DOWN; } } From 13a63efdab01e404e45b5302d045dd4fb1eda168 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 17:55:59 -0700 Subject: [PATCH 094/104] Change AltClickHandler to be functional --- src/SelectionManager.ts | 7 +- src/handlers/AltClickHandler.ts | 266 +++++++++++++++----------------- 2 files changed, 129 insertions(+), 144 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 344978cf..48a2fec8 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -8,13 +8,13 @@ import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; -import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from 'common/buffer/CellData'; import { IDisposable } from 'xterm'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService, IMouseService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; +import { moveToCellSequence } from 'handlers/AltClickHandler'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -659,9 +659,8 @@ export class SelectionManager implements ISelectionManager { false ); if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { - this._terminal.handler( - (new AltClickHandler()).move(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor) - ); + const sequence = moveToCellSequence(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor); + this._terminal.handler(sequence); } } } else if (this.hasSelection) { diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 8f8288a6..73e9b729 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -13,163 +13,149 @@ const enum Direction { LEFT = 'D' } -export class AltClickHandler { +/** + * Concatenates all the arrow sequences together. + * Resets the starting row to an unwrapped row, moves to the requested row, + * then moves to requested col. + */ +export function moveToCellSequence(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startX = bufferService.buffer.x; + const startY = bufferService.buffer.y; - constructor( - ) { + // The alt buffer should try to navigate between rows + if (!bufferService.buffer.hasScrollback) { + return resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) + + moveToRequestedRow(startY, targetY, bufferService, applicationCursor) + + moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor); } - /** - * Writes the escape sequences of arrows to the terminal - */ - public move(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - return this._arrowSequences(targetX, targetY, bufferService, applicationCursor); + // Only move horizontally for the normal buffer + return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); +} + +/** + * If the initial position of the cursor is on a row that is wrapped, move the + * cursor up to the first row that is not wrapped to have accurate vertical + * positioning. + */ +function resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) { + return ''; + } + return repeat(bufferLine( + startX, startY, startX, + startY - wrappedRowsForRow(bufferService, startY), false, bufferService + ).length, sequence(Direction.LEFT, applicationCursor)); +} + +/** + * Using the reset starting and ending row, move to the requested row, + * ignoring wrapped rows + */ +function moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const startRow = startY - wrappedRowsForRow(bufferService, startY); + const endRow = targetY - wrappedRowsForRow(bufferService, targetY); + + const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService); + + return repeat(rowsToMove, sequence(verticalDirection(startY, targetY), applicationCursor)); +} + +/** + * Move to the requested col on the ending row + */ +function moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + let startRow; + if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - wrappedRowsForRow(bufferService, targetY); + } else { + startRow = startY; } - /** - * Concatenates all the arrow sequences together. - * Resets the starting row to an unwrapped row, moves to the requested row, - * then moves to requested col. - */ - private _arrowSequences(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - const startX = bufferService.buffer.x; - const startY = bufferService.buffer.y; + const endRow = targetY; + const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); - // The alt buffer should try to navigate between rows - if (!bufferService.buffer.hasScrollback) { - return this._resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) + - this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor) + - this._moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor); + return repeat(bufferLine( + startX, startRow, targetX, endRow, + direction === Direction.RIGHT, bufferService + ).length, sequence(direction, applicationCursor)); +} + +function moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { + const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); + return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); +} + +/** + * Utility functions + */ + +/** + * Calculates the number of wrapped rows between the unwrapped starting and + * ending rows. These rows need to ignored since the cursor skips over them. + */ +function wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number { + let wrappedRows = 0; + const startRow = startY - wrappedRowsForRow(bufferService, startY); + const endRow = targetY - wrappedRowsForRow(bufferService, targetY); + + for (let i = 0; i < Math.abs(startRow - endRow); i++) { + const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; + + if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { + wrappedRows++; } - - // Only move horizontally for the normal buffer - return this._moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor); } - /** - * If the initial position of the cursor is on a row that is wrapped, move the - * cursor up to the first row that is not wrapped to have accurate vertical - * positioning. - */ - private _resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) { - return ''; - } - return repeat(bufferLine( - startX, startY, startX, - startY - this._wrappedRowsForRow(bufferService, startY), false, bufferService - ).length, sequence(Direction.LEFT, applicationCursor)); + return wrappedRows; +} + +/** + * Calculates the number of wrapped rows that make up a given row. + * @param currentRow The row to determine how many wrapped rows make it up + */ +function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { + let rowCount = 0; + let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; + + while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { + rowCount++; + currentRow--; + lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; } - /** - * Using the reset starting and ending row, move to the requested row, - * ignoring wrapped rows - */ - private _moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - const startRow = startY - this._wrappedRowsForRow(bufferService, startY); - const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); + return rowCount; +} - const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(startY, targetY, bufferService); +/** + * Direction determiners + */ - return repeat(rowsToMove, sequence(this._verticalDirection(startY, targetY), applicationCursor)); +/** + * Determines if the right or left arrow is needed + */ +function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction { + let startRow; + if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) { + startRow = targetY - wrappedRowsForRow(bufferService, targetY); + } else { + startRow = startY; } - /** - * Move to the requested col on the ending row - */ - private _moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - let startRow; - if (this._moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) { - startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - } else { - startRow = startY; - } - - const endRow = targetY; - const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); - - return repeat(bufferLine( - startX, startRow, targetX, endRow, - direction === Direction.RIGHT, bufferService - ).length, sequence(direction, applicationCursor)); + if ((startX < targetX && + startRow <= targetY) || // down/right or same y/right + (startX >= targetX && + startRow < targetY)) { // down/left or same y/left + return Direction.RIGHT; } + return Direction.LEFT; +} - private _moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - const direction = this._horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor); - return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor)); - } - - /** - * Utility functions - */ - - /** - * Calculates the number of wrapped rows between the unwrapped starting and - * ending rows. These rows need to ignored since the cursor skips over them. - */ - private _wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number { - let wrappedRows = 0; - const startRow = startY - this._wrappedRowsForRow(bufferService, startY); - const endRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - - for (let i = 0; i < Math.abs(startRow - endRow); i++) { - const direction = this._verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; - - if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { - wrappedRows++; - } - } - - return wrappedRows; - } - - /** - * Calculates the number of wrapped rows that make up a given row. - * @param currentRow The row to determine how many wrapped rows make it up - */ - private _wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { - let rowCount = 0; - let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; - - while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { - rowCount++; - currentRow--; - lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; - } - - return rowCount; - } - - /** - * Direction determiners - */ - - /** - * Determines if the right or left arrow is needed - */ - private _horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction { - let startRow; - if (this._moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) { - startRow = targetY - this._wrappedRowsForRow(bufferService, targetY); - } else { - startRow = startY; - } - - if ((startX < targetX && - startRow <= targetY) || // down/right or same y/right - (startX >= targetX && - startRow < targetY)) { // down/left or same y/left - return Direction.RIGHT; - } - return Direction.LEFT; - } - - /** - * Determines if the up or down arrow is needed - */ - private _verticalDirection(startY: number, targetY: number): Direction { - return startY > targetY ? Direction.UP : Direction.DOWN; - } +/** + * Determines if the up or down arrow is needed + */ +function verticalDirection(startY: number, targetY: number): Direction { + return startY > targetY ? Direction.UP : Direction.DOWN; } /** From 22a7e48f04ab014d79fc8d8e1e5aabdca0a1bfbc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 18:00:03 -0700 Subject: [PATCH 095/104] Move alt click into browser --- src/SelectionManager.ts | 2 +- .../input/MoveToCell.ts} | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) rename src/{handlers/AltClickHandler.ts => browser/input/MoveToCell.ts} (96%) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 48a2fec8..56d32b56 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -14,7 +14,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService, IMouseService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; -import { moveToCellSequence } from 'handlers/AltClickHandler'; +import { moveToCellSequence } from 'browser/input/MoveToCell'; /** * The number of pixels the mouse needs to be above or below the viewport in diff --git a/src/handlers/AltClickHandler.ts b/src/browser/input/MoveToCell.ts similarity index 96% rename from src/handlers/AltClickHandler.ts rename to src/browser/input/MoveToCell.ts index 73e9b729..406ec807 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/browser/input/MoveToCell.ts @@ -101,8 +101,8 @@ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBuffe for (let i = 0; i < Math.abs(startRow - endRow); i++) { const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; - - if (bufferService.buffer.lines.get(startRow + (direction * i)).isWrapped) { + const line = bufferService.buffer.lines.get(startRow + (direction * i)); + if (line && line.isWrapped) { wrappedRows++; } } @@ -116,12 +116,13 @@ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBuffe */ function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { let rowCount = 0; - let lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; + let line = bufferService.buffer.lines.get(currentRow); + let lineWraps = line && line.isWrapped; while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) { rowCount++; - currentRow--; - lineWraps = bufferService.buffer.lines.get(currentRow).isWrapped; + line = bufferService.buffer.lines.get(--currentRow); + lineWraps = line && line.isWrapped; } return rowCount; From f903dbde4508c8c79f751b10667ee7b51fe8362d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 18:16:33 -0700 Subject: [PATCH 096/104] Add some tests for MoveToCell --- src/browser/input/MoveToCell.test.ts | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/browser/input/MoveToCell.test.ts diff --git a/src/browser/input/MoveToCell.test.ts b/src/browser/input/MoveToCell.test.ts new file mode 100644 index 00000000..bc4012c0 --- /dev/null +++ b/src/browser/input/MoveToCell.test.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { IBufferService } from 'common/services/Services'; +import { MockBufferService } from 'common/TestUtils.test'; +import { moveToCellSequence } from './MoveToCell'; + +describe('MoveToCell', () => { + let bufferService: IBufferService; + + beforeEach(() => { + bufferService = new MockBufferService(5, 5); + bufferService.buffer.x = 3; + bufferService.buffer.y = 3; + }); + + describe('normal buffer', () => { + it('should use the right directional escape sequences', () => { + assert.equal(moveToCellSequence(2, 3, bufferService, false), '\x1b[D'); + assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C'); + }); + it('should ignore the Y value', () => { + assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[D\x1b[D'); + assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[D\x1b[D'); + }); + it('should use the correct character for application cursor', () => { + assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D'); + assert.equal(moveToCellSequence(2, 1, bufferService, true), '\x1bOD'); + }); + }); + + describe('alt buffer', () => { + beforeEach(() => { + bufferService.buffers.activateAltBuffer(); + bufferService.buffer.x = 3; + bufferService.buffer.y = 3; + }); + + it('should move the cursor across rows', () => { + assert.equal(moveToCellSequence(4, 4, bufferService, false), '\x1b[B\x1b[C'); + }); + }); +}); From 87897a7ee540215a9730496c7c59b4bd2720e0c5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 22 Jun 2019 18:27:25 -0700 Subject: [PATCH 097/104] Move selection manager types into browser --- src/Clipboard.ts | 2 +- src/SelectionManager.ts | 3 ++- src/TestUtils.test.ts | 3 ++- src/Types.d.ts | 19 +------------------ src/browser/selection/Types.d.ts | 22 ++++++++++++++++++++++ 5 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 src/browser/selection/Types.d.ts diff --git a/src/Clipboard.ts b/src/Clipboard.ts index 75b0da8e..1ee232ee 100644 --- a/src/Clipboard.ts +++ b/src/Clipboard.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ISelectionManager } from './Types'; +import { ISelectionManager } from 'browser/selection/Types'; /** * Prepares text to be pasted into the terminal by normalizing the line endings diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 56d32b56..4133fccc 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { ITerminal, ISelectionManager, ISelectionRedrawRequestEvent } from './Types'; +import { ITerminal } from './Types'; +import { ISelectionManager, ISelectionRedrawRequestEvent } from 'browser/selection/Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import * as Browser from 'common/Platform'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 07f91705..8394f5a7 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -15,6 +15,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { IColorManager, IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { EventEmitter } from 'common/EventEmitter'; +import { ISelectionManager } from 'browser/selection/Types'; export class TestTerminal extends Terminal { writeSync(data: string): void { diff --git a/src/Types.d.ts b/src/Types.d.ts index 5987d605..c3957ab7 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -9,6 +9,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; +import { ISelectionManager } from 'browser/selection/Types'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -296,24 +297,6 @@ export interface ITerminalOptions extends IPublicTerminalOptions { useFlowControl?: boolean; } -export interface ISelectionManager { - selectionText: string; - selectionStart: [number, number]; - selectionEnd: [number, number]; - - disable(): void; - enable(): void; - setSelection(row: number, col: number, length: number): void; - isClickInSelection(event: MouseEvent): boolean; - selectWordAtCursor(event: MouseEvent): void; -} - -export interface ISelectionRedrawRequestEvent { - start: [number, number]; - end: [number, number]; - columnSelectMode: boolean; -} - export interface ILinkifier { onLinkHover: IEvent; onLinkLeave: IEvent; diff --git a/src/browser/selection/Types.d.ts b/src/browser/selection/Types.d.ts new file mode 100644 index 00000000..241731f1 --- /dev/null +++ b/src/browser/selection/Types.d.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export interface ISelectionManager { + selectionText: string; + selectionStart: [number, number]; + selectionEnd: [number, number]; + + disable(): void; + enable(): void; + setSelection(row: number, col: number, length: number): void; + isClickInSelection(event: MouseEvent): boolean; + selectWordAtCursor(event: MouseEvent): void; +} + +export interface ISelectionRedrawRequestEvent { + start: [number, number]; + end: [number, number]; + columnSelectMode: boolean; +} From bfa7a2df766ccec168c214f24a6350eabb27f985 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 10:10:17 -0700 Subject: [PATCH 098/104] Move handler into CoreService and adopt This also fine tunes some of the data events to not clear selection and scroll to the bottom of the viewport anymore. Part of #1507 Fixes #2112 --- src/CompositionHelper.test.ts | 3 +- src/CompositionHelper.ts | 16 +++--- src/InputHandler.test.ts | 19 +++---- src/InputHandler.ts | 22 ++++----- src/SelectionManager.test.ts | 4 +- src/SelectionManager.ts | 11 +++-- src/Terminal.test.ts | 11 ++--- src/Terminal.ts | 79 ++++++++++++++++-------------- src/Types.d.ts | 2 - src/common/TestUtils.test.ts | 8 ++- src/common/services/CoreService.ts | 43 ++++++++++++++++ src/common/services/Services.d.ts | 15 ++++++ 12 files changed, 153 insertions(+), 80 deletions(-) create mode 100644 src/common/services/CoreService.ts diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 2d28f55d..6cf615fe 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -7,6 +7,7 @@ import { assert } from 'chai'; import { CompositionHelper } from './CompositionHelper'; import { ITerminal } from './Types'; import { MockCharSizeService } from 'browser/TestUtils.test'; +import { MockCoreService } from '../out/common/TestUtils.test'; describe('CompositionHelper', () => { let terminal: ITerminal; @@ -54,7 +55,7 @@ describe('CompositionHelper', () => { } } as any; handledText = ''; - compositionHelper = new CompositionHelper(textarea, compositionView, terminal, new MockCharSizeService(10, 10)); + compositionHelper = new CompositionHelper(textarea, compositionView, terminal, new MockCharSizeService(10, 10), new MockCoreService()); }); describe('Input', () => { diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index 2b2d3042..010585f8 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -5,6 +5,7 @@ import { ITerminal } from './Types'; import { ICharSizeService } from 'browser/services/Services'; +import { ICoreService } from 'common/services/Services'; interface IPosition { start: number; @@ -41,10 +42,11 @@ export class CompositionHelper { * @param _terminal The Terminal to forward the finished composition to. */ constructor( - private _textarea: HTMLTextAreaElement, - private _compositionView: HTMLElement, - private _terminal: ITerminal, - private _charSizeService: ICharSizeService + private readonly _textarea: HTMLTextAreaElement, + private readonly _compositionView: HTMLElement, + private readonly _terminal: ITerminal, + private readonly _charSizeService: ICharSizeService, + private readonly _coreService: ICoreService ) { this._isComposing = false; this._isSendingComposition = false; @@ -127,7 +129,7 @@ export class CompositionHelper { // Cancel any delayed composition send requests and send the input immediately. this._isSendingComposition = false; const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end); - this._terminal.handler(input); + this._coreService.triggerDataEvent(input, true); } else { // Make a deep copy of the composition position here as a new compositionstart event may // fire before the setTimeout executes. @@ -159,7 +161,7 @@ export class CompositionHelper { // (eg. 2) after a composition character. input = this._textarea.value.substring(currentCompositionPosition.start); } - this._terminal.handler(input); + this._coreService.triggerDataEvent(input, true); } }, 0); } @@ -179,7 +181,7 @@ export class CompositionHelper { const newValue = this._textarea.value; const diff = newValue.replace(oldValue, ''); if (diff.length > 0) { - this._terminal.handler(diff); + this._coreService.triggerDataEvent(diff, true); } } }, 0); diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 19d5f6a2..e93fc945 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -12,6 +12,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; +import { MockCoreService } from 'common/TestUtils.test'; describe('InputHandler', () => { describe('save and restore cursor', () => { @@ -20,7 +21,7 @@ describe('InputHandler', () => { terminal.buffer.y = 2; terminal.buffer.ybase = 0; terminal.curAttrData.fg = 3; - const inputHandler = new InputHandler(terminal); + const inputHandler = new InputHandler(terminal, new MockCoreService()); // Save cursor position inputHandler.saveCursor([]); assert.equal(terminal.buffer.x, 1); @@ -39,7 +40,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const terminal = new MockInputHandlingTerminal(); - const inputHandler = new InputHandler(terminal); + const inputHandler = new InputHandler(terminal, new MockCoreService()); const collect = ' '; inputHandler.setCursorStyle([0], collect); @@ -82,7 +83,7 @@ describe('InputHandler', () => { const terminal = new MockInputHandlingTerminal(); const collect = '?'; terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal); + const inputHandler = new InputHandler(terminal, new MockCoreService()); // Set bracketed paste mode inputHandler.setMode([2004], collect); assert.equal(terminal.bracketedPasteMode, true); @@ -100,7 +101,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // insert some data in first and second line inputHandler.parse(Array(term.cols - 9).join('a')); @@ -137,7 +138,7 @@ describe('InputHandler', () => { }); it('deleteChars', function(): void { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // insert some data in first and second line inputHandler.parse(Array(term.cols - 9).join('a')); @@ -177,7 +178,7 @@ describe('InputHandler', () => { }); it('eraseInLine', function(): void { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // fill 6 lines to test 3 different states inputHandler.parse(Array(term.cols + 1).join('a')); @@ -205,7 +206,7 @@ describe('InputHandler', () => { }); it('eraseInDisplay', function(): void { const term = new Terminal({cols: 80, rows: 7}); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // fill display with a's for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); @@ -340,7 +341,7 @@ describe('InputHandler', () => { describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -353,7 +354,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); - handler = new InputHandler(term); + handler = new InputHandler(term, new MockCoreService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a2ae4e08..194d2e84 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -19,6 +19,7 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; +import { ICoreService } from 'common/services/Services'; /** * Map collect to glevel. Used in `selectCharset`. @@ -113,8 +114,6 @@ export class InputHandler extends Disposable implements IInputHandler { private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } private _onLineFeed = new EventEmitter(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter(); @@ -122,6 +121,7 @@ export class InputHandler extends Disposable implements IInputHandler { constructor( protected _terminal: IInputHandlingTerminal, + private _coreService: ICoreService, private _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); @@ -1098,24 +1098,24 @@ export class InputHandler extends Disposable implements IInputHandler { if (!collect) { if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { - this._terminal.handler(C0.ESC + '[?1;2c'); + this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); } else if (this._terminal.is('linux')) { - this._terminal.handler(C0.ESC + '[?6c'); + this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } } else if (collect === '>') { // xterm and urxvt // seem to spit this // out around ~370 times (?). if (this._terminal.is('xterm')) { - this._terminal.handler(C0.ESC + '[>0;276;0c'); + this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c'); } else if (this._terminal.is('rxvt-unicode')) { - this._terminal.handler(C0.ESC + '[>85;95;0c'); + this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c'); } else if (this._terminal.is('linux')) { // not supported by linux console. // linux console echoes parameters. - this._terminal.handler(params[0] + 'c'); + this._coreService.triggerDataEvent(params[0] + 'c'); } else if (this._terminal.is('screen')) { - this._terminal.handler(C0.ESC + '[>83;40003;0c'); + this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c'); } } } @@ -1799,13 +1799,13 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params[0]) { case 5: // status report - this._onData.fire(`${C0.ESC}[0n`); + this._coreService.triggerDataEvent(`${C0.ESC}[0n`); break; case 6: // cursor position const y = this._terminal.buffer.y + 1; const x = this._terminal.buffer.x + 1; - this._onData.fire(`${C0.ESC}[${y};${x}R`); + this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`); break; } } else if (collect === '?') { @@ -1816,7 +1816,7 @@ export class InputHandler extends Disposable implements IInputHandler { // cursor position const y = this._terminal.buffer.y + 1; const x = this._terminal.buffer.x + 1; - this._onData.fire(`${C0.ESC}[?${y};${x}R`); + this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`); break; case 15: // no printer diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 814cb5fe..6b11698f 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,7 +10,7 @@ import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import { MockTerminal } from './TestUtils.test'; -import { MockBufferService, MockOptionsService } from 'common/TestUtils.test'; +import { MockBufferService, MockOptionsService, MockCoreService } from 'common/TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test'; @@ -26,7 +26,7 @@ class TestSelectionManager extends SelectionManager { bufferService: IBufferService, optionsService: IOptionsService ) { - super(terminal, null, new MockCharSizeService(10, 10), bufferService, new MockMouseService(), optionsService); + super(terminal, null, new MockCharSizeService(10, 10), bufferService, new MockCoreService(), new MockMouseService(), optionsService); } public get model(): SelectionModel { return this._model; } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 4133fccc..70023836 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -13,7 +13,7 @@ import { CellData } from 'common/buffer/CellData'; import { IDisposable } from 'xterm'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService, IMouseService } from 'browser/services/Services'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; import { moveToCellSequence } from 'browser/input/MoveToCell'; @@ -117,6 +117,7 @@ export class SelectionManager implements ISelectionManager { private readonly _screenElement: HTMLElement, private readonly _charSizeService: ICharSizeService, private readonly _bufferService: IBufferService, + private readonly _coreService: ICoreService, private readonly _mouseService: IMouseService, private readonly _optionsService: IOptionsService ) { @@ -137,7 +138,11 @@ export class SelectionManager implements ISelectionManager { private _initListeners(): void { this._mouseMoveListener = event => this._onMouseMove(event); this._mouseUpListener = event => this._onMouseUp(event); - + this._coreService.onUserInput(() => { + if (this.hasSelection) { + this.clearSelection(); + } + }); this.initBuffersListeners(); } @@ -661,7 +666,7 @@ export class SelectionManager implements ISelectionManager { ); if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { const sequence = moveToCellSequence(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor); - this._terminal.handler(sequence); + this._coreService.triggerDataEvent(sequence, true); } } } else if (this.hasSelection) { diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index f3f7fc58..f2f02eec 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -53,10 +53,11 @@ describe('Terminal', () => { }); describe('events', () => { - it('should fire the onData evnet', (done) => { - term.onData(() => done()); - term.handler('fake'); - }); + // TODO: Add an onData test back + // it('should fire the onData evnet', (done) => { + // term.onData(() => done()); + // term.handler('fake'); + // }); it('should fire the onCursorMove event', (done) => { term.onCursorMove(() => done()); term.write('foo'); @@ -142,7 +143,6 @@ describe('Terminal', () => { }; beforeEach(() => { - term.handler = () => { }; term.showCursor = () => { }; term.clearSelection = () => { }; }); @@ -520,7 +520,6 @@ describe('Terminal', () => { let evKeyPress: any; beforeEach(() => { - term.handler = () => { }; term.showCursor = () => { }; term.clearSelection = () => { }; // term.compositionHelper = { diff --git a/src/Terminal.ts b/src/Terminal.ts index 72cecb19..a9f11b4b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,7 +47,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { IOptionsService, IBufferService } from 'common/services/Services'; +import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService, IRenderService, IMouseService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; @@ -56,6 +56,7 @@ import { Disposable } from 'common/Lifecycle'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { Attributes } from 'common/buffer/Constants'; import { MouseService } from 'browser/services/MouseService'; +import { CoreService } from 'common/services/CoreService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -107,6 +108,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // common services private _bufferService: IBufferService; + private _coreService: ICoreService; public optionsService: IOptionsService; // browser services @@ -237,6 +239,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Setup and initialize common services this.optionsService = new OptionsService(options); this._bufferService = new BufferService(this.optionsService); + this._coreService = new CoreService(() => this.scrollToBottom(), this._bufferService, this.optionsService); + this._coreService.onData(e => this._onData.fire(e)); + this._setupOptionsListeners(); this._setup(); } @@ -249,7 +254,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } this._customKeyEventHandler = null; removeTerminalFromCache(this); - this.handler = () => {}; this.write = () => {}; if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); @@ -294,10 +298,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._userScrolling = false; // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this); + this._inputHandler = new InputHandler(this, this._coreService); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); - this._inputHandler.onData(e => this._onData.fire(e)); this.register(this._inputHandler); this.selectionManager = this.selectionManager || null; @@ -434,7 +437,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp */ private _onTextAreaFocus(ev: KeyboardEvent): void { if (this.sendFocus) { - this.handler(C0.ESC + '[I'); + this._coreService.triggerDataEvent(C0.ESC + '[I'); } this.updateCursorStyle(ev); this.element.classList.add('focus'); @@ -459,7 +462,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.textarea.value = ''; this.refresh(this.buffer.y, this.buffer.y); if (this.sendFocus) { - this.handler(C0.ESC + '[O'); + this._coreService.triggerDataEvent(C0.ESC + '[O'); } this.element.classList.remove('focus'); this._onBlur.fire(); @@ -480,7 +483,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } copyHandler(event, this.selectionManager); })); - const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this.textarea, this.bracketedPasteMode, e => this.handler(e)); + const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this.textarea, this.bracketedPasteMode, e => this._coreService.triggerDataEvent(e, true)); this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); @@ -607,7 +610,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); - this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this, this._charSizeService); + this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this, this._charSizeService, this._coreService); this._helperContainer.appendChild(this._compositionView); // Performance: Add viewport and helper elements from the fragment @@ -640,7 +643,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.register(this.onFocus(() => this._renderService.onFocus())); this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea())); - this.selectionManager = new SelectionManager(this, this.screenElement, this._charSizeService, this._bufferService, this._mouseService, this.optionsService); + this.selectionManager = new SelectionManager(this, this.screenElement, this._charSizeService, this._bufferService, this._coreService, this._mouseService, this.optionsService); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode))); @@ -814,7 +817,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp else if (button === 3) return; else data += '0'; data += '~[' + pos.x + ',' + pos.y + ']\r'; - self.handler(data); + self._coreService.triggerDataEvent(data, true); return; } @@ -827,7 +830,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp else if (button === 1) button = 4; else if (button === 2) button = 6; else if (button === 3) button = 3; - self.handler(C0.ESC + '[' + self._coreService.triggerDataEvent(C0.ESC + '[' + button + ';' + (button === 3 ? 4 : 0) @@ -838,7 +841,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp + ';' // Not sure what page is meant to be + (pos).page || 0 - + '&w'); + + '&w', true); return; } @@ -847,20 +850,20 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp pos.y -= 32; pos.x++; pos.y++; - self.handler(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); + self._coreService.triggerDataEvent(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M', true); return; } if (self.sgrMouse) { pos.x -= 32; pos.y -= 32; - self.handler(C0.ESC + '[<' + self._coreService.triggerDataEvent(C0.ESC + '[<' + (((button & 3) === 3 ? button & ~3 : button) - 32) + ';' + pos.x + ';' + pos.y - + ((button & 3) === 3 ? 'm' : 'M')); + + ((button & 3) === 3 ? 'm' : 'M'), true); return; } @@ -870,7 +873,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp encode(data, pos.x); encode(data, pos.y); - self.handler(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); + self._coreService.triggerDataEvent(C0.ESC + '[M' + String.fromCharCode.apply(String, data), true); } function getButton(ev: MouseEvent): number { @@ -1015,7 +1018,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp for (let i = 0; i < Math.abs(amount); i++) { data += sequence; } - this.handler(data); + this._coreService.triggerDataEvent(data, true); } return; } @@ -1240,7 +1243,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { // XOFF - stop pty pipe // XON will be triggered by emulator before processing data chunk - this.handler(C0.DC3); + this._coreService.triggerDataEvent(C0.DC3); this._xoffSentToCatchUp = true; } @@ -1268,7 +1271,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // If XOFF was sent in order to catch up with the pty process, resume it if // we reached the end of the writeBuffer to allow more data to come in. if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) { - this.handler(C0.DC1); + this._coreService.triggerDataEvent(C0.DC1); this._xoffSentToCatchUp = false; } @@ -1327,7 +1330,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { // XOFF - stop pty pipe // XON will be triggered by emulator before processing data chunk - this.handler(C0.DC3); + this._coreService.triggerDataEvent(C0.DC3); this._xoffSentToCatchUp = true; } @@ -1355,7 +1358,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // If XOFF was sent in order to catch up with the pty process, resume it if // we reached the end of the writeBuffer to allow more data to come in. if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) { - this.handler(C0.DC1); + this._coreService.triggerDataEvent(C0.DC1); this._xoffSentToCatchUp = false; } @@ -1587,7 +1590,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._onKey.fire({ key: result.key, domEvent: event }); this.showCursor(); - this.handler(result.key); + this._coreService.triggerDataEvent(result.key, true); return this.cancel(event, true); } @@ -1665,7 +1668,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._onKey.fire({ key, domEvent: ev }); this.showCursor(); - this.handler(key); + this._coreService.triggerDataEvent(key, true); return true; } @@ -1796,23 +1799,23 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * Emit the data event and populate the given data. * @param data The data to populate in the event. */ - public handler(data: string): void { - // Prevents all events to pty process if stdin is disabled - if (this.options.disableStdin) { - return; - } + // public handler(data: string): void { + // // Prevents all events to pty process if stdin is disabled + // if (this.options.disableStdin) { + // return; + // } - // Clear the selection if the selection manager is available and has an active selection - if (this.selectionManager && this.selectionManager.hasSelection) { - this.selectionManager.clearSelection(); - } + // // Clear the selection if the selection manager is available and has an active selection + // if (this.selectionManager && this.selectionManager.hasSelection) { + // this.selectionManager.clearSelection(); + // } - // Input is being sent to the terminal, the terminal should focus the prompt. - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - this._onData.fire(data); - } + // // Input is being sent to the terminal, the terminal should focus the prompt. + // if (this.buffer.ybase !== this.buffer.ydisp) { + // this.scrollToBottom(); + // } + // this._onData.fire(data); + // } /** * Emit the 'title' event and populate the given title. diff --git a/src/Types.d.ts b/src/Types.d.ts index c3957ab7..20e6c082 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -73,7 +73,6 @@ export interface IInputHandlingTerminal { refresh(start: number, end: number): void; error(text: string, data?: any): void; tabSet(): void; - handler(data: string): void; handleTitle(title: string): void; index(): void; reverseIndex(): void; @@ -217,7 +216,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc onA11yChar: IEvent; onA11yTab: IEvent; - handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; log(text: string): void; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index c6402fb1..86098d33 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferService, IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; +import { IBufferService, ICoreService, IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -27,6 +27,12 @@ export class MockBufferService implements IBufferService { reset(): void {} } +export class MockCoreService implements ICoreService { + onData: IEvent = new EventEmitter().event; + onUserInput: IEvent = new EventEmitter().event; + triggerDataEvent(data: string, wasUserInput?: boolean): void {} +} + export class MockOptionsService implements IOptionsService { options: ITerminalOptions = clone(DEFAULT_OPTIONS); onOptionChange: IEvent = new EventEmitter().event; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts new file mode 100644 index 00000000..bd731e8d --- /dev/null +++ b/src/common/services/CoreService.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ICoreService, IOptionsService, IBufferService } from 'common/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; + +export class CoreService implements ICoreService { + private _onData = new EventEmitter(); + public get onData(): IEvent { return this._onData.event; } + private _onUserInput = new EventEmitter(); + public get onUserInput(): IEvent { return this._onUserInput.event; } + + constructor( + // TODO: Move this into a service + private readonly _scrollToBottom: () => void, + private readonly _bufferService: IBufferService, + private readonly _optionsService: IOptionsService + ) { + } + + public triggerDataEvent(data: string, wasUserInput: boolean = false): void { + // Prevents all events to pty process if stdin is disabled + if (this._optionsService.options.disableStdin) { + return; + } + + // Input is being sent to the terminal, the terminal should focus the prompt. + const buffer = this._bufferService.buffer; + if (buffer.ybase !== buffer.ydisp) { + this._scrollToBottom(); + } + + // Fire onUserInput so listeners can react as well (eg. clear selection) + if (wasUserInput) { + this._onUserInput.fire(); + } + + // Fire onData API + this._onData.fire(data); + } +} diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index b8276f51..d9903e70 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -18,6 +18,21 @@ export interface IBufferService { reset(): void; } +export interface ICoreService { + readonly onData: IEvent; + readonly onUserInput: IEvent; + + /** + * Triggers the onData event in the public API. + * @param data The data that is being emitted. + * @param wasFromUser Whether the data originated from the user (as opposed to + * resulting from parsing incoming data). When true this will also: + * - Scroll to the bottom of the buffer.s + * - Fire the `onUserInput` event (so selection can be cleared). + */ + triggerDataEvent(data: string, wasUserInput?: boolean): void; +} + export interface IOptionsService { readonly options: ITerminalOptions; From e10adaf3d2ee7250a252aedb74f73d6e6d7d36a3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 13:10:37 -0700 Subject: [PATCH 099/104] Have yarn clean also delete addon out/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d291f147..ca803391 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "prepare": "npm run build", "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", - "clean": "rm -rf lib out addons/*/lib" + "clean": "rm -rf lib out addons/*/lib addons/*/out" }, "devDependencies": { "@types/chai": "^3.4.34", From fdae27e7eae3225be7143fd7216ce0ddafe7d8fd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 13:11:25 -0700 Subject: [PATCH 100/104] Remove unused Platform.ts in webgl --- addons/xterm-addon-webgl/src/Platform.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/Platform.ts diff --git a/addons/xterm-addon-webgl/src/Platform.ts b/addons/xterm-addon-webgl/src/Platform.ts deleted file mode 100644 index 55a10b7f..00000000 --- a/addons/xterm-addon-webgl/src/Platform.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -const isNode = (typeof navigator === 'undefined') ? true : false; -const userAgent = (isNode) ? 'node' : navigator.userAgent; - -export const isFirefox = !!~userAgent.indexOf('Firefox'); -export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); From 533f3bd0dcade8e339b6c02843e1450041960904 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 13:12:49 -0700 Subject: [PATCH 101/104] Remove unneeded TODOs --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 1 - addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 24b4994b..d4ce3b4c 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -275,7 +275,6 @@ export class RectangleRenderer { if (bg === INVERTED_DEFAULT_COLOR) { color = this._colors.foreground; } else if (is256Color(bg)) { - // TODO: Need to do a separate set for 16 color palette? color = this._colors.ansi[bg]; } else { // TODO: Add support for true color diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index c3da6955..6a941d55 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -171,7 +171,6 @@ export class WebglCharAtlas extends BaseCharAtlas { } else if (bg === INVERTED_DEFAULT_COLOR) { return this._config.colors.foreground; } else if (is256Color(bg)) { - // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(bg); } // TODO: Support true color @@ -182,7 +181,6 @@ export class WebglCharAtlas extends BaseCharAtlas { if (fg === INVERTED_DEFAULT_COLOR) { return this._config.colors.background; } else if (is256Color(fg)) { - // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(fg); } // TODO: Support true color From 83b8e1bd30477fbc70b0d5bd6576646ee5a925d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 14:21:05 -0700 Subject: [PATCH 102/104] Fix composition tests --- src/CompositionHelper.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 6cf615fe..64207fa0 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -44,9 +44,6 @@ describe('CompositionHelper', () => { return { offsetLeft: 0, offsetTop: 0 }; } }, - handler: (text: string) => { - handledText += text; - }, buffer: { isCursorInViewport: true }, @@ -54,8 +51,12 @@ describe('CompositionHelper', () => { lineHeight: 1 } } as any; + const coreService = new MockCoreService(); + coreService.triggerDataEvent = (text: string) => { + handledText += text; + }; handledText = ''; - compositionHelper = new CompositionHelper(textarea, compositionView, terminal, new MockCharSizeService(10, 10), new MockCoreService()); + compositionHelper = new CompositionHelper(textarea, compositionView, terminal, new MockCharSizeService(10, 10), coreService); }); describe('Input', () => { From 00d4c8162d56d61e7288cbcf5f4cd0d59bb75fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 24 Jun 2019 22:53:13 +0200 Subject: [PATCH 103/104] remove azure job, change api test path --- azure-pipelines.yml | 26 -------------------------- package.json | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 33c3e100..e8b23d34 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -83,32 +83,6 @@ jobs: yarn test-api --headless displayName: 'Integration tests' -- job: Benchmarks - pool: - vmImage: 'ubuntu-16.04' - steps: - - task: NodeTool@0 - inputs: - versionSpec: '8.x' - displayName: 'Install Node.js' - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.9.4" - displayName: 'Install Yarn' - - script: | - yarn - displayName: 'Install dependencies and build' - - script: | - yarn benchmark-baseline - displayName: 'Baseline data' - - script: | - git checkout - yarn clean && yarn - displayName: 'Checkout target' - - script: | - yarn benchmark-eval - displayName: 'Eval changes' - - job: Release dependsOn: - Linux diff --git a/package.json b/package.json index 9e965a0f..f26c010f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "lint": "tslint 'src/**/*.ts' './demo/**/*.ts' './addons/**/*.ts'", "test": "npm run test-unit", "posttest": "npm run lint", - "test-api": "mocha \"./out-test/api/*.api.js\"", + "test-api": "mocha \"**/*.api.js\"", "test-unit": "node ./bin/test.js", "build": "tsc -b ./tsconfig.all.json", "prepare": "npm run build", From 5459c48525c63e142f84e9250064738502486df3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 24 Jun 2019 20:20:08 -0700 Subject: [PATCH 104/104] Add wordSeparator to demo --- demo/client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index 005d171d..e2259841 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -235,7 +235,8 @@ function initOptions(term: TerminalType): void { fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - rendererType: ['dom', 'canvas'] + rendererType: ['dom', 'canvas'], + wordSeparator: null }; const options = Object.keys((term)._core.options); const booleanOptions = [];