diff --git a/src/Buffer.ts b/src/Buffer.ts
index 1ea303b2..d39a5549 100644
--- a/src/Buffer.ts
+++ b/src/Buffer.ts
@@ -8,6 +8,7 @@ import { LineData, CharData, ITerminal, IBuffer } from './Types';
import { EventEmitter } from './EventEmitter';
import { IDisposable, IMarker } from 'xterm';
+export const DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
export const CHAR_DATA_CHAR_INDEX = 1;
export const CHAR_DATA_WIDTH_INDEX = 2;
@@ -116,7 +117,7 @@ export class Buffer implements IBuffer {
if (this.lines.length > 0) {
// Deal with columns increasing (we don't do anything when columns reduce)
if (this._terminal.cols < newCols) {
- const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?
+ const ch: CharData = [DEFAULT_ATTR, ' ', 1, 32]; // does xterm use the default attr?
for (let i = 0; i < this.lines.length; i++) {
while (this.lines.get(i).length < newCols) {
this.lines.get(i).push(ch);
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 7b12b6de..e2e20c73 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -7,7 +7,7 @@
import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, ICharset } from './Types';
import { C0, C1 } from './EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
-import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer';
+import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR } from './Buffer';
import { FLAGS } from './renderer/Types';
import { wcwidth } from './CharWidth';
import { EscapeSequenceParser } from './EscapeSequenceParser';
@@ -970,7 +970,7 @@ export class InputHandler implements IInputHandler {
const buffer = this._terminal.buffer;
const line = buffer.lines.get(buffer.ybase + buffer.y);
- const ch = line[buffer.x - 1] || [this._terminal.defAttr, ' ', 1, 32];
+ const ch = line[buffer.x - 1] || [DEFAULT_ATTR, ' ', 1, 32];
while (param--) {
line[buffer.x++] = ch;
@@ -1553,7 +1553,7 @@ export class InputHandler implements IInputHandler {
public charAttributes(params: number[]): void {
// Optimize a single SGR0.
if (params.length === 1 && params[0] === 0) {
- this._terminal.curAttr = this._terminal.defAttr;
+ this._terminal.curAttr = DEFAULT_ATTR;
return;
}
@@ -1581,9 +1581,9 @@ export class InputHandler implements IInputHandler {
bg = p - 100;
} else if (p === 0) {
// default
- flags = this._terminal.defAttr >> 18;
- fg = (this._terminal.defAttr >> 9) & 0x1ff;
- bg = this._terminal.defAttr & 0x1ff;
+ flags = DEFAULT_ATTR >> 18;
+ fg = (DEFAULT_ATTR >> 9) & 0x1ff;
+ bg = DEFAULT_ATTR & 0x1ff;
// flags = 0;
// fg = 0x1ff;
// bg = 0x1ff;
@@ -1627,10 +1627,10 @@ export class InputHandler implements IInputHandler {
flags &= ~FLAGS.INVISIBLE;
} else if (p === 39) {
// reset fg
- fg = (this._terminal.defAttr >> 9) & 0x1ff;
+ fg = (DEFAULT_ATTR >> 9) & 0x1ff;
} else if (p === 49) {
// reset bg
- bg = this._terminal.defAttr & 0x1ff;
+ bg = DEFAULT_ATTR & 0x1ff;
} else if (p === 38) {
// fg color 256
if (params[i + 1] === 2) {
@@ -1663,8 +1663,8 @@ export class InputHandler implements IInputHandler {
}
} else if (p === 100) {
// reset fg/bg
- fg = (this._terminal.defAttr >> 9) & 0x1ff;
- bg = this._terminal.defAttr & 0x1ff;
+ fg = (DEFAULT_ATTR >> 9) & 0x1ff;
+ bg = DEFAULT_ATTR & 0x1ff;
} else {
this._terminal.error('Unknown SGR attribute: %d.', p);
}
@@ -1759,7 +1759,7 @@ export class InputHandler implements IInputHandler {
this._terminal.applicationCursor = false;
this._terminal.buffer.scrollTop = 0;
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
- this._terminal.curAttr = this._terminal.defAttr;
+ this._terminal.curAttr = DEFAULT_ATTR;
this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?
this._terminal.charset = null;
this._terminal.glevel = 0; // ??
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 0e4b4a41..18ff2f74 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -25,7 +25,7 @@ import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITermi
import { IMouseZoneManager } from './input/Types';
import { IRenderer } from './renderer/Types';
import { BufferSet } from './BufferSet';
-import { Buffer, MAX_BUFFER_SIZE } from './Buffer';
+import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR } from './Buffer';
import { CompositionHelper } from './CompositionHelper';
import { EventEmitter } from './EventEmitter';
import { Viewport } from './Viewport';
@@ -192,7 +192,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
private _refreshEnd: number;
public savedCols: number;
- public defAttr: number;
public curAttr: number;
public params: (string | number)[];
@@ -315,8 +314,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
// TODO: Can this be just []?
this.charsets = [null];
- this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
- this.curAttr = (0 << 18) | (257 << 9) | (256 << 0);
+ this.curAttr = DEFAULT_ATTR;
this.params = [];
this.currentParam = 0;
@@ -360,8 +358,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
* back_color_erase feature for xterm.
*/
public eraseAttr(): number {
- // if (this.is('screen')) return this.defAttr;
- return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);
+ // if (this.is('screen')) return DEFAULT_ATTR;
+ return (DEFAULT_ATTR & ~0x1ff) | (this.curAttr & 0x1ff);
}
/**
@@ -2063,7 +2061,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
* set, the terminal's current column count would be used.
*/
public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData {
- const attr = cur ? this.eraseAttr() : this.defAttr;
+ const attr = cur ? this.eraseAttr() : DEFAULT_ATTR;
const ch: CharData = [attr, ' ', 1, 32 /* ' '.charCodeAt(0) */]; // width defaults to 1 halfwidth character
const line: LineData = [];
@@ -2083,14 +2081,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
}
/**
- * If cur return the back color xterm feature attribute. Else return defAttr.
+ * If cur return the back color xterm feature attribute. Else return default attribute.
* @param cur
*/
public ch(cur?: boolean): CharData {
if (cur) {
return [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */];
}
- return [this.defAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */];
+ return [DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */];
}
/**
diff --git a/src/Types.ts b/src/Types.ts
index 5075daa9..7642ea16 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -43,7 +43,6 @@ export interface IInputHandlingTerminal extends IEventEmitter {
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
- defAttr: number;
curAttr: number;
savedCols: number;
x10Mouse: boolean;
@@ -213,7 +212,6 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
writeBuffer: string[];
cursorHidden: boolean;
cursorState: number;
- defAttr: number;
options: ITerminalOptions;
buffer: IBuffer;
buffers: IBufferSet;
diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts
index 06ff7728..c5263282 100644
--- a/src/renderer/dom/DomRenderer.ts
+++ b/src/renderer/dom/DomRenderer.ts
@@ -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});
diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts
new file mode 100644
index 00000000..93a60d95
--- /dev/null
+++ b/src/renderer/dom/DomRendererRowFactory.test.ts
@@ -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),
+ ' ' +
+ ' ' +
+ ' ' +
+ ' '
+ );
+ });
+
+ 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),
+ ' ' +
+ '語' +
+ ' '
+ );
+ });
+
+ it('should add class for cursor', () => {
+ const fragment = rowFactory.createRow(lineData, true, 1, 5);
+ assert.equal(getFragmentHtml(fragment),
+ ' ' +
+ ' ' +
+ ' ' +
+ ' '
+ );
+ });
+
+ 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),
+ ' ' +
+ 'a' +
+ ' ' +
+ ' '
+ );
+ });
+
+ 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),
+ ' ' +
+ 'a' +
+ ' ' +
+ ' '
+ );
+ });
+ });
+ });
+
+ 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;
+ }
+});
diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts
new file mode 100644
index 00000000..bfecace5
--- /dev/null
+++ b/src/renderer/dom/DomRendererRowFactory.ts
@@ -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;
+ }
+}
diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts
index e3ae89d3..ba154ebf 100644
--- a/src/utils/TestUtils.test.ts
+++ b/src/utils/TestUtils.test.ts
@@ -107,7 +107,6 @@ export class MockTerminal implements ITerminal {
children: HTMLElement[];
cursorHidden: boolean;
cursorState: number;
- defAttr: number;
scrollback: number;
buffers: IBufferSet;
buffer: IBuffer;
@@ -182,7 +181,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
- defAttr: number;
curAttr: number;
savedCols: number;
x10Mouse: boolean;