Refactor and add some tests to dom renderer

This commit is contained in:
Daniel Imms
2018-05-26 08:15:40 -07:00
parent 20975278c5
commit c2b5a243f2
8 changed files with 206 additions and 91 deletions
+7 -66
View File
@@ -3,20 +3,16 @@
* @license MIT
*/
import { IRenderer, IRenderDimensions, IColorSet, FLAGS } from '../Types';
import { IRenderer, IRenderDimensions, IColorSet } from '../Types';
import { ITerminal } from '../../Types';
import { ITheme } from 'xterm';
import { EventEmitter } from '../../EventEmitter';
import { ColorManager } from '../ColorManager';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer';
import { RenderDebouncer } from '../../utils/RenderDebouncer';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
const ROW_CONTAINER_CLASS = 'xterm-rows';
const BOLD_CLASS = 'xterm-bold';
const ITALIC_CLASS = 'xterm-italic';
const CURSOR_CLASS = 'xterm-cursor';
const FG_CLASS_PREFIX = 'xterm-fg-';
const BG_CLASS_PREFIX = 'xterm-bg-';
const FOCUS_CLASS = 'xterm-focus';
@@ -34,6 +30,7 @@ let nextTerminalId = 1;
*/
export class DomRenderer extends EventEmitter implements IRenderer {
private _renderDebouncer: RenderDebouncer;
private _rowFactory: DomRendererRowFactory;
private _terminalClass: number = nextTerminalId++;
private _themeStyleElement: HTMLStyleElement;
@@ -77,6 +74,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
this._updateDimensions();
this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
this._rowFactory = new DomRendererRowFactory(document);
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._terminal.screenElement.appendChild(this._rowContainer);
@@ -297,72 +295,15 @@ export class DomRenderer extends EventEmitter implements IRenderer {
const terminal = this._terminal;
const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;
const cursorX = this._terminal.buffer.x;
for (let y = start; y <= end; y++) {
const rowElement = this._rowElements[y];
rowElement.innerHTML = '';
const row = y + terminal.buffer.ydisp;
const line = terminal.buffer.lines.get(row);
for (let x = 0; x < terminal.cols; x++) {
const charData = line[x];
const char: string = charData[CHAR_DATA_CHAR_INDEX];
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
let width: number = charData[CHAR_DATA_WIDTH_INDEX];
// The character to the left is a wide character, drawing is owned by the char at x-1
if (width === 0) {
continue;
}
const charElement = document.createElement('span');
if (width > 1) {
charElement.style.width = `${terminal.charMeasure.width * width}px`;
}
const flags = attr >> 18;
let bg = attr & 0x1ff;
let fg = (attr >> 9) & 0x1ff;
if (row === cursorAbsoluteY && x === this._terminal.buffer.x) {
charElement.classList.add(CURSOR_CLASS);
}
// If inverse flag is on, the foreground should become the background.
if (flags & FLAGS.INVERSE) {
const temp = bg;
bg = fg;
fg = temp;
if (fg === 256) {
// TODO: INVERTED_DEFAULT_COLOR should not be in atlas
fg = INVERTED_DEFAULT_COLOR;
}
if (bg === 257) {
bg = INVERTED_DEFAULT_COLOR;
}
}
if (flags & FLAGS.BOLD) {
// Convert the FG color to the bold variant
if (fg < 8) {
fg += 8;
}
charElement.classList.add(BOLD_CLASS);
}
if (flags & FLAGS.ITALIC) {
charElement.classList.add(ITALIC_CLASS);
}
charElement.textContent = char;
if (fg !== 257) {
charElement.classList.add(`xterm-fg-${fg}`);
}
if (bg !== 256) {
charElement.classList.add(`xterm-bg-${bg}`);
}
rowElement.appendChild(charElement);
}
const lineData = terminal.buffer.lines.get(row);
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorX, terminal.charMeasure.width));
}
this._terminal.emit('refresh', {start, end});
@@ -0,0 +1,95 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { DomRendererRowFactory } from './DomRendererRowFactory';
import { LineData } from '../../Types';
import { DEFAULT_ATTR } from '../../Buffer';
import { FLAGS } from '../Types';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
let rowFactory: DomRendererRowFactory;
let lineData: LineData;
beforeEach(() => {
dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document);
lineData = createEmptyLineData(4);
});
describe('createRow', () => {
it('should create an element for every character in the row', () => {
const fragment = rowFactory.createRow(lineData, false, 0, 5);
assert.equal(getFragmentHtml(fragment),
'<span> </span>' +
'<span> </span>' +
'<span> </span>' +
'<span> </span>'
);
});
it('should set correct attributes for double width characters', () => {
lineData[1] = [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)];
// There should be no element for the following "empty" cell
lineData[2] = [DEFAULT_ATTR, '', 0, undefined];
const fragment = rowFactory.createRow(lineData, false, 0, 5);
assert.equal(getFragmentHtml(fragment),
'<span> </span>' +
'<span style="width: 10px;">語</span>' +
'<span> </span>'
);
});
it('should add class for cursor', () => {
const fragment = rowFactory.createRow(lineData, true, 1, 5);
assert.equal(getFragmentHtml(fragment),
'<span> </span>' +
'<span class="xterm-cursor"> </span>' +
'<span> </span>' +
'<span> </span>'
);
});
describe('attributes', () => {
it('should add class for bold', () => {
lineData[1] = [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5);
assert.equal(getFragmentHtml(fragment),
'<span> </span>' +
'<span class="xterm-bold">a</span>' +
'<span> </span>' +
'<span> </span>'
);
});
it('should add class for italic', () => {
lineData[1] = [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)];
const fragment = rowFactory.createRow(lineData, false, 0, 5);
assert.equal(getFragmentHtml(fragment),
'<span> </span>' +
'<span class="xterm-italic">a</span>' +
'<span> </span>' +
'<span> </span>'
);
});
});
});
function getFragmentHtml(fragment: DocumentFragment): string {
const element = dom.window.document.createElement('div');
element.appendChild(fragment);
return element.innerHTML;
}
function createEmptyLineData(cols: number): LineData {
const lineData: LineData = [];
for (let i = 0; i < cols; i++) {
lineData.push([DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */]);
}
return lineData;
}
});
+84
View File
@@ -0,0 +1,84 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { LineData } from '../../Types';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer';
import { FLAGS } from '../Types';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
export const BOLD_CLASS = 'xterm-bold';
export const ITALIC_CLASS = 'xterm-italic';
export const CURSOR_CLASS = 'xterm-cursor';
export class DomRendererRowFactory {
constructor(
private _document: Document
) {
}
public createRow(lineData: LineData, isCursorRow: boolean, cursorX: number, cellWidth: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
for (let x = 0; x < lineData.length; x++) {
const charData = lineData[x];
const char: string = charData[CHAR_DATA_CHAR_INDEX];
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
let width: number = charData[CHAR_DATA_WIDTH_INDEX];
// The character to the left is a wide character, drawing is owned by the char at x-1
if (width === 0) {
continue;
}
const charElement = this._document.createElement('span');
if (width > 1) {
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);
}
// If inverse flag is on, the foreground should become the background.
if (flags & FLAGS.INVERSE) {
const temp = bg;
bg = fg;
fg = temp;
if (fg === 256) {
// TODO: INVERTED_DEFAULT_COLOR should not be in atlas
fg = INVERTED_DEFAULT_COLOR;
}
if (bg === 257) {
bg = INVERTED_DEFAULT_COLOR;
}
}
if (flags & FLAGS.BOLD) {
// Convert the FG color to the bold variant
if (fg < 8) {
fg += 8;
}
charElement.classList.add(BOLD_CLASS);
}
if (flags & FLAGS.ITALIC) {
charElement.classList.add(ITALIC_CLASS);
}
charElement.textContent = char;
if (fg !== 257) {
charElement.classList.add(`xterm-fg-${fg}`);
}
if (bg !== 256) {
charElement.classList.add(`xterm-bg-${bg}`);
}
fragment.appendChild(charElement);
}
return fragment;
}
}