Merge branch 'master' into cleanup_sequences_files
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"paths": {
|
||||
"common/*": [ "../../../src/common/*" ],
|
||||
"browser/*": [ "../../../src/browser/*" ]
|
||||
}
|
||||
},
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
|
||||
+4
-2
@@ -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
@@ -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
@@ -14,13 +14,13 @@ import { concat } from 'common/TypedArrayUtils';
|
||||
import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IAttributeData, IDisposable } from 'common/Types';
|
||||
import { ICoreService } from 'common/services/Services';
|
||||
import { ISelectionService } from 'browser/services/Services';
|
||||
import { IDisposable } from 'common/Types';
|
||||
|
||||
/**
|
||||
* Map collect to glevel. Used in `selectCharset`.
|
||||
@@ -43,7 +43,7 @@ class DECRQSS implements IDcsHandler {
|
||||
|
||||
constructor(private _terminal: any) { }
|
||||
|
||||
hook(collect: string, params: number[], flag: number): void {
|
||||
hook(collect: string, params: IParams, flag: number): void {
|
||||
this._data = new Uint32Array(0);
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* custom fallback handlers
|
||||
*/
|
||||
this._parser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => {
|
||||
this._terminal.error('Unknown CSI code: ', { collect, params, flag: String.fromCharCode(flag) });
|
||||
this._parser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => {
|
||||
this._terminal.error('Unknown CSI code: ', { collect, params: params.toArray(), flag: String.fromCharCode(flag) });
|
||||
});
|
||||
this._parser.setEscHandlerFallback((collect: string, flag: number) => {
|
||||
this._terminal.error('Unknown ESC code: ', { collect, flag: String.fromCharCode(flag) });
|
||||
@@ -255,8 +255,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* ESC handlers
|
||||
*/
|
||||
this._parser.setEscHandler('7', () => this.saveCursor([]));
|
||||
this._parser.setEscHandler('8', () => this.restoreCursor([]));
|
||||
this._parser.setEscHandler('7', () => this.saveCursor());
|
||||
this._parser.setEscHandler('8', () => this.restoreCursor());
|
||||
this._parser.setEscHandler('D', () => this.index());
|
||||
this._parser.setEscHandler('E', () => this.nextLine());
|
||||
this._parser.setEscHandler('H', () => this.tabSet());
|
||||
@@ -485,7 +485,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* Forward addCsiHandler from parser.
|
||||
*/
|
||||
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
||||
public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
|
||||
return this._parser.addCsiHandler(flag, callback);
|
||||
}
|
||||
|
||||
@@ -586,13 +586,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps @
|
||||
* Insert Ps (Blank) Character(s) (default = 1) (ICH).
|
||||
*/
|
||||
public insertChars(params: number[]): void {
|
||||
public insertChars(params: IParams): void {
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells(
|
||||
this._terminal.buffer.x,
|
||||
params[0] || 1,
|
||||
params.params[0] || 1,
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
@@ -635,48 +635,32 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps A
|
||||
* Cursor Up Ps Times (default = 1) (CUU).
|
||||
*/
|
||||
public cursorUp(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(0, -param);
|
||||
public cursorUp(params: IParams): void {
|
||||
this._moveCursor(0, -(params.params[0] || 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps B
|
||||
* Cursor Down Ps Times (default = 1) (CUD).
|
||||
*/
|
||||
public cursorDown(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(0, param);
|
||||
public cursorDown(params: IParams): void {
|
||||
this._moveCursor(0, params.params[0] || 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps C
|
||||
* Cursor Forward Ps Times (default = 1) (CUF).
|
||||
*/
|
||||
public cursorForward(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(param, 0);
|
||||
public cursorForward(params: IParams): void {
|
||||
this._moveCursor(params.params[0] || 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps D
|
||||
* Cursor Backward Ps Times (default = 1) (CUB).
|
||||
*/
|
||||
public cursorBackward(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(-param, 0);
|
||||
public cursorBackward(params: IParams): void {
|
||||
this._moveCursor(-(params.params[0] || 1), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -684,12 +668,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Next Line Ps Times (default = 1) (CNL).
|
||||
* Other than cursorDown (CUD) also set the cursor to first column.
|
||||
*/
|
||||
public cursorNextLine(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(0, param);
|
||||
public cursorNextLine(params: IParams): void {
|
||||
this._moveCursor(0, params.params[0] || 1);
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
|
||||
@@ -699,12 +679,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Cursor Previous Line Ps Times (default = 1) (CPL).
|
||||
* Other than cursorUp (CUU) also set the cursor to first column.
|
||||
*/
|
||||
public cursorPrecedingLine(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(0, -param);
|
||||
public cursorPrecedingLine(params: IParams): void {
|
||||
this._moveCursor(0, -(params.params[0] || 1));
|
||||
this._terminal.buffer.x = 0;
|
||||
}
|
||||
|
||||
@@ -713,39 +689,31 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps G
|
||||
* Cursor Character Absolute [column] (default = [row,1]) (CHA).
|
||||
*/
|
||||
public cursorCharAbsolute(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._setCursor(param - 1, this._terminal.buffer.y);
|
||||
public cursorCharAbsolute(params: IParams): void {
|
||||
this._setCursor((params.params[0] || 1) - 1, this._terminal.buffer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps ; Ps H
|
||||
* Cursor Position [row;column] (default = [1,1]) (CUP).
|
||||
*/
|
||||
public cursorPosition(params: number[]): void {
|
||||
let col: number;
|
||||
const row: number = params[0] - 1;
|
||||
|
||||
if (params.length >= 2) {
|
||||
col = params[1] - 1;
|
||||
} else {
|
||||
col = 0;
|
||||
}
|
||||
this._setCursor(col, row);
|
||||
public cursorPosition(params: IParams): void {
|
||||
this._setCursor(
|
||||
// col
|
||||
(params.length >= 2) ? (params.params[1] || 1) - 1 : 0,
|
||||
// row
|
||||
(params.params[0] || 1) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps I
|
||||
* Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
|
||||
*/
|
||||
public cursorForwardTab(params: number[]): void {
|
||||
public cursorForwardTab(params: IParams): void {
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
return;
|
||||
}
|
||||
let param = params[0] || 1;
|
||||
let param = params.params[0] || 1;
|
||||
while (param--) {
|
||||
this._terminal.buffer.x = this._terminal.buffer.nextStop();
|
||||
}
|
||||
@@ -791,9 +759,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 1 -> Selective Erase Above.
|
||||
* Ps = 2 -> Selective Erase All.
|
||||
*/
|
||||
public eraseInDisplay(params: number[]): void {
|
||||
public eraseInDisplay(params: IParams): void {
|
||||
let j;
|
||||
switch (params[0]) {
|
||||
switch (params.params[0]) {
|
||||
case 0:
|
||||
j = this._terminal.buffer.y;
|
||||
this._terminal.updateRange(j);
|
||||
@@ -850,11 +818,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 1 -> Selective Erase to Left.
|
||||
* Ps = 2 -> Selective Erase All.
|
||||
*/
|
||||
public eraseInLine(params: number[]): void {
|
||||
public eraseInLine(params: IParams): void {
|
||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
||||
this._terminal.buffer.x = this._terminal.cols - 1;
|
||||
}
|
||||
switch (params[0]) {
|
||||
switch (params.params[0]) {
|
||||
case 0:
|
||||
this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols);
|
||||
break;
|
||||
@@ -872,11 +840,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps L
|
||||
* Insert Ps Line(s) (default = 1) (IL).
|
||||
*/
|
||||
public insertLines(params: number[]): void {
|
||||
let param: number = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
public insertLines(params: IParams): void {
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
@@ -902,11 +867,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps M
|
||||
* Delete Ps Line(s) (default = 1) (DL).
|
||||
*/
|
||||
public deleteLines(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
public deleteLines(params: IParams): void {
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
@@ -933,10 +895,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps P
|
||||
* Delete Ps Character(s) (default = 1) (DCH).
|
||||
*/
|
||||
public deleteChars(params: number[]): void {
|
||||
public deleteChars(params: IParams): void {
|
||||
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells(
|
||||
this._terminal.buffer.x,
|
||||
params[0] || 1,
|
||||
params.params[0] || 1,
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
@@ -945,8 +907,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* CSI Ps S Scroll up Ps lines (default = 1) (SU).
|
||||
*/
|
||||
public scrollUp(params: number[]): void {
|
||||
let param = params[0] || 1;
|
||||
public scrollUp(params: IParams): void {
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
@@ -963,9 +925,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* CSI Ps T Scroll down Ps lines (default = 1) (SD).
|
||||
*/
|
||||
public scrollDown(params: number[], collect?: string): void {
|
||||
public scrollDown(params: IParams, collect?: string): void {
|
||||
if (params.length < 2 && !collect) {
|
||||
let param = params[0] || 1;
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
@@ -984,10 +946,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Ps X
|
||||
* Erase Ps Character(s) (default = 1) (ECH).
|
||||
*/
|
||||
public eraseChars(params: number[]): void {
|
||||
public eraseChars(params: IParams): void {
|
||||
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells(
|
||||
this._terminal.buffer.x,
|
||||
this._terminal.buffer.x + (params[0] || 1),
|
||||
this._terminal.buffer.x + (params.params[0] || 1),
|
||||
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
|
||||
);
|
||||
this._terminal.updateRange(this._terminal.buffer.y);
|
||||
@@ -996,8 +958,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
|
||||
*/
|
||||
public cursorBackwardTab(params: number[]): void {
|
||||
let param = params[0] || 1;
|
||||
public cursorBackwardTab(params: IParams): void {
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
const buffer = this._terminal.buffer;
|
||||
@@ -1012,12 +974,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* [column] (default = [row,1]) (HPA).
|
||||
* Currently same functionality as CHA.
|
||||
*/
|
||||
public charPosAbsolute(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._setCursor(param - 1, this._terminal.buffer.y);
|
||||
public charPosAbsolute(params: IParams): void {
|
||||
this._setCursor((params.params[0] || 1) - 1, this._terminal.buffer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1025,12 +983,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* [columns] (default = [row,col+1]) (HPR)
|
||||
* Currently same functionality as CUF.
|
||||
*/
|
||||
public hPositionRelative(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(param, 0);
|
||||
public hPositionRelative(params: IParams): void {
|
||||
this._moveCursor(params.params[0] || 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1055,12 +1009,12 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Note: To get reset on a valid sequence working correctly without much runtime penalty,
|
||||
* the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`.
|
||||
*/
|
||||
public repeatPrecedingCharacter(params: number[]): void {
|
||||
public repeatPrecedingCharacter(params: IParams): void {
|
||||
if (!this._parser.precedingCodepoint) {
|
||||
return;
|
||||
}
|
||||
// call print to insert the chars and handle correct wrapping
|
||||
const length = params[0] || 1;
|
||||
const length = params.params[0] || 1;
|
||||
const data = new Uint32Array(length);
|
||||
for (let i = 0; i < length; ++i) {
|
||||
data[i] = this._parser.precedingCodepoint;
|
||||
@@ -1105,8 +1059,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* xterm/charproc.c - line 2012, for more information.
|
||||
* vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)
|
||||
*/
|
||||
public sendDeviceAttributes(params: number[], collect?: string): void {
|
||||
if (params[0] > 0) {
|
||||
public sendDeviceAttributes(params: IParams, collect?: string): void {
|
||||
if (params.params[0] > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1127,7 +1081,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
} else if (this._terminal.is('linux')) {
|
||||
// not supported by linux console.
|
||||
// linux console echoes parameters.
|
||||
this._coreService.triggerDataEvent(params[0] + 'c');
|
||||
this._coreService.triggerDataEvent(params.params[0] + 'c');
|
||||
} else if (this._terminal.is('screen')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c');
|
||||
}
|
||||
@@ -1138,12 +1092,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI Pm d Vertical Position Absolute (VPA)
|
||||
* [row] (default = [1,column])
|
||||
*/
|
||||
public linePosAbsolute(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._setCursor(this._terminal.buffer.x, param - 1);
|
||||
public linePosAbsolute(params: IParams): void {
|
||||
this._setCursor(this._terminal.buffer.x, (params.params[0] || 1) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1151,12 +1101,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* [rows] (default = [row+1,column])
|
||||
* reuse CSI Ps B ?
|
||||
*/
|
||||
public vPositionRelative(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
}
|
||||
this._moveCursor(0, param);
|
||||
public vPositionRelative(params: IParams): void {
|
||||
this._moveCursor(0, params.params[0] || 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1165,7 +1111,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* [1,1]) (HVP).
|
||||
* Same as CUP.
|
||||
*/
|
||||
public hVPosition(params: number[]): void {
|
||||
public hVPosition(params: IParams): void {
|
||||
this.cursorPosition(params);
|
||||
}
|
||||
|
||||
@@ -1177,9 +1123,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 2 -> Clear Stops on Line.
|
||||
* http://vt100.net/annarbor/aaa-ug/section6.html
|
||||
*/
|
||||
public tabClear(params: number[]): void {
|
||||
const param = params[0];
|
||||
if (param <= 0) {
|
||||
public tabClear(params: IParams): void {
|
||||
const param = params.params[0];
|
||||
if (param === 0) {
|
||||
delete this._terminal.buffer.tabs[this._terminal.buffer.x];
|
||||
} else if (param === 3) {
|
||||
this._terminal.buffer.tabs = {};
|
||||
@@ -1272,17 +1218,15 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Modes:
|
||||
* http: *vt100.net/docs/vt220-rm/chapter4.html
|
||||
*/
|
||||
public setMode(params: number[], collect?: string): void {
|
||||
if (params.length > 1) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
this.setMode([params[i]]);
|
||||
}
|
||||
|
||||
return;
|
||||
public setMode(params: IParams, collect?: string): void {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
this._setMode(params.params[i], collect);
|
||||
}
|
||||
}
|
||||
|
||||
private _setMode(param: number, collect?: string): void {
|
||||
if (!collect) {
|
||||
switch (params[0]) {
|
||||
switch (param) {
|
||||
case 4:
|
||||
this._terminal.insertMode = true;
|
||||
break;
|
||||
@@ -1291,7 +1235,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
break;
|
||||
}
|
||||
} else if (collect === '?') {
|
||||
switch (params[0]) {
|
||||
switch (param) {
|
||||
case 1:
|
||||
this._coreService.decPrivateModes.applicationCursorKeys = true;
|
||||
break;
|
||||
@@ -1334,9 +1278,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
|
||||
// TODO: Why are params[0] compares nested within a switch for params[0]?
|
||||
|
||||
this._terminal.x10Mouse = params[0] === 9;
|
||||
this._terminal.vt200Mouse = params[0] === 1000;
|
||||
this._terminal.normalMouse = params[0] > 1000;
|
||||
this._terminal.x10Mouse = param === 9;
|
||||
this._terminal.vt200Mouse = param === 1000;
|
||||
this._terminal.normalMouse = param > 1000;
|
||||
this._terminal.mouseEvents = true;
|
||||
if (this._terminal.element) {
|
||||
this._terminal.element.classList.add('enable-mouse-events');
|
||||
@@ -1374,10 +1318,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._terminal.cursorHidden = false;
|
||||
break;
|
||||
case 1048: // alt screen cursor
|
||||
this.saveCursor(params);
|
||||
this.saveCursor();
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
this.saveCursor(params);
|
||||
this.saveCursor();
|
||||
// FALL-THROUGH
|
||||
case 47: // alt screen buffer
|
||||
case 1047: // alt screen buffer
|
||||
@@ -1477,17 +1421,15 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.
|
||||
* Ps = 2 0 0 4 -> Reset bracketed paste mode.
|
||||
*/
|
||||
public resetMode(params: number[], collect?: string): void {
|
||||
if (params.length > 1) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
this.resetMode([params[i]]);
|
||||
}
|
||||
|
||||
return;
|
||||
public resetMode(params: IParams, collect?: string): void {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
this._resetMode(params.params[i], collect);
|
||||
}
|
||||
}
|
||||
|
||||
private _resetMode(param: number, collect?: string): void {
|
||||
if (!collect) {
|
||||
switch (params[0]) {
|
||||
switch (param) {
|
||||
case 4:
|
||||
this._terminal.insertMode = false;
|
||||
break;
|
||||
@@ -1496,7 +1438,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
break;
|
||||
}
|
||||
} else if (collect === '?') {
|
||||
switch (params[0]) {
|
||||
switch (param) {
|
||||
case 1:
|
||||
this._coreService.decPrivateModes.applicationCursorKeys = false;
|
||||
break;
|
||||
@@ -1553,7 +1495,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._terminal.cursorHidden = true;
|
||||
break;
|
||||
case 1048: // alt screen cursor
|
||||
this.restoreCursor(params);
|
||||
this.restoreCursor();
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
// FALL-THROUGH
|
||||
@@ -1561,8 +1503,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
case 1047: // normal screen buffer - clearing it first
|
||||
// Ensure the selection manager has the correct buffer
|
||||
this._terminal.buffers.activateNormalBuffer();
|
||||
if (params[0] === 1049) {
|
||||
this.restoreCursor(params);
|
||||
if (param === 1049) {
|
||||
this.restoreCursor();
|
||||
}
|
||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||
if (this._terminal.viewport) {
|
||||
@@ -1577,6 +1519,78 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to extract and apply color params/subparams.
|
||||
* Returns advance for params index.
|
||||
*/
|
||||
private _extractColor(params: IParams, pos: number, attr: IAttributeData): number {
|
||||
// normalize params
|
||||
// meaning: [target, CM, ign, val, val, val]
|
||||
// RGB : [ 38/48, 2, ign, r, g, b]
|
||||
// P256 : [ 38/48, 5, ign, v, ign, ign]
|
||||
const accu = [0, 0, -1, 0, 0, 0];
|
||||
|
||||
// alignment placeholder for non color space sequences
|
||||
let cSpace = 0;
|
||||
|
||||
// return advance we took in params
|
||||
let advance = 0;
|
||||
|
||||
do {
|
||||
accu[advance + cSpace] = params.params[pos + advance];
|
||||
if (params.hasSubParams(pos + advance)) {
|
||||
const subparams = params.getSubParams(pos + advance);
|
||||
let i = 0;
|
||||
do {
|
||||
if (accu[1] === 5) {
|
||||
cSpace = 1;
|
||||
}
|
||||
accu[advance + i + 1 + cSpace] = subparams[i];
|
||||
} while (++i < subparams.length && i + advance + 1 + cSpace < accu.length);
|
||||
break;
|
||||
}
|
||||
// exit early if can decide color mode with semicolons
|
||||
if ((accu[1] === 5 && advance + cSpace >= 2)
|
||||
|| (accu[1] === 2 && advance + cSpace >= 5)) {
|
||||
break;
|
||||
}
|
||||
// offset colorSpace slot for semicolon mode
|
||||
if (accu[1]) {
|
||||
cSpace = 1;
|
||||
}
|
||||
} while (++advance + pos < params.length && advance + cSpace < accu.length);
|
||||
|
||||
// set default values to 0
|
||||
for (let i = 2; i < accu.length; ++i) {
|
||||
if (accu[i] === -1) {
|
||||
accu[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// apply colors
|
||||
if (accu[0] === 38) {
|
||||
if (accu[1] === 2) {
|
||||
attr.fg |= Attributes.CM_RGB;
|
||||
attr.fg &= ~Attributes.RGB_MASK;
|
||||
attr.fg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]);
|
||||
} else if (accu[1] === 5) {
|
||||
attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
|
||||
attr.fg |= Attributes.CM_P256 | (accu[3] & 0xff);
|
||||
}
|
||||
} else if (accu[0] === 48) {
|
||||
if (accu[1] === 2) {
|
||||
attr.bg |= Attributes.CM_RGB;
|
||||
attr.bg &= ~Attributes.RGB_MASK;
|
||||
attr.bg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]);
|
||||
} else if (accu[1] === 5) {
|
||||
attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
|
||||
attr.bg |= Attributes.CM_P256 | (accu[3] & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
return advance;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Pm m Character Attributes (SGR).
|
||||
* Ps = 0 -> Normal (default).
|
||||
@@ -1642,9 +1656,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 4 8 ; 5 ; Ps -> Set background color to the second
|
||||
* Ps.
|
||||
*/
|
||||
public charAttributes(params: number[]): void {
|
||||
public charAttributes(params: IParams): void {
|
||||
// Optimize a single SGR0.
|
||||
if (params.length === 1 && params[0] === 0) {
|
||||
if (params.length === 1 && params.params[0] === 0) {
|
||||
this._terminal.curAttrData.fg = DEFAULT_ATTR_DATA.fg;
|
||||
this._terminal.curAttrData.bg = DEFAULT_ATTR_DATA.bg;
|
||||
return;
|
||||
@@ -1655,7 +1669,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
const attr = this._terminal.curAttrData;
|
||||
|
||||
for (let i = 0; i < l; i++) {
|
||||
p = params[i];
|
||||
p = params.params[i];
|
||||
if (p >= 30 && p <= 37) {
|
||||
// fg color 8
|
||||
attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
|
||||
@@ -1725,34 +1739,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// reset bg
|
||||
attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
|
||||
} else if (p === 38) {
|
||||
} else if (p === 38 || p === 48) {
|
||||
// fg color 256 and RGB
|
||||
if (params[i + 1] === 2) {
|
||||
i += 2;
|
||||
attr.fg |= Attributes.CM_RGB;
|
||||
attr.fg &= ~Attributes.RGB_MASK;
|
||||
attr.fg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]);
|
||||
i += 2;
|
||||
} else if (params[i + 1] === 5) {
|
||||
i += 2;
|
||||
p = params[i] & 0xff;
|
||||
attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
|
||||
attr.fg |= Attributes.CM_P256 | p;
|
||||
}
|
||||
} else if (p === 48) {
|
||||
// bg color 256 and RGB
|
||||
if (params[i + 1] === 2) {
|
||||
i += 2;
|
||||
attr.bg |= Attributes.CM_RGB;
|
||||
attr.bg &= ~Attributes.RGB_MASK;
|
||||
attr.bg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]);
|
||||
i += 2;
|
||||
} else if (params[i + 1] === 5) {
|
||||
i += 2;
|
||||
p = params[i] & 0xff;
|
||||
attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
|
||||
attr.bg |= Attributes.CM_P256 | p;
|
||||
}
|
||||
i += this._extractColor(params, i, attr);
|
||||
} else if (p === 100) {
|
||||
// reset fg/bg
|
||||
attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
@@ -1788,9 +1777,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI ? 5 3 n Locator available, if compiled-in, or
|
||||
* CSI ? 5 0 n No Locator, if not.
|
||||
*/
|
||||
public deviceStatus(params: number[], collect?: string): void {
|
||||
public deviceStatus(params: IParams, collect?: string): void {
|
||||
if (!collect) {
|
||||
switch (params[0]) {
|
||||
switch (params.params[0]) {
|
||||
case 5:
|
||||
// status report
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[0n`);
|
||||
@@ -1805,7 +1794,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
} else if (collect === '?') {
|
||||
// modern xterm doesnt seem to
|
||||
// respond to any of these except ?6, 6, and 5
|
||||
switch (params[0]) {
|
||||
switch (params.params[0]) {
|
||||
case 6:
|
||||
// cursor position
|
||||
const y = this._terminal.buffer.y + 1;
|
||||
@@ -1836,7 +1825,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI ! p Soft terminal reset (DECSTR).
|
||||
* http://vt100.net/docs/vt220-rm/table4-10.html
|
||||
*/
|
||||
public softReset(params: number[], collect?: string): void {
|
||||
public softReset(params: IParams, collect?: string): void {
|
||||
if (collect === '!') {
|
||||
this._terminal.cursorHidden = false;
|
||||
this._terminal.insertMode = false;
|
||||
@@ -1867,9 +1856,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 5 -> blinking bar (xterm).
|
||||
* Ps = 6 -> steady bar (xterm).
|
||||
*/
|
||||
public setCursorStyle(params?: number[], collect?: string): void {
|
||||
public setCursorStyle(params?: IParams, collect?: string): void {
|
||||
if (collect === ' ') {
|
||||
const param = params[0] < 1 ? 1 : params[0];
|
||||
const param = params.params[0] || 1;
|
||||
switch (param) {
|
||||
case 1:
|
||||
case 2:
|
||||
@@ -1894,13 +1883,14 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Set Scrolling Region [top;bottom] (default = full size of win-
|
||||
* dow) (DECSTBM).
|
||||
* CSI ? Pm r
|
||||
* currently skipped
|
||||
*/
|
||||
public setScrollRegion(params: number[], collect?: string): void {
|
||||
public setScrollRegion(params: IParams, collect?: string): void {
|
||||
if (collect) {
|
||||
return;
|
||||
}
|
||||
this._terminal.buffer.scrollTop = (params[0] || 1) - 1;
|
||||
this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
|
||||
this._terminal.buffer.scrollTop = (params.params[0] || 1) - 1;
|
||||
this._terminal.buffer.scrollBottom = (params.length > 1 && params.params[1] && params.params[1] <= this._terminal.rows ? params.params[1] : this._terminal.rows) - 1;
|
||||
this._terminal.buffer.x = 0;
|
||||
this._terminal.buffer.y = 0;
|
||||
}
|
||||
@@ -1911,7 +1901,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* ESC 7
|
||||
* Save cursor (ANSI.SYS).
|
||||
*/
|
||||
public saveCursor(params: number[]): void {
|
||||
public saveCursor(params?: IParams): void {
|
||||
this._terminal.buffer.savedX = this._terminal.buffer.x;
|
||||
this._terminal.buffer.savedY = this._terminal.buffer.ybase + this._terminal.buffer.y;
|
||||
this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg;
|
||||
@@ -1924,7 +1914,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* ESC 8
|
||||
* Restore cursor (ANSI.SYS).
|
||||
*/
|
||||
public restoreCursor(params: number[]): void {
|
||||
public restoreCursor(params?: IParams): void {
|
||||
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
|
||||
this._terminal.buffer.y = Math.max(this._terminal.buffer.savedY - this._terminal.buffer.ybase, 0);
|
||||
this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg;
|
||||
@@ -2088,7 +2078,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
|
||||
const buffer = this._terminal.buffer;
|
||||
|
||||
this.cursorPosition([1, 1]);
|
||||
this._setCursor(0, 0);
|
||||
for (let yOffset = 0; yOffset < this._terminal.rows; ++yOffset) {
|
||||
const row = buffer.y + buffer.ybase + yOffset;
|
||||
buffer.lines.get(row).fill(cell);
|
||||
@@ -2096,6 +2086,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
this._terminal.updateRange(0);
|
||||
this._terminal.updateRange(this._terminal.rows);
|
||||
this.cursorPosition([1, 1]);
|
||||
this._setCursor(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Vendored
+38
-41
@@ -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;
|
||||
|
||||
Vendored
+4
@@ -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);
|
||||
@@ -3,11 +3,13 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDcsHandler, IParsingState } from 'common/parser/Types';
|
||||
import { IDcsHandler, IParsingState, IParams, ParamsArray } from 'common/parser/Types';
|
||||
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
|
||||
import * as chai from 'chai';
|
||||
import { StringToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { ParserState } from 'common/parser/Constants';
|
||||
import { Params } from 'common/parser/Params';
|
||||
|
||||
|
||||
function r(a: number, b: number): string[] {
|
||||
let c = b - a;
|
||||
@@ -26,11 +28,14 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
|
||||
public set osc(value: string) {
|
||||
this._osc = value;
|
||||
}
|
||||
public get params(): number[] {
|
||||
return this._params;
|
||||
public get params(): ParamsArray {
|
||||
return this._params.toArray();
|
||||
}
|
||||
public set params(value: number[]) {
|
||||
this._params = value;
|
||||
public set params(value: ParamsArray) {
|
||||
this._params = Params.fromArray(value);
|
||||
}
|
||||
public get realParams(): IParams {
|
||||
return this._params;
|
||||
}
|
||||
public get collect(): string {
|
||||
return this._collect;
|
||||
@@ -65,14 +70,14 @@ const testTerminal: any = {
|
||||
actionExecute: function (flag: string): void {
|
||||
this.calls.push(['exe', flag]);
|
||||
},
|
||||
actionCSI: function (collect: string, params: number[], flag: string): void {
|
||||
this.calls.push(['csi', collect, params, flag]);
|
||||
actionCSI: function (collect: string, params: IParams, flag: string): void {
|
||||
this.calls.push(['csi', collect, params.toArray(), flag]);
|
||||
},
|
||||
actionESC: function (collect: string, flag: string): void {
|
||||
this.calls.push(['esc', collect, flag]);
|
||||
},
|
||||
actionDCSHook: function (collect: string, params: number[], flag: string): void {
|
||||
this.calls.push(['dcs hook', collect, params, flag]);
|
||||
actionDCSHook: function (collect: string, params: IParams, flag: string): void {
|
||||
this.calls.push(['dcs hook', collect, params.toArray(), flag]);
|
||||
},
|
||||
actionDCSPrint: function (data: Uint32Array, start: number, end: number): void {
|
||||
let s = '';
|
||||
@@ -88,7 +93,7 @@ const testTerminal: any = {
|
||||
|
||||
// dcs handler to map dcs actions into the test object `testTerminal`
|
||||
class DcsTest implements IDcsHandler {
|
||||
hook(collect: string, params: number[], flag: number): void {
|
||||
hook(collect: string, params: IParams, flag: number): void {
|
||||
testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag));
|
||||
}
|
||||
put(data: Uint32Array, start: number, end: number): void {
|
||||
@@ -120,7 +125,7 @@ let state: any;
|
||||
// parser with Uint8Array based transition table
|
||||
const testParser = new TestEscapeSequenceParser();
|
||||
testParser.setPrintHandler(testTerminal.print.bind(testTerminal));
|
||||
testParser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => {
|
||||
testParser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => {
|
||||
testTerminal.actionCSI(collect, params, String.fromCharCode(flag));
|
||||
});
|
||||
testParser.setEscHandlerFallback((collect: string, flag: number) => {
|
||||
@@ -562,16 +567,16 @@ describe('EscapeSequenceParser', function (): void {
|
||||
testTerminal.clear();
|
||||
}
|
||||
});
|
||||
it('trans CSI_ENTRY --> CSI_IGNORE', function (): void {
|
||||
it('trans CSI_ENTRY --> CSI_PARAM for ":" (0x3a)', function (): void {
|
||||
parser.reset();
|
||||
parser.currentState = ParserState.CSI_ENTRY;
|
||||
parse(parser, '\x3a');
|
||||
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
|
||||
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
|
||||
parser.reset();
|
||||
});
|
||||
it('trans CSI_PARAM --> CSI_IGNORE', function (): void {
|
||||
parser.reset();
|
||||
const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f'];
|
||||
const chars = ['\x3c', '\x3d', '\x3e', '\x3f'];
|
||||
for (let i = 0; i < chars.length; ++i) {
|
||||
parser.currentState = ParserState.CSI_PARAM;
|
||||
parse(parser, '\x3b' + chars[i]);
|
||||
@@ -580,6 +585,18 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.reset();
|
||||
}
|
||||
});
|
||||
it('trans CSI_PARAM --> CSI_IGNORE', function (): void {
|
||||
parser.reset();
|
||||
const chars = ['\x3c', '\x3d', '\x3e', '\x3f'];
|
||||
for (let i = 0; i < chars.length; ++i) {
|
||||
chai.expect(parser.params).eql([0]);
|
||||
parser.currentState = ParserState.CSI_PARAM;
|
||||
parse(parser, '\x3b' + chars[i]);
|
||||
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
|
||||
chai.expect(parser.params).eql([0, 0]);
|
||||
parser.reset();
|
||||
}
|
||||
});
|
||||
it('trans CSI_INTERMEDIATE --> CSI_IGNORE', function (): void {
|
||||
parser.reset();
|
||||
const chars = r(0x30, 0x40);
|
||||
@@ -785,16 +802,16 @@ describe('EscapeSequenceParser', function (): void {
|
||||
chai.expect(parser.params).eql([0, 0]);
|
||||
parser.reset();
|
||||
});
|
||||
it('trans DCS_ENTRY --> DCS_IGNORE', function (): void {
|
||||
it('trans DCS_ENTRY --> DCS_PARAM for ":" (0x3a)', function (): void {
|
||||
parser.reset();
|
||||
parser.currentState = ParserState.DCS_ENTRY;
|
||||
parse(parser, '\x3a');
|
||||
chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE);
|
||||
chai.expect(parser.currentState).equal(ParserState.DCS_PARAM);
|
||||
parser.reset();
|
||||
});
|
||||
it('trans DCS_PARAM --> DCS_IGNORE', function (): void {
|
||||
parser.reset();
|
||||
const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f'];
|
||||
const chars = ['\x3c', '\x3d', '\x3e', '\x3f'];
|
||||
for (let i = 0; i < chars.length; ++i) {
|
||||
parser.currentState = ParserState.DCS_PARAM;
|
||||
parse(parser, '\x3b' + chars[i]);
|
||||
@@ -1028,6 +1045,23 @@ describe('EscapeSequenceParser', function (): void {
|
||||
['print', 'defg']
|
||||
], null);
|
||||
});
|
||||
it('colon notation in CSI params', () => {
|
||||
test('\x1b[<31;5::123:;8mHello World! öäü€\nabc',
|
||||
[
|
||||
['csi', '<', [31, 5, [-1, 123, -1], 8], 'm'],
|
||||
['print', 'Hello World! öäü€'],
|
||||
['exe', '\n'],
|
||||
['print', 'abc']
|
||||
], null);
|
||||
});
|
||||
it('colon notation in DCS params', function (): void {
|
||||
test('abc\x901;2::55;3+$abc;de\x9c', [
|
||||
['print', 'abc'],
|
||||
['dcs hook', '+$', [1, 2, [-1, 55], 3], 'a'],
|
||||
['dcs put', 'bc;de'],
|
||||
['dcs unhook']
|
||||
], null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('coverage tests', function (): void {
|
||||
@@ -1078,10 +1112,10 @@ describe('EscapeSequenceParser', function (): void {
|
||||
let parser2: TestEscapeSequenceParser;
|
||||
let print = '';
|
||||
const esc: string[] = [];
|
||||
const csi: [string, number[], string][] = [];
|
||||
const csi: [string, ParamsArray, string][] = [];
|
||||
const exe: string[] = [];
|
||||
const osc: [number, string][] = [];
|
||||
const dcs: ([string] | [string, string] | [string, string, number[], number])[] = [];
|
||||
const dcs: ([string] | [string, string] | [string, string, ParamsArray, number])[] = [];
|
||||
function clearAccu(): void {
|
||||
print = '';
|
||||
esc.length = 0;
|
||||
@@ -1128,8 +1162,8 @@ describe('EscapeSequenceParser', function (): void {
|
||||
chai.expect(esc).eql([]);
|
||||
});
|
||||
it('CSI handler', function (): void {
|
||||
parser2.setCsiHandler('m', function (params: number[], collect: string): void {
|
||||
csi.push(['m', params, collect]);
|
||||
parser2.setCsiHandler('m', function (params: IParams, collect: string): void {
|
||||
csi.push(['m', params.toArray(), collect]);
|
||||
});
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
@@ -1141,38 +1175,38 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
describe('CSI custom handlers', () => {
|
||||
it('Prevent fallback', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Allow fallback', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return false; });
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return false; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Multiple custom handlers fallback once', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
const csiCustom2: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return false; });
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
const csiCustom2: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray(), collect]); return false; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Multiple custom handlers no fallback', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
const csiCustom2: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return true; });
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
const csiCustom2: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray(), collect]); return true; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([], 'Should not fallback once');
|
||||
@@ -1187,18 +1221,18 @@ describe('EscapeSequenceParser', function (): void {
|
||||
chai.expect(order).eql([3, 2, 1]);
|
||||
});
|
||||
it('Dispose should work', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
customHandler.dispose();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
|
||||
});
|
||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
customHandler.dispose();
|
||||
customHandler.dispose();
|
||||
parse(parser2, INPUT);
|
||||
@@ -1302,8 +1336,8 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
it('DCS handler', function (): void {
|
||||
parser2.setDcsHandler('+p', {
|
||||
hook: function (collect: string, params: number[], flag: number): void {
|
||||
dcs.push(['hook', collect, params, flag]);
|
||||
hook: function (collect: string, params: IParams, flag: number): void {
|
||||
dcs.push(['hook', collect, params.toArray(), flag]);
|
||||
},
|
||||
put: function (data: Uint32Array, start: number, end: number): void {
|
||||
let s = '';
|
||||
@@ -1343,7 +1377,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
currentState: ParserState.CSI_PARAM,
|
||||
osc: '',
|
||||
collect: '',
|
||||
params: [1, 2, 0], // extra zero here
|
||||
params: Params.fromArray([1, 2, 0]), // extra zero here
|
||||
abort: false
|
||||
});
|
||||
parser2.clearErrorHandler();
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types';
|
||||
import { ParserState, ParserAction } from 'common/parser/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { utf32ToString } from 'common/input/TextDecoder';
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { Params } from 'common/parser/Params';
|
||||
|
||||
interface IHandlerCollection<T> {
|
||||
[key: string]: T[];
|
||||
}
|
||||
|
||||
type CsiHandler = (params: number[], collect: string) => boolean | void;
|
||||
type CsiHandler = (params: IParams, collect: string) => boolean | void;
|
||||
type OscHandler = (data: string) => boolean | void;
|
||||
|
||||
/**
|
||||
@@ -143,17 +144,14 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
||||
// csi entries
|
||||
table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
|
||||
table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
|
||||
table.addMany(r(0x30, 0x3a), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
|
||||
table.add(0x3b, ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
|
||||
table.addMany(r(0x30, 0x3c), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
|
||||
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM);
|
||||
table.addMany(r(0x30, 0x3a), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
|
||||
table.add(0x3b, ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
|
||||
table.addMany(r(0x30, 0x3c), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
|
||||
table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND);
|
||||
table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);
|
||||
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);
|
||||
table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
|
||||
table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
|
||||
table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND);
|
||||
table.add(0x3a, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_IGNORE);
|
||||
table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
|
||||
table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
|
||||
table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
|
||||
@@ -173,9 +171,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
||||
table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
|
||||
table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
|
||||
table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
|
||||
table.add(0x3a, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_IGNORE);
|
||||
table.addMany(r(0x30, 0x3a), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
|
||||
table.add(0x3b, ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
|
||||
table.addMany(r(0x30, 0x3c), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
|
||||
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);
|
||||
table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
|
||||
table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
|
||||
@@ -183,9 +179,8 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
||||
table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
|
||||
table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
|
||||
table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
|
||||
table.addMany(r(0x30, 0x3a), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
|
||||
table.add(0x3b, ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
|
||||
table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);
|
||||
table.addMany(r(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
|
||||
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);
|
||||
table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
|
||||
table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
|
||||
table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
|
||||
@@ -212,7 +207,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
||||
* Dummy DCS handler as default fallback.
|
||||
*/
|
||||
class DcsDummy implements IDcsHandler {
|
||||
hook(collect: string, params: number[], flag: number): void { }
|
||||
hook(collect: string, params: IParams, flag: number): void { }
|
||||
put(data: Uint32Array, start: number, end: number): void { }
|
||||
unhook(): void { }
|
||||
}
|
||||
@@ -221,12 +216,22 @@ class DcsDummy implements IDcsHandler {
|
||||
* EscapeSequenceParser.
|
||||
* This class implements the ANSI/DEC compatible parser described by
|
||||
* Paul Williams (https://vt100.net/emu/dec_ansi_parser).
|
||||
*
|
||||
* To implement custom ANSI compliant escape sequences it is not needed to
|
||||
* alter this parser, instead consider registering a custom handler.
|
||||
* For non ANSI compliant sequences change the transition table with
|
||||
* the optional `transitions` contructor argument and
|
||||
* reimplement the `parse` method.
|
||||
* NOTE: The parameter element notation is currently not supported.
|
||||
*
|
||||
* This parser is currently hardcoded to operate in ZDM (Zero Default Mode)
|
||||
* as suggested by the original parser, thus empty parameters are set to 0.
|
||||
* This this is not in line with the latest ECMA specification
|
||||
* (ZDM was part of the early specs and got completely removed later on).
|
||||
*
|
||||
* Other than the original parser from vt100.net this parser supports
|
||||
* sub parameters in digital parameters separated by colons. Empty sub parameters
|
||||
* are set to -1.
|
||||
*
|
||||
* TODO: implement error recovery hook via error handler return values
|
||||
*/
|
||||
export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
|
||||
@@ -236,7 +241,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
|
||||
// buffers over several parse calls
|
||||
protected _osc: string;
|
||||
protected _params: number[];
|
||||
protected _params: Params;
|
||||
protected _collect: string;
|
||||
|
||||
// handler lookup containers
|
||||
@@ -252,7 +257,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
// fallback handlers
|
||||
protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void;
|
||||
protected _executeHandlerFb: (code: number) => void;
|
||||
protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void;
|
||||
protected _csiHandlerFb: (collect: string, params: IParams, flag: number) => void;
|
||||
protected _escHandlerFb: (collect: string, flag: number) => void;
|
||||
protected _oscHandlerFb: (identifier: number, data: string) => void;
|
||||
protected _dcsHandlerFb: IDcsHandler;
|
||||
@@ -264,14 +269,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this.initialState = ParserState.GROUND;
|
||||
this.currentState = this.initialState;
|
||||
this._osc = '';
|
||||
this._params = [0];
|
||||
this._params = new Params(); // defaults to 32 storable params/subparams
|
||||
this._params.addParam(0); // ZDM
|
||||
this._collect = '';
|
||||
this.precedingCodepoint = 0;
|
||||
|
||||
// set default fallback handlers and handler lookup containers
|
||||
this._printHandlerFb = (data, start, end): void => { };
|
||||
this._executeHandlerFb = (code: number): void => { };
|
||||
this._csiHandlerFb = (collect: string, params: number[], flag: number): void => { };
|
||||
this._csiHandlerFb = (collect: string, params: IParams, flag: number): void => { };
|
||||
this._escHandlerFb = (collect: string, flag: number): void => { };
|
||||
this._oscHandlerFb = (identifier: number, data: string): void => { };
|
||||
this._dcsHandlerFb = new DcsDummy();
|
||||
@@ -329,13 +335,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
};
|
||||
}
|
||||
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
|
||||
setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): void {
|
||||
this._csiHandlers[flag.charCodeAt(0)] = [callback];
|
||||
}
|
||||
clearCsiHandler(flag: string): void {
|
||||
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
|
||||
}
|
||||
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void {
|
||||
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void {
|
||||
this._csiHandlerFb = callback;
|
||||
}
|
||||
|
||||
@@ -394,7 +400,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
reset(): void {
|
||||
this.currentState = this.initialState;
|
||||
this._osc = '';
|
||||
this._params = [0];
|
||||
this._params.reset();
|
||||
this._params.addParam(0); // ZDM
|
||||
this._collect = '';
|
||||
this._activeDcsHandler = null;
|
||||
this.precedingCodepoint = 0;
|
||||
@@ -402,10 +409,12 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
|
||||
/**
|
||||
* Parse UTF32 codepoints in `data` up to `length`.
|
||||
*
|
||||
* Note: For several actions with high data load the parsing is optimized
|
||||
* by using local read ahead loops with hardcoded conditions to
|
||||
* avoid costly table lookups. Make sure that any change of table values
|
||||
* will be reflected in the loop conditions as well. Affected states/actions:
|
||||
* will be reflected in the loop conditions as well and vice versa.
|
||||
* Affected states/actions:
|
||||
* - GROUND:PRINT
|
||||
* - CSI_PARAM:PARAM
|
||||
* - DCS_PARAM:PARAM
|
||||
@@ -418,7 +427,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
let currentState = this.currentState;
|
||||
let osc = this._osc;
|
||||
let collect = this._collect;
|
||||
let params = this._params;
|
||||
const params = this._params;
|
||||
const table: Uint8Array = this.TRANSITIONS.table;
|
||||
let dcsHandler: IDcsHandler | null = this._activeDcsHandler;
|
||||
let callback: Function | null = null;
|
||||
@@ -494,11 +503,23 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
case ParserAction.PARAM:
|
||||
// inner loop: digits (0x30 - 0x39) and ; (0x3b)
|
||||
// inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
|
||||
let isSub = false;
|
||||
do {
|
||||
if (code === 0x3b) params.push(0);
|
||||
else params[params.length - 1] = params[params.length - 1] * 10 + code - 48;
|
||||
} while (++i < length && (code = data[i]) > 0x2f && (code < 0x3a || code === 0x3b));
|
||||
switch (code) {
|
||||
case 0x3b:
|
||||
params.addParam(0); // ZDM
|
||||
isSub = false;
|
||||
break;
|
||||
case 0x3a:
|
||||
params.addSubParam(-1);
|
||||
isSub = true;
|
||||
break;
|
||||
default: // 0x30 - 0x39
|
||||
if (isSub) params.addSubParamDigit(code - 48);
|
||||
else params.addParamDigit(code - 48);
|
||||
}
|
||||
} while (++i < length && (code = data[i]) > 0x2f && code < 0x3c);
|
||||
i--;
|
||||
break;
|
||||
case ParserAction.COLLECT:
|
||||
@@ -512,7 +533,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
break;
|
||||
case ParserAction.CLEAR:
|
||||
osc = '';
|
||||
params = [0];
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
break;
|
||||
case ParserAction.DCS_HOOK:
|
||||
@@ -540,7 +562,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
osc = '';
|
||||
params = [0];
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
@@ -586,7 +609,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
osc = '';
|
||||
params = [0];
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { Params } from 'common/parser/Params';
|
||||
import { ParamsArray } from 'common/parser/Types';
|
||||
|
||||
class TestParams extends Params {
|
||||
public get subParams(): Int32Array {
|
||||
return this._subParams;
|
||||
}
|
||||
public get subParamsLength(): number {
|
||||
return this._subParamsLength;
|
||||
}
|
||||
}
|
||||
|
||||
/** `Params` parser shim */
|
||||
function parse(params: Params, s: string): void {
|
||||
params.reset();
|
||||
params.addParam(0);
|
||||
let isSub = false;
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
let code = s.charCodeAt(i);
|
||||
do {
|
||||
switch (code) {
|
||||
case 0x3b:
|
||||
params.addParam(0);
|
||||
isSub = false;
|
||||
break;
|
||||
case 0x3a:
|
||||
params.addSubParam(-1);
|
||||
isSub = true;
|
||||
break;
|
||||
default: // 0x30 - 0x39
|
||||
if (isSub) params.addSubParamDigit(code - 48);
|
||||
else params.addParamDigit(code - 48);
|
||||
}
|
||||
} while (++i < s.length && (code = s.charCodeAt(i)) > 0x2f && code < 0x3c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
describe('Params', () => {
|
||||
it('should respect ctor args', () => {
|
||||
const params = new TestParams(12, 23);
|
||||
assert.equal(params.params.length, 12);
|
||||
assert.equal(params.subParams.length, 23);
|
||||
assert.deepEqual(params.toArray(), []);
|
||||
});
|
||||
it('addParam', () => {
|
||||
const params = new TestParams();
|
||||
params.addParam(1);
|
||||
assert.equal(params.length, 1);
|
||||
assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1]);
|
||||
assert.deepEqual(params.toArray(), [1]);
|
||||
params.addParam(23);
|
||||
assert.equal(params.length, 2);
|
||||
assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1, 23]);
|
||||
assert.deepEqual(params.toArray(), [1, 23]);
|
||||
assert.equal(params.subParamsLength, 0);
|
||||
});
|
||||
it('addSubParam', () => {
|
||||
const params = new TestParams();
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
assert.equal(params.length, 1);
|
||||
assert.equal(params.subParamsLength, 2);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3]]);
|
||||
params.addParam(12345);
|
||||
params.addSubParam(-1);
|
||||
assert.equal(params.length, 2);
|
||||
assert.equal(params.subParamsLength, 3);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]);
|
||||
});
|
||||
it('should not add sub params without previous param', () => {
|
||||
const params = new TestParams();
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
assert.equal(params.length, 0);
|
||||
assert.equal(params.subParamsLength, 0);
|
||||
assert.deepEqual(params.toArray(), []);
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
assert.equal(params.length, 1);
|
||||
assert.equal(params.subParamsLength, 2);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3]]);
|
||||
});
|
||||
it('reset', () => {
|
||||
const params = new TestParams();
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
params.addParam(12345);
|
||||
params.addSubParam(-1);
|
||||
params.reset();
|
||||
assert.equal(params.length, 0);
|
||||
assert.equal(params.subParamsLength, 0);
|
||||
assert.deepEqual(params.toArray(), []);
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
params.addParam(12345);
|
||||
params.addSubParam(-1);
|
||||
assert.equal(params.length, 2);
|
||||
assert.equal(params.subParamsLength, 3);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]);
|
||||
});
|
||||
it('Params.fromArray --> toArray', () => {
|
||||
let data: ParamsArray = [];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [1, [2, 3], 12345, [-1]];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [38, 2, 50, 100, 150];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [38, 2, 50, 100, [150]];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [38, [2, 50, 100, 150]];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
// strip empty sub params
|
||||
data = [38, [2, 50, 100, 150], 5, [], 6];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), [38, [2, 50, 100, 150], 5, 6]);
|
||||
});
|
||||
it('clone', () => {
|
||||
const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6, 1, [2, 3], 12345, [-1]]);
|
||||
assert.deepEqual(params.clone(), params);
|
||||
});
|
||||
it('hasSubParams / getSubParams', () => {
|
||||
const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6]);
|
||||
assert.equal(params.hasSubParams(0), true);
|
||||
assert.deepEqual(params.getSubParams(0), new Int32Array([2, 50, 100, 150]));
|
||||
assert.equal(params.hasSubParams(1), false);
|
||||
assert.deepEqual(params.getSubParams(1), null);
|
||||
assert.equal(params.hasSubParams(2), false);
|
||||
assert.deepEqual(params.getSubParams(2), null);
|
||||
});
|
||||
it('getSubParamsAll', () => {
|
||||
const params = Params.fromArray([1, [2, 3], 7, 12345, [-1]]);
|
||||
assert.deepEqual(params.getSubParamsAll(), {0: new Int32Array([2, 3]), 2: new Int32Array([-1])});
|
||||
});
|
||||
describe('parse tests', () => {
|
||||
it('param defaults to 0 (ZDM - zero default mode)', () => {
|
||||
const params = new Params();
|
||||
parse(params, '');
|
||||
assert.deepEqual(params.toArray(), [0]);
|
||||
});
|
||||
it('sub param defaults to -1', () => {
|
||||
const params = new Params();
|
||||
parse(params, ':');
|
||||
assert.deepEqual(params.toArray(), [0, [-1]]);
|
||||
});
|
||||
it('should correctly reset on new sequence', () => {
|
||||
const params = new Params();
|
||||
parse(params, '1;2;3');
|
||||
assert.deepEqual(params.toArray(), [1, 2, 3]);
|
||||
parse(params, '4');
|
||||
assert.deepEqual(params.toArray(), [4]);
|
||||
parse(params, '4::123:5;6;7');
|
||||
assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]);
|
||||
parse(params, '');
|
||||
assert.deepEqual(params.toArray(), [0]);
|
||||
});
|
||||
it('should handle length restrictions correctly', () => {
|
||||
// restrict to 3 params and 3 sub params
|
||||
const params = new Params(3, 3);
|
||||
parse(params, '1;2;3');
|
||||
assert.deepEqual(params.toArray(), [1, 2, 3]);
|
||||
parse(params, '4');
|
||||
assert.deepEqual(params.toArray(), [4]);
|
||||
parse(params, '4::123:5;6;7');
|
||||
assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]);
|
||||
parse(params, '');
|
||||
assert.deepEqual(params.toArray(), [0]);
|
||||
// overlong params
|
||||
parse(params, '1;2;3;4;5;6;7');
|
||||
assert.deepEqual(params.toArray(), [1, 2, 3]);
|
||||
// overlong sub params
|
||||
parse(params, '4;38:2::50:100:150;48:5:22');
|
||||
assert.deepEqual(params.toArray(), [4, 38, [2, -1, 50], 48]);
|
||||
});
|
||||
it('typical sequences', () => {
|
||||
const params = new Params();
|
||||
// SGR with semicolon syntax
|
||||
parse(params, '0;4;38;2;50;100;150;48;5;22');
|
||||
assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, 100, 150, 48, 5, 22]);
|
||||
// SGR mixed style (partly wrong)
|
||||
parse(params, '0;4;38;2;50:100:150;48;5:22');
|
||||
assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, [100, 150], 48, 5, [22]]);
|
||||
// SGR colon style
|
||||
parse(params, '0;4;38:2::50:100:150;48:5:22');
|
||||
assert.deepEqual(params.toArray(), [0, 4, 38, [2, -1, 50, 100, 150], 48, [5, 22]]);
|
||||
});
|
||||
});
|
||||
describe('should not overflow to negative', () => {
|
||||
it('reject params lesser -1', () => {
|
||||
const params = new Params();
|
||||
params.addParam(-1);
|
||||
assert.throws(() => params.addParam(-2), 'values lesser than -1 are not allowed');
|
||||
});
|
||||
it('reject subparams lesser -1', () => {
|
||||
const params = new Params();
|
||||
params.addParam(-1);
|
||||
params.addSubParam(-1);
|
||||
assert.throws(() => params.addSubParam(-2), 'values lesser than -1 are not allowed');
|
||||
assert.deepEqual(params.toArray(), [-1, [-1]]);
|
||||
});
|
||||
it('clamp parsed params', () => {
|
||||
const params = new Params();
|
||||
parse(params, '2147483648');
|
||||
assert.deepEqual(params.toArray(), [0x7FFFFFFF]);
|
||||
});
|
||||
it('clamp parsed subparams', () => {
|
||||
const params = new Params();
|
||||
parse(params, ':2147483648');
|
||||
assert.deepEqual(params.toArray(), [0, [0x7FFFFFFF]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { IParams, ParamsArray } from 'common/parser/Types';
|
||||
|
||||
// max value supported for a single param/subparam (clamped to positive int32 range)
|
||||
const MAX_VALUE = 0x7FFFFFFF;
|
||||
// max allowed subparams for a single sequence (hardcoded limitation)
|
||||
const MAX_SUBPARAMS = 256;
|
||||
|
||||
/**
|
||||
* Params storage class.
|
||||
* This type is used by the parser to accumulate sequence parameters and sub parameters
|
||||
* and transmit them to the input handler actions.
|
||||
*
|
||||
* NOTES:
|
||||
* - params object for action handlers is borrowed, use `.toArray` or `.clone` to get a copy
|
||||
* - never read beyond `params.length - 1` (likely to contain arbitrary data)
|
||||
* - `.getSubParams` returns a borrowed typed array, use `.getSubParamsAll` for cloned sub params
|
||||
* - hardcoded limitations:
|
||||
* - max. value for a single (sub) param is 2^31 - 1 (greater values are clamped to that)
|
||||
* - max. 256 sub params possible
|
||||
* - negative values are not allowed beside -1 (placeholder for default value)
|
||||
*
|
||||
* About ZDM (Zero Default Mode):
|
||||
* ZDM is not orchestrated by this class. If the parser is in ZDM,
|
||||
* it should add 0 for empty params, otherwise -1. This does not apply
|
||||
* to subparams, empty subparams should always be added with -1.
|
||||
*/
|
||||
export class Params implements IParams {
|
||||
// params store and length
|
||||
public params: Int32Array;
|
||||
public length: number;
|
||||
|
||||
// sub params store and length
|
||||
protected _subParams: Int32Array;
|
||||
protected _subParamsLength: number;
|
||||
|
||||
// sub params offsets from param: param idx --> [start, end] offset
|
||||
private _subParamsIdx: Uint16Array;
|
||||
private _rejectDigits: boolean;
|
||||
private _rejectSubDigits: boolean;
|
||||
|
||||
/**
|
||||
* Create a `Params` type from JS array representation.
|
||||
*/
|
||||
public static fromArray(values: ParamsArray): Params {
|
||||
const params = new Params();
|
||||
if (!values.length) {
|
||||
return params;
|
||||
}
|
||||
// skip leading sub params
|
||||
for (let i = (values[0] instanceof Array) ? 1 : 0; i < values.length; ++i) {
|
||||
const value = values[i];
|
||||
if (value instanceof Array) {
|
||||
for (let k = 0; k < value.length; ++k) {
|
||||
params.addSubParam(value[k]);
|
||||
}
|
||||
} else {
|
||||
params.addParam(value);
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxLength max length of storable parameters
|
||||
* @param maxSubParamsLength max length of storable sub parameters
|
||||
*/
|
||||
constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) {
|
||||
if (maxSubParamsLength > MAX_SUBPARAMS) {
|
||||
throw new Error('maxSubParamsLength must not be greater than 256');
|
||||
}
|
||||
this.params = new Int32Array(maxLength);
|
||||
this.length = 0;
|
||||
this._subParams = new Int32Array(maxSubParamsLength);
|
||||
this._subParamsLength = 0;
|
||||
this._subParamsIdx = new Uint16Array(maxLength);
|
||||
this._rejectDigits = false;
|
||||
this._rejectSubDigits = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone object.
|
||||
*/
|
||||
public clone(): Params {
|
||||
const newParams = new Params(this.maxLength, this.maxSubParamsLength);
|
||||
newParams.params.set(this.params);
|
||||
newParams.length = this.length;
|
||||
newParams._subParams.set(this._subParams);
|
||||
newParams._subParamsLength = this._subParamsLength;
|
||||
newParams._subParamsIdx.set(this._subParamsIdx);
|
||||
return newParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JS array representation of the current parameters and sub parameters.
|
||||
* The array is structured as follows:
|
||||
* sequence: "1;2:3:4;5::6"
|
||||
* array : [1, 2, [3, 4], 5, [-1, 6]]
|
||||
*/
|
||||
public toArray(): ParamsArray {
|
||||
const res: ParamsArray = [];
|
||||
for (let i = 0; i < this.length; ++i) {
|
||||
res.push(this.params[i]);
|
||||
const start = this._subParamsIdx[i] >> 8;
|
||||
const end = this._subParamsIdx[i] & 0xFF;
|
||||
if (end - start > 0) {
|
||||
res.push(Array.prototype.slice.call(this._subParams, start, end));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to initial empty state.
|
||||
*/
|
||||
public reset(): void {
|
||||
this.length = 0;
|
||||
this._subParamsLength = 0;
|
||||
this._rejectDigits = false;
|
||||
this._rejectSubDigits = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a parameter value.
|
||||
* `Params` only stores up to `maxLength` parameters, any later
|
||||
* parameter will be ignored.
|
||||
* Note: VT devices only stored up to 16 values, xterm seems to
|
||||
* store up to 30.
|
||||
*/
|
||||
public addParam(value: number): void {
|
||||
if (this.length >= this.maxLength) {
|
||||
this._rejectDigits = true;
|
||||
return;
|
||||
}
|
||||
if (value < -1) {
|
||||
throw new Error('values lesser than -1 are not allowed');
|
||||
}
|
||||
this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength;
|
||||
this.params[this.length++] = value > MAX_VALUE ? MAX_VALUE : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sub parameter value.
|
||||
* The sub parameter is automatically associated with the last parameter value.
|
||||
* Thus it is not possible to add a subparameter without any parameter added yet.
|
||||
* `Params` only stores up to `subParamsLength` sub parameters, any later
|
||||
* sub parameter will be ignored.
|
||||
*/
|
||||
public addSubParam(value: number): void {
|
||||
if (!this.length) {
|
||||
return;
|
||||
}
|
||||
if (this._subParamsLength >= this.maxSubParamsLength) {
|
||||
this._rejectSubDigits = true;
|
||||
return;
|
||||
}
|
||||
if (value < -1) {
|
||||
throw new Error('values lesser than -1 are not allowed');
|
||||
}
|
||||
this._subParams[this._subParamsLength++] = value > MAX_VALUE ? MAX_VALUE : value;
|
||||
this._subParamsIdx[this.length - 1]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether parameter at index `idx` has sub parameters.
|
||||
*/
|
||||
public hasSubParams(idx: number): boolean {
|
||||
return ((this._subParamsIdx[idx] & 0xFF) - (this._subParamsIdx[idx] >> 8) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sub parameters for parameter at index `idx`.
|
||||
* Note: The values are borrowed, thus you need to copy
|
||||
* the values if you need to hold them in nonlocal scope.
|
||||
*/
|
||||
public getSubParams(idx: number): Int32Array | null {
|
||||
const start = this._subParamsIdx[idx] >> 8;
|
||||
const end = this._subParamsIdx[idx] & 0xFF;
|
||||
if (end - start > 0) {
|
||||
return this._subParams.subarray(start, end);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all sub parameters as {idx: subparams} mapping.
|
||||
* Note: The values are not borrowed.
|
||||
*/
|
||||
public getSubParamsAll(): {[idx: number]: Int32Array} {
|
||||
const result: {[idx: number]: Int32Array} = {};
|
||||
for (let i = 0; i < this.length; ++i) {
|
||||
const start = this._subParamsIdx[i] >> 8;
|
||||
const end = this._subParamsIdx[i] & 0xFF;
|
||||
if (end - start > 0) {
|
||||
result[i] = this._subParams.slice(start, end);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single digit value to current parameter.
|
||||
* This is used by the parser to account digits on a char by char basis.
|
||||
* Do not use this method directly, consider using `addParam` instead.
|
||||
*/
|
||||
public addParamDigit(value: number): void {
|
||||
if (this._rejectDigits) {
|
||||
return;
|
||||
}
|
||||
const v = this.params[this.length - 1] * 10 + value;
|
||||
this.params[this.length - 1] = v > MAX_VALUE ? MAX_VALUE : v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single digit value to current sub parameter.
|
||||
* This is used by the parser to account digits on a char by char basis.
|
||||
* Do not use this method directly, consider using `addSubParam` instead.
|
||||
*/
|
||||
public addSubParamDigit(value: number): void {
|
||||
if (!this._subParamsLength || this._rejectDigits || this._rejectSubDigits) {
|
||||
return;
|
||||
}
|
||||
if (this._subParams[this._subParamsLength - 1] === -1) {
|
||||
this._subParams[this._subParamsLength - 1] = value;
|
||||
} else {
|
||||
const v = this._subParams[this._subParamsLength - 1] * 10 + value;
|
||||
this._subParams[this._subParamsLength - 1] = v > MAX_VALUE ? MAX_VALUE : v;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+39
-6
@@ -6,6 +6,38 @@
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { ParserState } from 'common/parser/Constants';
|
||||
|
||||
/** sequence params serialized to js arrays */
|
||||
export type ParamsArray = (number | number[])[];
|
||||
|
||||
/** Params constructor type. */
|
||||
export interface IParamsConstructor {
|
||||
new(maxLength: number, maxSubParamsLength: number): IParams;
|
||||
|
||||
/** create params from ParamsArray */
|
||||
fromArray(values: ParamsArray): IParams;
|
||||
}
|
||||
|
||||
/** Interface of Params storage class. */
|
||||
export interface IParams {
|
||||
/** from ctor */
|
||||
maxLength: number;
|
||||
maxSubParamsLength: number;
|
||||
|
||||
/** param values and its length */
|
||||
params: Int32Array;
|
||||
length: number;
|
||||
|
||||
/** methods */
|
||||
clone(): IParams;
|
||||
toArray(): ParamsArray;
|
||||
reset(): void;
|
||||
addParam(value: number): void;
|
||||
addSubParam(value: number): void;
|
||||
hasSubParams(idx: number): boolean;
|
||||
getSubParams(idx: number): Int32Array | null;
|
||||
getSubParamsAll(): {[idx: number]: Int32Array};
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal state of EscapeSequenceParser.
|
||||
* Used as argument of the error handler to allow
|
||||
@@ -26,7 +58,7 @@ export interface IParsingState {
|
||||
// collect buffer with intermediate characters
|
||||
collect: string;
|
||||
// params buffer
|
||||
params: number[];
|
||||
params: IParams;
|
||||
// should abort (default: false)
|
||||
abort: boolean;
|
||||
}
|
||||
@@ -53,7 +85,7 @@ export interface IParsingState {
|
||||
* `unhook` marks the end of the current DCS sequence.
|
||||
*/
|
||||
export interface IDcsHandler {
|
||||
hook(collect: string, params: number[], flag: number): void;
|
||||
hook(collect: string, params: IParams, flag: number): void;
|
||||
put(data: Uint32Array, start: number, end: number): void;
|
||||
unhook(): void;
|
||||
}
|
||||
@@ -68,13 +100,14 @@ export interface IEscapeSequenceParser extends IDisposable {
|
||||
* It gets reset by the parser for any valid sequence beside REP itself.
|
||||
*/
|
||||
precedingCodepoint: number;
|
||||
|
||||
/**
|
||||
* Reset the parser to its initial state (handlers are kept).
|
||||
*/
|
||||
reset(): void;
|
||||
|
||||
/**
|
||||
* Parse string `data`.
|
||||
* Parse UTF32 codepoints in `data` up to `length`.
|
||||
* @param data The data to parse.
|
||||
*/
|
||||
parse(data: Uint32Array, length: number): void;
|
||||
@@ -86,10 +119,10 @@ export interface IEscapeSequenceParser extends IDisposable {
|
||||
clearExecuteHandler(flag: string): void;
|
||||
setExecuteHandlerFallback(callback: (code: number) => void): void;
|
||||
|
||||
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
|
||||
setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): void;
|
||||
clearCsiHandler(flag: string): void;
|
||||
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
|
||||
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void;
|
||||
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
|
||||
setEscHandler(collectAndFlag: string, callback: () => void): void;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Terminal as TerminalCore } from '../Terminal';
|
||||
import * as Strings from '../browser/LocalizableStrings';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { AddonManager } from './AddonManager';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
|
||||
export class Terminal implements ITerminalApi {
|
||||
private _core: ITerminal;
|
||||
@@ -56,8 +57,8 @@ export class Terminal implements ITerminalApi {
|
||||
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
|
||||
}
|
||||
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
||||
return this._core.addCsiHandler(flag, callback);
|
||||
public addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable {
|
||||
return this._core.addCsiHandler(flag, (params: IParams, collect: string) => callback(params.toArray(), collect));
|
||||
}
|
||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
return this._core.addOscHandler(ident, callback);
|
||||
|
||||
@@ -309,6 +309,23 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addCsiHandler', () => {
|
||||
it('should call custom CSI handler with js array params', async () => {
|
||||
await page.evaluate(`
|
||||
window.term.reset();
|
||||
const _customCsiHandlerParams = [];
|
||||
const _customCsiHandler = window.term.addCsiHandler('m', (params, collect) => {
|
||||
_customCsiHandlerParams.push(params);
|
||||
return false;
|
||||
}, '');
|
||||
`);
|
||||
await page.evaluate(`
|
||||
window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams');
|
||||
`);
|
||||
assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-be
|
||||
|
||||
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
||||
import { C0, C1 } from 'common/data/EscapeSequences';
|
||||
import { IDcsHandler } from 'common/parser/Types';
|
||||
import { IDcsHandler, IParams } from 'common/parser/Types';
|
||||
|
||||
|
||||
function toUtf32(s: string): Uint32Array {
|
||||
@@ -18,7 +18,7 @@ function toUtf32(s: string): Uint32Array {
|
||||
}
|
||||
|
||||
class DcsHandler implements IDcsHandler {
|
||||
hook(collect: string, params: number[], flag: number) : void {}
|
||||
hook(collect: string, params: IParams, flag: number) : void {}
|
||||
put(data: Uint32Array, start: number, end: number) : void {}
|
||||
unhook() :void {}
|
||||
}
|
||||
|
||||
Vendored
+4
-2
@@ -489,12 +489,14 @@ declare module 'xterm' {
|
||||
* final character (e.g "m" for SGR) of the CSI sequence.
|
||||
* @param callback The function to handle the escape sequence. The callback
|
||||
* is called with the numerical params, as well as the special characters
|
||||
* (e.g. "$" for DECSCPP). Return true if the sequence was handled; false if
|
||||
* (e.g. "$" for DECSCPP). If the sequence has subparams the array will
|
||||
* contain subarrays with their numercial values.
|
||||
* Return true if the sequence was handled; false if
|
||||
* we should try a previous handler (set by addCsiHandler or setCsiHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
|
||||
addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a handler for OSC escape sequences.
|
||||
|
||||
Reference in New Issue
Block a user