Merge pull request #1895 from jerch/true_color

True color support
This commit is contained in:
Daniel Imms
2019-04-07 13:45:08 -04:00
committed by GitHub
17 changed files with 689 additions and 443 deletions
+59 -17
View File
@@ -6,11 +6,9 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { DomRendererRowFactory } from './DomRendererRowFactory';
import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
import { BufferLine, CellData } from '../../BufferLine';
import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR_DATA } from '../../Buffer';
import { BufferLine, CellData, FgFlags, BgFlags, Attributes } from '../../BufferLine';
import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR } from '../atlas/Types';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
@@ -73,7 +71,9 @@ describe('DomRendererRowFactory', () => {
describe('attributes', () => {
it('should add class for bold', () => {
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]));
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bold">a</span>'
@@ -81,7 +81,9 @@ describe('DomRendererRowFactory', () => {
});
it('should add class for italic', () => {
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]));
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-italic">a</span>'
@@ -89,9 +91,12 @@ describe('DomRendererRowFactory', () => {
});
it('should add classes for 256 foreground colors', () => {
const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0);
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_P256;
for (let i = 0; i < 256; i++) {
lineData.setCell(0, CellData.fromCharData([defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]));
cell.fg &= ~Attributes.PCOLOR_MASK;
cell.fg |= i;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-fg-${i}">a</span>`
@@ -100,9 +105,12 @@ describe('DomRendererRowFactory', () => {
});
it('should add classes for 256 background colors', () => {
const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0);
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.bg |= Attributes.CM_P256;
for (let i = 0; i < 256; i++) {
lineData.setCell(0, CellData.fromCharData([defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]));
cell.bg &= ~Attributes.PCOLOR_MASK;
cell.bg |= i;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bg-${i}">a</span>`
@@ -111,38 +119,72 @@ describe('DomRendererRowFactory', () => {
});
it('should correctly invert colors', () => {
lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]));
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE;
cell.bg |= Attributes.CM_P16 | 1;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
);
});
it('should correctly invert default fg color', () => {
lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]));
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= FgFlags.INVERSE;
cell.bg |= Attributes.CM_P16 | 1;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
);
});
it('should correctly invert default bg color', () => {
lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]));
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
);
});
it('should turn bold fg text bright', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= FgFlags.BOLD | Attributes.CM_P16;
for (let i = 0; i < 8; i++) {
lineData.setCell(0, CellData.fromCharData([(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]));
cell.fg &= ~Attributes.PCOLOR_MASK;
cell.fg |= i;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
);
}
});
it('should set style attribute for RBG', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3;
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="color:rgb(1,2,3);background-color:rgb(4,5,6);">a</span>'
);
});
it('should correctly invert RGB colors', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE;
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="background-color:rgb(1,2,3);color:rgb(4,5,6);">a</span>'
);
});
});
});
+32 -32
View File
@@ -4,10 +4,9 @@
*/
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types';
import { CellData } from '../../BufferLine';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
import { CellData, AttributeData } from '../../BufferLine';
export const BOLD_CLASS = 'xterm-bold';
export const DIM_CLASS = 'xterm-dim';
@@ -45,7 +44,6 @@ export class DomRendererRowFactory {
for (let x = 0; x < lineLength; x++) {
lineData.loadCell(x, this._workCell);
const attr = this._workCell.fg;
const width = this._workCell.getWidth();
// The character to the left is a wide character, drawing is owned by the char at x-1
@@ -58,10 +56,6 @@ export class DomRendererRowFactory {
charElement.style.width = `${cellWidth * width}px`;
}
const flags = attr >> 18;
let bg = attr & 0x1ff;
let fg = (attr >> 9) & 0x1ff;
if (isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS);
@@ -82,29 +76,11 @@ export class DomRendererRowFactory {
}
}
// If inverse flag is on, the foreground should become the background.
if (flags & FLAGS.INVERSE) {
const temp = bg;
bg = fg;
fg = temp;
if (fg === DEFAULT_COLOR) {
fg = INVERTED_DEFAULT_COLOR;
}
if (bg === DEFAULT_COLOR) {
bg = INVERTED_DEFAULT_COLOR;
}
}
if (flags & FLAGS.BOLD && this._terminalOptions.enableBold) {
// Convert the FG color to the bold variant. This should not happen when
// the fg is the inverse default color as there is no bold variant.
if (fg < 8 && this._terminalOptions.drawBoldTextInBrightColors) {
fg += 8;
}
if (this._workCell.isBold() && this._terminalOptions.enableBold) {
charElement.classList.add(BOLD_CLASS);
}
if (flags & FLAGS.ITALIC) {
if (this._workCell.isItalic()) {
charElement.classList.add(ITALIC_CLASS);
}
@@ -113,12 +89,36 @@ export class DomRendererRowFactory {
}
charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
if (fg !== DEFAULT_COLOR) {
charElement.classList.add(`xterm-fg-${fg}`);
const swapColor = this._workCell.isInverse();
// fg
if (this._workCell.isFgRGB()) {
let style = charElement.getAttribute('style') || '';
style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._workCell.getFgColor())).join(',')});`;
charElement.setAttribute('style', style);
} else if (this._workCell.isFgPalette()) {
let fg = this._workCell.getFgColor();
if (this._workCell.isBold() && fg < 8 && !swapColor &&
this._terminalOptions.enableBold && this._terminalOptions.drawBoldTextInBrightColors) {
fg += 8;
}
charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`);
} else if (swapColor) {
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
}
if (bg !== DEFAULT_COLOR) {
charElement.classList.add(`xterm-bg-${bg}`);
// bg
if (this._workCell.isBgRGB()) {
let style = charElement.getAttribute('style') || '';
style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._workCell.getBgColor())).join(',')});`;
charElement.setAttribute('style', style);
} else if (this._workCell.isBgPalette()) {
charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._workCell.getBgColor()}`);
} else if (swapColor) {
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
}
fragment.appendChild(charElement);
}
return fragment;