Merge branch 'master' into cleanup_sequences_files

This commit is contained in:
Jörg Breitbart
2019-07-06 20:45:50 +02:00
29 changed files with 1193 additions and 630 deletions
+17 -10
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { createProgram, PROJECTION_MATRIX } from './WebglUtils';
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
import { INDICIES_PER_CELL } from './WebglRenderer';
@@ -75,7 +75,7 @@ const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT;
const CELL_POSITION_INDICES = 2;
export class GlyphRenderer {
private _atlas: WebglCharAtlas;
private _atlas: WebglCharAtlas | undefined;
private _program: WebGLProgram;
private _vertexArrayObject: IWebGLVertexArrayObject;
@@ -104,12 +104,16 @@ export class GlyphRenderer {
) {
const gl = this._gl;
this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource);
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
if (program === undefined) {
throw new Error('Could not create WebGL program');
}
this._program = program;
// 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');
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution'));
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
// Create and set the vertex array object
this._vertexArrayObject = gl.createVertexArray();
@@ -131,7 +135,7 @@ export class GlyphRenderer {
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW);
// Setup attributes
this._attributesBuffer = gl.createBuffer();
this._attributesBuffer = throwIfFalsy(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);
@@ -150,7 +154,7 @@ export class GlyphRenderer {
gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1);
// Setup empty texture atlas
this._atlasTexture = gl.createTexture();
this._atlasTexture = throwIfFalsy(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);
@@ -165,7 +169,7 @@ export class GlyphRenderer {
}
public beginFrame(): boolean {
return this._atlas.beginFrame();
return this._atlas ? this._atlas.beginFrame() : true;
}
public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void {
@@ -184,6 +188,9 @@ export class GlyphRenderer {
}
let rasterizedGlyph: IRasterizedGlyph;
if (!this._atlas) {
throw new Error('atlas must be set before updating cell');
}
if (chars && chars.length > 1) {
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg);
} else {
@@ -264,7 +271,7 @@ export class GlyphRenderer {
if (!line) {
line = terminal.buffer.getLine(row);
}
const chars = line.getCell(x).char;
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);
@@ -3,7 +3,7 @@
* @license MIT
*/
import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
import { fill } from 'common/TypedArrayUtils';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
@@ -66,8 +66,8 @@ export class RectangleRenderer {
private _resolutionLocation: WebGLUniformLocation;
private _attributesBuffer: WebGLBuffer;
private _projectionLocation: WebGLUniformLocation;
private _bgFloat: Float32Array;
private _selectionFloat: Float32Array;
private _bgFloat!: Float32Array;
private _selectionFloat!: Float32Array;
private _vertices: IVertices = {
count: 0,
@@ -83,11 +83,11 @@ export class RectangleRenderer {
) {
const gl = this._gl;
this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource);
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
// Uniform locations
this._resolutionLocation = gl.getUniformLocation(this._program, 'u_resolution');
this._projectionLocation = gl.getUniformLocation(this._program, 'u_projection');
this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution'));
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
// Create and set the vertex array object
this._vertexArrayObject = gl.createVertexArray();
@@ -109,7 +109,7 @@ export class RectangleRenderer {
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW);
// Setup attributes
this._attributesBuffer = gl.createBuffer();
this._attributesBuffer = throwIfFalsy(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);
+14 -14
View File
@@ -27,7 +27,7 @@ export const INDICIES_PER_CELL = 4;
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
private _charAtlas: WebglCharAtlas;
private _charAtlas: WebglCharAtlas | undefined;
private _devicePixelRatio: number;
private _model: RenderModel = new RenderModel();
@@ -57,18 +57,18 @@ export class WebglRenderer extends Disposable implements IRenderer {
new CursorRenderLayer(this._core.screenElement, 3, this._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
scaledCharWidth: 0,
scaledCharHeight: 0,
scaledCellWidth: 0,
scaledCellHeight: 0,
scaledCharLeft: 0,
scaledCharTop: 0,
scaledCanvasWidth: 0,
scaledCanvasHeight: 0,
canvasWidth: 0,
canvasHeight: 0,
actualCellWidth: 0,
actualCellHeight: 0
};
this._devicePixelRatio = window.devicePixelRatio;
this._updateDimensions();
@@ -252,7 +252,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
for (let y = start; y <= end; y++) {
const row = y + terminal.buffer.ydisp;
const line = terminal.buffer.lines.get(row);
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);
+11 -4
View File
@@ -15,9 +15,9 @@ export const PROJECTION_MATRIX = new Float32Array([
]);
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));
const program = throwIfFalsy(gl.createProgram());
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
@@ -29,7 +29,7 @@ export function createProgram(gl: WebGLRenderingContext, vertexSource: string, f
}
export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {
const shader = gl.createShader(type);
const shader = throwIfFalsy(gl.createShader(type));
gl.shaderSource(shader, source);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
@@ -49,3 +49,10 @@ export function expandFloat32Array(source: Float32Array, max: number): Float32Ar
}
return newArray;
}
export function throwIfFalsy<T>(value: T | undefined | null): T {
if (!value) {
throw new Error('value must not be falsy');
}
return value;
}
@@ -1,56 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IGlyphIdentifier } from './Types';
import { IDisposable } from 'xterm';
export 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;
}
@@ -4,14 +4,13 @@
*/
import { generateConfig, configEquals } from './CharAtlasUtils';
import { BaseCharAtlas } from './BaseCharAtlas';
import { WebglCharAtlas } from './WebglCharAtlas';
import { ICharAtlasConfig } from './Types';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
interface ICharAtlasCacheEntry {
atlas: BaseCharAtlas;
atlas: WebglCharAtlas;
config: ICharAtlasConfig;
// N.B. This implementation potentially holds onto copies of the terminal forever, so
// this may cause memory leaks.
@@ -31,7 +30,7 @@ export function acquireCharAtlas(
colors: IColorSet,
scaledCharWidth: number,
scaledCharHeight: number
): BaseCharAtlas {
): WebglCharAtlas {
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
// Check to see if the terminal already owns this config
@@ -6,16 +6,21 @@
import { ICharAtlasConfig } from './Types';
import { DEFAULT_COLOR } from 'common/buffer/Constants';
import { Terminal, FontWeight } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IColorSet, IColor } from 'browser/Types';
const NULL_COLOR: IColor = {
css: '',
rgba: 0
};
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,
background: colors.background,
cursor: null,
cursorAccent: null,
selection: null,
cursor: NULL_COLOR,
cursorAccent: NULL_COLOR,
selection: NULL_COLOR,
// 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()
@@ -3,14 +3,15 @@
* @license MIT
*/
import { IGlyphIdentifier, ICharAtlasConfig } from './Types';
import { 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';
import { is256Color } from './CharAtlasUtils';
import { throwIfFalsy } from '../WebglUtils';
import { IColor } from 'browser/Types';
import { FLAGS } from '../Constants';
import { IDisposable } 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.
@@ -42,7 +43,9 @@ const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = {
const TMP_CANVAS_GLYPH_PADDING = 2;
export class WebglCharAtlas extends BaseCharAtlas {
export class WebglCharAtlas implements IDisposable {
private _didWarmUp: boolean = false;
private _cacheMap: { [code: number]: IRasterizedGlyphSet } = {};
private _cacheMapCombined: { [chars: string]: IRasterizedGlyphSet } = {};
@@ -67,20 +70,18 @@ export class WebglCharAtlas extends BaseCharAtlas {
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._cacheCtx = throwIfFalsy(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._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
// This is useful for debugging
document.body.appendChild(this.cacheCanvas);
@@ -92,6 +93,13 @@ export class WebglCharAtlas extends BaseCharAtlas {
}
}
public warmUp(): void {
if (!this._didWarmUp) {
this._doWarmUp();
this._didWarmUp = true;
}
}
protected _doWarmUp(): void {
// Pre-fill with ASCII 33-126
for (let i = 33; i < 126; i++) {
@@ -146,15 +154,6 @@ export class WebglCharAtlas extends BaseCharAtlas {
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) {
throw new Error('No color found for idx ' + idx);
@@ -8,17 +8,18 @@ import { ICellData } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
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';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
import { WebglCharAtlas } from 'atlas/WebglCharAtlas';
import { throwIfFalsy } from '../WebglUtils';
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
protected _ctx: CanvasRenderingContext2D;
protected _ctx!: CanvasRenderingContext2D;
private _scaledCharWidth: number = 0;
private _scaledCharHeight: number = 0;
private _scaledCellWidth: number = 0;
@@ -26,7 +27,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _scaledCharLeft: number = 0;
private _scaledCharTop: number = 0;
protected _charAtlas: BaseCharAtlas;
protected _charAtlas: WebglCharAtlas | undefined;
/**
* An object that's reused when drawing glyphs in order to reduce GC.
@@ -63,7 +64,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
private _initCanvas(): void {
this._ctx = this._canvas.getContext('2d', {alpha: this._alpha});
this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
// Draw the background if this is an opaque layer
if (!this._alpha) {
this._clearAll();
@@ -249,115 +250,6 @@ export abstract class BaseRenderLayer implements IRenderLayer {
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();
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(), !!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.
@@ -26,17 +26,17 @@ 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 _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
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
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
this._cursorRenderers = {
'bar': this._renderBarCursor.bind(this),
@@ -50,11 +50,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
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
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
}
@@ -62,7 +62,6 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = null;
this.onOptionsChanged(terminal);
}
}
@@ -92,7 +91,6 @@ export class CursorRenderLayer extends BaseRenderLayer {
} 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
@@ -184,11 +182,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
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
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
}
}
@@ -227,16 +225,16 @@ export class CursorRenderLayer extends BaseRenderLayer {
class CursorBlinkStateManager {
public isCursorVisible: boolean;
private _animationFrame: number;
private _blinkStartTimeout: number;
private _blinkInterval: number;
private _animationFrame: number | undefined;
private _blinkStartTimeout: number | undefined;
private _blinkInterval: number | undefined;
/**
* 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;
private _animationTimeRestarted: number | undefined;
constructor(
terminal: Terminal,
@@ -253,15 +251,15 @@ class CursorBlinkStateManager {
public dispose(): void {
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = null;
this._blinkInterval = undefined;
}
if (this._blinkStartTimeout) {
window.clearTimeout(this._blinkStartTimeout);
this._blinkStartTimeout = null;
this._blinkStartTimeout = undefined;
}
if (this._animationFrame) {
window.cancelAnimationFrame(this._animationFrame);
this._animationFrame = null;
this._animationFrame = undefined;
}
}
@@ -276,7 +274,7 @@ class CursorBlinkStateManager {
if (!this._animationFrame) {
this._animationFrame = window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = null;
this._animationFrame = undefined;
});
}
}
@@ -296,7 +294,7 @@ class CursorBlinkStateManager {
// started
if (this._animationTimeRestarted) {
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
this._animationTimeRestarted = null;
this._animationTimeRestarted = undefined;
if (time > 0) {
this._restartInterval(time);
return;
@@ -307,7 +305,7 @@ class CursorBlinkStateManager {
this.isCursorVisible = false;
this._animationFrame = window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = null;
this._animationFrame = undefined;
});
// Setup the blink interval
@@ -317,7 +315,7 @@ class CursorBlinkStateManager {
// calc time diff
// Make restart interval do a setTimeout initially?
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
this._animationTimeRestarted = null;
this._animationTimeRestarted = undefined;
this._restartInterval(time);
return;
}
@@ -326,7 +324,7 @@ class CursorBlinkStateManager {
this.isCursorVisible = !this.isCursorVisible;
this._animationFrame = window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = null;
this._animationFrame = undefined;
});
}, BLINK_INTERVAL);
}, timeToStart);
@@ -336,20 +334,20 @@ class CursorBlinkStateManager {
this.isCursorVisible = true;
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = null;
this._blinkInterval = undefined;
}
if (this._blinkStartTimeout) {
window.clearTimeout(this._blinkStartTimeout);
this._blinkStartTimeout = null;
this._blinkStartTimeout = undefined;
}
if (this._animationFrame) {
window.cancelAnimationFrame(this._animationFrame);
this._animationFrame = null;
this._animationFrame = undefined;
}
}
public resume(terminal: Terminal): void {
this._animationTimeRestarted = null;
this._animationTimeRestarted = undefined;
this._restartInterval();
this.restartBlinkAnimation(terminal);
}
@@ -12,7 +12,7 @@ import { IColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: ILinkifierEvent = null;
private _state: ILinkifierEvent | undefined;
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) {
super(container, 'link', zIndex, true, colors);
@@ -23,7 +23,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
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;
this._state = undefined;
}
public reset(terminal: Terminal): void {
@@ -38,7 +38,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);
}
this._clearCells(0, this._state.y2, this._state.x2, 1);
this._state = null;
this._state = undefined;
}
}
+2 -1
View File
@@ -14,7 +14,8 @@
"paths": {
"common/*": [ "../../../src/common/*" ],
"browser/*": [ "../../../src/browser/*" ]
}
},
"strict": true
},
"include": [
"./**/*",
+4 -2
View File
@@ -37,14 +37,16 @@ function startServer() {
});
app.post('/terminals', function (req, res) {
const env = Object.assign({}, process.env);
env['COLORTERM'] = 'truecolor';
var cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
name: 'xterm-256color',
cols: cols || 80,
rows: rows || 24,
cwd: process.env.PWD,
env: process.env,
cwd: env.PWD,
env: env,
encoding: USE_BINARY_UTF8 ? null : 'utf8'
});
+178 -27
View File
@@ -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 { Params } from 'common/parser/Params';
import { MockCoreService } from 'common/TestUtils.test';
describe('InputHandler', () => {
@@ -23,7 +24,7 @@ describe('InputHandler', () => {
terminal.curAttrData.fg = 3;
const inputHandler = new InputHandler(terminal, new MockCoreService());
// Save cursor position
inputHandler.saveCursor([]);
inputHandler.saveCursor();
assert.equal(terminal.buffer.x, 1);
assert.equal(terminal.buffer.y, 2);
assert.equal(terminal.curAttrData.fg, 3);
@@ -32,7 +33,7 @@ describe('InputHandler', () => {
terminal.buffer.y = 20;
terminal.curAttrData.fg = 30;
// Restore cursor position
inputHandler.restoreCursor([]);
inputHandler.restoreCursor();
assert.equal(terminal.buffer.x, 1);
assert.equal(terminal.buffer.y, 2);
assert.equal(terminal.curAttrData.fg, 3);
@@ -43,37 +44,37 @@ describe('InputHandler', () => {
const inputHandler = new InputHandler(terminal, new MockCoreService());
const collect = ' ';
inputHandler.setCursorStyle([0], collect);
inputHandler.setCursorStyle(Params.fromArray([0]), collect);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([1], collect);
inputHandler.setCursorStyle(Params.fromArray([1]), collect);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([2], collect);
inputHandler.setCursorStyle(Params.fromArray([2]), collect);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], false);
terminal.options = {};
inputHandler.setCursorStyle([3], collect);
inputHandler.setCursorStyle(Params.fromArray([3]), collect);
assert.equal(terminal.options['cursorStyle'], 'underline');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([4], collect);
inputHandler.setCursorStyle(Params.fromArray([4]), collect);
assert.equal(terminal.options['cursorStyle'], 'underline');
assert.equal(terminal.options['cursorBlink'], false);
terminal.options = {};
inputHandler.setCursorStyle([5], collect);
inputHandler.setCursorStyle(Params.fromArray([5]), collect);
assert.equal(terminal.options['cursorStyle'], 'bar');
assert.equal(terminal.options['cursorBlink'], true);
terminal.options = {};
inputHandler.setCursorStyle([6], collect);
inputHandler.setCursorStyle(Params.fromArray([6]), collect);
assert.equal(terminal.options['cursorStyle'], 'bar');
assert.equal(terminal.options['cursorBlink'], false);
});
@@ -85,10 +86,10 @@ describe('InputHandler', () => {
terminal.bracketedPasteMode = false;
const inputHandler = new InputHandler(terminal, new MockCoreService());
// Set bracketed paste mode
inputHandler.setMode([2004], collect);
inputHandler.setMode(Params.fromArray([2004]), collect);
assert.equal(terminal.bracketedPasteMode, true);
// Reset bracketed paste mode
inputHandler.resetMode([2004], collect);
inputHandler.resetMode(Params.fromArray([2004]), collect);
assert.equal(terminal.bracketedPasteMode, false);
});
});
@@ -114,25 +115,25 @@ describe('InputHandler', () => {
// insert one char from params = [0]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.insertChars([0]);
inputHandler.insertChars(Params.fromArray([0]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456789');
// insert one char from params = [1]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.insertChars([1]);
inputHandler.insertChars(Params.fromArray([1]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 12345678');
// insert two chars from params = [2]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.insertChars([2]);
inputHandler.insertChars(Params.fromArray([2]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456');
// insert 10 chars from params = [10]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.insertChars([10]);
inputHandler.insertChars(Params.fromArray([10]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a'));
});
@@ -151,28 +152,28 @@ describe('InputHandler', () => {
// delete one char from params = [0]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.deleteChars([0]);
inputHandler.deleteChars(Params.fromArray([0]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '234567890 ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '234567890');
// insert one char from params = [1]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.deleteChars([1]);
inputHandler.deleteChars(Params.fromArray([1]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '34567890 ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '34567890');
// insert two chars from params = [2]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.deleteChars([2]);
inputHandler.deleteChars(Params.fromArray([2]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '567890 ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '567890');
// insert 10 chars from params = [10]
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.deleteChars([10]);
inputHandler.deleteChars(Params.fromArray([10]));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a'));
});
@@ -188,19 +189,19 @@ describe('InputHandler', () => {
// params[0] - right erase
term.buffer.y = 0;
term.buffer.x = 70;
inputHandler.eraseInLine([0]);
inputHandler.eraseInLine(Params.fromArray([0]));
expect(term.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' ');
// params[1] - left erase
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.eraseInLine([1]);
inputHandler.eraseInLine(Params.fromArray([1]));
expect(term.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa');
// params[1] - left erase
term.buffer.y = 2;
term.buffer.x = 70;
inputHandler.eraseInLine([2]);
inputHandler.eraseInLine(Params.fromArray([2]));
expect(term.buffer.lines.get(2).translateToString(false)).equals(Array(term.cols + 1).join(' '));
});
@@ -214,7 +215,7 @@ describe('InputHandler', () => {
// params [0] - right and below erase
term.buffer.y = 5;
term.buffer.x = 40;
inputHandler.eraseInDisplay([0]);
inputHandler.eraseInDisplay(Params.fromArray([0]));
expect(termContent(term, false)).eql([
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
@@ -242,7 +243,7 @@ describe('InputHandler', () => {
// params [1] - left and above
term.buffer.y = 5;
term.buffer.x = 40;
inputHandler.eraseInDisplay([1]);
inputHandler.eraseInDisplay(Params.fromArray([1]));
expect(termContent(term, false)).eql([
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
@@ -270,7 +271,7 @@ describe('InputHandler', () => {
// params [2] - whole screen
term.buffer.y = 5;
term.buffer.x = 40;
inputHandler.eraseInDisplay([2]);
inputHandler.eraseInDisplay(Params.fromArray([2]));
expect(termContent(term, false)).eql([
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
@@ -302,7 +303,7 @@ describe('InputHandler', () => {
expect(term.buffer.lines.get(2).isWrapped).true;
term.buffer.y = 2;
term.buffer.x = 40;
inputHandler.eraseInDisplay([1]);
inputHandler.eraseInDisplay(Params.fromArray([1]));
expect(term.buffer.lines.get(2).isWrapped).false;
// reset and add a wrapped line
@@ -317,7 +318,7 @@ describe('InputHandler', () => {
expect(term.buffer.lines.get(2).isWrapped).true;
term.buffer.y = 1;
term.buffer.x = 90; // Cursor is beyond last column
inputHandler.eraseInDisplay([1]);
inputHandler.eraseInDisplay(Params.fromArray([1]));
expect(term.buffer.lines.get(2).isWrapped).false;
});
});
@@ -546,6 +547,156 @@ describe('InputHandler', () => {
assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]);
});
});
describe('colon notation', () => {
let termColon: TestTerminal;
let termSemicolon: TestTerminal;
beforeEach(() => {
termColon = new TestTerminal();
termSemicolon = new TestTerminal();
});
describe('should equal to semicolon', () => {
it('CSI 38:2::50:100:150 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2;50;100;150m');
termColon.writeSync('\x1b[38:2::50:100:150m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38:2::50:100: m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2;50;100;m');
termColon.writeSync('\x1b[38:2::50:100:m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38:2::50:: m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2;50;;m');
termColon.writeSync('\x1b[38:2::50::m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38:2:::: m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2;;;m');
termColon.writeSync('\x1b[38:2::::m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38;2::50:100:150 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2;50;100;150m');
termColon.writeSync('\x1b[38;2::50:100:150m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38;2;50:100:150 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2;50;100;150m');
termColon.writeSync('\x1b[38;2;50:100:150m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38;2;50;100:150 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2;50;100;150m');
termColon.writeSync('\x1b[38;2;50;100:150m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38:5:50 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;5;50m');
termColon.writeSync('\x1b[38:5:50m');
assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38:5: m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;5;m');
termColon.writeSync('\x1b[38:5:m');
assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38;5:50 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;5;50m');
termColon.writeSync('\x1b[38;5:50m');
assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
});
describe('should fill early sequence end with default of 0', () => {
it('CSI 38:2 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;2m');
termColon.writeSync('\x1b[38:2m');
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 38:5 m', () => {
termColon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.curAttrData.fg = 0xFFFFFFFF;
termSemicolon.writeSync('\x1b[38;5m');
termColon.writeSync('\x1b[38:5m');
assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
});
describe('should not interfere with leading/following SGR attrs', () => {
it('CSI 1 ; 38:2::50:100:150 ; 4 m', () => {
termSemicolon.writeSync('\x1b[1;38;2;50;100;150;4m');
termColon.writeSync('\x1b[1;38:2::50:100:150;4m');
assert.equal(!!termSemicolon.curAttrData.isBold(), true);
assert.equal(!!termSemicolon.curAttrData.isUnderline(), true);
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 1 ; 38:2::50:100: ; 4 m', () => {
termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m');
termColon.writeSync('\x1b[1;38:2::50:100:;4m');
assert.equal(!!termSemicolon.curAttrData.isBold(), true);
assert.equal(!!termSemicolon.curAttrData.isUnderline(), true);
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 1 ; 38:2::50:100 ; 4 m', () => {
termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m');
termColon.writeSync('\x1b[1;38:2::50:100;4m');
assert.equal(!!termSemicolon.curAttrData.isBold(), true);
assert.equal(!!termSemicolon.curAttrData.isUnderline(), true);
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 1 ; 38:2:: ; 4 m', () => {
termSemicolon.writeSync('\x1b[1;38;2;;;;4m');
termColon.writeSync('\x1b[1;38:2::;4m');
assert.equal(!!termSemicolon.curAttrData.isBold(), true);
assert.equal(!!termSemicolon.curAttrData.isUnderline(), true);
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
it('CSI 1 ; 38;2:: ; 4 m', () => {
termSemicolon.writeSync('\x1b[1;38;2;;;;4m');
termColon.writeSync('\x1b[1;38;2::;4m');
assert.equal(!!termSemicolon.curAttrData.isBold(), true);
assert.equal(!!termSemicolon.curAttrData.isUnderline(), true);
assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0);
assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg);
});
});
});
describe('cursor positioning', () => {
let term: TestTerminal;
beforeEach(() => {
+180 -190
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -34,7 +34,7 @@ import { SelectionService } from './browser/services/SelectionService';
import * as Browser from 'common/Platform';
import { addDisposableDomListener } from 'browser/Lifecycle';
import * as Strings from './browser/LocalizableStrings';
import { SoundManager } from './SoundManager';
import { SoundService } from 'browser/services/SoundService';
import { MouseZoneManager } from './MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
@@ -49,13 +49,14 @@ import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services';
import { OptionsService } from 'common/services/OptionsService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService } from 'browser/services/Services';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
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 { IParams } from 'common/parser/Types';
import { CoreService } from 'common/services/CoreService';
// Let it work inside Node.js for automated testing purposes.
@@ -116,6 +117,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _mouseService: IMouseService;
private _renderService: IRenderService;
private _selectionService: ISelectionService;
private _soundService: ISoundService;
// modes
public applicationKeypad: boolean;
@@ -174,7 +176,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _userScrolling: boolean;
private _inputHandler: InputHandler;
public soundManager: SoundManager;
public linkifier: ILinkifier;
public viewport: IViewport;
private _compositionHelper: ICompositionHelper;
@@ -304,7 +305,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._selectionService = this._selectionService || null;
this.linkifier = this.linkifier || new Linkifier(this);
this._mouseZoneManager = this._mouseZoneManager || null;
this.soundManager = this.soundManager || new SoundManager(this);
if (this.options.windowsMode) {
this._windowsMode = applyWindowsMode(this);
@@ -619,6 +619,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._renderService.onRender(e => this._onRender.fire(e));
this.onResize(e => this._renderService.resize(e.cols, e.rows));
this._soundService = new SoundService(this.optionsService);
this._mouseService = new MouseService(this._renderService, this._charSizeService);
this._mouseZoneManager = new MouseZoneManager(this, this._mouseService);
@@ -1413,7 +1414,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
/** Add handler for CSI escape sequence. See xterm.d.ts for details. */
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
return this._inputHandler.addCsiHandler(flag, callback);
}
/** Add handler for OSC escape sequence. See xterm.d.ts for details. */
@@ -1676,7 +1677,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
*/
public bell(): void {
if (this._soundBell()) {
this.soundManager.playBellSound();
this._soundService.playBellSound();
}
if (this._visualBell()) {
+2 -1
View File
@@ -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 { IParams } from 'common/parser/Types';
import { ISelectionService } from 'browser/services/Services';
export class TestTerminal extends Terminal {
@@ -71,7 +72,7 @@ export class MockTerminal implements ITerminal {
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
throw new Error('Method not implemented.');
}
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+38 -41
View File
@@ -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 { IParams } from 'common/parser/Types';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
@@ -109,42 +110,42 @@ export interface IInputHandler {
/** C0 SO */ shiftOut(): void;
/** C0 SI */ shiftIn(): void;
/** CSI @ */ insertChars(params?: number[]): void;
/** CSI A */ cursorUp(params?: number[]): void;
/** CSI B */ cursorDown(params?: number[]): void;
/** CSI C */ cursorForward(params?: number[]): void;
/** CSI D */ cursorBackward(params?: number[]): void;
/** CSI E */ cursorNextLine(params?: number[]): void;
/** CSI F */ cursorPrecedingLine(params?: number[]): void;
/** CSI G */ cursorCharAbsolute(params?: number[]): void;
/** CSI H */ cursorPosition(params?: number[]): void;
/** CSI I */ cursorForwardTab(params?: number[]): void;
/** CSI J */ eraseInDisplay(params?: number[]): void;
/** CSI K */ eraseInLine(params?: number[]): void;
/** CSI L */ insertLines(params?: number[]): void;
/** CSI M */ deleteLines(params?: number[]): void;
/** CSI P */ deleteChars(params?: number[]): void;
/** CSI S */ scrollUp(params?: number[]): void;
/** CSI T */ scrollDown(params?: number[], collect?: string): void;
/** CSI X */ eraseChars(params?: number[]): void;
/** CSI Z */ cursorBackwardTab(params?: number[]): void;
/** CSI ` */ charPosAbsolute(params?: number[]): void;
/** CSI a */ hPositionRelative(params?: number[]): void;
/** CSI b */ repeatPrecedingCharacter(params?: number[]): void;
/** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): void;
/** CSI d */ linePosAbsolute(params?: number[]): void;
/** CSI e */ vPositionRelative(params?: number[]): void;
/** CSI f */ hVPosition(params?: number[]): void;
/** CSI g */ tabClear(params?: number[]): void;
/** CSI h */ setMode(params?: number[], collect?: string): void;
/** CSI l */ resetMode(params?: number[], collect?: string): void;
/** CSI m */ charAttributes(params?: number[]): void;
/** CSI n */ deviceStatus(params?: number[], collect?: string): void;
/** CSI p */ softReset(params?: number[], collect?: string): void;
/** CSI q */ setCursorStyle(params?: number[], collect?: string): void;
/** CSI r */ setScrollRegion(params?: number[], collect?: string): void;
/** CSI s */ saveCursor(params?: number[]): void;
/** CSI u */ restoreCursor(params?: number[]): void;
/** CSI @ */ insertChars(params: IParams): void;
/** CSI A */ cursorUp(params: IParams): void;
/** CSI B */ cursorDown(params: IParams): void;
/** CSI C */ cursorForward(params: IParams): void;
/** CSI D */ cursorBackward(params: IParams): void;
/** CSI E */ cursorNextLine(params: IParams): void;
/** CSI F */ cursorPrecedingLine(params: IParams): void;
/** CSI G */ cursorCharAbsolute(params: IParams): void;
/** CSI H */ cursorPosition(params: IParams): void;
/** CSI I */ cursorForwardTab(params: IParams): void;
/** CSI J */ eraseInDisplay(params: IParams): void;
/** CSI K */ eraseInLine(params: IParams): void;
/** CSI L */ insertLines(params: IParams): void;
/** CSI M */ deleteLines(params: IParams): void;
/** CSI P */ deleteChars(params: IParams): void;
/** CSI S */ scrollUp(params: IParams): void;
/** CSI T */ scrollDown(params: IParams, collect?: string): void;
/** CSI X */ eraseChars(params: IParams): void;
/** CSI Z */ cursorBackwardTab(params: IParams): void;
/** CSI ` */ charPosAbsolute(params: IParams): void;
/** CSI a */ hPositionRelative(params: IParams): void;
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
/** CSI c */ sendDeviceAttributes(params: IParams, collect?: string): void;
/** CSI d */ linePosAbsolute(params: IParams): void;
/** CSI e */ vPositionRelative(params: IParams): void;
/** CSI f */ hVPosition(params: IParams): void;
/** CSI g */ tabClear(params: IParams): void;
/** CSI h */ setMode(params: IParams, collect?: string): void;
/** CSI l */ resetMode(params: IParams, collect?: string): void;
/** CSI m */ charAttributes(params: IParams): void;
/** CSI n */ deviceStatus(params: IParams, collect?: string): void;
/** CSI p */ softReset(params: IParams, collect?: string): void;
/** CSI q */ setCursorStyle(params: IParams, collect?: string): void;
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
/** CSI s */ saveCursor(params: IParams): void;
/** CSI u */ restoreCursor(params: IParams): void;
/** OSC 0
OSC 2 */ setTitle(data: string): void;
/** ESC E */ nextLine(): void;
@@ -240,7 +241,7 @@ export interface IPublicTerminal extends IDisposable {
writeln(data: string): void;
open(parent: HTMLElement): void;
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
deregisterLinkMatcher(matcherId: number): void;
@@ -347,10 +348,6 @@ export interface IBrowser {
isWindows: boolean;
}
export interface ISoundManager {
playBellSound(): void;
}
export interface IMouseZoneManager extends IDisposable {
add(zone: IMouseZone): void;
clearAll(start?: number, end?: number): void;
+4
View File
@@ -72,3 +72,7 @@ export interface ISelectionService {
refresh(isLinuxMouseSelection?: boolean): void;
onMouseDown(event: MouseEvent): void;
}
export interface ISoundService {
playBellSound(): void;
}
@@ -3,35 +3,36 @@
* @license MIT
*/
import { ITerminal, ISoundManager } from './Types';
import { IOptionsService } from 'common/services/Services';
import { ISoundService } from 'browser/services/Services';
export class SoundManager implements ISoundManager {
export class SoundService implements ISoundService {
private static _audioContext: AudioContext;
static get audioContext(): AudioContext | null {
if (!SoundManager._audioContext) {
if (!SoundService._audioContext) {
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
if (!audioContextCtor) {
console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version');
return null;
}
SoundManager._audioContext = new audioContextCtor();
SoundService._audioContext = new audioContextCtor();
}
return SoundManager._audioContext;
return SoundService._audioContext;
}
constructor(
private _terminal: ITerminal
private _optionsService: IOptionsService
) {
}
public playBellSound(): void {
const ctx = SoundManager.audioContext;
const ctx = SoundService.audioContext;
if (!ctx) {
return;
}
const bellAudioSource = ctx.createBufferSource();
ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.options.bellSound)), (buffer) => {
bellAudioSource.buffer = buffer;
bellAudioSource.connect(ctx.destination);
bellAudioSource.start(0);

Some files were not shown because too many files have changed in this diff Show More