mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into 1473_slow_build
This commit is contained in:
@@ -179,7 +179,7 @@ export class AccessibilityManager implements IDisposable {
|
||||
this._refreshRowsDimensions();
|
||||
}
|
||||
|
||||
public _createAccessibilityTreeNode(): HTMLElement {
|
||||
private _createAccessibilityTreeNode(): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.setAttribute('role', 'listitem');
|
||||
element.tabIndex = -1;
|
||||
|
||||
+4
-3
@@ -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);
|
||||
@@ -337,9 +338,9 @@ export class Buffer implements IBuffer {
|
||||
}
|
||||
|
||||
export class Marker extends EventEmitter implements IMarker {
|
||||
private static NEXT_ID = 1;
|
||||
private static _nextId = 1;
|
||||
|
||||
private _id: number = Marker.NEXT_ID++;
|
||||
private _id: number = Marker._nextId++;
|
||||
public isDisposed: boolean = false;
|
||||
public disposables: IDisposable[] = [];
|
||||
|
||||
|
||||
+2
-2
@@ -121,7 +121,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu
|
||||
}
|
||||
const control = opts.control | 0;
|
||||
let table: number[] | Uint32Array = null;
|
||||
function init_table(): number[] | Uint32Array {
|
||||
function initTable(): number[] | Uint32Array {
|
||||
// lookup table for BMP
|
||||
const CODEPOINTS = 65536; // BMP holds 65536 codepoints
|
||||
const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2
|
||||
@@ -161,7 +161,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu
|
||||
if (num < 127) {
|
||||
return 1;
|
||||
}
|
||||
const t = table || init_table();
|
||||
const t = table || initTable();
|
||||
if (num < 65536) {
|
||||
return t[num >> 4] >> ((num & 15) << 1) & 3;
|
||||
}
|
||||
|
||||
@@ -169,12 +169,12 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
it('constructor', function (): void {
|
||||
let p: EscapeSequenceParser = new EscapeSequenceParser();
|
||||
chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE);
|
||||
chai.expect(p.TRANSITIONS).equal(VT500_TRANSITION_TABLE);
|
||||
p = new EscapeSequenceParser(VT500_TRANSITION_TABLE);
|
||||
chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE);
|
||||
chai.expect(p.TRANSITIONS).equal(VT500_TRANSITION_TABLE);
|
||||
const tansitions: TransitionTable = new TransitionTable(10);
|
||||
p = new EscapeSequenceParser(tansitions);
|
||||
chai.expect(p.transitions).equal(tansitions);
|
||||
chai.expect(p.TRANSITIONS).equal(tansitions);
|
||||
});
|
||||
it('inital states', function (): void {
|
||||
chai.expect(parser.initialState).equal(ParserState.GROUND);
|
||||
|
||||
@@ -235,7 +235,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser {
|
||||
protected _dcsHandlerFb: IDcsHandler;
|
||||
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
|
||||
|
||||
constructor(readonly transitions: TransitionTable = VT500_TRANSITION_TABLE) {
|
||||
constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
|
||||
this.initialState = ParserState.GROUND;
|
||||
this.currentState = this.initialState;
|
||||
this._osc = '';
|
||||
@@ -342,7 +342,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser {
|
||||
let osc = this._osc;
|
||||
let collect = this._collect;
|
||||
let params = this._params;
|
||||
const table: Uint8Array | number[] = this.transitions.table;
|
||||
const table: Uint8Array | number[] = this.TRANSITIONS.table;
|
||||
let dcsHandler: IDcsHandler | null = this._activeDcsHandler;
|
||||
let callback: Function | null = null;
|
||||
|
||||
|
||||
+17
-17
@@ -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';
|
||||
@@ -163,12 +163,12 @@ export class InputHandler implements IInputHandler {
|
||||
this._parser.setCsiHandler('X', (params, collect) => this.eraseChars(params));
|
||||
this._parser.setCsiHandler('Z', (params, collect) => this.cursorBackwardTab(params));
|
||||
this._parser.setCsiHandler('`', (params, collect) => this.charPosAbsolute(params));
|
||||
this._parser.setCsiHandler('a', (params, collect) => this.HPositionRelative(params));
|
||||
this._parser.setCsiHandler('a', (params, collect) => this.hPositionRelative(params));
|
||||
this._parser.setCsiHandler('b', (params, collect) => this.repeatPrecedingCharacter(params));
|
||||
this._parser.setCsiHandler('c', (params, collect) => this.sendDeviceAttributes(params, collect));
|
||||
this._parser.setCsiHandler('d', (params, collect) => this.linePosAbsolute(params));
|
||||
this._parser.setCsiHandler('e', (params, collect) => this.VPositionRelative(params));
|
||||
this._parser.setCsiHandler('f', (params, collect) => this.HVPosition(params));
|
||||
this._parser.setCsiHandler('e', (params, collect) => this.vPositionRelative(params));
|
||||
this._parser.setCsiHandler('f', (params, collect) => this.hVPosition(params));
|
||||
this._parser.setCsiHandler('g', (params, collect) => this.tabClear(params));
|
||||
this._parser.setCsiHandler('h', (params, collect) => this.setMode(params, collect));
|
||||
this._parser.setCsiHandler('l', (params, collect) => this.resetMode(params, collect));
|
||||
@@ -949,7 +949,7 @@ export class InputHandler implements IInputHandler {
|
||||
* [columns] (default = [row,col+1]) (HPR)
|
||||
* reuse CSI Ps C ?
|
||||
*/
|
||||
public HPositionRelative(params: number[]): void {
|
||||
public hPositionRelative(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
@@ -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;
|
||||
@@ -1063,7 +1063,7 @@ export class InputHandler implements IInputHandler {
|
||||
* [rows] (default = [row+1,column])
|
||||
* reuse CSI Ps B ?
|
||||
*/
|
||||
public VPositionRelative(params: number[]): void {
|
||||
public vPositionRelative(params: number[]): void {
|
||||
let param = params[0];
|
||||
if (param < 1) {
|
||||
param = 1;
|
||||
@@ -1083,7 +1083,7 @@ export class InputHandler implements IInputHandler {
|
||||
* Horizontal and Vertical Position [row;column] (default =
|
||||
* [1,1]) (HVP).
|
||||
*/
|
||||
public HVPosition(params: number[]): void {
|
||||
public hVPosition(params: number[]): void {
|
||||
if (params[0] < 1) params[0] = 1;
|
||||
if (params[1] < 1) params[1] = 1;
|
||||
|
||||
@@ -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; // ??
|
||||
|
||||
@@ -11,9 +11,9 @@ import { MockBuffer, MockTerminal } from './utils/TestUtils.test';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
|
||||
class TestLinkifier extends Linkifier {
|
||||
constructor(_terminal: ITerminal) {
|
||||
super(_terminal);
|
||||
Linkifier.TIME_BEFORE_LINKIFY = 0;
|
||||
constructor(terminal: ITerminal) {
|
||||
super(terminal);
|
||||
(<any>Linkifier).TIME_BEFORE_LINKIFY = 0;
|
||||
}
|
||||
|
||||
public get linkMatchers(): ILinkMatcher[] { return this._linkMatchers; }
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
* the costly operation of searching every row multiple times, potentially a
|
||||
* huge amount of times.
|
||||
*/
|
||||
protected static TIME_BEFORE_LINKIFY = 200;
|
||||
protected static readonly TIME_BEFORE_LINKIFY = 200;
|
||||
|
||||
protected _linkMatchers: ILinkMatcher[] = [];
|
||||
|
||||
|
||||
+3
-3
@@ -124,12 +124,12 @@ csiStateHandler['T'] = (handler, params, prefix) => {
|
||||
csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);
|
||||
csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);
|
||||
csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);
|
||||
csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);
|
||||
csiStateHandler['a'] = (handler, params, prefix) => handler.hPositionRelative(params);
|
||||
csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);
|
||||
csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);
|
||||
csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);
|
||||
csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);
|
||||
csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);
|
||||
csiStateHandler['e'] = (handler, params, prefix) => handler.vPositionRelative(params);
|
||||
csiStateHandler['f'] = (handler, params, prefix) => handler.hVPosition(params);
|
||||
csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);
|
||||
csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);
|
||||
csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);
|
||||
|
||||
@@ -79,23 +79,23 @@ function terminalToString(term: Terminal): string {
|
||||
|
||||
// Skip tests on Windows since pty.open isn't supported
|
||||
if (os.platform() !== 'win32') {
|
||||
const CONSOLE_LOG = console.log;
|
||||
const consoleLog = console.log;
|
||||
|
||||
// expect files need terminal at 80x25!
|
||||
const COLS = 80;
|
||||
const ROWS = 25;
|
||||
const cols = 80;
|
||||
const rows = 25;
|
||||
|
||||
/** some helpers for pty interaction */
|
||||
// we need a pty in between to get the termios decorations
|
||||
// for the basic test cases a raw pty device is enough
|
||||
primitivePty = pty.native.open(COLS, ROWS);
|
||||
primitivePty = pty.native.open(cols, rows);
|
||||
|
||||
/** tests */
|
||||
describe('xterm output comparison', () => {
|
||||
let xterm: TestTerminal;
|
||||
|
||||
beforeEach(() => {
|
||||
xterm = new TestTerminal({ cols: COLS, rows: ROWS });
|
||||
xterm = new TestTerminal({ cols: cols, rows: rows });
|
||||
xterm.refresh = () => {};
|
||||
xterm.viewport = <IViewport>{
|
||||
syncScrollArea: () => {}
|
||||
@@ -133,8 +133,9 @@ if (os.platform() !== 'win32') {
|
||||
xterm.innerWrite();
|
||||
|
||||
const fromEmulator = terminalToString(xterm);
|
||||
console.log = CONSOLE_LOG;
|
||||
console.log = consoleLog;
|
||||
const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
|
||||
|
||||
// Some of the tests have whitespace on the right of lines, we trim all the linex
|
||||
// from xterm.js so ignore this for now at least.
|
||||
const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n');
|
||||
|
||||
+52
-53
@@ -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';
|
||||
@@ -49,6 +49,7 @@ import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ScreenDprMonitor } from './utils/ScreenDprMonitor';
|
||||
import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm';
|
||||
import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache';
|
||||
import { DomRenderer } from './renderer/dom/DomRenderer';
|
||||
|
||||
// reg + shift key mappings for digits and special chars
|
||||
const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = {
|
||||
@@ -123,7 +124,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
allowTransparency: false,
|
||||
tabStopWidth: 8,
|
||||
theme: null,
|
||||
rightClickSelectsWord: Browser.isMac
|
||||
rightClickSelectsWord: Browser.isMac,
|
||||
rendererType: 'canvas'
|
||||
};
|
||||
|
||||
export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {
|
||||
@@ -190,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)[];
|
||||
@@ -311,8 +312,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;
|
||||
@@ -356,8 +356,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -693,7 +693,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
// Performance: Add viewport and helper elements from the fragment
|
||||
this.element.appendChild(fragment);
|
||||
|
||||
this.renderer = new Renderer(this, this.options.theme);
|
||||
switch (this.options.rendererType) {
|
||||
case 'canvas': this.renderer = new Renderer(this, this.options.theme); break;
|
||||
case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break;
|
||||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
|
||||
}
|
||||
this.options.theme = null;
|
||||
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure);
|
||||
this.viewport.onThemeChanged(this.renderer.colorManager.colors);
|
||||
@@ -706,7 +710,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
// dprchange should handle this case, we need this as well for browsers that don't support the
|
||||
// matchMedia query.
|
||||
this._disposables.push(Dom.addDisposableListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio)));
|
||||
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows));
|
||||
this.charMeasure.on('charsizechanged', () => this.renderer.onCharSizeChanged());
|
||||
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
|
||||
|
||||
this.selectionManager = new SelectionManager(this, this.charMeasure);
|
||||
@@ -2050,7 +2054,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 = [];
|
||||
@@ -2070,14 +2074,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) */];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2200,7 +2204,42 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
|
||||
// TODO: Remove when true color is implemented
|
||||
public matchColor(r1: number, g1: number, b1: number): number {
|
||||
return matchColor_(r1, g1, b1);
|
||||
const hash = (r1 << 16) | (g1 << 8) | b1;
|
||||
|
||||
if (matchColorCache[hash] != null) {
|
||||
return matchColorCache[hash];
|
||||
}
|
||||
|
||||
let ldiff = Infinity;
|
||||
let li = -1;
|
||||
let i = 0;
|
||||
let c: number;
|
||||
let r2: number;
|
||||
let g2: number;
|
||||
let b2: number;
|
||||
let diff: number;
|
||||
|
||||
for (; i < DEFAULT_ANSI_COLORS.length; i++) {
|
||||
c = DEFAULT_ANSI_COLORS[i].rgba;
|
||||
r2 = c >>> 24;
|
||||
g2 = c >>> 16 & 0xFF;
|
||||
b2 = c >>> 8 & 0xFF;
|
||||
// assume that alpha is 0xFF
|
||||
|
||||
diff = matchColorDistance(r1, g1, b1, r2, g2, b2);
|
||||
|
||||
if (diff === 0) {
|
||||
li = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (diff < ldiff) {
|
||||
ldiff = diff;
|
||||
li = i;
|
||||
}
|
||||
}
|
||||
|
||||
return matchColorCache[hash] = li;
|
||||
}
|
||||
|
||||
private _visualBell(): boolean {
|
||||
@@ -2256,43 +2295,3 @@ function matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2:
|
||||
+ Math.pow(59 * (g1 - g2), 2)
|
||||
+ Math.pow(11 * (b1 - b2), 2);
|
||||
}
|
||||
|
||||
|
||||
function matchColor_(r1: number, g1: number, b1: number): number {
|
||||
const hash = (r1 << 16) | (g1 << 8) | b1;
|
||||
|
||||
if (matchColorCache[hash] != null) {
|
||||
return matchColorCache[hash];
|
||||
}
|
||||
|
||||
let ldiff = Infinity;
|
||||
let li = -1;
|
||||
let i = 0;
|
||||
let c: number;
|
||||
let r2: number;
|
||||
let g2: number;
|
||||
let b2: number;
|
||||
let diff: number;
|
||||
|
||||
for (; i < DEFAULT_ANSI_COLORS.length; i++) {
|
||||
c = DEFAULT_ANSI_COLORS[i].rgba;
|
||||
r2 = c >>> 24;
|
||||
g2 = c >>> 16 & 0xFF;
|
||||
b2 = c >>> 8 & 0xFF;
|
||||
// assume that alpha is 0xFF
|
||||
|
||||
diff = matchColorDistance(r1, g1, b1, r2, g2, b2);
|
||||
|
||||
if (diff === 0) {
|
||||
li = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (diff < ldiff) {
|
||||
ldiff = diff;
|
||||
li = i;
|
||||
}
|
||||
}
|
||||
|
||||
return matchColorCache[hash] = li;
|
||||
}
|
||||
|
||||
+3
-5
@@ -43,7 +43,6 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
||||
insertMode: boolean;
|
||||
wraparoundMode: boolean;
|
||||
bracketedPasteMode: boolean;
|
||||
defAttr: number;
|
||||
curAttr: number;
|
||||
savedCols: number;
|
||||
x10Mouse: boolean;
|
||||
@@ -139,12 +138,12 @@ export interface IInputHandler {
|
||||
/** CSI X */ eraseChars(params?: number[]): void;
|
||||
/** CSI Z */ cursorBackwardTab(params?: number[]): void;
|
||||
/** CSI ` */ charPosAbsolute(params?: number[]): void;
|
||||
/** CSI a */ HPositionRelative(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 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -46,13 +46,13 @@ function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {
|
||||
|
||||
let zsentry;
|
||||
|
||||
function _shouldWrite(): boolean {
|
||||
function shouldWrite(): boolean {
|
||||
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
|
||||
}
|
||||
|
||||
zsentry = new zmodem.Sentry({
|
||||
to_terminal: (octets: ArrayLike<number>) => {
|
||||
if (_shouldWrite()) {
|
||||
if (shouldWrite()) {
|
||||
term.write(
|
||||
String.fromCharCode.apply(String, octets)
|
||||
);
|
||||
@@ -70,7 +70,7 @@ function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {
|
||||
// may be specific to xterm.js’s demo, ultimately we
|
||||
// should reject anything that isn’t binary.
|
||||
if (typeof evt.data === 'string') {
|
||||
if (_shouldWrite()) {
|
||||
if (shouldWrite()) {
|
||||
term.write(evt.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ export const enum FLAGS {
|
||||
ITALIC = 64
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that IRenderer implementations should emit the refresh event after
|
||||
* rendering rows to the screen.
|
||||
*/
|
||||
export interface IRenderer extends IEventEmitter {
|
||||
dimensions: IRenderDimensions;
|
||||
colorManager: IColorManager;
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class StaticCharAtlas extends BaseCharAtlas {
|
||||
return canvas;
|
||||
}
|
||||
|
||||
public _doWarmUp(): void {
|
||||
protected _doWarmUp(): void {
|
||||
const result = generateStaticCharAtlasTexture(window, this._canvasFactory, this._config);
|
||||
if (result instanceof HTMLCanvasElement) {
|
||||
this._texture = result;
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderer, IRenderDimensions, IColorSet } from '../Types';
|
||||
import { ITerminal } from '../../Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { EventEmitter } from '../../EventEmitter';
|
||||
import { ColorManager } from '../ColorManager';
|
||||
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 FG_CLASS_PREFIX = 'xterm-fg-';
|
||||
const BG_CLASS_PREFIX = 'xterm-bg-';
|
||||
const FOCUS_CLASS = 'xterm-focus';
|
||||
const SELECTION_CLASS = 'xterm-selection';
|
||||
|
||||
let nextTerminalId = 1;
|
||||
|
||||
// TODO: Pull into an addon when TS composite projects allow easier sharing of code (not just
|
||||
// interfaces) between core and addons
|
||||
|
||||
/**
|
||||
* A fallback renderer for when canvas is slow. This is not meant to be
|
||||
* particularly fast or feature complete, more just stable and usable for when
|
||||
* canvas is not an option.
|
||||
*/
|
||||
export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
private _renderDebouncer: RenderDebouncer;
|
||||
private _rowFactory: DomRendererRowFactory;
|
||||
private _terminalClass: number = nextTerminalId++;
|
||||
|
||||
private _themeStyleElement: HTMLStyleElement;
|
||||
private _dimensionsStyleElement: HTMLStyleElement;
|
||||
private _rowContainer: HTMLElement;
|
||||
private _rowElements: HTMLElement[] = [];
|
||||
private _selectionContainer: HTMLElement;
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
public colorManager: ColorManager;
|
||||
|
||||
constructor(private _terminal: ITerminal, theme: ITheme | undefined) {
|
||||
super();
|
||||
const allowTransparency = this._terminal.options.allowTransparency;
|
||||
this.colorManager = new ColorManager(document, allowTransparency);
|
||||
this.setTheme(theme);
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
|
||||
this._rowContainer.style.lineHeight = 'normal';
|
||||
this._rowContainer.setAttribute('aria-hidden', 'true');
|
||||
this._refreshRowElements(this._terminal.rows, this._terminal.cols);
|
||||
this._selectionContainer = document.createElement('div');
|
||||
this._selectionContainer.classList.add(SELECTION_CLASS);
|
||||
this._selectionContainer.setAttribute('aria-hidden', 'true');
|
||||
|
||||
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
|
||||
};
|
||||
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);
|
||||
this._terminal.screenElement.appendChild(this._selectionContainer);
|
||||
}
|
||||
|
||||
private _updateDimensions(): void {
|
||||
this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio;
|
||||
this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio;
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth;
|
||||
this.dimensions.scaledCellHeight = this.dimensions.scaledCharHeight;
|
||||
this.dimensions.scaledCharLeft = 0;
|
||||
this.dimensions.scaledCharTop = 0;
|
||||
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;
|
||||
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;
|
||||
this.dimensions.canvasWidth = this._terminal.charMeasure.width * this._terminal.cols;
|
||||
this.dimensions.canvasHeight = this._terminal.charMeasure.height * this._terminal.rows;
|
||||
this.dimensions.actualCellWidth = this._terminal.charMeasure.width;
|
||||
this.dimensions.actualCellHeight = this._terminal.charMeasure.height;
|
||||
|
||||
this._rowElements.forEach(element => {
|
||||
element.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
element.style.height = `${this._terminal.charMeasure.height}px`;
|
||||
});
|
||||
|
||||
if (!this._dimensionsStyleElement) {
|
||||
this._dimensionsStyleElement = document.createElement('style');
|
||||
this._terminal.screenElement.appendChild(this._dimensionsStyleElement);
|
||||
}
|
||||
|
||||
const styles =
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
|
||||
` display: inline-block;` +
|
||||
` height: 100%;` +
|
||||
` vertical-align: top;` +
|
||||
` width: ${this._terminal.charMeasure.width}px` +
|
||||
`}`;
|
||||
|
||||
this._dimensionsStyleElement.innerHTML = styles;
|
||||
|
||||
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
|
||||
this._rowContainer.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._rowContainer.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
}
|
||||
|
||||
public setTheme(theme: ITheme | undefined): IColorSet {
|
||||
if (theme) {
|
||||
this.colorManager.setTheme(theme);
|
||||
}
|
||||
|
||||
if (!this._themeStyleElement) {
|
||||
this._themeStyleElement = document.createElement('style');
|
||||
this._terminal.screenElement.appendChild(this._themeStyleElement);
|
||||
}
|
||||
|
||||
// Base CSS
|
||||
let styles =
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
|
||||
` color: ${this.colorManager.colors.foreground.css};` +
|
||||
` background-color: ${this.colorManager.colors.background.css};` +
|
||||
` font-family: ${this._terminal.getOption('fontFamily')};` +
|
||||
` font-size: ${this._terminal.getOption('fontSize')}px;` +
|
||||
`}`;
|
||||
// Text styles
|
||||
styles +=
|
||||
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
|
||||
` font-weight: ${this._terminal.options.fontWeight};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${BOLD_CLASS} {` +
|
||||
` font-weight: ${this._terminal.options.fontWeightBold};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
|
||||
` font-style: italic;` +
|
||||
`}`;
|
||||
// Cursor
|
||||
styles +=
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS} {` +
|
||||
` background-color: ${this.colorManager.colors.cursor.css};` +
|
||||
` color: ${this.colorManager.colors.cursorAccent.css};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +
|
||||
` outline: 1px solid #fff;` +
|
||||
` outline-offset: -1px;` +
|
||||
`}`;
|
||||
// Selection
|
||||
styles +=
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} {` +
|
||||
` position: absolute;` +
|
||||
` top: 0;` +
|
||||
` left: 0;` +
|
||||
` z-index: 1;` +
|
||||
` pointer-events: none;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
|
||||
` position: absolute;` +
|
||||
` background-color: ${this.colorManager.colors.selection.css};` +
|
||||
`}`;
|
||||
// Colors
|
||||
this.colorManager.colors.ansi.forEach((c, i) => {
|
||||
styles +=
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
|
||||
});
|
||||
|
||||
this._themeStyleElement.innerHTML = styles;
|
||||
return this.colorManager.colors;
|
||||
}
|
||||
|
||||
public onWindowResize(devicePixelRatio: number): void {
|
||||
this._updateDimensions();
|
||||
}
|
||||
|
||||
private _refreshRowElements(cols: number, rows: number): void {
|
||||
// Add missing elements
|
||||
for (let i = this._rowElements.length; i <= rows; i++) {
|
||||
const row = document.createElement('div');
|
||||
this._rowContainer.appendChild(row);
|
||||
this._rowElements.push(row);
|
||||
}
|
||||
// Remove excess elements
|
||||
while (this._rowElements.length > rows) {
|
||||
this._rowContainer.removeChild(this._rowElements.pop());
|
||||
}
|
||||
}
|
||||
|
||||
public onResize(cols: number, rows: number): void {
|
||||
this._refreshRowElements(cols, rows);
|
||||
this._updateDimensions();
|
||||
}
|
||||
|
||||
public onCharSizeChanged(): void {
|
||||
this._updateDimensions();
|
||||
}
|
||||
|
||||
public onBlur(): void {
|
||||
this._rowContainer.classList.remove(FOCUS_CLASS);
|
||||
}
|
||||
|
||||
public onFocus(): void {
|
||||
this._rowContainer.classList.add(FOCUS_CLASS);
|
||||
}
|
||||
|
||||
public onSelectionChanged(start: [number, number], end: [number, number]): void {
|
||||
// Remove all selections
|
||||
while (this._selectionContainer.children.length) {
|
||||
this._selectionContainer.removeChild(this._selectionContainer.children[0]);
|
||||
}
|
||||
|
||||
// Selection does not exist
|
||||
if (!start || !end) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Translate from buffer position to viewport position
|
||||
const viewportStartRow = start[1] - this._terminal.buffer.ydisp;
|
||||
const viewportEndRow = end[1] - this._terminal.buffer.ydisp;
|
||||
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
||||
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
|
||||
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the selections
|
||||
const documentFragment = document.createDocumentFragment();
|
||||
// Draw first row
|
||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
|
||||
// Draw middle rows
|
||||
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));
|
||||
// Draw final row
|
||||
if (viewportCappedStartRow !== viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewporttartRow
|
||||
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
|
||||
}
|
||||
this._selectionContainer.appendChild(documentFragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a selection element at the specified position.
|
||||
* @param row The row of the selection.
|
||||
* @param colStart The start column.
|
||||
* @param colEnd The end columns.
|
||||
*/
|
||||
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;
|
||||
element.style.top = `${row * this._terminal.charMeasure.height}px`;
|
||||
element.style.left = `${colStart * this._terminal.charMeasure.width}px`;
|
||||
element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;
|
||||
return element;
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
// No-op, the cursor is drawn when rows are drawn
|
||||
}
|
||||
|
||||
public onOptionsChanged(): void {
|
||||
// Force a refresh
|
||||
this._updateDimensions();
|
||||
this.setTheme(undefined);
|
||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._rowElements.forEach(e => e.innerHTML = '');
|
||||
}
|
||||
|
||||
public refreshRows(start: number, end: number): void {
|
||||
this._renderDebouncer.refresh(start, end);
|
||||
}
|
||||
|
||||
private _renderRows(start: number, end: number): void {
|
||||
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 lineData = terminal.buffer.lines.get(row);
|
||||
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorX, terminal.charMeasure.width));
|
||||
}
|
||||
|
||||
this._terminal.emit('refresh', {start, end});
|
||||
}
|
||||
|
||||
private get _terminalSelector(): string {
|
||||
return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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(2);
|
||||
});
|
||||
|
||||
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>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should set correct attributes for double width characters', () => {
|
||||
lineData[0] = [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)];
|
||||
// There should be no element for the following "empty" cell
|
||||
lineData[1] = [DEFAULT_ATTR, '', 0, undefined];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span style="width: 10px;">語</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add class for cursor', () => {
|
||||
const fragment = rowFactory.createRow(lineData, true, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-cursor"> </span>' +
|
||||
'<span> </span>'
|
||||
);
|
||||
});
|
||||
|
||||
describe('attributes', () => {
|
||||
it('should add class for bold', () => {
|
||||
lineData[0] = [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-bold">a</span>' +
|
||||
'<span> </span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add class for italic', () => {
|
||||
lineData[0] = [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-italic">a</span>' +
|
||||
'<span> </span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add classes for 256 foreground colors', () => {
|
||||
const defaultAttrNoFgColor = (0 << 9) | (256 << 0);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
lineData[0] = [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
`<span class="xterm-fg-${i}">a</span>` +
|
||||
'<span> </span>'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should add classes for 256 background colors', () => {
|
||||
const defaultAttrNoBgColor = (257 << 9) | (0 << 0);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
lineData[0] = [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
`<span class="xterm-bg-${i}">a</span>` +
|
||||
'<span> </span>'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly invert colors', () => {
|
||||
lineData[0] = [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-2">a</span>' +
|
||||
'<span> </span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly invert default fg color', () => {
|
||||
lineData[0] = [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-15">a</span>' +
|
||||
'<span> </span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly invert default bg color', () => {
|
||||
lineData[0] = [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-0 xterm-bg-1">a</span>' +
|
||||
'<span> </span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should turn bold fg text bright', () => {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
lineData[0] = [(FLAGS.BOLD << 18) | (i << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)];
|
||||
const fragment = rowFactory.createRow(lineData, false, 0, 5);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
`<span class="xterm-bold xterm-fg-${i + 8}">a</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;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
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];
|
||||
const 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) {
|
||||
fg = 0;
|
||||
}
|
||||
if (bg === 257) {
|
||||
bg = 15;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+10
-1
@@ -94,7 +94,16 @@
|
||||
|
||||
"naming-convention": [
|
||||
true,
|
||||
{"type": "property", "modifiers": ["public", "static", "const"], "format": "UPPER_CASE"}
|
||||
{"type": "default", "format": "camelCase", "leadingUnderscore": "forbid"},
|
||||
{"type": "type", "format": "PascalCase"},
|
||||
{"type": "class", "format": "PascalCase"},
|
||||
{"type": "property", "modifiers": ["const"], "format": "UPPER_CASE"},
|
||||
{"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "allow"},
|
||||
// TODO: Change allow to require when there aren't many PRs out
|
||||
// {"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "require"},
|
||||
{"type": "member", "modifiers": ["private"], "format": "camelCase", "leadingUnderscore": "require"},
|
||||
{"type": "variable", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]},
|
||||
{"type": "interface", "prefix": "I"}
|
||||
],
|
||||
"no-else-after-return": {
|
||||
"options": "allow-else-if"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user