extend AttributeData with eAttrs, implement SGR 4 subparams, tests

This commit is contained in:
Jörg Breitbart
2020-03-08 18:57:05 +01:00
parent ae135ac181
commit fd5f5e3204
8 changed files with 540 additions and 152 deletions
+42 -5
View File
@@ -497,7 +497,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char
if (buffer.x && end - start > 0 && bufferRow.getWidth(buffer.x - 1) === 2) {
bufferRow.setCellFromCodePoint(buffer.x - 1, 0, 1, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
for (let pos = start; pos < end; ++pos) {
@@ -577,12 +577,12 @@ export class InputHandler extends Disposable implements IInputHandler {
// a halfwidth char any fullwidth shifted there is lost
// and will be set to empty cell
if (bufferRow.getWidth(cols - 1) === 2) {
bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg, curAttr.extended);
}
}
// write current char to buffer and advance cursor
bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended);
// fullwidth char - also set next cell to placeholder stub and advance cursor
// for graphemes bigger than fullwidth we can simply loop to zero
@@ -590,7 +590,7 @@ export class InputHandler extends Disposable implements IInputHandler {
if (chWidth > 0) {
while (--chWidth) {
// other than a regular empty cell a cell following a wide char has no width
bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
}
}
}
@@ -611,7 +611,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// handle wide chars: reset cell to the right if it is second cell of a wide char
if (buffer.x < cols && end - start > 0 && bufferRow.getWidth(buffer.x) === 0 && !bufferRow.hasContent(buffer.x)) {
bufferRow.setCellFromCodePoint(buffer.x, 0, 1, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
this._dirtyRowService.markDirty(buffer.y);
@@ -2098,6 +2098,40 @@ export class InputHandler extends Disposable implements IInputHandler {
return advance;
}
/**
* SGR 4 subparams:
* 4:0 - equal to SGR 24 (turn off all underline)
* 4:1 - equal to SGR 4 (single underline)
* 4:2 - equal to SGR 21 (double underline)
* 4:3 - curly underline
* 4:4 - dotted underline
* 4:5 - dashed underline
*/
private _processUnderline(subparams: Int32Array, attr: IAttributeData): void {
// treat extended attrs as immutable, thus always clone from old one
// this is needed since the buffer only holds references to it
attr.extended = attr.extended.clone();
let style = subparams[0];
// default to 1 == single underline
if (!~style || style > 5) {
style = 1;
}
attr.extended.underlineStyle = style;
// 0 deactivates underline
if (style === 0) {
attr.fg &= ~FgFlags.UNDERLINE;
}
// update HAS_EXTENDED in BG
if (attr.extended.isEmpty()) {
attr.bg &= ~BgFlags.HAS_EXTENDED;
} else {
attr.bg |= BgFlags.HAS_EXTENDED;
}
}
/**
* CSI Pm m Character Attributes (SGR).
*
@@ -2210,6 +2244,9 @@ export class InputHandler extends Disposable implements IInputHandler {
} else if (p === 4) {
// underlined text
attr.fg |= FgFlags.UNDERLINE;
if (params.hasSubParams(i)) {
this._processUnderline(params.getSubParams(i), attr);
}
} else if (p === 5) {
// blink
attr.fg |= FgFlags.BLINK;
+14 -1
View File
@@ -68,10 +68,18 @@ export interface ICharset {
export type CharData = [number, string, number, number];
export type IColorRGB = [number, number, number];
export interface IExtendedAttrs {
underlineStyle: number;
underlineColor: number;
clone(): IExtendedAttrs;
isEmpty(): boolean;
}
/** Attribute data */
export interface IAttributeData {
fg: number;
bg: number;
extended: IExtendedAttrs;
clone(): IAttributeData;
@@ -98,6 +106,11 @@ export interface IAttributeData {
// colors
getFgColor(): number;
getBgColor(): number;
// extended attrs
hasExtendedAttrs(): number;
getUnderlineColor(): number;
getUnderlineStyle(): number;
}
/** Cell data */
@@ -122,7 +135,7 @@ export interface IBufferLine {
set(index: number, value: CharData): void;
loadCell(index: number, cell: ICellData): ICellData;
setCell(index: number, cell: ICellData): void;
setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void;
setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void;
addCodepointToCell(index: number, codePoint: number): void;
insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void;
deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void;
+49 -4
View File
@@ -3,8 +3,8 @@
* @license MIT
*/
import { IAttributeData, IColorRGB } from 'common/Types';
import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
import { IAttributeData, IColorRGB, IExtendedAttrs } from 'common/Types';
import { Attributes, FgFlags, BgFlags, UnderlineStyle } from 'common/buffer/Constants';
export class AttributeData implements IAttributeData {
static toColorRGB(value: number): IColorRGB {
@@ -22,12 +22,14 @@ export class AttributeData implements IAttributeData {
const newObj = new AttributeData();
newObj.fg = this.fg;
newObj.bg = this.bg;
newObj.extended = this.extended.clone();
return newObj;
}
// data
public fg: number = 0;
public bg: number = 0;
public fg = 0;
public bg = 0;
public extended = new ExtendedAttrs();
// flags
public isInverse(): number { return this.fg & FgFlags.INVERSE; }
@@ -66,4 +68,47 @@ export class AttributeData implements IAttributeData {
default: return -1; // CM_DEFAULT defaults to -1
}
}
// extended attrs
public hasExtendedAttrs(): number {
return this.bg & BgFlags.HAS_EXTENDED;
}
public getUnderlineColor(): number {
if ((this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor) {
return this.extended.underlineColor;
}
return this.getFgColor();
}
public getUnderlineStyle(): UnderlineStyle {
return this.fg & FgFlags.UNDERLINE
? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)
: UnderlineStyle.NONE;
}
}
/**
* Extended attributes for a cell.
* Holds information about different underline styles and color.
*/
export class ExtendedAttrs implements IExtendedAttrs {
constructor(
// underline style, NONE is empty
public underlineStyle: UnderlineStyle = UnderlineStyle.NONE,
// underline color, -1 is empty (same as FG)
public underlineColor: number = -1
) {}
public clone(): IExtendedAttrs {
return new ExtendedAttrs(this.underlineStyle, this.underlineColor);
}
/**
* Convenient method to indicate whether the object holds no additional information
* and can be removed from the global attr object.
*/
public isEmpty(): boolean {
// needs to test for every single attribute stored
return !(this.underlineStyle !== UnderlineStyle.NONE || ~this.underlineColor);
}
}
+5
View File
@@ -13,6 +13,7 @@ import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGe
import { Marker } from 'common/buffer/Marker';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { DEFAULT_CHARSET } from 'common/data/Charsets';
import { ExtendedAttrs } from 'common/buffer/AttributeData';
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
@@ -60,9 +61,11 @@ export class Buffer implements IBuffer {
if (attr) {
this._nullCell.fg = attr.fg;
this._nullCell.bg = attr.bg;
this._nullCell.extended = attr.extended;
} else {
this._nullCell.fg = 0;
this._nullCell.bg = 0;
this._nullCell.extended = new ExtendedAttrs();
}
return this._nullCell;
}
@@ -71,9 +74,11 @@ export class Buffer implements IBuffer {
if (attr) {
this._whitespaceCell.fg = attr.fg;
this._whitespaceCell.bg = attr.bg;
this._whitespaceCell.extended = attr.extended;
} else {
this._whitespaceCell.fg = 0;
this._whitespaceCell.bg = 0;
this._whitespaceCell.extended = new ExtendedAttrs();
}
return this._whitespaceCell;
}
+381 -122
View File
@@ -2,11 +2,13 @@
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as chai from 'chai';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content } from 'common/buffer/Constants';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content, UnderlineStyle, BgFlags, Attributes, FgFlags } from 'common/buffer/Constants';
import { BufferLine } from 'common/buffer//BufferLine';
import { CellData } from 'common/buffer/CellData';
import { CharData, IBufferLine } from '../Types';
import { assert, expect } from 'chai';
import { AttributeData } from 'common/buffer/AttributeData';
class TestBufferLine extends BufferLine {
public get combined(): {[index: number]: string} {
@@ -22,49 +24,98 @@ class TestBufferLine extends BufferLine {
}
}
describe('AttributeData', () => {
describe('extended attributes', () => {
it('hasExtendedAttrs', () => {
const attrs = new AttributeData();
assert.equal(!!attrs.hasExtendedAttrs(), false);
attrs.bg |= BgFlags.HAS_EXTENDED;
assert.equal(!!attrs.hasExtendedAttrs(), true);
});
it('getUnderlineColor', () => {
const attrs = new AttributeData();
attrs.extended.underlineColor = (1 << 16) | (2 << 8) | 3;
// should use FG color if BgFlags.HAS_EXTENDED is not set
assert.equal(attrs.getUnderlineColor(), -1);
// should use underlineColor if BgFlags.HAS_EXTENDED is set and underlineColor holds a value
attrs.bg |= BgFlags.HAS_EXTENDED;
assert.equal(attrs.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);
// should use FG color if underlineColor holds no value
attrs.extended.underlineColor = -1;
attrs.fg |= Attributes.CM_P256 | 123;
assert.equal(attrs.getUnderlineColor(), 123);
});
it('getUnderlineStyle', () => {
const attrs = new AttributeData();
// defaults to no underline style
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);
// should return NONE if UNDERLINE is not set
attrs.extended.underlineStyle = UnderlineStyle.CURLY;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);
// should return SINGLE style if UNDERLINE is set and HAS_EXTENDED is false
attrs.fg |= FgFlags.UNDERLINE;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.SINGLE);
// should return correct style if both is set
attrs.bg |= BgFlags.HAS_EXTENDED;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.CURLY);
// should return NONE if UNDERLINE is not set, but HAS_EXTENDED is true
attrs.fg &= ~FgFlags.UNDERLINE;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);
});
});
});
describe('CellData', () => {
it('CharData <--> CellData equality', () => {
const cell = new CellData();
// ASCII
cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), 0);
assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]);
assert.equal(cell.isCombined(), 0);
// combining
cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
// surrogate
cell.setFromCharData([123, '𝄞', 1, 0x1D11E]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]);
chai.assert.equal(cell.isCombined(), 0);
assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]);
assert.equal(cell.isCombined(), 0);
// surrogate + combining
cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
// wide char
cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), 0);
assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]);
assert.equal(cell.isCombined(), 0);
});
});
describe('BufferLine', function(): void {
it('ctor', function(): void {
let line: IBufferLine = new TestBufferLine(0);
chai.expect(line.length).equals(0);
chai.expect(line.isWrapped).equals(false);
expect(line.length).equals(0);
expect(line.isWrapped).equals(false);
line = new TestBufferLine(10);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(false);
expect(line.length).equals(10);
expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
expect(line.isWrapped).equals(false);
line = new TestBufferLine(10, undefined, true);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(true);
expect(line.length).equals(10);
expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
expect(line.isWrapped).equals(true);
line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
chai.expect(line.isWrapped).equals(true);
expect(line.length).equals(10);
expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
expect(line.isWrapped).equals(true);
});
it('insertCells', function(): void {
const line = new TestBufferLine(3);
@@ -72,7 +123,7 @@ describe('BufferLine', function(): void {
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)]
@@ -86,7 +137,7 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[5, 'e', 0, 'e'.charCodeAt(0)],
@@ -102,7 +153,7 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[2, 'b', 0, 'b'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
@@ -118,7 +169,7 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
@@ -134,9 +185,9 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
const line2 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
expect(line2.length).equals(line.length);
expect(line2.isWrapped).equals(line.isWrapped);
});
it('copyFrom', function(): void {
const line = new TestBufferLine(5);
@@ -147,92 +198,92 @@ describe('BufferLine', function(): void {
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
expect(line2.toArray()).eql(line.toArray());
expect(line2.length).equals(line.length);
expect(line2.isWrapped).equals(line.isWrapped);
});
it('should support combining chars', function(): void {
// CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print
// --> set code to the last charCodeAt value of the string
// Note: needs to be fixed once the string pointer is in place
const line = new TestBufferLine(2, CellData.fromCharData([1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, '\u0301'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
expect(line2.toArray()).eql(line.toArray());
const line3 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray());
expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray());
});
describe('resize', function(): void {
it('enlarge(false)', function(): void {
const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('enlarge(true)', function(): void {
const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(true) - should apply new size', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink to 0 length', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('should remove combining data on replaced cells after shrinking then enlarging', () => {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.set(2, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);
line.set(9, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);
chai.expect(line.translateToString()).eql('aa😁aaaaaa😁');
chai.expect(Object.keys(line.combined).length).eql(2);
expect(line.translateToString()).eql('aa😁aaaaaa😁');
expect(Object.keys(line.combined).length).eql(2);
line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aa');
expect(line.translateToString()).eql('aa😁aa');
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aaaaaaa');
chai.expect(Object.keys(line.combined).length).eql(1);
expect(line.translateToString()).eql('aa😁aaaaaaa');
expect(Object.keys(line.combined).length).eql(1);
});
});
describe('getTrimLength', function(): void {
it('empty line', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.getTrimmedLength()).equal(0);
expect(line.getTrimmedLength()).equal(0);
});
it('ASCII', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
expect(line.getTrimmedLength()).equal(3);
});
it('surrogate', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
expect(line.getTrimmedLength()).equal(3);
});
it('combining', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
expect(line.getTrimmedLength()).equal(3);
});
it('fullwidth', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([0, '', 0, 0]));
chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth
expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth
});
});
describe('translateToString with and w\'o trimming', function(): void {
it('empty line', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
expect(line.translateToString(false)).equal(' ');
expect(line.translateToString(true)).equal('');
});
it('ASCII', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -240,14 +291,14 @@ describe('BufferLine', function(): void {
line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa');
chai.expect(line.translateToString(false, 0, 5)).equal('a a a');
chai.expect(line.translateToString(false, 0, 4)).equal('a a ');
chai.expect(line.translateToString(false, 0, 3)).equal('a a');
chai.expect(line.translateToString(true, 0, 5)).equal('a a a');
chai.expect(line.translateToString(true, 0, 4)).equal('a a ');
chai.expect(line.translateToString(true, 0, 3)).equal('a a');
expect(line.translateToString(false)).equal('a a aa ');
expect(line.translateToString(true)).equal('a a aa');
expect(line.translateToString(false, 0, 5)).equal('a a a');
expect(line.translateToString(false, 0, 4)).equal('a a ');
expect(line.translateToString(false, 0, 3)).equal('a a');
expect(line.translateToString(true, 0, 5)).equal('a a a');
expect(line.translateToString(true, 0, 4)).equal('a a ');
expect(line.translateToString(true, 0, 3)).equal('a a');
});
it('surrogate', function(): void {
@@ -256,14 +307,14 @@ describe('BufferLine', function(): void {
line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 ');
chai.expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞');
chai.expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞');
chai.expect(line.translateToString(false, 0, 4)).equal('a 𝄞 ');
chai.expect(line.translateToString(false, 0, 3)).equal('a 𝄞');
chai.expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞');
chai.expect(line.translateToString(true, 0, 4)).equal('a 𝄞 ');
chai.expect(line.translateToString(true, 0, 3)).equal('a 𝄞');
expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 ');
expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞');
expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞');
expect(line.translateToString(false, 0, 4)).equal('a 𝄞 ');
expect(line.translateToString(false, 0, 3)).equal('a 𝄞');
expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞');
expect(line.translateToString(true, 0, 4)).equal('a 𝄞 ');
expect(line.translateToString(true, 0, 3)).equal('a 𝄞');
});
it('combining', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -271,14 +322,14 @@ describe('BufferLine', function(): void {
line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 ');
chai.expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301');
chai.expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301');
chai.expect(line.translateToString(false, 0, 4)).equal('a e\u0301 ');
chai.expect(line.translateToString(false, 0, 3)).equal('a e\u0301');
chai.expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301');
chai.expect(line.translateToString(true, 0, 4)).equal('a e\u0301 ');
chai.expect(line.translateToString(true, 0, 3)).equal('a e\u0301');
expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 ');
expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301');
expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301');
expect(line.translateToString(false, 0, 4)).equal('a e\u0301 ');
expect(line.translateToString(false, 0, 3)).equal('a e\u0301');
expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301');
expect(line.translateToString(true, 0, 4)).equal('a e\u0301 ');
expect(line.translateToString(true, 0, 3)).equal('a e\u0301');
});
it('fullwidth', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -289,20 +340,20 @@ describe('BufferLine', function(): void {
line.setCell(6, CellData.fromCharData([0, '', 0, 0]));
line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(8, CellData.fromCharData([0, '', 0, 0]));
chai.expect(line.translateToString(false)).equal('a 1 11 ');
chai.expect(line.translateToString(true)).equal('a 1 11');
chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1');
chai.expect(line.translateToString(false, 0, 6)).equal('a 1 1');
chai.expect(line.translateToString(false, 0, 5)).equal('a 1 ');
chai.expect(line.translateToString(false, 0, 4)).equal('a 1');
chai.expect(line.translateToString(false, 0, 3)).equal('a 1');
chai.expect(line.translateToString(false, 0, 2)).equal('a ');
chai.expect(line.translateToString(true, 0, 7)).equal('a 1 1');
chai.expect(line.translateToString(true, 0, 6)).equal('a 1 1');
chai.expect(line.translateToString(true, 0, 5)).equal('a 1 ');
chai.expect(line.translateToString(true, 0, 4)).equal('a 1');
chai.expect(line.translateToString(true, 0, 3)).equal('a 1');
chai.expect(line.translateToString(true, 0, 2)).equal('a ');
expect(line.translateToString(false)).equal('a 1 11 ');
expect(line.translateToString(true)).equal('a 1 11');
expect(line.translateToString(false, 0, 7)).equal('a 1 1');
expect(line.translateToString(false, 0, 6)).equal('a 1 1');
expect(line.translateToString(false, 0, 5)).equal('a 1 ');
expect(line.translateToString(false, 0, 4)).equal('a 1');
expect(line.translateToString(false, 0, 3)).equal('a 1');
expect(line.translateToString(false, 0, 2)).equal('a ');
expect(line.translateToString(true, 0, 7)).equal('a 1 1');
expect(line.translateToString(true, 0, 6)).equal('a 1 1');
expect(line.translateToString(true, 0, 5)).equal('a 1 ');
expect(line.translateToString(true, 0, 4)).equal('a 1');
expect(line.translateToString(true, 0, 3)).equal('a 1');
expect(line.translateToString(true, 0, 2)).equal('a ');
});
it('space at end', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -311,21 +362,21 @@ describe('BufferLine', function(): void {
line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(6, CellData.fromCharData([1, ' ', 1, ' '.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa ');
expect(line.translateToString(false)).equal('a a aa ');
expect(line.translateToString(true)).equal('a a aa ');
});
it('should always return some sane value', function(): void {
// sanity check - broken line with invalid out of bound null width cells
// this can atm happen with deleting/inserting chars in inputhandler by "breaking"
// fullwidth pairs --> needs to be fixed after settling BufferLine impl
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
expect(line.translateToString(false)).equal(' ');
expect(line.translateToString(true)).equal('');
});
it('should work with endCol=0', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(true, 0, 0)).equal('');
expect(line.translateToString(true, 0, 0)).equal('');
});
});
describe('addCharToCell', () => {
@@ -335,9 +386,9 @@ describe('BufferLine', function(): void {
const cell = line.loadCell(0, new CellData());
// chars contains single combining char
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\u0301', 1, 0x0301]);
assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), 0);
assert.equal(cell.isCombined(), 0);
});
it('should add char to combining string in cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -348,9 +399,9 @@ describe('BufferLine', function(): void {
line.loadCell(0, cell);
// chars contains 3 chars
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]);
assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
});
it('should create combining string on taken cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -361,9 +412,9 @@ describe('BufferLine', function(): void {
line.loadCell(0, cell);
// chars contains 2 chars
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]);
assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
});
});
describe('correct fullwidth handling', () => {
@@ -377,83 +428,291 @@ describe('BufferLine', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.insertCells(9, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), '¥¥¥¥ a');
assert.equal(line.translateToString(), '¥¥¥¥ a');
line.insertCells(8, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), '¥¥¥¥a ');
assert.equal(line.translateToString(), '¥¥¥¥a ');
line.insertCells(1, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' a ¥¥¥a');
assert.equal(line.translateToString(), ' a ¥¥¥a');
});
it('insert - wide char at end', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.insertCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa¥¥¥ ');
assert.equal(line.translateToString(), 'aaa¥¥¥ ');
line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa a ¥¥');
assert.equal(line.translateToString(), 'aaa a ¥¥');
line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa aa ¥ ');
assert.equal(line.translateToString(), 'aaa aa ¥ ');
});
it('delete', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.deleteCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥¥¥a');
assert.equal(line.translateToString(), ' ¥¥¥¥a');
line.deleteCells(5, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥¥aaa');
assert.equal(line.translateToString(), ' ¥¥¥aaa');
line.deleteCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥aaaaa');
assert.equal(line.translateToString(), ' ¥¥aaaaa');
});
it('replace - start at 0', () => {
let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'a ¥¥¥¥');
assert.equal(line.translateToString(), 'a ¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aa¥¥¥¥');
assert.equal(line.translateToString(), 'aa¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa ¥¥¥');
assert.equal(line.translateToString(), 'aaa ¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaa¥');
assert.equal(line.translateToString(), 'aaaaaaaa¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaaa ');
assert.equal(line.translateToString(), 'aaaaaaaaa ');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaaaa');
assert.equal(line.translateToString(), 'aaaaaaaaaa');
});
it('replace - start at 1', () => {
let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' a¥¥¥¥');
assert.equal(line.translateToString(), ' a¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aa ¥¥¥');
assert.equal(line.translateToString(), ' aa ¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaa¥¥¥');
assert.equal(line.translateToString(), ' aaa¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaa¥');
assert.equal(line.translateToString(), ' aaaaaaa¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaaa ');
assert.equal(line.translateToString(), ' aaaaaaaa ');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaaaa');
assert.equal(line.translateToString(), ' aaaaaaaaa');
});
});
describe('extended attributes', () => {
it('setCells', function(): void {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
line.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
line.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
line.setCell(4, cell);
assert.deepEqual(line.toArray(), [
[1, 'a', 0, 'a'.charCodeAt(0)],
[1, 'a', 0, 'a'.charCodeAt(0)],
[1, 'A', 0, 'A'.charCodeAt(0)],
[1, 'A', 0, 'A'.charCodeAt(0)],
[1, 'A', 0, 'A'.charCodeAt(0)]
]);
assert.equal((line as any)._extendedAttrs[0], undefined);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.DOTTED);
assert.equal((line as any)._extendedAttrs[4], undefined);
// should be ref to the same object
assert.equal((line as any)._extendedAttrs[1], (line as any)._extendedAttrs[2]);
// should be a different obj
assert.notEqual((line as any)._extendedAttrs[1], (line as any)._extendedAttrs[3]);
});
it('loadCell', () => {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
line.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
line.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
line.setCell(4, cell);
const cell0 = new CellData();
line.loadCell(0, cell0);
const cell1 = new CellData();
line.loadCell(1, cell1);
const cell2 = new CellData();
line.loadCell(2, cell2);
const cell3 = new CellData();
line.loadCell(3, cell3);
const cell4 = new CellData();
line.loadCell(4, cell4);
assert.equal(cell0.extended.underlineStyle, UnderlineStyle.NONE);
assert.equal(cell1.extended.underlineStyle, UnderlineStyle.CURLY);
assert.equal(cell2.extended.underlineStyle, UnderlineStyle.CURLY);
assert.equal(cell3.extended.underlineStyle, UnderlineStyle.DOTTED);
assert.equal(cell4.extended.underlineStyle, UnderlineStyle.NONE);
assert.equal(cell1.extended, cell2.extended);
assert.notEqual(cell2.extended, cell3.extended);
});
it('fill', () => {
const line = new TestBufferLine(3);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.fill(cell);
assert.equal((line as any)._extendedAttrs[0].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.CURLY);
});
it('insertCells', () => {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.insertCells(1, 3, cell);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[4], undefined);
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.insertCells(2, 2, cell);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.DOTTED);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.DOTTED);
assert.equal((line as any)._extendedAttrs[4].underlineStyle, UnderlineStyle.CURLY);
});
it('deleteCells', () => {
const line = new TestBufferLine(5);
const fillCell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
fillCell.extended.underlineStyle = UnderlineStyle.CURLY;
fillCell.bg |= BgFlags.HAS_EXTENDED;
line.fill(fillCell);
fillCell.extended = fillCell.extended.clone();
fillCell.extended.underlineStyle = UnderlineStyle.DOUBLE;
line.deleteCells(1, 3, fillCell);
assert.equal((line as any)._extendedAttrs[0].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[4].underlineStyle, UnderlineStyle.DOUBLE);
});
it('replaceCells', () => {
const line = new TestBufferLine(5);
const fillCell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
fillCell.extended.underlineStyle = UnderlineStyle.CURLY;
fillCell.bg |= BgFlags.HAS_EXTENDED;
line.fill(fillCell);
fillCell.extended = fillCell.extended.clone();
fillCell.extended.underlineStyle = UnderlineStyle.DOUBLE;
line.replaceCells(1, 3, fillCell);
assert.equal((line as any)._extendedAttrs[0].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[4].underlineStyle, UnderlineStyle.CURLY);
});
it('clone', () => {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
line.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
line.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
line.setCell(4, cell);
const nLine = line.clone();
assert.equal((nLine as any)._extendedAttrs[0], (line as any)._extendedAttrs[0]);
assert.equal((nLine as any)._extendedAttrs[1], (line as any)._extendedAttrs[1]);
assert.equal((nLine as any)._extendedAttrs[2], (line as any)._extendedAttrs[2]);
assert.equal((nLine as any)._extendedAttrs[3], (line as any)._extendedAttrs[3]);
assert.equal((nLine as any)._extendedAttrs[4], (line as any)._extendedAttrs[4]);
});
it('copyFrom', () => {
const initial = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
initial.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
initial.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
initial.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
initial.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
initial.setCell(4, cell);
const line = new TestBufferLine(5);
line.fill(CellData.fromCharData([1, 'b', 0, 'b'.charCodeAt(0)]));
line.copyFrom(initial);
assert.equal((line as any)._extendedAttrs[0], (initial as any)._extendedAttrs[0]);
assert.equal((line as any)._extendedAttrs[1], (initial as any)._extendedAttrs[1]);
assert.equal((line as any)._extendedAttrs[2], (initial as any)._extendedAttrs[2]);
assert.equal((line as any)._extendedAttrs[3], (initial as any)._extendedAttrs[3]);
assert.equal((line as any)._extendedAttrs[4], (initial as any)._extendedAttrs[4]);
});
});
});
+30 -12
View File
@@ -3,11 +3,11 @@
* @license MIT
*/
import { CharData, IBufferLine, ICellData, IAttributeData } from 'common/Types';
import { CharData, IBufferLine, ICellData, IAttributeData, IExtendedAttrs } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content, BgFlags } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
/**
* buffer memory layout:
@@ -55,6 +55,7 @@ export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
export class BufferLine implements IBufferLine {
protected _data: Uint32Array;
protected _combined: {[index: number]: string} = {};
protected _extendedAttrs: {[index: number]: ExtendedAttrs} = {};
public length: number;
constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {
@@ -174,6 +175,9 @@ export class BufferLine implements IBufferLine {
if (cell.content & Content.IS_COMBINED_MASK) {
cell.combinedData = this._combined[index];
}
if (cell.bg & BgFlags.HAS_EXTENDED) {
cell.extended = this._extendedAttrs[index];
}
return cell;
}
@@ -184,6 +188,9 @@ export class BufferLine implements IBufferLine {
if (cell.content & Content.IS_COMBINED_MASK) {
this._combined[index] = cell.combinedData;
}
if (cell.bg & BgFlags.HAS_EXTENDED) {
this._extendedAttrs[index] = cell.extended;
}
this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;
this._data[index * CELL_SIZE + Cell.FG] = cell.fg;
this._data[index * CELL_SIZE + Cell.BG] = cell.bg;
@@ -194,7 +201,10 @@ export class BufferLine implements IBufferLine {
* Since the input handler see the incoming chars as UTF32 codepoints,
* it gets an optimized access method.
*/
public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void {
public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void {
if (bg & BgFlags.HAS_EXTENDED) {
this._extendedAttrs[index] = eAttrs;
}
this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
this._data[index * CELL_SIZE + Cell.FG] = fg;
this._data[index * CELL_SIZE + Cell.BG] = bg;
@@ -233,7 +243,7 @@ export class BufferLine implements IBufferLine {
// handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
if (pos && this.getWidth(pos - 1) === 2) {
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
if (n < this.length - pos) {
@@ -252,7 +262,7 @@ export class BufferLine implements IBufferLine {
// handle fullwidth at line end: reset last cell if it is first cell of a wide char
if (this.getWidth(this.length - 1) === 2) {
this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
}
@@ -276,21 +286,21 @@ export class BufferLine implements IBufferLine {
// - reset pos-1 if wide char
// - reset pos if width==0 (previous second cell of a wide char)
if (pos && this.getWidth(pos - 1) === 2) {
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
if (this.getWidth(pos) === 0 && !this.hasContent(pos)) {
this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
}
public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
// handle fullwidth at start: reset cell one to the left if start is second cell of a wide char
if (start && this.getWidth(start - 1) === 2) {
this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
// handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char
if (end < this.length && this.getWidth(end - 1) === 2) {
this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
while (start < end && start < this.length) {
@@ -320,7 +330,7 @@ export class BufferLine implements IBufferLine {
const data = new Uint32Array(cols * CELL_SIZE);
data.set(this._data.subarray(0, cols * CELL_SIZE));
this._data = data;
// Remove any cut off combined data
// Remove any cut off combined data, FIXME: repeat this for extended attrs
const keys = Object.keys(this._combined);
for (let i = 0; i < keys.length; i++) {
const key = parseInt(keys[i], 10);
@@ -339,6 +349,7 @@ export class BufferLine implements IBufferLine {
/** fill a line with fillCharData */
public fill(fillCellData: ICellData): void {
this._combined = {};
this._extendedAttrs = {};
for (let i = 0; i < this.length; ++i) {
this.setCell(i, fillCellData);
}
@@ -357,6 +368,10 @@ export class BufferLine implements IBufferLine {
for (const el in line._combined) {
this._combined[el] = line._combined[el];
}
this._extendedAttrs = {};
for (const el in line._extendedAttrs) {
this._extendedAttrs[el] = line._extendedAttrs[el];
}
this.isWrapped = line.isWrapped;
}
@@ -368,6 +383,9 @@ export class BufferLine implements IBufferLine {
for (const el in this._combined) {
newLine._combined[el] = this._combined[el];
}
for (const el in this._extendedAttrs) {
newLine._extendedAttrs[el] = this._extendedAttrs[el];
}
newLine.isWrapped = this.isWrapped;
return newLine;
}
@@ -397,7 +415,7 @@ export class BufferLine implements IBufferLine {
}
}
// Move any combined data over as needed
// Move any combined data over as needed, FIXME: repeat for extended attrs
const srcCombinedKeys = Object.keys(src._combined);
for (let i = 0; i < srcCombinedKeys.length; i++) {
const key = parseInt(srcCombinedKeys[i], 10);
+7 -6
View File
@@ -3,10 +3,10 @@
* @license MIT
*/
import { CharData, ICellData } from 'common/Types';
import { CharData, ICellData, IExtendedAttrs } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from 'common/buffer/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
/**
* CellData - represents a single Cell in the terminal buffer.
@@ -19,10 +19,11 @@ export class CellData extends AttributeData implements ICellData {
return obj;
}
/** Primitives from terminal buffer. */
public content: number = 0;
public fg: number = 0;
public bg: number = 0;
public combinedData: string = '';
public content = 0;
public fg = 0;
public bg = 0;
public extended: IExtendedAttrs = new ExtendedAttrs();
public combinedData = '';
/** Whether cell contains a combined string. */
public isCombined(): number {
return this.content & Content.IS_COMBINED_MASK;
+12 -2
View File
@@ -121,8 +121,18 @@ export const enum FgFlags {
export const enum BgFlags {
/**
* bit 27..32 (upper 4 unused)
* bit 27..32 (upper 3 unused)
*/
ITALIC = 0x4000000,
DIM = 0x8000000
DIM = 0x8000000,
HAS_EXTENDED = 0x10000000
}
export const enum UnderlineStyle {
NONE = 0,
SINGLE = 1,
DOUBLE = 2,
CURLY = 3,
DOTTED = 4,
DASHED = 5
}